mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
[soc2009/multidb] Merged in all of Justin Bronn's GIS work. Multidb should now work fully with GIS. This is backwards incompatible, if you are using GIS, your ENGINE setting should now be django.contrib.gis.db.backends.XXX where XXX is the name of your DB backend. Thanks to Justin for all his work. This also resolves merge conflicts from the previous commit.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/multidb@11813 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
c88113683d
commit
049dc42bde
@ -131,12 +131,9 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit
|
||||
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
|
||||
|
||||
<<<<<<< HEAD:django/conf/global_settings.py
|
||||
DATABASES = {
|
||||
}
|
||||
|
||||
=======
|
||||
>>>>>>> master:django/conf/global_settings.py
|
||||
# The email backend to use. For possible shortcuts see django.core.mail.
|
||||
# The default is to use the SMTP backend.
|
||||
# Third-party backends can be specified by providing a Python path
|
||||
|
Binary file not shown.
@ -5,11 +5,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Django\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
"POT-Creation-Date: 2009-10-25 20:56+0100\n"
|
||||
=======
|
||||
"POT-Creation-Date: 2009-12-11 10:11+0100\n"
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
"PO-Revision-Date: 2008-02-25 15:53+0100\n"
|
||||
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@ -227,11 +223,7 @@ msgstr "chiński tradycyjny"
|
||||
msgid "Successfully deleted %(count)d %(items)s."
|
||||
msgstr "Usunięto %(count)d %(items)s."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/actions.py:67 contrib/admin/options.py:1027
|
||||
=======
|
||||
#: contrib/admin/actions.py:67 contrib/admin/options.py:1034
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
msgid "Are you sure?"
|
||||
msgstr "Jesteś pewien?"
|
||||
|
||||
@ -327,11 +319,7 @@ msgstr "brak"
|
||||
msgid "Changed %s."
|
||||
msgstr "Zmieniono %s"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:519 contrib/admin/options.py:529
|
||||
=======
|
||||
#: contrib/admin/options.py:522 contrib/admin/options.py:532
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
|
||||
#: forms/models.py:596
|
||||
msgid "and"
|
||||
@ -356,94 +344,53 @@ msgstr "Usunięto %(name)s \"%(object)s\"."
|
||||
msgid "No fields changed."
|
||||
msgstr "Żadne pole nie zmienione."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:599 contrib/auth/admin.py:67
|
||||
=======
|
||||
#: contrib/admin/options.py:602 contrib/auth/admin.py:68
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "The %(name)s \"%(obj)s\" was added successfully."
|
||||
msgstr "%(name)s \"%(obj)s\" dodany pomyślnie."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:603 contrib/admin/options.py:636
|
||||
#: contrib/auth/admin.py:75
|
||||
msgid "You may edit it again below."
|
||||
msgstr "Możesz ponownie edytować wpis poniżej."
|
||||
|
||||
#: contrib/admin/options.py:613 contrib/admin/options.py:646
|
||||
=======
|
||||
#: contrib/admin/options.py:606 contrib/admin/options.py:639
|
||||
#: contrib/auth/admin.py:77
|
||||
msgid "You may edit it again below."
|
||||
msgstr "Możesz ponownie edytować wpis poniżej."
|
||||
|
||||
#: contrib/admin/options.py:616 contrib/admin/options.py:649
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "You may add another %s below."
|
||||
msgstr "Możesz dodać nowy wpis %s poniżej."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:634
|
||||
=======
|
||||
#: contrib/admin/options.py:637
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "The %(name)s \"%(obj)s\" was changed successfully."
|
||||
msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:642
|
||||
=======
|
||||
#: contrib/admin/options.py:645
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
|
||||
msgstr ""
|
||||
"%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:773
|
||||
=======
|
||||
#: contrib/admin/options.py:778
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "Add %s"
|
||||
msgstr "Dodaj %s"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:804 contrib/admin/options.py:1005
|
||||
=======
|
||||
#: contrib/admin/options.py:810 contrib/admin/options.py:1012
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "%(name)s object with primary key %(key)r does not exist."
|
||||
msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:861
|
||||
=======
|
||||
#: contrib/admin/options.py:867
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "Change %s"
|
||||
msgstr "Zmień %s"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:905
|
||||
msgid "Database error"
|
||||
msgstr "Błąd bazy danych"
|
||||
|
||||
#: contrib/admin/options.py:941
|
||||
=======
|
||||
#: contrib/admin/options.py:911
|
||||
msgid "Database error"
|
||||
msgstr "Błąd bazy danych"
|
||||
|
||||
#: contrib/admin/options.py:947
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "%(count)s %(name)s was changed successfully."
|
||||
msgid_plural "%(count)s %(name)s were changed successfully."
|
||||
@ -451,29 +398,17 @@ msgstr[0] "%(count)s %(name)s został pomyślnie zmieniony."
|
||||
msgstr[1] "%(count)s %(name)s zostały pomyślnie zmienione."
|
||||
msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:1020
|
||||
=======
|
||||
#: contrib/admin/options.py:1027
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
|
||||
msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/options.py:1057
|
||||
=======
|
||||
#: contrib/admin/options.py:1064
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "Change history: %s"
|
||||
msgstr "Historia zmian: %s"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:21 contrib/admin/views/decorators.py:14
|
||||
=======
|
||||
#: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/auth/forms.py:80
|
||||
msgid ""
|
||||
"Please enter a correct username and password. Note that both fields are case-"
|
||||
@ -482,19 +417,11 @@ msgstr ""
|
||||
"Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma "
|
||||
"znaczenie."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:288 contrib/admin/views/decorators.py:40
|
||||
msgid "Please log in again, because your session has expired."
|
||||
msgstr "Twoja sesja wygasła, zaloguj się ponownie."
|
||||
|
||||
#: contrib/admin/sites.py:295 contrib/admin/views/decorators.py:47
|
||||
=======
|
||||
#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
|
||||
msgid "Please log in again, because your session has expired."
|
||||
msgstr "Twoja sesja wygasła, zaloguj się ponownie."
|
||||
|
||||
#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
msgid ""
|
||||
"Looks like your browser isn't configured to accept cookies. Please enable "
|
||||
"cookies, reload this page, and try again."
|
||||
@ -502,47 +429,27 @@ msgstr ""
|
||||
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
|
||||
"spróbuj ponownie."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:311 contrib/admin/sites.py:317
|
||||
=======
|
||||
#: contrib/admin/sites.py:315 contrib/admin/sites.py:321
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/views/decorators.py:66
|
||||
msgid "Usernames cannot contain the '@' character."
|
||||
msgstr "Nazwy użytkowników nie mogą zawierać znaku '@'."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:314 contrib/admin/views/decorators.py:62
|
||||
=======
|
||||
#: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "Your e-mail address is not your username. Try '%s' instead."
|
||||
msgstr "Podany adres e-mail nie jest Twoją nazwą użytkownika. Spróbuj '%s'."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:370
|
||||
msgid "Site administration"
|
||||
msgstr "Administracja stroną"
|
||||
|
||||
#: contrib/admin/sites.py:384 contrib/admin/templates/admin/login.html:26
|
||||
=======
|
||||
#: contrib/admin/sites.py:374
|
||||
msgid "Site administration"
|
||||
msgstr "Administracja stroną"
|
||||
|
||||
#: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/templates/registration/password_reset_complete.html:14
|
||||
#: contrib/admin/views/decorators.py:20
|
||||
msgid "Log in"
|
||||
msgstr "Zaloguj się"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/admin/sites.py:429
|
||||
=======
|
||||
#: contrib/admin/sites.py:433
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "%s administration"
|
||||
msgstr "%s - administracja"
|
||||
@ -763,13 +670,8 @@ msgid ""
|
||||
"Are you sure you want to delete the selected %(object_name)s objects? All of "
|
||||
"the following objects and their related items will be deleted:"
|
||||
msgstr ""
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne od "
|
||||
"nich zostaną skasowane:"
|
||||
=======
|
||||
"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne "
|
||||
"od nich zostaną skasowane:"
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
|
||||
#: contrib/admin/templates/admin/filter.html:2
|
||||
#, python-format
|
||||
@ -1524,11 +1426,7 @@ msgstr "użytkownicy"
|
||||
msgid "message"
|
||||
msgstr "wiadomość"
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: contrib/auth/views.py:58
|
||||
=======
|
||||
#: contrib/auth/views.py:60
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
msgid "Logged out"
|
||||
msgstr "Wylogowany"
|
||||
|
||||
@ -4025,22 +3923,14 @@ msgstr ""
|
||||
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
|
||||
msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM[:ss[.uuuuuu]]."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: db/models/fields/related.py:816
|
||||
=======
|
||||
#: db/models/fields/related.py:869
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
msgid ""
|
||||
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
|
||||
msgstr ""
|
||||
"Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby "
|
||||
"zaznaczyć więcej niż jeden wybór."
|
||||
|
||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#: db/models/fields/related.py:894
|
||||
=======
|
||||
#: db/models/fields/related.py:930
|
||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
||||
#, python-format
|
||||
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
|
||||
msgid_plural ""
|
||||
|
@ -1,20 +0,0 @@
|
||||
"""
|
||||
This module provides the backend for spatial SQL construction with Django.
|
||||
|
||||
Specifically, this module will import the correct routines and modules
|
||||
needed for GeoDjango to interface with the spatial database.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.contrib.gis.db.backend.util import gqn
|
||||
|
||||
# Retrieving the necessary settings from the backend.
|
||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||
from django.contrib.gis.db.backend.postgis import create_test_spatial_db, get_geo_where_clause, SpatialBackend
|
||||
elif settings.DATABASE_ENGINE == 'oracle':
|
||||
from django.contrib.gis.db.backend.oracle import create_test_spatial_db, get_geo_where_clause, SpatialBackend
|
||||
elif settings.DATABASE_ENGINE == 'mysql':
|
||||
from django.contrib.gis.db.backend.mysql import create_test_spatial_db, get_geo_where_clause, SpatialBackend
|
||||
elif settings.DATABASE_ENGINE == 'sqlite3':
|
||||
from django.contrib.gis.db.backend.spatialite import create_test_spatial_db, get_geo_where_clause, SpatialBackend
|
||||
else:
|
||||
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE)
|
@ -1,26 +0,0 @@
|
||||
"""
|
||||
This module holds the base `SpatialBackend` object, which is
|
||||
instantiated by each spatial backend with the features it has.
|
||||
"""
|
||||
# TODO: Create a `Geometry` protocol and allow user to use
|
||||
# different Geometry objects -- for now we just use GEOSGeometry.
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
|
||||
class BaseSpatialBackend(object):
|
||||
Geometry = GEOSGeometry
|
||||
GeometryException = GEOSException
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs.setdefault('distance_functions', {})
|
||||
kwargs.setdefault('limited_where', {})
|
||||
for k, v in kwargs.iteritems(): setattr(self, k, v)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
All attributes of the spatial backend return False by default.
|
||||
"""
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
return False
|
||||
|
@ -1,13 +0,0 @@
|
||||
__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
|
||||
|
||||
from django.contrib.gis.db.backend.base import BaseSpatialBackend
|
||||
from django.contrib.gis.db.backend.adaptor import WKTAdaptor
|
||||
from django.contrib.gis.db.backend.mysql.creation import create_test_spatial_db
|
||||
from django.contrib.gis.db.backend.mysql.field import MySQLGeoField
|
||||
from django.contrib.gis.db.backend.mysql.query import *
|
||||
|
||||
SpatialBackend = BaseSpatialBackend(name='mysql', mysql=True,
|
||||
gis_terms=MYSQL_GIS_TERMS,
|
||||
select=GEOM_SELECT,
|
||||
Adaptor=WKTAdaptor,
|
||||
Field=MySQLGeoField)
|
@ -1,5 +0,0 @@
|
||||
|
||||
def create_test_spatial_db(verbosity=1, autoclobber=False):
|
||||
"A wrapper over the MySQL `create_test_db` method."
|
||||
from django.db import connection
|
||||
connection.creation.create_test_db(verbosity, autoclobber)
|
@ -1,53 +0,0 @@
|
||||
from django.db import connection
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.db.backend.mysql.query import GEOM_FROM_TEXT
|
||||
|
||||
# Quotename & geographic quotename, respectively.
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
class MySQLGeoField(Field):
|
||||
"""
|
||||
The backend-specific geographic field for MySQL.
|
||||
"""
|
||||
|
||||
def _geom_index(self, style, db_table):
|
||||
"""
|
||||
Creates a spatial index for the geometry column. If MyISAM tables are
|
||||
used an R-Tree index is created, otherwise a B-Tree index is created.
|
||||
Thus, for best spatial performance, you should use MyISAM tables
|
||||
(which do not support transactions). For more information, see Ch.
|
||||
16.6.1 of the MySQL 5.0 documentation.
|
||||
"""
|
||||
|
||||
# Getting the index name.
|
||||
idx_name = '%s_%s_id' % (db_table, self.column)
|
||||
|
||||
sql = (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(self.column)) + ');')
|
||||
return sql
|
||||
|
||||
def post_create_sql(self, style, db_table):
|
||||
"""
|
||||
Returns SQL that will be executed after the model has been
|
||||
created.
|
||||
"""
|
||||
# Getting the geometric index for this Geometry column.
|
||||
if self.spatial_index:
|
||||
return (self._geom_index(style, db_table),)
|
||||
else:
|
||||
return ()
|
||||
|
||||
def db_type(self):
|
||||
"The OpenGIS name is returned for the MySQL database column type."
|
||||
return self.geom_type
|
||||
|
||||
def get_placeholder(self, value):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
return '%s(%%s)' % GEOM_FROM_TEXT
|
@ -1,59 +0,0 @@
|
||||
"""
|
||||
This module contains the spatial lookup types, and the `get_geo_where_clause`
|
||||
routine for MySQL.
|
||||
|
||||
Please note that MySQL only supports bounding box queries, also
|
||||
known as MBRs (Minimum Bounding Rectangles). Moreover, spatial
|
||||
indices may only be used on MyISAM tables -- if you need
|
||||
transactions, take a look at PostGIS.
|
||||
"""
|
||||
from django.db import connection
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# To ease implementation, WKT is passed to/from MySQL.
|
||||
GEOM_FROM_TEXT = 'GeomFromText'
|
||||
GEOM_FROM_WKB = 'GeomFromWKB'
|
||||
GEOM_SELECT = 'AsText(%s)'
|
||||
|
||||
# WARNING: MySQL is NOT compliant w/the OpenGIS specification and
|
||||
# _every_ one of these lookup types is on the _bounding box_ only.
|
||||
MYSQL_GIS_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',
|
||||
}
|
||||
|
||||
# This lookup type does not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# Assacceptable lookup types for Oracle spatial.
|
||||
MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
|
||||
MYSQL_GIS_TERMS += MISC_TERMS
|
||||
MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
|
||||
|
||||
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
|
||||
"Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
|
||||
# Getting the quoted field as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_alias), qn(name))
|
||||
|
||||
# See if a MySQL Geometry function matches the lookup type next
|
||||
lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
|
||||
if lookup_info:
|
||||
return "%s(%s, %%s)" % (lookup_info, geo_col)
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
# TODO: Is this needed because MySQL cannot handle NULL
|
||||
# geometries in its spatial indices.
|
||||
if lookup_type == 'isnull':
|
||||
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
@ -1,35 +0,0 @@
|
||||
__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
|
||||
|
||||
from django.contrib.gis.db.backend.base import BaseSpatialBackend
|
||||
from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor
|
||||
from django.contrib.gis.db.backend.oracle.creation import create_test_spatial_db
|
||||
from django.contrib.gis.db.backend.oracle.field import OracleSpatialField
|
||||
from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
|
||||
from django.contrib.gis.db.backend.oracle.query import *
|
||||
|
||||
SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True,
|
||||
area=AREA,
|
||||
centroid=CENTROID,
|
||||
difference=DIFFERENCE,
|
||||
distance=DISTANCE,
|
||||
distance_functions=DISTANCE_FUNCTIONS,
|
||||
extent=EXTENT,
|
||||
gis_terms=ORACLE_SPATIAL_TERMS,
|
||||
gml=ASGML,
|
||||
intersection=INTERSECTION,
|
||||
length=LENGTH,
|
||||
limited_where = {'relate' : None},
|
||||
num_geom=NUM_GEOM,
|
||||
num_points=NUM_POINTS,
|
||||
perimeter=LENGTH,
|
||||
point_on_surface=POINT_ON_SURFACE,
|
||||
select=GEOM_SELECT,
|
||||
sym_difference=SYM_DIFFERENCE,
|
||||
transform=TRANSFORM,
|
||||
unionagg=UNIONAGG,
|
||||
union=UNION,
|
||||
Adaptor=OracleSpatialAdaptor,
|
||||
Field=OracleSpatialField,
|
||||
GeometryColumns=GeometryColumns,
|
||||
SpatialRefSys=SpatialRefSys,
|
||||
)
|
@ -1,5 +0,0 @@
|
||||
from cx_Oracle import CLOB
|
||||
from django.contrib.gis.db.backend.adaptor import WKTAdaptor
|
||||
|
||||
class OracleSpatialAdaptor(WKTAdaptor):
|
||||
input_size = CLOB
|
@ -1,5 +0,0 @@
|
||||
|
||||
def create_test_spatial_db(verbosity=1, autoclobber=False):
|
||||
"A wrapper over the Oracle `create_test_db` routine."
|
||||
from django.db import connection
|
||||
connection.creation.create_test_db(verbosity, autoclobber)
|
@ -1,102 +0,0 @@
|
||||
from django.db import connection
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.db.backend.util import gqn
|
||||
from django.contrib.gis.db.backend.oracle.query import TRANSFORM
|
||||
|
||||
# Quotename & geographic quotename, respectively.
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
class OracleSpatialField(Field):
|
||||
"""
|
||||
The backend-specific geographic field for Oracle Spatial.
|
||||
"""
|
||||
|
||||
empty_strings_allowed = False
|
||||
|
||||
def __init__(self, extent=(-180.0, -90.0, 180.0, 90.0), tolerance=0.05, **kwargs):
|
||||
"""
|
||||
Oracle Spatial backend needs to have the extent -- for projected coordinate
|
||||
systems _you must define the extent manually_, since the coordinates are
|
||||
for geodetic systems. The `tolerance` keyword specifies the tolerance
|
||||
for error (in meters), and defaults to 0.05 (5 centimeters).
|
||||
"""
|
||||
# Oracle Spatial specific keyword arguments.
|
||||
self._extent = extent
|
||||
self._tolerance = tolerance
|
||||
# Calling the Django field initialization.
|
||||
super(OracleSpatialField, self).__init__(**kwargs)
|
||||
|
||||
def _add_geom(self, style, db_table):
|
||||
"""
|
||||
Adds this geometry column into the Oracle USER_SDO_GEOM_METADATA
|
||||
table.
|
||||
"""
|
||||
# Checking the dimensions.
|
||||
# TODO: Add support for 3D geometries.
|
||||
if self.dim != 2:
|
||||
raise Exception('3D geometries not yet supported on Oracle Spatial backend.')
|
||||
|
||||
# Constructing the SQL that will be used to insert information about
|
||||
# the geometry column into the USER_GSDO_GEOM_METADATA table.
|
||||
meta_sql = (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(self.column)) + ',\n ' +
|
||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' +
|
||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
|
||||
("('LONG', %s, %s, %s),\n " % (self._extent[0], self._extent[2], self._tolerance)) +
|
||||
style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
|
||||
("('LAT', %s, %s, %s)\n ),\n" % (self._extent[1], self._extent[3], self._tolerance)) +
|
||||
' %s\n );' % self.srid)
|
||||
return meta_sql
|
||||
|
||||
def _geom_index(self, style, db_table):
|
||||
"Creates an Oracle Geometry index (R-tree) for this geometry field."
|
||||
|
||||
# Getting the index name, Oracle doesn't allow object
|
||||
# names > 30 characters.
|
||||
idx_name = truncate_name('%s_%s_id' % (db_table, self.column), 30)
|
||||
|
||||
sql = (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(self.column)) + ') ' +
|
||||
style.SQL_KEYWORD('INDEXTYPE IS ') +
|
||||
style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';')
|
||||
return sql
|
||||
|
||||
def post_create_sql(self, style, db_table):
|
||||
"""
|
||||
Returns SQL that will be executed after the model has been
|
||||
created.
|
||||
"""
|
||||
# Getting the meta geometry information.
|
||||
post_sql = self._add_geom(style, db_table)
|
||||
|
||||
# Getting the geometric index for this Geometry column.
|
||||
if self.spatial_index:
|
||||
return (post_sql, self._geom_index(style, db_table))
|
||||
else:
|
||||
return (post_sql,)
|
||||
|
||||
def db_type(self):
|
||||
"The Oracle geometric data type is MDSYS.SDO_GEOMETRY."
|
||||
return 'MDSYS.SDO_GEOMETRY'
|
||||
|
||||
def get_placeholder(self, 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'
|
||||
elif value.srid != self.srid:
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (TRANSFORM, value.srid, self.srid)
|
||||
else:
|
||||
return 'SDO_GEOMETRY(%%s, %s)' % self.srid
|
@ -1,154 +0,0 @@
|
||||
"""
|
||||
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 import connection
|
||||
from django.contrib.gis.db.backend.util import SpatialFunction
|
||||
from django.contrib.gis.measure import Distance
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# The GML, distance, transform, and union procedures.
|
||||
AREA = 'SDO_GEOM.SDO_AREA'
|
||||
ASGML = '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'
|
||||
POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE'
|
||||
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 =)
|
||||
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
|
||||
|
||||
#### Classes used in constructing Oracle spatial SQL ####
|
||||
class SDOOperation(SpatialFunction):
|
||||
"Base class for SDO* Oracle operations."
|
||||
def __init__(self, func, **kwargs):
|
||||
kwargs.setdefault('operator', '=')
|
||||
kwargs.setdefault('result', 'TRUE')
|
||||
kwargs.setdefault('end_subst', ") %s '%s'")
|
||||
super(SDOOperation, self).__init__(func, **kwargs)
|
||||
|
||||
class SDODistance(SpatialFunction):
|
||||
"Class for Distance queries."
|
||||
def __init__(self, op, tolerance=0.05):
|
||||
super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
|
||||
operator=op, result='%%s')
|
||||
|
||||
class SDOGeomRelate(SpatialFunction):
|
||||
"Class for using SDO_GEOM.RELATE."
|
||||
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').
|
||||
end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
|
||||
beg_subst = "%%s(%%s, '%s'" % mask
|
||||
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
|
||||
|
||||
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)
|
||||
def __init__(self, mask):
|
||||
func = 'SDO_RELATE'
|
||||
if not self.mask_regex.match(mask):
|
||||
raise ValueError('Invalid %s mask: "%s"' % (func, mask))
|
||||
super(SDORelate, self).__init__(func, end_subst=", 'mask=%s') = 'TRUE'" % mask)
|
||||
|
||||
#### Lookup type mapping dictionaries of Oracle spatial operations ####
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int, long)
|
||||
DISTANCE_FUNCTIONS = {
|
||||
'distance_gt' : (SDODistance('>'), dtypes),
|
||||
'distance_gte' : (SDODistance('>='), dtypes),
|
||||
'distance_lt' : (SDODistance('<'), dtypes),
|
||||
'distance_lte' : (SDODistance('<='), dtypes),
|
||||
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
|
||||
beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
|
||||
}
|
||||
|
||||
ORACLE_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, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
|
||||
'touches' : SDOOperation('SDO_TOUCH'),
|
||||
'within' : SDOOperation('SDO_INSIDE'),
|
||||
}
|
||||
ORACLE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
|
||||
|
||||
# This lookup type does not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# Acceptable lookup types for Oracle spatial.
|
||||
ORACLE_SPATIAL_TERMS = ORACLE_GEOMETRY_FUNCTIONS.keys()
|
||||
ORACLE_SPATIAL_TERMS += MISC_TERMS
|
||||
ORACLE_SPATIAL_TERMS = dict((term, None) for term in ORACLE_SPATIAL_TERMS) # Making dictionary for fast lookups
|
||||
|
||||
#### The `get_geo_where_clause` function for Oracle ####
|
||||
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
|
||||
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
||||
# Getting the quoted table name as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_alias), qn(name))
|
||||
|
||||
# See if a Oracle Geometry function matches the lookup type next
|
||||
lookup_info = ORACLE_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
|
||||
|
||||
# Ensuring that a tuple _value_ was passed in from the user
|
||||
if not isinstance(geo_annot.value, tuple):
|
||||
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
|
||||
if len(geo_annot.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(geo_annot.value[1], arg_type):
|
||||
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
|
||||
|
||||
if lookup_type == 'relate':
|
||||
# The SDORelate class handles construction for these queries,
|
||||
# and verifies the mask argument.
|
||||
return sdo_op(geo_annot.value[1]).as_sql(geo_col)
|
||||
else:
|
||||
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
||||
return sdo_op.as_sql(geo_col)
|
||||
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)
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
@ -1,51 +0,0 @@
|
||||
__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
|
||||
|
||||
from django.contrib.gis.db.backend.base import BaseSpatialBackend
|
||||
from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor
|
||||
from django.contrib.gis.db.backend.postgis.creation import create_test_spatial_db
|
||||
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
||||
from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
|
||||
from django.contrib.gis.db.backend.postgis.query import *
|
||||
|
||||
SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
|
||||
area=AREA,
|
||||
centroid=CENTROID,
|
||||
collect=COLLECT,
|
||||
difference=DIFFERENCE,
|
||||
distance=DISTANCE,
|
||||
distance_functions=DISTANCE_FUNCTIONS,
|
||||
distance_sphere=DISTANCE_SPHERE,
|
||||
distance_spheroid=DISTANCE_SPHEROID,
|
||||
envelope=ENVELOPE,
|
||||
extent=EXTENT,
|
||||
extent3d=EXTENT3D,
|
||||
gis_terms=POSTGIS_TERMS,
|
||||
geojson=ASGEOJSON,
|
||||
gml=ASGML,
|
||||
intersection=INTERSECTION,
|
||||
kml=ASKML,
|
||||
length=LENGTH,
|
||||
length3d=LENGTH3D,
|
||||
length_spheroid=LENGTH_SPHEROID,
|
||||
make_line=MAKE_LINE,
|
||||
mem_size=MEM_SIZE,
|
||||
num_geom=NUM_GEOM,
|
||||
num_points=NUM_POINTS,
|
||||
perimeter=PERIMETER,
|
||||
perimeter3d=PERIMETER3D,
|
||||
point_on_surface=POINT_ON_SURFACE,
|
||||
scale=SCALE,
|
||||
select=GEOM_SELECT,
|
||||
snap_to_grid=SNAP_TO_GRID,
|
||||
svg=ASSVG,
|
||||
sym_difference=SYM_DIFFERENCE,
|
||||
transform=TRANSFORM,
|
||||
translate=TRANSLATE,
|
||||
union=UNION,
|
||||
unionagg=UNIONAGG,
|
||||
version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2),
|
||||
Adaptor=PostGISAdaptor,
|
||||
Field=PostGISField,
|
||||
GeometryColumns=GeometryColumns,
|
||||
SpatialRefSys=SpatialRefSys,
|
||||
)
|
@ -1,231 +0,0 @@
|
||||
import os, re, sys
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.db import connection
|
||||
from django.db.backends.creation import TEST_DATABASE_PREFIX
|
||||
from django.contrib.gis.db.backend.util import getstatusoutput
|
||||
|
||||
def create_lang(db_name, verbosity=1):
|
||||
"Sets up the pl/pgsql language on the given database."
|
||||
|
||||
# Getting the command-line options for the shell command
|
||||
options = get_cmd_options(db_name)
|
||||
|
||||
# Constructing the 'createlang' command.
|
||||
createlang_cmd = 'createlang %splpgsql' % options
|
||||
if verbosity >= 1: print createlang_cmd
|
||||
|
||||
# Must have database super-user privileges to execute createlang -- it must
|
||||
# also be in your path.
|
||||
status, output = getstatusoutput(createlang_cmd)
|
||||
|
||||
# Checking the status of the command, 0 => execution successful
|
||||
if status:
|
||||
raise Exception("Error executing 'plpgsql' command: %s\n" % output)
|
||||
|
||||
def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
||||
"Creates database with psycopg2 cursor."
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# Constructing the necessary SQL to create the database.
|
||||
create_sql = 'CREATE DATABASE %s' % qn(db_name)
|
||||
|
||||
# If there's a template database for PostGIS set, then use it.
|
||||
if hasattr(settings, 'POSTGIS_TEMPLATE'):
|
||||
create_sql += ' TEMPLATE %s' % qn(settings.POSTGIS_TEMPLATE)
|
||||
|
||||
# The DATABASE_USER must possess the privileges to create a spatial database.
|
||||
if settings.DATABASE_USER:
|
||||
create_sql += ' OWNER %s' % qn(settings.DATABASE_USER)
|
||||
|
||||
cursor = connection.cursor()
|
||||
connection.creation.set_autocommit()
|
||||
|
||||
try:
|
||||
# Trying to create the database first.
|
||||
cursor.execute(create_sql)
|
||||
except Exception, e:
|
||||
if 'already exists' in e.pgerror.lower():
|
||||
# Database already exists, drop and recreate if user agrees.
|
||||
if not autoclobber:
|
||||
confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
|
||||
if autoclobber or confirm == 'yes':
|
||||
if verbosity >= 1: print 'Destroying old spatial database...'
|
||||
drop_db(db_name)
|
||||
if verbosity >= 1: print 'Creating new spatial database...'
|
||||
cursor.execute(create_sql)
|
||||
else:
|
||||
raise Exception('Spatial database creation canceled.')
|
||||
else:
|
||||
raise Exception('Spatial database creation failed: "%s"' % e.pgerror.strip())
|
||||
|
||||
created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists')
|
||||
def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
||||
"""
|
||||
If no spatial database already exists, then using a cursor will not work.
|
||||
Thus, a `createdb` command will be issued through the shell to bootstrap
|
||||
creation of the spatial database.
|
||||
|
||||
TODO: Actually allow this method to be used without a spatial database
|
||||
in place first.
|
||||
"""
|
||||
# Getting the command-line options for the shell command
|
||||
options = get_cmd_options(False)
|
||||
if hasattr(settings, 'POSTGIS_TEMPLATE'):
|
||||
options += '-T %s ' % settings.POSTGIS_TEMPlATE
|
||||
|
||||
create_cmd = 'createdb -O %s %s%s' % (settings.DATABASE_USER, options, db_name)
|
||||
if verbosity >= 1: print create_cmd
|
||||
|
||||
# Attempting to create the database.
|
||||
status, output = getstatusoutput(create_cmd)
|
||||
|
||||
if status:
|
||||
if created_regex.match(output):
|
||||
if not autoclobber:
|
||||
confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
|
||||
if autoclobber or confirm == 'yes':
|
||||
if verbosity >= 1: print 'Destroying old spatial database...'
|
||||
drop_cmd = 'dropdb %s%s' % (options, db_name)
|
||||
status, output = getstatusoutput(drop_cmd)
|
||||
if status != 0:
|
||||
raise Exception('Could not drop database %s: %s' % (db_name, output))
|
||||
if verbosity >= 1: print 'Creating new spatial database...'
|
||||
status, output = getstatusoutput(create_cmd)
|
||||
if status != 0:
|
||||
raise Exception('Could not create database after dropping: %s' % output)
|
||||
else:
|
||||
raise Exception('Spatial Database Creation canceled.')
|
||||
else:
|
||||
raise Exception('Unknown error occurred in creating database: %s' % output)
|
||||
|
||||
def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
|
||||
"Creates a test spatial database based on the settings."
|
||||
|
||||
# Making sure we're using PostgreSQL and psycopg2
|
||||
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
|
||||
raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
|
||||
|
||||
# Getting the spatial database name
|
||||
db_name = get_spatial_db(test=True)
|
||||
_create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
||||
|
||||
# If a template database is used, then don't need to do any of the following.
|
||||
if not hasattr(settings, 'POSTGIS_TEMPLATE'):
|
||||
# Creating the db language, does not need to be done on NT platforms
|
||||
# since the PostGIS installer enables this capability.
|
||||
if os.name != 'nt':
|
||||
create_lang(db_name, verbosity=verbosity)
|
||||
|
||||
# Now adding in the PostGIS routines.
|
||||
load_postgis_sql(db_name, verbosity=verbosity)
|
||||
|
||||
if verbosity >= 1: print 'Creation of spatial database %s successful.' % db_name
|
||||
|
||||
# Closing the connection
|
||||
connection.close()
|
||||
settings.DATABASE_NAME = db_name
|
||||
connection.settings_dict["DATABASE_NAME"] = db_name
|
||||
can_rollback = connection.creation._rollback_works()
|
||||
settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
|
||||
connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
|
||||
|
||||
# Syncing the database
|
||||
call_command('syncdb', verbosity=verbosity, interactive=interactive)
|
||||
|
||||
def drop_db(db_name=False, test=False):
|
||||
"""
|
||||
Drops the given database (defaults to what is returned from
|
||||
get_spatial_db()). All exceptions are propagated up to the caller.
|
||||
"""
|
||||
if not db_name: db_name = get_spatial_db(test=test)
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('DROP DATABASE %s' % connection.ops.quote_name(db_name))
|
||||
|
||||
def get_cmd_options(db_name):
|
||||
"Obtains the command-line PostgreSQL connection options for shell commands."
|
||||
# The db_name parameter is optional
|
||||
options = ''
|
||||
if db_name:
|
||||
options += '-d %s ' % db_name
|
||||
if settings.DATABASE_USER:
|
||||
options += '-U %s ' % settings.DATABASE_USER
|
||||
if settings.DATABASE_HOST:
|
||||
options += '-h %s ' % settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
options += '-p %s ' % settings.DATABASE_PORT
|
||||
return options
|
||||
|
||||
def get_spatial_db(test=False):
|
||||
"""
|
||||
Returns the name of the spatial database. The 'test' keyword may be set
|
||||
to return the test spatial database name.
|
||||
"""
|
||||
if test:
|
||||
if settings.TEST_DATABASE_NAME:
|
||||
test_db_name = settings.TEST_DATABASE_NAME
|
||||
else:
|
||||
test_db_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
|
||||
return test_db_name
|
||||
else:
|
||||
if not settings.DATABASE_NAME:
|
||||
raise Exception('must configure DATABASE_NAME in settings.py')
|
||||
return settings.DATABASE_NAME
|
||||
|
||||
def load_postgis_sql(db_name, verbosity=1):
|
||||
"""
|
||||
This routine loads up the PostGIS SQL files lwpostgis.sql and
|
||||
spatial_ref_sys.sql.
|
||||
"""
|
||||
# Getting the path to the PostGIS SQL
|
||||
try:
|
||||
# POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the
|
||||
# PostGIS SQL files are located. This is especially useful on Win32
|
||||
# platforms since the output of pg_config looks like "C:/PROGRA~1/..".
|
||||
sql_path = settings.POSTGIS_SQL_PATH
|
||||
except AttributeError:
|
||||
status, sql_path = getstatusoutput('pg_config --sharedir')
|
||||
if status:
|
||||
sql_path = '/usr/local/share'
|
||||
|
||||
# The PostGIS SQL post-creation files.
|
||||
lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql')
|
||||
srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql')
|
||||
if not os.path.isfile(lwpostgis_file):
|
||||
raise Exception('Could not find PostGIS function definitions in %s' % lwpostgis_file)
|
||||
if not os.path.isfile(srefsys_file):
|
||||
raise Exception('Could not find PostGIS spatial reference system definitions in %s' % srefsys_file)
|
||||
|
||||
# Getting the psql command-line options, and command format.
|
||||
options = get_cmd_options(db_name)
|
||||
cmd_fmt = 'psql %s-f "%%s"' % options
|
||||
|
||||
# Now trying to load up the PostGIS functions
|
||||
cmd = cmd_fmt % lwpostgis_file
|
||||
if verbosity >= 1: print cmd
|
||||
status, output = getstatusoutput(cmd)
|
||||
if status:
|
||||
raise Exception('Error in loading PostGIS lwgeometry routines.')
|
||||
|
||||
# Now trying to load up the Spatial Reference System table
|
||||
cmd = cmd_fmt % srefsys_file
|
||||
if verbosity >= 1: print cmd
|
||||
status, output = getstatusoutput(cmd)
|
||||
if status:
|
||||
raise Exception('Error in loading PostGIS spatial_ref_sys table.')
|
||||
|
||||
# Setting the permissions because on Windows platforms the owner
|
||||
# of the spatial_ref_sys and geometry_columns tables is always
|
||||
# the postgres user, regardless of how the db is created.
|
||||
if os.name == 'nt': set_permissions(db_name)
|
||||
|
||||
def set_permissions(db_name):
|
||||
"""
|
||||
Sets the permissions on the given database to that of the user specified
|
||||
in the settings. Needed specifically for PostGIS on Win32 platforms.
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
user = settings.DATABASE_USER
|
||||
cursor.execute('ALTER TABLE geometry_columns OWNER TO %s' % user)
|
||||
cursor.execute('ALTER TABLE spatial_ref_sys OWNER TO %s' % user)
|
@ -1,95 +0,0 @@
|
||||
from django.db import connection
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.db.backend.util import gqn
|
||||
from django.contrib.gis.db.backend.postgis.query import TRANSFORM
|
||||
|
||||
# Quotename & geographic quotename, respectively
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
class PostGISField(Field):
|
||||
"""
|
||||
The backend-specific geographic field for PostGIS.
|
||||
"""
|
||||
|
||||
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(gqn(db_table)) + ', ' +
|
||||
style.SQL_FIELD(gqn(self.column)) + ', ' +
|
||||
style.SQL_FIELD(str(self.srid)) + ', ' +
|
||||
style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
|
||||
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(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' ALTER ') +
|
||||
style.SQL_FIELD(qn(self.column)) +
|
||||
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(qn('%s_%s_id' % (db_table, self.column))) +
|
||||
style.SQL_KEYWORD(' ON ') +
|
||||
style.SQL_TABLE(qn(db_table)) +
|
||||
style.SQL_KEYWORD(' USING ') +
|
||||
style.SQL_COLTYPE(index_type) + ' ( ' +
|
||||
style.SQL_FIELD(qn(self.column)) + ' ' +
|
||||
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.spatial_index:
|
||||
return (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(gqn(db_table)) + ', ' +
|
||||
style.SQL_FIELD(gqn(self.column)) + ');')
|
||||
return sql
|
||||
|
||||
def db_type(self):
|
||||
"""
|
||||
PostGIS geometry columns are added by stored procedures, should be
|
||||
None.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_placeholder(self, 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 == self.srid:
|
||||
return '%s'
|
||||
else:
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
return '%s(%%s, %s)' % (TRANSFORM, self.srid)
|
@ -1,54 +0,0 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
import re
|
||||
|
||||
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]
|
||||
|
||||
### PostGIS management functions ###
|
||||
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')
|
||||
|
||||
### Routines for parsing output of management functions. ###
|
||||
version_regex = re.compile('^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
|
||||
def postgis_version_tuple():
|
||||
"Returns the PostGIS version as a tuple."
|
||||
|
||||
# Getting the PostGIS version
|
||||
version = postgis_lib_version()
|
||||
m = 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)
|
@ -1,313 +0,0 @@
|
||||
"""
|
||||
This module contains the spatial lookup types, and the get_geo_where_clause()
|
||||
routine for PostGIS.
|
||||
"""
|
||||
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
|
||||
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
# Get the PostGIS version information.
|
||||
# To avoid the need to do a database query to determine the PostGIS version
|
||||
# each time the server starts up, one can optionally specify a
|
||||
# POSTGIS_VERSION setting. This setting is intentionally undocumented and
|
||||
# should be considered experimental, because an upcoming GIS backend
|
||||
# refactoring might remove the need for it.
|
||||
if hasattr(settings, 'POSTGIS_VERSION') and settings.POSTGIS_VERSION is not None:
|
||||
version_tuple = settings.POSTGIS_VERSION
|
||||
else:
|
||||
# This import is intentionally within the 'else' so that it isn't executed
|
||||
# if the POSTGIS_VERSION setting is available.
|
||||
from django.contrib.gis.db.backend.postgis.management import postgis_version_tuple
|
||||
version_tuple = postgis_version_tuple()
|
||||
POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = version_tuple
|
||||
|
||||
# The supported PostGIS versions.
|
||||
# TODO: Confirm tests with PostGIS versions 1.1.x -- should work.
|
||||
# Versions <= 1.0.x do not use GEOS C API, and will not be supported.
|
||||
if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
|
||||
raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
|
||||
|
||||
# Versions of PostGIS >= 1.2.2 changed their naming convention to be
|
||||
# 'SQL-MM-centric' to conform with the ISO standard. Practically, this
|
||||
# means that 'ST_' prefixes geometry function names.
|
||||
GEOM_FUNC_PREFIX = ''
|
||||
if MAJOR_VERSION >= 1:
|
||||
if (MINOR_VERSION1 > 2 or
|
||||
(MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 2)):
|
||||
GEOM_FUNC_PREFIX = 'ST_'
|
||||
|
||||
def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func)
|
||||
|
||||
# Custom selection not needed for PostGIS because GEOS geometries are
|
||||
# instantiated directly from the HEXEWKB returned by default. If
|
||||
# WKT is needed for some reason in the future, this value may be changed,
|
||||
# e.g,, 'AsText(%s)'.
|
||||
GEOM_SELECT = None
|
||||
|
||||
# Functions used by the GeoManager & GeoQuerySet
|
||||
AREA = get_func('Area')
|
||||
ASGEOJSON = get_func('AsGeoJson')
|
||||
ASKML = get_func('AsKML')
|
||||
ASGML = get_func('AsGML')
|
||||
ASSVG = get_func('AsSVG')
|
||||
CENTROID = get_func('Centroid')
|
||||
COLLECT = get_func('Collect')
|
||||
DIFFERENCE = get_func('Difference')
|
||||
DISTANCE = get_func('Distance')
|
||||
DISTANCE_SPHERE = get_func('distance_sphere')
|
||||
DISTANCE_SPHEROID = get_func('distance_spheroid')
|
||||
ENVELOPE = get_func('Envelope')
|
||||
EXTENT = get_func('Extent')
|
||||
EXTENT3D = get_func('Extent3D')
|
||||
GEOM_FROM_TEXT = get_func('GeomFromText')
|
||||
GEOM_FROM_EWKB = get_func('GeomFromEWKB')
|
||||
GEOM_FROM_WKB = get_func('GeomFromWKB')
|
||||
INTERSECTION = get_func('Intersection')
|
||||
LENGTH = get_func('Length')
|
||||
LENGTH3D = get_func('Length3D')
|
||||
LENGTH_SPHEROID = get_func('length_spheroid')
|
||||
MAKE_LINE = get_func('MakeLine')
|
||||
MEM_SIZE = get_func('mem_size')
|
||||
NUM_GEOM = get_func('NumGeometries')
|
||||
NUM_POINTS = get_func('npoints')
|
||||
PERIMETER = get_func('Perimeter')
|
||||
PERIMETER3D = get_func('Perimeter3D')
|
||||
POINT_ON_SURFACE = get_func('PointOnSurface')
|
||||
SCALE = get_func('Scale')
|
||||
SNAP_TO_GRID = get_func('SnapToGrid')
|
||||
SYM_DIFFERENCE = get_func('SymDifference')
|
||||
TRANSFORM = get_func('Transform')
|
||||
TRANSLATE = get_func('Translate')
|
||||
|
||||
# Special cases for union, KML, and GeoJSON methods.
|
||||
if MINOR_VERSION1 < 3:
|
||||
UNIONAGG = 'GeomUnion'
|
||||
UNION = 'Union'
|
||||
else:
|
||||
UNIONAGG = 'ST_Union'
|
||||
UNION = 'ST_Union'
|
||||
|
||||
if MINOR_VERSION1 == 1:
|
||||
ASKML = False
|
||||
|
||||
# Only 1.3.4+ have AsGeoJson.
|
||||
if (MINOR_VERSION1 < 3 or
|
||||
(MINOR_VERSION1 == 3 and MINOR_VERSION2 < 4)):
|
||||
ASGEOJSON = False
|
||||
else:
|
||||
raise NotImplementedError('PostGIS versions < 1.0 are not supported.')
|
||||
|
||||
#### 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, beg_subst='%s %s %%s')
|
||||
|
||||
class PostGISFunction(SpatialFunction):
|
||||
"For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
|
||||
def __init__(self, function, **kwargs):
|
||||
super(PostGISFunction, self).__init__(get_func(function), **kwargs)
|
||||
|
||||
class PostGISFunctionParam(PostGISFunction):
|
||||
"For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
|
||||
def __init__(self, func):
|
||||
super(PostGISFunctionParam, self).__init__(func, end_subst=', %%s)')
|
||||
|
||||
class PostGISDistance(PostGISFunction):
|
||||
"For PostGIS distance operations."
|
||||
dist_func = 'Distance'
|
||||
def __init__(self, operator):
|
||||
super(PostGISDistance, self).__init__(self.dist_func, end_subst=') %s %s',
|
||||
operator=operator, result='%%s')
|
||||
|
||||
class PostGISSpheroidDistance(PostGISFunction):
|
||||
"For PostGIS spherical distance operations (using the spheroid)."
|
||||
dist_func = 'distance_spheroid'
|
||||
def __init__(self, operator):
|
||||
# An extra parameter in `end_subst` is needed for the spheroid string.
|
||||
super(PostGISSpheroidDistance, self).__init__(self.dist_func,
|
||||
beg_subst='%s(%s, %%s, %%s',
|
||||
end_subst=') %s %s',
|
||||
operator=operator, result='%%s')
|
||||
|
||||
class PostGISSphereDistance(PostGISFunction):
|
||||
"For PostGIS spherical distance operations."
|
||||
dist_func = 'distance_sphere'
|
||||
def __init__(self, operator):
|
||||
super(PostGISSphereDistance, self).__init__(self.dist_func, end_subst=') %s %s',
|
||||
operator=operator, result='%%s')
|
||||
|
||||
class PostGISRelate(PostGISFunctionParam):
|
||||
"For PostGIS 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(PostGISRelate, self).__init__('Relate')
|
||||
|
||||
#### Lookup type mapping dictionaries of PostGIS operations. ####
|
||||
|
||||
# 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' : 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('&&'),
|
||||
}
|
||||
|
||||
# For PostGIS >= 1.2.2 the following lookup types will do a bounding box query
|
||||
# first before calling the more computationally expensive GEOS routines (called
|
||||
# "inline index magic"):
|
||||
# 'touches', 'crosses', 'contains', 'intersects', 'within', 'overlaps', and
|
||||
# 'covers'.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS = {
|
||||
'equals' : PostGISFunction('Equals'),
|
||||
'disjoint' : PostGISFunction('Disjoint'),
|
||||
'touches' : PostGISFunction('Touches'),
|
||||
'crosses' : PostGISFunction('Crosses'),
|
||||
'within' : PostGISFunction('Within'),
|
||||
'overlaps' : PostGISFunction('Overlaps'),
|
||||
'contains' : PostGISFunction('Contains'),
|
||||
'intersects' : PostGISFunction('Intersects'),
|
||||
'relate' : (PostGISRelate, basestring),
|
||||
}
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int, long)
|
||||
def get_dist_ops(operator):
|
||||
"Returns operations for both regular and spherical distances."
|
||||
return (PostGISDistance(operator), PostGISSphereDistance(operator), PostGISSpheroidDistance(operator))
|
||||
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),
|
||||
}
|
||||
|
||||
if GEOM_FUNC_PREFIX == 'ST_':
|
||||
# The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2+
|
||||
POSTGIS_GEOMETRY_FUNCTIONS.update(
|
||||
{'coveredby' : PostGISFunction('CoveredBy'),
|
||||
'covers' : PostGISFunction('Covers'),
|
||||
})
|
||||
DISTANCE_FUNCTIONS['dwithin'] = (PostGISFunctionParam('DWithin'), dtypes)
|
||||
|
||||
# Distance functions are a part of PostGIS geometry functions.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
|
||||
|
||||
# Any other lookup types that do not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types
|
||||
# allowed for geographic queries.
|
||||
POSTGIS_TERMS = POSTGIS_OPERATORS.keys() # Getting the operators first
|
||||
POSTGIS_TERMS += POSTGIS_GEOMETRY_FUNCTIONS.keys() # Adding on the Geometry Functions
|
||||
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||
POSTGIS_TERMS = dict((term, None) for term in POSTGIS_TERMS) # Making a dictionary for fast lookups
|
||||
|
||||
# For checking tuple parameters -- not very pretty but gets job done.
|
||||
def exactly_two(val): return val == 2
|
||||
def two_to_three(val): return val >= 2 and val <=3
|
||||
def num_params(lookup_type, val):
|
||||
if lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': return two_to_three(val)
|
||||
else: return exactly_two(val)
|
||||
|
||||
#### The `get_geo_where_clause` function for PostGIS. ####
|
||||
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
|
||||
"Returns the SQL WHERE clause for use in PostGIS SQL construction."
|
||||
# Getting the quoted field as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_alias), qn(name))
|
||||
if lookup_type in POSTGIS_OPERATORS:
|
||||
# See if a PostGIS operator matches the lookup type.
|
||||
return POSTGIS_OPERATORS[lookup_type].as_sql(geo_col)
|
||||
elif lookup_type in POSTGIS_GEOMETRY_FUNCTIONS:
|
||||
# See if a PostGIS geometry function matches the lookup type.
|
||||
tmp = POSTGIS_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(geo_annot.value, (tuple, list)):
|
||||
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
|
||||
|
||||
# Number of valid tuple parameters depends on the lookup type.
|
||||
nparams = len(geo_annot.value)
|
||||
if not 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(geo_annot.value[1], arg_type):
|
||||
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.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(geo_annot.value[1])
|
||||
elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin':
|
||||
if geo_annot.geodetic:
|
||||
# Geodetic distances are only availble from Points to PointFields.
|
||||
if geo_annot.geom_type != 'POINT':
|
||||
raise TypeError('PostGIS spherical operations are only valid on PointFields.')
|
||||
if geo_annot.value[0].geom_typeid != 0:
|
||||
raise TypeError('PostGIS geometry distance parameter is required to be of type Point.')
|
||||
# Setting up the geodetic operation appropriately.
|
||||
if nparams == 3 and geo_annot.value[2] == 'spheroid': op = op[2]
|
||||
else: op = op[1]
|
||||
else:
|
||||
op = op[0]
|
||||
else:
|
||||
op = tmp
|
||||
# Calling the `as_sql` function on the operation instance.
|
||||
return op.as_sql(geo_col)
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
@ -1,60 +0,0 @@
|
||||
__all__ = ['create_test_spatial_db', 'get_geo_where_clause', 'SpatialBackend']
|
||||
|
||||
from ctypes.util import find_library
|
||||
from django.conf import settings
|
||||
from django.db.backends.signals import connection_created
|
||||
|
||||
from django.contrib.gis.db.backend.base import BaseSpatialBackend
|
||||
from django.contrib.gis.db.backend.spatialite.adaptor import SpatiaLiteAdaptor
|
||||
from django.contrib.gis.db.backend.spatialite.creation import create_test_spatial_db
|
||||
from django.contrib.gis.db.backend.spatialite.field import SpatiaLiteField
|
||||
from django.contrib.gis.db.backend.spatialite.models import GeometryColumns, SpatialRefSys
|
||||
from django.contrib.gis.db.backend.spatialite.query import *
|
||||
|
||||
# Here we are figuring out the path to the SpatiLite library (`libspatialite`).
|
||||
# If it's not in the system PATH, it may be set manually in the settings via
|
||||
# the `SPATIALITE_LIBRARY_PATH` setting.
|
||||
spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH', find_library('spatialite'))
|
||||
if spatialite_lib:
|
||||
def initialize_spatialite(sender=None, **kwargs):
|
||||
"""
|
||||
This function initializes the pysqlite2 connection to enable the
|
||||
loading of extensions, and to load up the SpatiaLite library
|
||||
extension.
|
||||
"""
|
||||
from django.db import connection
|
||||
connection.connection.enable_load_extension(True)
|
||||
connection.cursor().execute("SELECT load_extension(%s)", (spatialite_lib,))
|
||||
connection_created.connect(initialize_spatialite)
|
||||
else:
|
||||
# No SpatiaLite library found.
|
||||
raise Exception('Unable to locate SpatiaLite, needed to use GeoDjango with sqlite3.')
|
||||
|
||||
SpatialBackend = BaseSpatialBackend(name='spatialite', spatialite=True,
|
||||
area=AREA,
|
||||
centroid=CENTROID,
|
||||
contained=CONTAINED,
|
||||
difference=DIFFERENCE,
|
||||
distance=DISTANCE,
|
||||
distance_functions=DISTANCE_FUNCTIONS,
|
||||
envelope=ENVELOPE,
|
||||
from_text=GEOM_FROM_TEXT,
|
||||
gis_terms=SPATIALITE_TERMS,
|
||||
intersection=INTERSECTION,
|
||||
length=LENGTH,
|
||||
num_geom=NUM_GEOM,
|
||||
num_points=NUM_POINTS,
|
||||
point_on_surface=POINT_ON_SURFACE,
|
||||
scale=SCALE,
|
||||
select=GEOM_SELECT,
|
||||
svg=ASSVG,
|
||||
sym_difference=SYM_DIFFERENCE,
|
||||
transform=TRANSFORM,
|
||||
translate=TRANSLATE,
|
||||
union=UNION,
|
||||
unionagg=UNIONAGG,
|
||||
Adaptor=SpatiaLiteAdaptor,
|
||||
Field=SpatiaLiteField,
|
||||
GeometryColumns=GeometryColumns,
|
||||
SpatialRefSys=SpatialRefSys,
|
||||
)
|
@ -1,61 +0,0 @@
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.db import connection
|
||||
|
||||
def spatialite_init_file():
|
||||
# SPATIALITE_SQL may be placed in settings to tell
|
||||
# GeoDjango to use a specific user-supplied file.
|
||||
return getattr(settings, 'SPATIALITE_SQL', 'init_spatialite-2.3.sql')
|
||||
|
||||
def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False):
|
||||
"Creates a spatial database based on the settings."
|
||||
|
||||
# Making sure we're using PostgreSQL and psycopg2
|
||||
if settings.DATABASE_ENGINE != 'sqlite3':
|
||||
raise Exception('SpatiaLite database creation only supported on sqlite3 platform.')
|
||||
|
||||
# Getting the test database name using the SQLite backend's
|
||||
# `_create_test_db`. Unless `TEST_DATABASE_NAME` is defined,
|
||||
# it returns ":memory:".
|
||||
db_name = connection.creation._create_test_db(verbosity, autoclobber)
|
||||
|
||||
# Closing out the current connection to the database set in
|
||||
# originally in the settings. This makes it so `initialize_spatialite`
|
||||
# function will be run on the connection for the _test_ database instead.
|
||||
connection.close()
|
||||
|
||||
# Point to the new database
|
||||
settings.DATABASE_NAME = db_name
|
||||
connection.settings_dict["DATABASE_NAME"] = db_name
|
||||
can_rollback = connection.creation._rollback_works()
|
||||
settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
|
||||
connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
|
||||
|
||||
# Finally, loading up the SpatiaLite SQL file.
|
||||
load_spatialite_sql(db_name, verbosity=verbosity)
|
||||
|
||||
if verbosity >= 1:
|
||||
print 'Creation of spatial database %s successful.' % db_name
|
||||
|
||||
# Syncing the database
|
||||
call_command('syncdb', verbosity=verbosity, interactive=interactive)
|
||||
|
||||
def load_spatialite_sql(db_name, verbosity=1):
|
||||
"""
|
||||
This routine loads up the SpatiaLite SQL file.
|
||||
"""
|
||||
# Getting the location of the SpatiaLite SQL file, and confirming
|
||||
# it exists.
|
||||
spatialite_sql = spatialite_init_file()
|
||||
if not os.path.isfile(spatialite_sql):
|
||||
raise Exception('Could not find the SpatiaLite initialization SQL file: %s' % spatialite_sql)
|
||||
|
||||
# Opening up the SpatiaLite SQL initialization file and executing
|
||||
# as a script.
|
||||
sql_fh = open(spatialite_sql, 'r')
|
||||
try:
|
||||
cur = connection.cursor()
|
||||
cur.executescript(sql_fh.read())
|
||||
finally:
|
||||
sql_fh.close()
|
@ -1,82 +0,0 @@
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
|
||||
# Quotename & geographic quotename, respectively
|
||||
from django.db import connection
|
||||
qn = connection.ops.quote_name
|
||||
from django.contrib.gis.db.backend.util import gqn
|
||||
from django.contrib.gis.db.backend.spatialite.query import GEOM_FROM_TEXT, TRANSFORM
|
||||
|
||||
class SpatiaLiteField(Field):
|
||||
"""
|
||||
The backend-specific geographic field for SpatiaLite.
|
||||
"""
|
||||
|
||||
def _add_geom(self, style, db_table):
|
||||
"""
|
||||
Constructs the addition of the geometry to the table using the
|
||||
AddGeometryColumn(...) OpenGIS 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(gqn(db_table)) + ', ' +
|
||||
style.SQL_FIELD(gqn(self.column)) + ', ' +
|
||||
style.SQL_FIELD(str(self.srid)) + ', ' +
|
||||
style.SQL_COLTYPE(gqn(self.geom_type)) + ', ' +
|
||||
style.SQL_KEYWORD(str(self.dim)) + ', ' +
|
||||
style.SQL_KEYWORD(str(int(not self.null))) +
|
||||
');')
|
||||
return sql
|
||||
|
||||
def _geom_index(self, style, db_table):
|
||||
"Creates a spatial index for this geometry field."
|
||||
sql = (style.SQL_KEYWORD('SELECT ') +
|
||||
style.SQL_TABLE('CreateSpatialIndex') + '(' +
|
||||
style.SQL_TABLE(gqn(db_table)) + ', ' +
|
||||
style.SQL_FIELD(gqn(self.column)) + ');')
|
||||
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
|
||||
OpenGIS AddGeometryColumn() function.
|
||||
"""
|
||||
# Getting the AddGeometryColumn() SQL necessary to create a OpenGIS
|
||||
# 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.spatial_index:
|
||||
return (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(gqn(db_table)) + ', ' +
|
||||
style.SQL_FIELD(gqn(self.column)) + ');')
|
||||
return sql
|
||||
|
||||
def db_type(self):
|
||||
"""
|
||||
SpatiaLite geometry columns are added by stored procedures;
|
||||
should be None.
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_placeholder(self, 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).
|
||||
"""
|
||||
if value is None or value.srid == self.srid:
|
||||
return '%s(%%s,%s)' % (GEOM_FROM_TEXT, self.srid)
|
||||
else:
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
return '%s(%s(%%s,%s), %s)' % (TRANSFORM, GEOM_FROM_TEXT, value.srid, self.srid)
|
@ -1,160 +0,0 @@
|
||||
"""
|
||||
This module contains the spatial lookup types, and the get_geo_where_clause()
|
||||
routine for SpatiaLite.
|
||||
"""
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from django.db import connection
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.contrib.gis.db.backend.util import SpatialOperation, SpatialFunction
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
GEOM_SELECT = 'AsText(%s)'
|
||||
|
||||
# Dummy func, in case we need it later:
|
||||
def get_func(str):
|
||||
return str
|
||||
|
||||
# Functions used by the GeoManager & GeoQuerySet
|
||||
AREA = get_func('Area')
|
||||
ASSVG = get_func('AsSVG')
|
||||
CENTROID = get_func('Centroid')
|
||||
CONTAINED = get_func('MbrWithin')
|
||||
DIFFERENCE = get_func('Difference')
|
||||
DISTANCE = get_func('Distance')
|
||||
ENVELOPE = get_func('Envelope')
|
||||
GEOM_FROM_TEXT = get_func('GeomFromText')
|
||||
GEOM_FROM_WKB = get_func('GeomFromWKB')
|
||||
INTERSECTION = get_func('Intersection')
|
||||
LENGTH = get_func('GLength') # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
|
||||
NUM_GEOM = get_func('NumGeometries')
|
||||
NUM_POINTS = get_func('NumPoints')
|
||||
POINT_ON_SURFACE = get_func('PointOnSurface')
|
||||
SCALE = get_func('ScaleCoords')
|
||||
SYM_DIFFERENCE = get_func('SymDifference')
|
||||
TRANSFORM = get_func('Transform')
|
||||
TRANSLATE = get_func('ShiftCoords')
|
||||
UNION = 'GUnion'# OpenGis defines Union, but this conflicts with an SQLite reserved keyword
|
||||
UNIONAGG = 'GUnion'
|
||||
|
||||
#### Classes used in constructing SpatiaLite spatial SQL ####
|
||||
class SpatiaLiteOperator(SpatialOperation):
|
||||
"For SpatiaLite operators (e.g. `&&`, `~`)."
|
||||
def __init__(self, operator):
|
||||
super(SpatiaLiteOperator, self).__init__(operator=operator, beg_subst='%s %s %%s')
|
||||
|
||||
class SpatiaLiteFunction(SpatialFunction):
|
||||
"For SpatiaLite function calls."
|
||||
def __init__(self, function, **kwargs):
|
||||
super(SpatiaLiteFunction, self).__init__(get_func(function), **kwargs)
|
||||
|
||||
class SpatiaLiteFunctionParam(SpatiaLiteFunction):
|
||||
"For SpatiaLite functions that take another parameter."
|
||||
def __init__(self, func):
|
||||
super(SpatiaLiteFunctionParam, self).__init__(func, end_subst=', %%s)')
|
||||
|
||||
class SpatiaLiteDistance(SpatiaLiteFunction):
|
||||
"For SpatiaLite distance operations."
|
||||
dist_func = 'Distance'
|
||||
def __init__(self, operator):
|
||||
super(SpatiaLiteDistance, self).__init__(self.dist_func, end_subst=') %s %s',
|
||||
operator=operator, result='%%s')
|
||||
|
||||
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')
|
||||
|
||||
|
||||
SPATIALITE_GEOMETRY_FUNCTIONS = {
|
||||
'equals' : SpatiaLiteFunction('Equals'),
|
||||
'disjoint' : SpatiaLiteFunction('Disjoint'),
|
||||
'touches' : SpatiaLiteFunction('Touches'),
|
||||
'crosses' : SpatiaLiteFunction('Crosses'),
|
||||
'within' : SpatiaLiteFunction('Within'),
|
||||
'overlaps' : SpatiaLiteFunction('Overlaps'),
|
||||
'contains' : SpatiaLiteFunction('Contains'),
|
||||
'intersects' : SpatiaLiteFunction('Intersects'),
|
||||
'relate' : (SpatiaLiteRelate, basestring),
|
||||
# Retruns true if B's bounding box completely contains A's bounding box.
|
||||
'contained' : SpatiaLiteFunction('MbrWithin'),
|
||||
# Returns true if A's bounding box completely contains B's bounding box.
|
||||
'bbcontains' : SpatiaLiteFunction('MbrContains'),
|
||||
# Returns true if A's bounding box overlaps B's bounding box.
|
||||
'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
|
||||
# These are implemented here as synonyms for Equals
|
||||
'same_as' : SpatiaLiteFunction('Equals'),
|
||||
'exact' : SpatiaLiteFunction('Equals'),
|
||||
}
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int, long)
|
||||
def get_dist_ops(operator):
|
||||
"Returns operations for regular distances; spherical distances are not currently supported."
|
||||
return (SpatiaLiteDistance(operator),)
|
||||
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),
|
||||
}
|
||||
|
||||
# Distance functions are a part of SpatiaLite geometry functions.
|
||||
SPATIALITE_GEOMETRY_FUNCTIONS.update(DISTANCE_FUNCTIONS)
|
||||
|
||||
# Any other lookup types that do not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# These are the SpatiaLite-customized QUERY_TERMS -- a list of the lookup types
|
||||
# allowed for geographic queries.
|
||||
SPATIALITE_TERMS = SPATIALITE_GEOMETRY_FUNCTIONS.keys() # Getting the Geometry Functions
|
||||
SPATIALITE_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||
SPATIALITE_TERMS = dict((term, None) for term in SPATIALITE_TERMS) # Making a dictionary for fast lookups
|
||||
|
||||
#### The `get_geo_where_clause` function for SpatiaLite. ####
|
||||
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
|
||||
"Returns the SQL WHERE clause for use in SpatiaLite SQL construction."
|
||||
# Getting the quoted field as `geo_col`.
|
||||
geo_col = '%s.%s' % (qn(table_alias), qn(name))
|
||||
if lookup_type in SPATIALITE_GEOMETRY_FUNCTIONS:
|
||||
# See if a SpatiaLite geometry function matches the lookup type.
|
||||
tmp = SPATIALITE_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(geo_annot.value, (tuple, list)):
|
||||
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
|
||||
|
||||
# Number of valid tuple parameters depends on the lookup type.
|
||||
if len(geo_annot.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(geo_annot.value[1], arg_type):
|
||||
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.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(geo_annot.value[1])
|
||||
elif lookup_type in DISTANCE_FUNCTIONS:
|
||||
op = op[0]
|
||||
else:
|
||||
op = tmp
|
||||
# Calling the `as_sql` function on the operation instance.
|
||||
return op.as_sql(geo_col)
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not geo_annot.value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
0
django/contrib/gis/db/backends/__init__.py
Normal file
0
django/contrib/gis/db/backends/__init__.py
Normal file
@ -1,4 +1,4 @@
|
||||
class WKTAdaptor(object):
|
||||
class WKTAdapter(object):
|
||||
"""
|
||||
This provides an adaptor for Geometries sent to the
|
||||
MySQL and Oracle database backends.
|
294
django/contrib/gis/db/backends/base.py
Normal file
294
django/contrib/gis/db/backends/base.py
Normal file
@ -0,0 +1,294 @@
|
||||
"""
|
||||
|
||||
"""
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.contrib.gis import gdal
|
||||
|
||||
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 = {}
|
||||
gis_terms = {}
|
||||
limited_where = {}
|
||||
|
||||
# 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
|
||||
|
||||
area = False
|
||||
centroid = False
|
||||
difference = False
|
||||
distance = False
|
||||
distance_sphere = False
|
||||
distance_spheroid = False
|
||||
envelope = False
|
||||
force_rhr = False
|
||||
mem_size = False
|
||||
num_geom = False
|
||||
num_points = False
|
||||
perimeter = False
|
||||
perimeter3d = False
|
||||
point_on_surface = 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
|
||||
|
||||
def geo_quote_name(self, name):
|
||||
if isinstance(name, unicode):
|
||||
name = name.encode('ascii')
|
||||
return "'%s'" % name
|
||||
|
||||
# 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.')
|
||||
|
||||
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 NotImplmentedError
|
||||
|
||||
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, msg:
|
||||
pass
|
||||
|
||||
try:
|
||||
self._srs = gdal.SpatialReference(self.proj4text)
|
||||
return self.srs
|
||||
except Exception, 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 __unicode__(self):
|
||||
"""
|
||||
Returns the string representation. If GDAL is installed,
|
||||
it will be 'pretty' OGC WKT.
|
||||
"""
|
||||
try:
|
||||
return unicode(self.srs)
|
||||
except:
|
||||
return unicode(self.wkt)
|
0
django/contrib/gis/db/backends/mysql/__init__.py
Normal file
0
django/contrib/gis/db/backends/mysql/__init__.py
Normal file
11
django/contrib/gis/db/backends/mysql/base.py
Normal file
11
django/contrib/gis/db/backends/mysql/base.py
Normal file
@ -0,0 +1,11 @@
|
||||
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.operations import MySQLOperations
|
||||
|
||||
class DatabaseWrapper(MySQLDatabaseWrapper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = MySQLCreation(self)
|
||||
self.ops = MySQLOperations()
|
18
django/contrib/gis/db/backends/mysql/creation.py
Normal file
18
django/contrib/gis/db/backends/mysql/creation.py
Normal file
@ -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):
|
||||
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
|
62
django/contrib/gis/db/backends/mysql/operations.py
Normal file
62
django/contrib/gis/db/backends/mysql/operations.py
Normal file
@ -0,0 +1,62 @@
|
||||
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.models.sql.compiler'
|
||||
mysql = True
|
||||
name = 'mysql'
|
||||
select = 'AsText(%s)'
|
||||
from_wkb = 'GeomFromWKB'
|
||||
from_text = 'GeomFromText'
|
||||
|
||||
Adapter = WKTAdapter
|
||||
|
||||
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 = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
|
||||
|
||||
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 = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
else:
|
||||
placeholder = '%s(%%s)' % self.from_text
|
||||
return placeholder
|
||||
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||
qn = self.quote_name
|
||||
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:
|
||||
return "%s(%s, %s)" % (lookup_info, geo_col,
|
||||
self.get_geom_placeholder(value, field.srid))
|
||||
|
||||
# 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, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
0
django/contrib/gis/db/backends/oracle/__init__.py
Normal file
0
django/contrib/gis/db/backends/oracle/__init__.py
Normal file
5
django/contrib/gis/db/backends/oracle/adapter.py
Normal file
5
django/contrib/gis/db/backends/oracle/adapter.py
Normal file
@ -0,0 +1,5 @@
|
||||
from cx_Oracle import CLOB
|
||||
from django.contrib.gis.db.backends.adapter import WKTAdapter
|
||||
|
||||
class OracleSpatialAdapter(WKTAdapter):
|
||||
input_size = CLOB
|
10
django/contrib/gis/db/backends/oracle/base.py
Normal file
10
django/contrib/gis/db/backends/oracle/base.py
Normal file
@ -0,0 +1,10 @@
|
||||
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.operations import OracleOperations
|
||||
|
||||
class DatabaseWrapper(OracleDatabaseWrapper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = OracleCreation(self)
|
||||
self.ops = OracleOperations()
|
22
django/contrib/gis/db/backends/oracle/compiler.py
Normal file
22
django/contrib/gis/db/backends/oracle/compiler.py
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
42
django/contrib/gis/db/backends/oracle/creation.py
Normal file
42
django/contrib/gis/db/backends/oracle/creation.py
Normal file
@ -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
|
@ -7,7 +7,9 @@
|
||||
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.db import models
|
||||
from django.contrib.gis.db import models
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
|
||||
|
||||
class GeometryColumns(models.Model):
|
||||
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
||||
@ -39,17 +41,20 @@ class GeometryColumns(models.Model):
|
||||
def __unicode__(self):
|
||||
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
|
||||
|
||||
class SpatialRefSys(models.Model):
|
||||
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)
|
||||
#cs_bounds = models.GeometryField() # TODO
|
||||
# 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:
|
||||
abstract = True
|
||||
app_label = 'gis'
|
||||
db_table = 'CS_SRS'
|
||||
managed = False
|
||||
|
253
django/contrib/gis/db/backends/oracle/operations.py
Normal file
253
django/contrib/gis/db/backends/oracle/operations.py
Normal file
@ -0,0 +1,253 @@
|
||||
"""
|
||||
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 import Geometry
|
||||
from django.contrib.gis.measure import Distance
|
||||
|
||||
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, int, long)
|
||||
|
||||
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
|
||||
|
||||
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'
|
||||
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, basestring), # 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 = ['isnull']
|
||||
gis_terms += geometry_functions.keys()
|
||||
gis_terms = dict([(term, None) for term in gis_terms])
|
||||
|
||||
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 get_geom_placeholder(self, value, srid):
|
||||
"""
|
||||
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(value, srid):
|
||||
return value.srid != srid
|
||||
|
||||
if hasattr(value, 'expression'):
|
||||
if transform_value(value, srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
else:
|
||||
if transform_value(value, srid):
|
||||
return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, srid)
|
||||
else:
|
||||
return 'SDO_GEOMETRY(%%s, %s)' % srid
|
||||
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||
"Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
|
||||
qn = self.quote_name
|
||||
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(geom, field.srid))
|
||||
else:
|
||||
# Otherwise, just call the `as_sql` method on the SDOOperation instance.
|
||||
return sdo_op.as_sql(geo_col, self.get_geom_placeholder(geom, field.srid))
|
||||
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(value, field.srid))
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
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
|
0
django/contrib/gis/db/backends/postgis/__init__.py
Normal file
0
django/contrib/gis/db/backends/postgis/__init__.py
Normal file
@ -2,11 +2,10 @@
|
||||
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
|
||||
"""
|
||||
|
||||
from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_EWKB
|
||||
from psycopg2 import Binary
|
||||
from psycopg2.extensions import ISQLQuote
|
||||
|
||||
class PostGISAdaptor(object):
|
||||
class PostGISAdapter(object):
|
||||
def __init__(self, geom):
|
||||
"Initializes on the geometry."
|
||||
# Getting the WKB (in string form, to allow easy pickling of
|
||||
@ -22,7 +21,7 @@ class PostGISAdaptor(object):
|
||||
raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.wkb == other.wkb) and (self.srid == other.srid)
|
||||
return (self.ewkb == other.ewkb) and (self.srid == other.srid)
|
||||
|
||||
def __str__(self):
|
||||
return self.getquoted()
|
||||
@ -30,7 +29,7 @@ class PostGISAdaptor(object):
|
||||
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(E%s)" % (GEOM_FROM_EWKB, Binary(self.ewkb))
|
||||
return 'ST_GeomFromEWKB(E%s)' % Binary(self.ewkb)
|
||||
|
||||
def prepare_database_save(self, unused):
|
||||
return self
|
10
django/contrib/gis/db/backends/postgis/base.py
Normal file
10
django/contrib/gis/db/backends/postgis/base.py
Normal file
@ -0,0 +1,10 @@
|
||||
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.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)
|
49
django/contrib/gis/db/backends/postgis/creation.py
Normal file
49
django/contrib/gis/db/backends/postgis/creation.py
Normal file
@ -0,0 +1,49 @@
|
||||
from django.conf import settings
|
||||
from django.db.backends.postgresql.creation import DatabaseCreation
|
||||
|
||||
class PostGISCreation(DatabaseCreation):
|
||||
geom_index_type = 'GIST'
|
||||
geom_index_opts = 'GIST_GEOMETRY_OPS'
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
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)) + ' ' +
|
||||
style.SQL_KEYWORD(self.geom_index_opts) + ' );')
|
||||
return output
|
||||
|
||||
def sql_table_creation_suffix(self):
|
||||
qn = self.connection.ops.quote_name
|
||||
return ' TEMPLATE %s' % qn(getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis'))
|
@ -2,6 +2,7 @@
|
||||
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
|
||||
"""
|
||||
from django.db import models
|
||||
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
|
||||
|
||||
class GeometryColumns(models.Model):
|
||||
"""
|
||||
@ -42,7 +43,7 @@ class GeometryColumns(models.Model):
|
||||
(self.f_table_name, self.f_geometry_column,
|
||||
self.coord_dimension, self.type, self.srid)
|
||||
|
||||
class SpatialRefSys(models.Model):
|
||||
class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||
"""
|
||||
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
|
||||
documentaiton at Ch. 4.2.1.
|
||||
@ -54,7 +55,7 @@ class SpatialRefSys(models.Model):
|
||||
proj4text = models.CharField(max_length=2048)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
app_label = 'gis'
|
||||
db_table = 'spatial_ref_sys'
|
||||
managed = False
|
||||
|
444
django/contrib/gis/db/backends/postgis/operations.py
Normal file
444
django/contrib/gis/db/backends/postgis/operations.py
Normal file
@ -0,0 +1,444 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db.backends.postgresql.operations import DatabaseOperations
|
||||
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 import Geometry
|
||||
from django.contrib.gis.measure import Distance
|
||||
|
||||
#### 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
|
||||
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
|
||||
|
||||
def __init__(self, connection):
|
||||
super(PostGISOperations, self).__init__(connection)
|
||||
|
||||
# Trying to get the PostGIS version because the function
|
||||
# signatures will depend on the version used.
|
||||
try:
|
||||
vtup = self.postgis_version_tuple()
|
||||
version = vtup[1:]
|
||||
if version >= (1, 2, 2):
|
||||
prefix = 'ST_'
|
||||
else:
|
||||
prefix = ''
|
||||
self.geom_func_prefix = prefix
|
||||
self.spatial_version = version
|
||||
except Exception, e:
|
||||
# TODO: Plain raising right now.
|
||||
raise
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# operators come from Section 7.6 of the PostGIS 1.4 documentation.
|
||||
self.spatial_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, basestring),
|
||||
}
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int, long)
|
||||
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),
|
||||
}
|
||||
|
||||
# Versions 1.2.2+ have KML serialization support.
|
||||
if version < (1, 2, 2):
|
||||
ASKML = False
|
||||
else:
|
||||
ASKML = 'ST_AsKML'
|
||||
self.geometry_functions.update(
|
||||
{'coveredby' : PostGISFunction(prefix, 'CoveredBy'),
|
||||
'covers' : PostGISFunction(prefix, 'Covers'),
|
||||
})
|
||||
self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes)
|
||||
|
||||
# Adding the distance functions to the geometries lookup.
|
||||
self.geometry_functions.update(self.distance_functions)
|
||||
|
||||
# ST_ContainsProperly and GeoHash serialization added in 1.4.
|
||||
if version >= (1, 4, 0):
|
||||
GEOHASH = 'ST_GeoHash'
|
||||
self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
|
||||
else:
|
||||
GEOHASH = False
|
||||
|
||||
# Creating a dictionary lookup of all GIS terms for PostGIS.
|
||||
gis_terms = ['isnull']
|
||||
gis_terms += self.spatial_operators.keys()
|
||||
gis_terms += self.geometry_functions.keys()
|
||||
self.gis_terms = dict([(term, None) for term in gis_terms])
|
||||
|
||||
# The union aggregate and topology operation use the same signature
|
||||
# in versions 1.3+.
|
||||
if version < (1, 3, 0):
|
||||
UNIONAGG = 'GeomUnion'
|
||||
UNION = 'Union'
|
||||
else:
|
||||
UNIONAGG = 'ST_Union'
|
||||
UNION = 'ST_Union'
|
||||
|
||||
# Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
|
||||
if version < (1, 3, 4):
|
||||
GEOJSON = False
|
||||
else:
|
||||
GEOJSON = prefix + 'AsGeoJson'
|
||||
|
||||
self.area = prefix + 'Area'
|
||||
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.extent3d = prefix + 'Extent3D'
|
||||
self.geohash = GEOHASH
|
||||
self.geojson = GEOJSON
|
||||
self.gml = prefix + 'AsGML'
|
||||
self.intersection = prefix + 'Intersection'
|
||||
self.kml = ASKML
|
||||
self.length = prefix + 'Length'
|
||||
self.length3d = prefix + 'Length3D'
|
||||
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.perimeter3d = prefix + 'Perimeter3D'
|
||||
self.point_on_surface = prefix + 'PointOnSurface'
|
||||
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 = UNION
|
||||
self.unionagg = UNIONAGG
|
||||
|
||||
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):
|
||||
# Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
|
||||
# parsing out and returning as a 4-tuple.
|
||||
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):
|
||||
# Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
|
||||
# parsing out and returning as a 4-tuple.
|
||||
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 get_geom_placeholder(self, value, srid):
|
||||
"""
|
||||
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 == srid:
|
||||
placeholder = '%s'
|
||||
else:
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, 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 % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
|
||||
return placeholder
|
||||
|
||||
def _get_postgis_func(self, func):
|
||||
"""
|
||||
Helper routine for calling PostGIS functions and returning their result.
|
||||
"""
|
||||
cursor = self.connection._cursor()
|
||||
try:
|
||||
cursor.execute('SELECT %s()' % func)
|
||||
row = cursor.fetchone()
|
||||
except:
|
||||
# TODO: raise helpful exception here.
|
||||
raise
|
||||
finally:
|
||||
cursor.close()
|
||||
return row[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 num_params(self, lookup_type, val):
|
||||
def exactly_two(val): return val == 2
|
||||
def two_to_three(val): return val >= 2 and val <=3
|
||||
if (lookup_type in self.distance_functions and
|
||||
lookup_type != 'dwithin'):
|
||||
return two_to_three(val)
|
||||
else:
|
||||
return exactly_two(val)
|
||||
|
||||
def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
|
||||
"""
|
||||
Constructs spatial SQL from the given lookup value tuple a
|
||||
(alias, col, db_type), the lookup type string, lookup value, and
|
||||
the geometry field.
|
||||
"""
|
||||
qn = self.quote_name
|
||||
alias, col, db_type = lvalue
|
||||
|
||||
# Getting the quoted geometry column.
|
||||
geo_col = '%s.%s' % (qn(alias), qn(col))
|
||||
|
||||
if lookup_type in self.spatial_operators:
|
||||
# Handling a PostGIS operator.
|
||||
op = self.spatial_operators[lookup_type]
|
||||
return op.as_sql(geo_col, self.get_geom_placeholder(value, field.srid))
|
||||
elif lookup_type in self.geometry_functions:
|
||||
# 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 field.geodetic(self.connection):
|
||||
# Geodetic distances are only availble from Points to PointFields.
|
||||
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(geom, field.srid))
|
||||
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
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):
|
||||
from django.contrib.gis.db.backends.postgis.models import GeometryColumns
|
||||
return GeometryColumns
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
|
||||
return SpatialRefSys
|
@ -1,7 +1,7 @@
|
||||
from django.db.backends.sqlite3.base import Database
|
||||
from django.contrib.gis.db.backend.adaptor import WKTAdaptor
|
||||
from django.contrib.gis.db.backends.adapter import WKTAdapter
|
||||
|
||||
class SpatiaLiteAdaptor(WKTAdaptor):
|
||||
class SpatiaLiteAdapter(WKTAdapter):
|
||||
"SQLite adaptor for geometry objects."
|
||||
def __conform__(self, protocol):
|
||||
if protocol is Database.PrepareProtocol:
|
73
django/contrib/gis/db/backends/spatialite/base.py
Normal file
73
django/contrib/gis/db/backends/spatialite/base.py
Normal file
@ -0,0 +1,73 @@
|
||||
from ctypes.util import find_library
|
||||
from django.conf import settings
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.sqlite3.base import *
|
||||
from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWrapper, \
|
||||
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp
|
||||
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.operations import SpatiaLiteOperations
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def _cursor(self):
|
||||
if self.connection is None:
|
||||
settings_dict = self.settings_dict
|
||||
if not settings_dict['NAME']:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured, "Please fill out the database NAME in the settings module before using the database."
|
||||
kwargs = {
|
||||
'database': settings_dict['NAME'],
|
||||
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
|
||||
}
|
||||
kwargs.update(settings_dict['OPTIONS'])
|
||||
self.connection = Database.connect(**kwargs)
|
||||
# Register extract, date_trunc, and regexp functions.
|
||||
self.connection.create_function("django_extract", 2, _sqlite_extract)
|
||||
self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
|
||||
self.connection.create_function("regexp", 2, _sqlite_regexp)
|
||||
|
||||
try:
|
||||
self.connection.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.'
|
||||
)
|
||||
|
||||
connection_created.send(sender=self.__class__)
|
||||
return self.connection.cursor(factory=SQLiteCursorWrapper)
|
||||
|
||||
def load_spatialite(self):
|
||||
"""
|
||||
Loads the SpatiaLite library.
|
||||
"""
|
||||
try:
|
||||
self._cursor().execute("SELECT load_extension(%s)", (self.spatialite_lib,))
|
||||
except Exception, msg:
|
||||
raise ImproperlyConfigured('Unable to load the SpatiaLite extension '
|
||||
'"%s" because: %s' % (self.spatialite_lib, msg))
|
5
django/contrib/gis/db/backends/spatialite/client.py
Normal file
5
django/contrib/gis/db/backends/spatialite/client.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.db.backends.sqlite3.client import DatabaseClient
|
||||
|
||||
class SpatiaLiteClient(DatabaseClient):
|
||||
executable_name = 'spatialite'
|
||||
|
97
django/contrib/gis/db/backends/spatialite/creation.py
Normal file
97
django/contrib/gis/db/backends/spatialite/creation.py
Normal file
@ -0,0 +1,97 @@
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management import call_command
|
||||
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.
|
||||
"""
|
||||
if verbosity >= 1:
|
||||
print "Creating test database '%s'..." % self.connection.alias
|
||||
|
||||
test_database_name = self._create_test_db(verbosity, autoclobber)
|
||||
|
||||
self.connection.close()
|
||||
|
||||
self.connection.settings_dict["NAME"] = test_database_name
|
||||
can_rollback = self._rollback_works()
|
||||
self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
|
||||
# Need to load the SpatiaLite library and initializatin SQL before running `syncdb`.
|
||||
self.connection.load_spatialite()
|
||||
self.load_spatialite_sql()
|
||||
call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
|
||||
|
||||
if settings.CACHE_BACKEND.startswith('db://'):
|
||||
from django.core.cache import parse_backend_uri
|
||||
_, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND)
|
||||
call_command('createcachetable', cache_name)
|
||||
|
||||
# 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.
|
||||
"""
|
||||
# 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.
|
||||
sql_fh = open(spatialite_sql, 'r')
|
||||
try:
|
||||
cur = self.connection._cursor()
|
||||
cur.executescript(sql_fh.read())
|
||||
finally:
|
||||
sql_fh.close()
|
||||
|
||||
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])
|
@ -2,6 +2,7 @@
|
||||
The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
|
||||
"""
|
||||
from django.db import models
|
||||
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
|
||||
|
||||
class GeometryColumns(models.Model):
|
||||
"""
|
||||
@ -40,7 +41,7 @@ class GeometryColumns(models.Model):
|
||||
(self.f_table_name, self.f_geometry_column,
|
||||
self.coord_dimension, self.type, self.srid)
|
||||
|
||||
class SpatialRefSys(models.Model):
|
||||
class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||
"""
|
||||
The 'spatial_ref_sys' table from SpatiaLite.
|
||||
"""
|
||||
@ -56,6 +57,6 @@ class SpatialRefSys(models.Model):
|
||||
return SpatialReference(self.proj4text).wkt
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
app_label = 'gis'
|
||||
db_table = 'spatial_ref_sys'
|
||||
managed = False
|
295
django/contrib/gis/db/backends/spatialite/operations.py
Normal file
295
django/contrib/gis/db/backends/spatialite/operations.py
Normal file
@ -0,0 +1,295 @@
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.gis.db.backends.base import BaseSpatialOperations
|
||||
from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
|
||||
from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
|
||||
from django.contrib.gis.geometry import Geometry
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db.backends.sqlite3.base import DatabaseOperations
|
||||
|
||||
class SpatiaLiteOperator(SpatialOperation):
|
||||
"For SpatiaLite operators (e.g. `&&`, `~`)."
|
||||
def __init__(self, operator):
|
||||
super(SpatiaLiteOperator, self).__init__(operator=operator)
|
||||
|
||||
class SpatiaLiteFunction(SpatialFunction):
|
||||
"For SpatiaLite function calls."
|
||||
def __init__(self, function, **kwargs):
|
||||
super(SpatiaLiteFunction, self).__init__(function, **kwargs)
|
||||
|
||||
class SpatiaLiteFunctionParam(SpatiaLiteFunction):
|
||||
"For SpatiaLite functions that take another parameter."
|
||||
sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
|
||||
|
||||
class SpatiaLiteDistance(SpatiaLiteFunction):
|
||||
"For SpatiaLite distance operations."
|
||||
dist_func = 'Distance'
|
||||
sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
|
||||
|
||||
def __init__(self, operator):
|
||||
super(SpatiaLiteDistance, self).__init__(self.dist_func,
|
||||
operator=operator)
|
||||
|
||||
class SpatiaLiteRelate(SpatiaLiteFunctionParam):
|
||||
"For SpatiaLite Relate(<geom>, <pattern>) calls."
|
||||
pattern_regex = re.compile(r'^[012TF\*]{9}$')
|
||||
def __init__(self, pattern):
|
||||
if not self.pattern_regex.match(pattern):
|
||||
raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
|
||||
super(SpatiaLiteRelate, self).__init__('Relate')
|
||||
|
||||
# Valid distance types and substitutions
|
||||
dtypes = (Decimal, Distance, float, int, long)
|
||||
def get_dist_ops(operator):
|
||||
"Returns operations for regular distances; spherical distances are not currently supported."
|
||||
return (SpatiaLiteDistance(operator),)
|
||||
|
||||
class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
|
||||
compiler_module = 'django.contrib.gis.db.models.sql.compiler'
|
||||
name = 'spatialite'
|
||||
spatialite = True
|
||||
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
|
||||
valid_aggregates = dict([(k, None) for k in
|
||||
('Extent', 'Union')])
|
||||
|
||||
Adapter = SpatiaLiteAdapter
|
||||
|
||||
area = 'Area'
|
||||
centroid = 'Centroid'
|
||||
contained = 'MbrWithin'
|
||||
difference = 'Difference'
|
||||
distance = 'Distance'
|
||||
envelope = 'Envelope'
|
||||
intersection = 'Intersection'
|
||||
length = 'GLength' # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
|
||||
num_geom = 'NumGeometries'
|
||||
num_points = 'NumPoints'
|
||||
point_on_surface = 'PointOnSurface'
|
||||
scale = 'ScaleCoords'
|
||||
svg = 'AsSVG'
|
||||
sym_difference = 'SymDifference'
|
||||
transform = 'Transform'
|
||||
translate = 'ShiftCoords'
|
||||
union = 'GUnion' # OpenGis defines Union, but this conflicts with an SQLite reserved keyword
|
||||
unionagg = 'GUnion'
|
||||
|
||||
from_text = 'GeomFromText'
|
||||
from_wkb = 'GeomFromWKB'
|
||||
select = 'AsText(%s)'
|
||||
|
||||
geometry_functions = {
|
||||
'equals' : SpatiaLiteFunction('Equals'),
|
||||
'disjoint' : SpatiaLiteFunction('Disjoint'),
|
||||
'touches' : SpatiaLiteFunction('Touches'),
|
||||
'crosses' : SpatiaLiteFunction('Crosses'),
|
||||
'within' : SpatiaLiteFunction('Within'),
|
||||
'overlaps' : SpatiaLiteFunction('Overlaps'),
|
||||
'contains' : SpatiaLiteFunction('Contains'),
|
||||
'intersects' : SpatiaLiteFunction('Intersects'),
|
||||
'relate' : (SpatiaLiteRelate, basestring),
|
||||
# Retruns true if B's bounding box completely contains A's bounding box.
|
||||
'contained' : SpatiaLiteFunction('MbrWithin'),
|
||||
# Returns true if A's bounding box completely contains B's bounding box.
|
||||
'bbcontains' : SpatiaLiteFunction('MbrContains'),
|
||||
# Returns true if A's bounding box overlaps B's bounding box.
|
||||
'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
|
||||
# These are implemented here as synonyms for Equals
|
||||
'same_as' : SpatiaLiteFunction('Equals'),
|
||||
'exact' : SpatiaLiteFunction('Equals'),
|
||||
}
|
||||
|
||||
distance_functions = {
|
||||
'distance_gt' : (get_dist_ops('>'), dtypes),
|
||||
'distance_gte' : (get_dist_ops('>='), dtypes),
|
||||
'distance_lt' : (get_dist_ops('<'), dtypes),
|
||||
'distance_lte' : (get_dist_ops('<='), dtypes),
|
||||
}
|
||||
geometry_functions.update(distance_functions)
|
||||
|
||||
def __init__(self, connection):
|
||||
super(DatabaseOperations, self).__init__()
|
||||
self.connection = connection
|
||||
|
||||
# Load the spatialite library (must be done before getting the
|
||||
# SpatiaLite version).
|
||||
self.connection.load_spatialite()
|
||||
|
||||
try:
|
||||
vtup = self.spatialite_version_tuple()
|
||||
version = vtup[1:]
|
||||
self.spatial_version = version
|
||||
if version < (2, 3, 1):
|
||||
raise Exception('GeoDjango only supports SpatiaLite versions 2.3.1+')
|
||||
except Exception, e:
|
||||
raise
|
||||
|
||||
# Creating the GIS terms dictionary.
|
||||
gis_terms = ['isnull']
|
||||
gis_terms += self.geometry_functions.keys()
|
||||
self.gis_terms = dict([(term, None) for term in gis_terms])
|
||||
|
||||
def check_aggregate_support(self, aggregate):
|
||||
"""
|
||||
Checks if the given aggregate name is supported (that is, if it's
|
||||
in `self.valid_aggregates`).
|
||||
"""
|
||||
agg_name = aggregate.__class__.__name__
|
||||
return agg_name in self.valid_aggregates
|
||||
|
||||
def convert_geom(self, wkt, geo_field):
|
||||
"""
|
||||
Converts geometry WKT returned from a SpatiaLite aggregate.
|
||||
"""
|
||||
if wkt:
|
||||
return Geometry(wkt, geo_field.srid)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_geom_placeholder(self, value, srid):
|
||||
"""
|
||||
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, srid):
|
||||
placeholder = '%s(%%s, %s)' % (self.transform, srid)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
# No geometry value used for F expression, substitue in
|
||||
# the column name instead.
|
||||
return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
|
||||
else:
|
||||
if transform_value(value, srid):
|
||||
# Adding Transform() to the SQL placeholder.
|
||||
return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, srid)
|
||||
else:
|
||||
return '%s(%%s,%s)' % (self.from_text, srid)
|
||||
|
||||
def _get_spatialite_func(self, func):
|
||||
"""
|
||||
Helper routine for calling PostGIS functions and returning their result.
|
||||
"""
|
||||
cursor = self.connection._cursor()
|
||||
try:
|
||||
cursor.execute('SELECT %s()' % func)
|
||||
row = cursor.fetchone()
|
||||
except:
|
||||
# TODO: raise helpful exception here.
|
||||
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 PostGIS version
|
||||
version = self.spatialite_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 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):
|
||||
"""
|
||||
Returns the SpatiaLite-specific SQL for the given lookup value
|
||||
[a tuple of (alias, column, db_type)], lookup type, lookup
|
||||
value, and the model field.
|
||||
"""
|
||||
qn = self.quote_name
|
||||
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(geom, field.srid))
|
||||
elif lookup_type == 'isnull':
|
||||
# Handling 'isnull' lookup type
|
||||
return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||
|
||||
# Routines for getting the OGC-compliant models.
|
||||
def geometry_columns(self):
|
||||
from django.contrib.gis.db.backends.spatialite.models import GeometryColumns
|
||||
return GeometryColumns
|
||||
|
||||
def spatial_ref_sys(self):
|
||||
from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
|
||||
return SpatialRefSys
|
@ -33,37 +33,38 @@ class SpatialOperation(object):
|
||||
"""
|
||||
Base class for generating spatial SQL.
|
||||
"""
|
||||
def __init__(self, function='', operator='', result='', beg_subst='', end_subst=''):
|
||||
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.beg_subst = beg_subst
|
||||
try:
|
||||
# Try and put the operator and result into to the
|
||||
# end substitution.
|
||||
self.end_subst = end_subst % (operator, result)
|
||||
except TypeError:
|
||||
self.end_subst = end_subst
|
||||
self.extra = kwargs
|
||||
|
||||
@property
|
||||
def sql_subst(self):
|
||||
return ''.join([self.beg_subst, self.end_subst])
|
||||
def as_sql(self, geo_col, geometry='%s'):
|
||||
return self.sql_template % self.params(geo_col, geometry)
|
||||
|
||||
def as_sql(self, geo_col):
|
||||
return self.sql_subst % self.params(geo_col)
|
||||
|
||||
def params(self, geo_col):
|
||||
return (geo_col, self.operator)
|
||||
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.
|
||||
"""
|
||||
def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''):
|
||||
# Getting the function prefix.
|
||||
kwargs = {'function' : func, 'operator' : operator, 'result' : result,
|
||||
'beg_subst' : beg_subst, 'end_subst' : end_subst,}
|
||||
super(SpatialFunction, self).__init__(**kwargs)
|
||||
sql_template = '%(function)s(%(geo_col)s, %(geometry)s)'
|
||||
|
||||
def params(self, geo_col):
|
||||
return (self.function, geo_col)
|
||||
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)
|
@ -1,34 +1,17 @@
|
||||
from django.db.models import Aggregate
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models.sql import GeomField
|
||||
|
||||
class GeoAggregate(Aggregate):
|
||||
|
||||
def add_to_query(self, query, alias, col, source, is_summary):
|
||||
if hasattr(source, 'geom_type'):
|
||||
# Doing additional setup on the Query object for spatial aggregates.
|
||||
aggregate = getattr(query.aggregates_module, self.name)
|
||||
|
||||
# Adding a conversion class instance and any selection wrapping
|
||||
# SQL (e.g., needed by Oracle).
|
||||
if aggregate.conversion_class is GeomField:
|
||||
query.extra_select_fields[alias] = GeomField()
|
||||
if SpatialBackend.select:
|
||||
query.custom_select[alias] = SpatialBackend.select
|
||||
|
||||
super(GeoAggregate, self).add_to_query(query, alias, col, source, is_summary)
|
||||
|
||||
class Collect(GeoAggregate):
|
||||
class Collect(Aggregate):
|
||||
name = 'Collect'
|
||||
|
||||
class Extent(GeoAggregate):
|
||||
class Extent(Aggregate):
|
||||
name = 'Extent'
|
||||
|
||||
class Extent3D(GeoAggregate):
|
||||
class Extent3D(Aggregate):
|
||||
name = 'Extent3D'
|
||||
|
||||
class MakeLine(GeoAggregate):
|
||||
class MakeLine(Aggregate):
|
||||
name = 'MakeLine'
|
||||
|
||||
class Union(GeoAggregate):
|
||||
class Union(Aggregate):
|
||||
name = 'Union'
|
||||
|
@ -1,15 +1,19 @@
|
||||
from django.db.models.fields import Field
|
||||
from django.contrib.gis import forms
|
||||
# Getting the SpatialBackend container and the geographic quoting method.
|
||||
from django.contrib.gis.db.backend import SpatialBackend, gqn
|
||||
# GeometryProxy, GEOS, and Distance imports.
|
||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
||||
from django.contrib.gis.geometry import Geometry, GeometryException
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
|
||||
# Local cache of the spatial_ref_sys table, which holds static data.
|
||||
# This exists so that we don't have to hit the database each time.
|
||||
_srid_cache = {}
|
||||
# This exists so that we don't have to hit the database each time
|
||||
# we construct a distance query.
|
||||
_srid_cache = {'postgis' : {},
|
||||
'oracle' : {},
|
||||
'spatialite' : {},
|
||||
}
|
||||
|
||||
def get_srid_info(srid):
|
||||
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
|
||||
@ -17,19 +21,26 @@ def get_srid_info(srid):
|
||||
"""
|
||||
global _srid_cache
|
||||
|
||||
if SpatialBackend.mysql:
|
||||
# No `spatial_ref_sys` table in MySQL.
|
||||
if connection.ops.mysql:
|
||||
return None, None, None
|
||||
|
||||
if not srid in _srid_cache:
|
||||
from django.contrib.gis.models import SpatialRefSys
|
||||
name = connection.ops.name
|
||||
if not srid in _srid_cache[name]:
|
||||
if connection.ops.postgis:
|
||||
from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
|
||||
elif connection.ops.oracle:
|
||||
from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
|
||||
elif connection.ops.spatialite:
|
||||
from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
|
||||
sr = SpatialRefSys.objects.get(srid=srid)
|
||||
units, units_name = sr.units
|
||||
spheroid = SpatialRefSys.get_spheroid(sr.wkt)
|
||||
_srid_cache[srid] = (units, units_name, spheroid)
|
||||
_srid_cache[name][srid] = (units, units_name, spheroid)
|
||||
|
||||
return _srid_cache[srid]
|
||||
return _srid_cache[name][srid]
|
||||
|
||||
class GeometryField(SpatialBackend.Field):
|
||||
class GeometryField(Field):
|
||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
|
||||
# The OpenGIS Geometry name.
|
||||
@ -38,7 +49,8 @@ class GeometryField(SpatialBackend.Field):
|
||||
# Geodetic units.
|
||||
geodetic_units = ('Decimal Degree', 'degree')
|
||||
|
||||
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2, **kwargs):
|
||||
def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
|
||||
**kwargs):
|
||||
"""
|
||||
The initialization function for geometry fields. Takes the following
|
||||
as keyword arguments:
|
||||
@ -54,6 +66,9 @@ class GeometryField(SpatialBackend.Field):
|
||||
|
||||
dim:
|
||||
The number of dimensions for this geometry. Defaults to 2.
|
||||
|
||||
Oracle-specific keywords:
|
||||
extent, tolerance.
|
||||
"""
|
||||
|
||||
# Setting the index flag with the value of the `spatial_index` keyword.
|
||||
@ -70,121 +85,117 @@ class GeometryField(SpatialBackend.Field):
|
||||
# first parameter, so this works like normal fields.
|
||||
kwargs['verbose_name'] = verbose_name
|
||||
|
||||
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
|
||||
# Oracle-specific private attributes for creating the entrie 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)
|
||||
|
||||
# The following properties are used to get the units, their name, and
|
||||
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):
|
||||
def _get_srid_info(self, connection):
|
||||
# Get attributes from `get_srid_info`.
|
||||
self._units, self._units_name, self._spheroid = get_srid_info(self.srid)
|
||||
self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection)
|
||||
|
||||
@property
|
||||
def spheroid(self):
|
||||
def spheroid(self, connection):
|
||||
if not hasattr(self, '_spheroid'):
|
||||
self._get_srid_info()
|
||||
self._get_srid_info(connection)
|
||||
return self._spheroid
|
||||
|
||||
@property
|
||||
def units(self):
|
||||
def units(self, connection):
|
||||
if not hasattr(self, '_units'):
|
||||
self._get_srid_info()
|
||||
self._get_srid_info(connection)
|
||||
return self._units
|
||||
|
||||
@property
|
||||
def units_name(self):
|
||||
def units_name(self, connection):
|
||||
if not hasattr(self, '_units_name'):
|
||||
self._get_srid_info()
|
||||
self._get_srid_info(connection)
|
||||
return self._units_name
|
||||
|
||||
# The following properties are for formerly private variables that are now
|
||||
# public for GeometryField. Because of their use by third-party applications,
|
||||
# a deprecation warning is issued to notify them to use new attribute name.
|
||||
def _deprecated_warning(self, old_name, new_name):
|
||||
from warnings import warn
|
||||
warn('The `%s` attribute name is deprecated, please update your code to use `%s` instead.' %
|
||||
(old_name, new_name))
|
||||
|
||||
@property
|
||||
def _geom(self):
|
||||
self._deprecated_warning('_geom', 'geom_type')
|
||||
return self.geom_type
|
||||
|
||||
@property
|
||||
def _index(self):
|
||||
self._deprecated_warning('_index', 'spatial_index')
|
||||
return self.spatial_index
|
||||
|
||||
@property
|
||||
def _srid(self):
|
||||
self._deprecated_warning('_srid', 'srid')
|
||||
return self.srid
|
||||
|
||||
### Routines specific to GeometryField ###
|
||||
@property
|
||||
def geodetic(self):
|
||||
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 in self.geodetic_units
|
||||
return self.units_name(connection) in self.geodetic_units
|
||||
|
||||
def get_distance(self, dist_val, lookup_type):
|
||||
def get_distance(self, dist_val, 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.
|
||||
"""
|
||||
# Getting the distance parameter and any options.
|
||||
if len(dist_val) == 1: dist, option = dist_val[0], None
|
||||
else: dist, option = dist_val
|
||||
if len(dist_val) == 1:
|
||||
dist, option = dist_val[0], None
|
||||
else:
|
||||
dist, option = dist_val
|
||||
|
||||
if isinstance(dist, Distance):
|
||||
if self.geodetic:
|
||||
if self.geodetic(connection):
|
||||
# Won't allow Distance objects w/DWithin lookups on PostGIS.
|
||||
if SpatialBackend.postgis and lookup_type == 'dwithin':
|
||||
raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
|
||||
if connection.ops.postgis and lookup_type == 'dwithin':
|
||||
raise ValueError('Only numeric values of degree units are allowed on geographic DWithin queries.')
|
||||
|
||||
# Spherical distance calculation parameter should be in meters.
|
||||
dist_param = dist.m
|
||||
else:
|
||||
dist_param = getattr(dist, Distance.unit_attname(self.units_name))
|
||||
dist_param = getattr(dist, Distance.unit_attname(self.units_name(connection)))
|
||||
else:
|
||||
# Assuming the distance is in the units of the field.
|
||||
dist_param = dist
|
||||
|
||||
if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid':
|
||||
if connection.ops.oracle and lookup_type == 'dwithin':
|
||||
dist_param = 'distance=%s' % dist_param
|
||||
|
||||
if connection.ops.postgis and self.geodetic(connection) and lookup_type != 'dwithin' and option == 'spheroid':
|
||||
# On PostGIS, by default `ST_distance_sphere` is used; but if the
|
||||
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
||||
# needs to be passed to the SQL stored procedure.
|
||||
return [gqn(self._spheroid), dist_param]
|
||||
return [self._spheroid, dist_param]
|
||||
else:
|
||||
return [dist_param]
|
||||
|
||||
def get_geometry(self, value):
|
||||
def get_prep_value(self, value):
|
||||
"""
|
||||
Retrieves the geometry, setting the default SRID from the given
|
||||
lookup parameters.
|
||||
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, (tuple, list)):
|
||||
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, SpatialBackend.Geometry):
|
||||
if isinstance(geom, Geometry):
|
||||
pass
|
||||
elif isinstance(geom, basestring):
|
||||
try:
|
||||
geom = SpatialBackend.Geometry(geom)
|
||||
except SpatialBackend.GeometryException:
|
||||
geom = Geometry(geom)
|
||||
except GeometryException:
|
||||
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
|
||||
else:
|
||||
raise TypeError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
|
||||
raise ValueError('Cannot use parameter of `%s` type as lookup parameter.' % type(value))
|
||||
|
||||
# Assigning the SRID value.
|
||||
geom.srid = self.get_srid(geom)
|
||||
|
||||
return geom
|
||||
if seq_value:
|
||||
lookup_val = [geom]
|
||||
lookup_val.extend(value[1:])
|
||||
return tuple(lookup_val)
|
||||
else:
|
||||
return geom
|
||||
|
||||
def get_srid(self, geom):
|
||||
"""
|
||||
@ -203,7 +214,20 @@ class GeometryField(SpatialBackend.Field):
|
||||
super(GeometryField, self).contribute_to_class(cls, name)
|
||||
|
||||
# Setup for lazy-instantiated Geometry object.
|
||||
setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self))
|
||||
setattr(cls, self.attname, GeometryProxy(Geometry, self))
|
||||
|
||||
def db_type(self, connection):
|
||||
if (connection.ops.postgis or
|
||||
connection.ops.spatialite):
|
||||
# Geometry columns on these spatial backends are initialized via
|
||||
# the `AddGeometryColumn` stored procedure.
|
||||
return None
|
||||
elif connection.ops.mysql:
|
||||
return self.geom_type
|
||||
elif connection.ops.oracle:
|
||||
return 'MDSYS.SDO_GEOMETRY'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class' : forms.GeometryField,
|
||||
@ -214,46 +238,50 @@ class GeometryField(SpatialBackend.Field):
|
||||
defaults.update(kwargs)
|
||||
return super(GeometryField, self).formfield(**defaults)
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
"""
|
||||
Returns the spatial WHERE clause and associated parameters for the
|
||||
given lookup type and value. The value will be prepared for database
|
||||
lookup (e.g., spatial transformation SQL will be added if necessary).
|
||||
XXX: Document me.
|
||||
"""
|
||||
if lookup_type in SpatialBackend.gis_terms:
|
||||
if lookup_type in connection.ops.gis_terms:
|
||||
# special case for isnull lookup
|
||||
if lookup_type == 'isnull': return [], []
|
||||
|
||||
# Get the geometry with SRID; defaults SRID to that of the field
|
||||
# if it is None.
|
||||
geom = self.get_geometry(value)
|
||||
|
||||
# Getting the WHERE clause list and the associated params list. The params
|
||||
# list is populated with the Adaptor wrapping the Geometry for the
|
||||
# backend. The WHERE clause list contains the placeholder for the adaptor
|
||||
# (e.g. any transformation SQL).
|
||||
where = [self.get_placeholder(geom)]
|
||||
params = [SpatialBackend.Adaptor(geom)]
|
||||
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)):
|
||||
if lookup_type in SpatialBackend.distance_functions:
|
||||
params = [connection.ops.Adapter(value[0])]
|
||||
if lookup_type in connection.ops.distance_functions:
|
||||
# Getting the distance parameter in the units of the field.
|
||||
where += self.get_distance(value[1:], lookup_type)
|
||||
elif lookup_type in SpatialBackend.limited_where:
|
||||
params += self.get_distance(value[1:], lookup_type, connection)
|
||||
elif lookup_type in connection.ops.limited_where:
|
||||
pass
|
||||
else:
|
||||
# Otherwise, making sure any other parameters are properly quoted.
|
||||
where += map(gqn, value[1:])
|
||||
return where, params
|
||||
params += value[1:]
|
||||
elif isinstance(value, SQLEvaluator):
|
||||
params = []
|
||||
else:
|
||||
params = [connection.ops.Adapter(value)]
|
||||
|
||||
return params
|
||||
else:
|
||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
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 SpatialBackend.Adaptor(self.get_geometry(value))
|
||||
return connection.ops.Adapter(self.get_prep_value(value))
|
||||
|
||||
def get_placeholder(self, value, connection):
|
||||
return connection.ops.get_geom_placeholder(value, self.srid)
|
||||
|
||||
# The OpenGIS Geometry Type Fields
|
||||
class PointField(GeometryField):
|
@ -1,20 +1,19 @@
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import connection
|
||||
from django.db import connections
|
||||
from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
|
||||
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models import aggregates
|
||||
from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
|
||||
from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
|
||||
from django.contrib.gis.geometry import Geometry
|
||||
from django.contrib.gis.measure import Area, Distance
|
||||
|
||||
class GeoQuerySet(QuerySet):
|
||||
"The Geographic QuerySet."
|
||||
|
||||
### Methods overloaded from QuerySet ###
|
||||
def __init__(self, model=None, query=None):
|
||||
def __init__(self, model=None, query=None, using=None):
|
||||
super(GeoQuerySet, self).__init__(model=model, query=query)
|
||||
self.query = query or GeoQuery(self.model, connection)
|
||||
self.query = query or GeoQuery(self.model)
|
||||
|
||||
def values(self, *fields):
|
||||
return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
|
||||
@ -42,14 +41,16 @@ class GeoQuerySet(QuerySet):
|
||||
'geo_field' : geo_field,
|
||||
'setup' : False,
|
||||
}
|
||||
if SpatialBackend.oracle:
|
||||
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 SpatialBackend.postgis or SpatialBackend.spatialite:
|
||||
if not geo_field.geodetic:
|
||||
elif backend.postgis or backend.spatialite:
|
||||
if not geo_field.geodetic(connection):
|
||||
# Getting the area units of the geographic field.
|
||||
s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name))
|
||||
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.')
|
||||
@ -127,7 +128,8 @@ class GeoQuerySet(QuerySet):
|
||||
the coordinate reference system and the bounding box to be included
|
||||
in the GeoJSON representation of the geometry.
|
||||
"""
|
||||
if not SpatialBackend.postgis or not SpatialBackend.geojson:
|
||||
backend = connections[self.db].ops
|
||||
if not backend.geojson:
|
||||
raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
|
||||
|
||||
if not isinstance(precision, (int, long)):
|
||||
@ -135,8 +137,7 @@ class GeoQuerySet(QuerySet):
|
||||
|
||||
# Setting the options flag -- which depends on which version of
|
||||
# PostGIS we're using.
|
||||
major, minor1, minor2 = SpatialBackend.version
|
||||
if major >=1 and (minor1 >= 4):
|
||||
if backend.spatial_version >= (1, 4, 0):
|
||||
options = 0
|
||||
if crs and bbox: options = 3
|
||||
elif bbox: options = 1
|
||||
@ -157,12 +158,12 @@ class GeoQuerySet(QuerySet):
|
||||
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 SpatialBackend.postgis:
|
||||
if backend.postgis:
|
||||
# PostGIS AsGML() aggregate function parameter order depends on the
|
||||
# version -- uggh.
|
||||
major, minor1, minor2 = SpatialBackend.version
|
||||
if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
|
||||
if backend.spatial_version > (1, 3, 1):
|
||||
procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
|
||||
else:
|
||||
procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
|
||||
@ -248,7 +249,7 @@ class GeoQuerySet(QuerySet):
|
||||
Scales the geometry to a new size by multiplying the ordinates
|
||||
with the given x,y,z scale factors.
|
||||
"""
|
||||
if SpatialBackend.spatialite:
|
||||
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',
|
||||
@ -333,7 +334,7 @@ class GeoQuerySet(QuerySet):
|
||||
Translates the geometry to a new location using the given numeric
|
||||
parameters as offsets.
|
||||
"""
|
||||
if SpatialBackend.spatialite:
|
||||
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',
|
||||
@ -368,7 +369,7 @@ class GeoQuerySet(QuerySet):
|
||||
|
||||
# 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)' % (SpatialBackend.transform, geo_col, srid)
|
||||
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
|
||||
@ -396,9 +397,13 @@ class GeoQuerySet(QuerySet):
|
||||
Performs set up for executing the spatial function.
|
||||
"""
|
||||
# Does the spatial backend support this?
|
||||
func = getattr(SpatialBackend, att, False)
|
||||
connection = connections[self.db]
|
||||
func = getattr(connection.ops, att, False)
|
||||
if desc is None: desc = att
|
||||
if not func: raise ImproperlyConfigured('%s stored procedure not available.' % desc)
|
||||
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}
|
||||
@ -442,7 +447,7 @@ class GeoQuerySet(QuerySet):
|
||||
# Adding any keyword parameters for the Aggregate object. Oracle backends
|
||||
# in particular need an additional `tolerance` parameter.
|
||||
agg_kwargs = {}
|
||||
if SpatialBackend.oracle: agg_kwargs['tolerance'] = tolerance
|
||||
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']
|
||||
@ -479,6 +484,9 @@ class GeoQuerySet(QuerySet):
|
||||
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)
|
||||
@ -491,13 +499,16 @@ class GeoQuerySet(QuerySet):
|
||||
|
||||
# Special handling for any argument that is a geometry.
|
||||
for name in settings['geom_args']:
|
||||
# Using the field's get_db_prep_lookup() to get any needed
|
||||
# transformation SQL -- we pass in a 'dummy' `contains` lookup.
|
||||
where, params = geo_field.get_db_prep_lookup('contains', settings['procedure_args'][name])
|
||||
# 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 = where[0] % '%%s'
|
||||
new_fmt = geom_placeholder % '%%s'
|
||||
settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
|
||||
settings['select_params'].extend(params)
|
||||
|
||||
@ -507,8 +518,10 @@ class GeoQuerySet(QuerySet):
|
||||
# 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 SpatialBackend.select:
|
||||
self.query.custom_select[model_att] = SpatialBackend.select
|
||||
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
|
||||
@ -527,10 +540,13 @@ class GeoQuerySet(QuerySet):
|
||||
# If geodetic defaulting distance attribute to meters (Oracle and
|
||||
# PostGIS spherical distances return meters). Otherwise, use the
|
||||
# units of the geometry field.
|
||||
if geo_field.geodetic:
|
||||
connection = connections[self.db]
|
||||
geodetic = geo_field.geodetic(connection)
|
||||
|
||||
if geodetic:
|
||||
dist_att = 'm'
|
||||
else:
|
||||
dist_att = Distance.unit_attname(geo_field.units_name)
|
||||
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.
|
||||
@ -546,19 +562,23 @@ class GeoQuerySet(QuerySet):
|
||||
# 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 (SpatialBackend.postgis and geo_field.geodetic and length):
|
||||
if spheroid or (backend.postgis and geodetic and length):
|
||||
lookup_params.append('spheroid')
|
||||
where, params = geo_field.get_db_prep_lookup('distance_lte', lookup_params)
|
||||
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 SpatialBackend.oracle:
|
||||
if backend.oracle:
|
||||
if distance:
|
||||
procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
|
||||
elif length or perimeter:
|
||||
@ -568,12 +588,10 @@ class GeoQuerySet(QuerySet):
|
||||
# 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)
|
||||
u, unit_name, s = get_srid_info(self.query.transformed_srid, connection)
|
||||
geodetic = unit_name in geo_field.geodetic_units
|
||||
else:
|
||||
geodetic = geo_field.geodetic
|
||||
|
||||
if SpatialBackend.spatialite and geodetic:
|
||||
if backend.spatialite and geodetic:
|
||||
raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
|
||||
|
||||
if distance:
|
||||
@ -583,14 +601,14 @@ class GeoQuerySet(QuerySet):
|
||||
# (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)' % (SpatialBackend.transform, self.query.transformed_srid)
|
||||
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 SpatialBackend.spatialite:
|
||||
procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.from_text, self.query.transformed_srid)
|
||||
if backend.spatialite:
|
||||
procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid)
|
||||
else:
|
||||
procedure_fmt += ', %%s'
|
||||
else:
|
||||
@ -598,11 +616,11 @@ class GeoQuerySet(QuerySet):
|
||||
# so wrapping the geometry placeholder in transformation SQL.
|
||||
# SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
|
||||
# constructor.
|
||||
if SpatialBackend.spatialite:
|
||||
procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (SpatialBackend.transform, SpatialBackend.from_text,
|
||||
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)' % (SpatialBackend.transform, self.query.transformed_srid)
|
||||
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'
|
||||
@ -614,29 +632,29 @@ class GeoQuerySet(QuerySet):
|
||||
# some error checking is required.
|
||||
if not isinstance(geo_field, PointField):
|
||||
raise ValueError('Spherical distance calculation only supported on PointFields.')
|
||||
if not str(SpatialBackend.Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
|
||||
if not str(Geometry(buffer(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' : SpatialBackend.distance_spheroid, 'spheroid' : where[1]})
|
||||
procedure_fmt += ",'%(spheroid)s'"
|
||||
procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]})
|
||||
else:
|
||||
procedure_args.update({'function' : SpatialBackend.distance_sphere})
|
||||
procedure_args.update({'function' : backend.distance_sphere})
|
||||
elif length or perimeter:
|
||||
procedure_fmt = '%(geo_col)s'
|
||||
if geodetic and length:
|
||||
# There's no `length_sphere`, and `length_spheroid` also
|
||||
# works on 3D geometries.
|
||||
procedure_fmt += ',%(spheroid)s'
|
||||
procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
|
||||
elif geom_3d and SpatialBackend.postgis:
|
||||
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' : SpatialBackend.perimeter3d})
|
||||
procedure_args.update({'function' : backend.perimeter3d})
|
||||
elif length:
|
||||
procedure_args.update({'function' : SpatialBackend.length3d})
|
||||
procedure_args.update({'function' : backend.length3d})
|
||||
|
||||
# Setting up the settings for `_spatial_attribute`.
|
||||
s = {'select_field' : DistanceField(dist_att),
|
||||
@ -651,7 +669,7 @@ class GeoQuerySet(QuerySet):
|
||||
elif geom:
|
||||
# The geometry is passed in as a parameter because we handled
|
||||
# transformation conditions in this routine.
|
||||
s['select_params'] = [SpatialBackend.Adaptor(geom)]
|
||||
s['select_params'] = [backend.Adapter(geom)]
|
||||
return self._spatial_attribute(func, s, **kwargs)
|
||||
|
||||
def _geom_attribute(self, func, tolerance=0.05, **kwargs):
|
||||
@ -660,7 +678,7 @@ class GeoQuerySet(QuerySet):
|
||||
Geometry attribute (e.g., `centroid`, `point_on_surface`).
|
||||
"""
|
||||
s = {'select_field' : GeomField(),}
|
||||
if SpatialBackend.oracle:
|
||||
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)
|
||||
@ -677,7 +695,7 @@ class GeoQuerySet(QuerySet):
|
||||
'procedure_fmt' : '%(geo_col)s,%(geom)s',
|
||||
'procedure_args' : {'geom' : geom},
|
||||
}
|
||||
if SpatialBackend.oracle:
|
||||
if connections[self.db].ops.oracle:
|
||||
s['procedure_fmt'] += ',%(tolerance)s'
|
||||
s['procedure_args']['tolerance'] = tolerance
|
||||
return self._spatial_attribute(func, s, **kwargs)
|
||||
@ -694,16 +712,17 @@ class GeoQuerySet(QuerySet):
|
||||
# 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])
|
||||
self.query.pre_sql_setup()
|
||||
compiler = self.query.get_compiler(self.db)
|
||||
compiler.pre_sql_setup()
|
||||
rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
|
||||
return self.query._field_column(geo_field, rel_table)
|
||||
return compiler._field_column(geo_field, rel_table)
|
||||
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._field_column(geo_field, parent_model._meta.db_table)
|
||||
return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
|
||||
else:
|
||||
return self.query._field_column(geo_field)
|
||||
return self.query.get_compiler(self.db)._field_column(geo_field)
|
||||
|
||||
class GeoValuesQuerySet(ValuesQuerySet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -1,84 +1,10 @@
|
||||
from django.db.models.sql.aggregates import *
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.gis.db.models.sql.conversion import GeomField
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
|
||||
# Default SQL template for spatial aggregates.
|
||||
geo_template = '%(function)s(%(field)s)'
|
||||
|
||||
# Default conversion functions for aggregates; will be overridden if implemented
|
||||
# for the spatial backend.
|
||||
def convert_extent(box):
|
||||
raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
|
||||
|
||||
def convert_extent3d(box):
|
||||
raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
|
||||
|
||||
def convert_geom(wkt, geo_field):
|
||||
raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
|
||||
|
||||
if SpatialBackend.postgis:
|
||||
def convert_extent(box):
|
||||
# Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";
|
||||
# parsing out and returning as a 4-tuple.
|
||||
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(box3d):
|
||||
# Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
|
||||
# parsing out and returning as a 4-tuple.
|
||||
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(hex, geo_field):
|
||||
if hex: return SpatialBackend.Geometry(hex)
|
||||
else: return None
|
||||
elif SpatialBackend.oracle:
|
||||
# Oracle spatial aggregates need a tolerance.
|
||||
geo_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'
|
||||
|
||||
def convert_extent(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 = SpatialBackend.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(clob, geo_field):
|
||||
if clob:
|
||||
return SpatialBackend.Geometry(clob.read(), geo_field.srid)
|
||||
else:
|
||||
return None
|
||||
elif SpatialBackend.spatialite:
|
||||
# SpatiaLite returns WKT.
|
||||
def convert_geom(wkt, geo_field):
|
||||
if wkt:
|
||||
return SpatialBackend.Geometry(wkt, geo_field.srid)
|
||||
else:
|
||||
return None
|
||||
|
||||
class GeoAggregate(Aggregate):
|
||||
# Overriding the SQL template with the geographic one.
|
||||
sql_template = geo_template
|
||||
# Default SQL template for spatial aggregates.
|
||||
sql_template = '%(function)s(%(field)s)'
|
||||
|
||||
# Conversion class, if necessary.
|
||||
conversion_class = None
|
||||
@ -86,41 +12,50 @@ class GeoAggregate(Aggregate):
|
||||
# Flags for indicating the type of the aggregate.
|
||||
is_extent = False
|
||||
|
||||
def __init__(self, col, source=None, is_summary=False, **extra):
|
||||
def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra):
|
||||
super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
|
||||
|
||||
if not self.is_extent and SpatialBackend.oracle:
|
||||
self.extra.setdefault('tolerance', 0.05)
|
||||
# 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.')
|
||||
|
||||
# Making sure the SQL function is available for this spatial backend.
|
||||
if not self.sql_function:
|
||||
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
|
||||
def as_sql(self, qn, connection):
|
||||
"Return the aggregate, rendered as SQL."
|
||||
|
||||
if connection.ops.oracle:
|
||||
self.extra['tolerance'] = self.tolerance
|
||||
|
||||
if hasattr(self.col, 'as_sql'):
|
||||
field_name = 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)
|
||||
|
||||
params = {
|
||||
'function': sql_function,
|
||||
'field': field_name
|
||||
}
|
||||
params.update(self.extra)
|
||||
|
||||
return sql_template % params
|
||||
|
||||
class Collect(GeoAggregate):
|
||||
conversion_class = GeomField
|
||||
sql_function = SpatialBackend.collect
|
||||
pass
|
||||
|
||||
class Extent(GeoAggregate):
|
||||
is_extent = '2D'
|
||||
sql_function = SpatialBackend.extent
|
||||
|
||||
if SpatialBackend.oracle:
|
||||
# Have to change Extent's attributes here for Oracle.
|
||||
Extent.conversion_class = GeomField
|
||||
Extent.sql_template = '%(function)s(%(field)s)'
|
||||
|
||||
class Extent3D(GeoAggregate):
|
||||
is_extent = '3D'
|
||||
sql_function = SpatialBackend.extent3d
|
||||
|
||||
class MakeLine(GeoAggregate):
|
||||
conversion_class = GeomField
|
||||
sql_function = SpatialBackend.make_line
|
||||
pass
|
||||
|
||||
class Union(GeoAggregate):
|
||||
conversion_class = GeomField
|
||||
sql_function = SpatialBackend.unionagg
|
||||
pass
|
||||
|
276
django/contrib/gis/db/models/sql/compiler.py
Normal file
276
django/contrib/gis/db/models/sql/compiler.py
Normal file
@ -0,0 +1,276 @@
|
||||
from itertools import izip
|
||||
from django.db.backends.util import truncate_name
|
||||
from django.db.models.sql import compiler
|
||||
from django.db.models.sql.constants import TABLE_NAME
|
||||
from django.db.models.sql.query import get_proxied_model
|
||||
|
||||
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 self.query.extra_select.iteritems()]
|
||||
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 izip(self.query.select, self.query.select_fields):
|
||||
if isinstance(col, (list, tuple)):
|
||||
alias, column = col
|
||||
table = self.query.alias_map[alias][TABLE_NAME]
|
||||
if table in only_load and col 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:
|
||||
result.append(col.as_sql(qn=qn))
|
||||
|
||||
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()
|
||||
result.extend([
|
||||
'%s%s' % (
|
||||
self.get_extra_select_format(alias) % aggregate.as_sql(qn=qn, connection=self.connection),
|
||||
alias is not None
|
||||
and ' AS %s' % qn(truncate_name(alias, max_name_length))
|
||||
or ''
|
||||
)
|
||||
for alias, aggregate in self.query.aggregate_select.items()
|
||||
])
|
||||
|
||||
# This loop customized for GeoQuery.
|
||||
for (table, col), field in izip(self.query.related_select_cols, self.query.related_select_fields):
|
||||
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
|
||||
|
||||
def get_default_columns(self, with_aliases=False, col_aliases=None,
|
||||
start_alias=None, opts=None, as_pairs=False):
|
||||
"""
|
||||
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.model._meta
|
||||
aliases = set()
|
||||
only_load = self.deferred_to_columns()
|
||||
# Skip all proxy to the root proxied model
|
||||
proxied_model = get_proxied_model(opts)
|
||||
|
||||
if start_alias:
|
||||
seen = {None: start_alias}
|
||||
for field, model in opts.get_fields_with_model():
|
||||
if start_alias:
|
||||
try:
|
||||
alias = seen[model]
|
||||
except KeyError:
|
||||
if model is proxied_model:
|
||||
alias = start_alias
|
||||
else:
|
||||
link_field = opts.get_ancestor_link(model)
|
||||
alias = self.query.join((start_alias, model._meta.db_table,
|
||||
link_field.column, model._meta.pk.column))
|
||||
seen[model] = alias
|
||||
else:
|
||||
# If we're starting from the base model of the queryset, the
|
||||
# aliases will have already been set up in pre_sql_setup(), so
|
||||
# we can save time here.
|
||||
alias = self.query.included_inherited_models[model]
|
||||
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.column))
|
||||
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 = self.query.extra_select.keys()
|
||||
if self.query.aggregates:
|
||||
# If we have an aggregate annotation, must extend the aliases
|
||||
# so their corresponding row values are included.
|
||||
aliases.extend([None for i in xrange(len(self.query.aggregates))])
|
||||
|
||||
# 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 izip(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 izip(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 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.model._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):
|
||||
pass
|
@ -2,15 +2,13 @@
|
||||
This module holds simple classes used by GeoQuery.convert_values
|
||||
to convert geospatial values from the database.
|
||||
"""
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
|
||||
class BaseField(object):
|
||||
empty_strings_allowed = True
|
||||
def get_internal_type(self):
|
||||
"Overloaded method so OracleQuery.convert_values doesn't balk."
|
||||
return None
|
||||
|
||||
if SpatialBackend.oracle: BaseField.empty_strings_allowed = False
|
||||
|
||||
class AreaField(BaseField):
|
||||
"Wrapper for Area values."
|
||||
def __init__(self, area_att):
|
||||
|
@ -1,21 +1,25 @@
|
||||
from itertools import izip
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db.models.query import sql
|
||||
from django.db.models.fields.related import ForeignKey
|
||||
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.gis.db.models.sql import aggregates as gis_aggregates_module
|
||||
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 import Geometry
|
||||
from django.contrib.gis.measure import Area, Distance
|
||||
|
||||
# Valid GIS query types.
|
||||
ALL_TERMS = sql.constants.QUERY_TERMS.copy()
|
||||
ALL_TERMS.update(SpatialBackend.gis_terms)
|
||||
|
||||
# Pulling out other needed constants/routines to avoid attribute lookups.
|
||||
TABLE_NAME = sql.constants.TABLE_NAME
|
||||
get_proxied_model = sql.query.get_proxied_model
|
||||
ALL_TERMS = dict([(x, None) for x in (
|
||||
'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):
|
||||
"""
|
||||
@ -23,11 +27,13 @@ class GeoQuery(sql.Query):
|
||||
"""
|
||||
# Overridding the valid query terms.
|
||||
query_terms = ALL_TERMS
|
||||
aggregates_module = gis_aggregates_module
|
||||
aggregates_module = gis_aggregates
|
||||
|
||||
compiler = 'GeoSQLCompiler'
|
||||
|
||||
#### Methods overridden from the base Query class ####
|
||||
def __init__(self, model, conn):
|
||||
super(GeoQuery, self).__init__(model, conn, where=GeoWhereNode)
|
||||
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.
|
||||
@ -35,13 +41,6 @@ class GeoQuery(sql.Query):
|
||||
self.transformed_srid = None
|
||||
self.extra_select_fields = {}
|
||||
|
||||
if SpatialBackend.oracle:
|
||||
# Have to override this so that GeoQuery, instead of OracleQuery,
|
||||
# is returned when unpickling.
|
||||
def __reduce__(self):
|
||||
callable, args, data = super(GeoQuery, self).__reduce__()
|
||||
return (unpickle_geoquery, (), data)
|
||||
|
||||
def clone(self, *args, **kwargs):
|
||||
obj = super(GeoQuery, self).clone(*args, **kwargs)
|
||||
# Customized selection dictionary and transformed srid flag have
|
||||
@ -51,199 +50,14 @@ class GeoQuery(sql.Query):
|
||||
obj.extra_select_fields = self.extra_select_fields.copy()
|
||||
return obj
|
||||
|
||||
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 self.extra_select.iteritems()]
|
||||
aliases = set(self.extra_select.keys())
|
||||
if with_aliases:
|
||||
col_aliases = aliases.copy()
|
||||
else:
|
||||
col_aliases = set()
|
||||
if self.select:
|
||||
only_load = self.deferred_to_columns()
|
||||
# This loop customized for GeoQuery.
|
||||
for col, field in izip(self.select, self.select_fields):
|
||||
if isinstance(col, (list, tuple)):
|
||||
alias, column = col
|
||||
table = self.alias_map[alias][TABLE_NAME]
|
||||
if table in only_load and col 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:
|
||||
result.append(col.as_sql(qn=qn))
|
||||
|
||||
if hasattr(col, 'alias'):
|
||||
aliases.add(col.alias)
|
||||
col_aliases.add(col.alias)
|
||||
|
||||
elif self.default_cols:
|
||||
cols, new_aliases = self.get_default_columns(with_aliases,
|
||||
col_aliases)
|
||||
result.extend(cols)
|
||||
aliases.update(new_aliases)
|
||||
|
||||
result.extend([
|
||||
'%s%s' % (
|
||||
self.get_extra_select_format(alias) % aggregate.as_sql(qn=qn, connection=self.connection),
|
||||
alias is not None and ' AS %s' % alias or ''
|
||||
)
|
||||
for alias, aggregate in self.aggregate_select.items()
|
||||
])
|
||||
|
||||
# This loop customized for GeoQuery.
|
||||
for (table, col), field in izip(self.related_select_cols, self.related_select_fields):
|
||||
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
|
||||
|
||||
def get_default_columns(self, with_aliases=False, col_aliases=None,
|
||||
start_alias=None, opts=None, as_pairs=False):
|
||||
"""
|
||||
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.model._meta
|
||||
aliases = set()
|
||||
only_load = self.deferred_to_columns()
|
||||
# Skip all proxy to the root proxied model
|
||||
proxied_model = get_proxied_model(opts)
|
||||
|
||||
if start_alias:
|
||||
seen = {None: start_alias}
|
||||
for field, model in opts.get_fields_with_model():
|
||||
if start_alias:
|
||||
try:
|
||||
alias = seen[model]
|
||||
except KeyError:
|
||||
if model is proxied_model:
|
||||
alias = start_alias
|
||||
else:
|
||||
link_field = opts.get_ancestor_link(model)
|
||||
alias = self.join((start_alias, model._meta.db_table,
|
||||
link_field.column, model._meta.pk.column))
|
||||
seen[model] = alias
|
||||
else:
|
||||
# If we're starting from the base model of the queryset, the
|
||||
# aliases will have already been set up in pre_sql_setup(), so
|
||||
# we can save time here.
|
||||
alias = self.included_inherited_models[model]
|
||||
table = self.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.column))
|
||||
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 = self.extra_select.keys()
|
||||
if self.aggregates:
|
||||
# If we have an aggregate annotation, must extend the aliases
|
||||
# so their corresponding row values are included.
|
||||
aliases.extend([None for i in xrange(len(self.aggregates))])
|
||||
|
||||
# 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 SpatialBackend.oracle:
|
||||
if self.high_mark is not None or self.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.convert_values(v, self.extra_select_fields.get(a, None))
|
||||
for v, a in izip(row[rn_offset:index_start], aliases)]
|
||||
if SpatialBackend.oracle or getattr(self, '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 izip(row[index_start:], fields):
|
||||
values.append(self.convert_values(value, field))
|
||||
else:
|
||||
values.extend(row[index_start:])
|
||||
return tuple(values)
|
||||
|
||||
def convert_values(self, value, field):
|
||||
"""
|
||||
Using the same routines that Oracle does we can convert our
|
||||
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 SpatialBackend.oracle:
|
||||
if connection.ops.oracle:
|
||||
# Running through Oracle's first.
|
||||
value = super(GeoQuery, self).convert_values(value, field or GeomField())
|
||||
value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
|
||||
|
||||
if isinstance(field, DistanceField):
|
||||
# Using the field's distance attribute, can instantiate
|
||||
@ -252,10 +66,20 @@ class GeoQuery(sql.Query):
|
||||
elif isinstance(field, AreaField):
|
||||
value = Area(**{field.area_att : value})
|
||||
elif isinstance(field, (GeomField, GeometryField)) and value:
|
||||
value = SpatialBackend.Geometry(value)
|
||||
value = Geometry(value)
|
||||
return value
|
||||
|
||||
def resolve_aggregate(self, value, aggregate):
|
||||
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.
|
||||
@ -263,77 +87,15 @@ class GeoQuery(sql.Query):
|
||||
if isinstance(aggregate, self.aggregates_module.GeoAggregate):
|
||||
if aggregate.is_extent:
|
||||
if aggregate.is_extent == '3D':
|
||||
return self.aggregates_module.convert_extent3d(value)
|
||||
return connection.ops.convert_extent3d(value)
|
||||
else:
|
||||
return self.aggregates_module.convert_extent(value)
|
||||
return connection.ops.convert_extent(value)
|
||||
else:
|
||||
return self.aggregates_module.convert_geom(value, aggregate.source)
|
||||
return connection.ops.convert_geom(value, aggregate.source)
|
||||
else:
|
||||
return super(GeoQuery, self).resolve_aggregate(value, aggregate)
|
||||
|
||||
#### Routines unique to GeoQuery ####
|
||||
def get_extra_select_format(self, alias):
|
||||
sel_fmt = '%s'
|
||||
if alias in self.custom_select:
|
||||
sel_fmt = sel_fmt % self.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.custom_select:
|
||||
field_sel = sel_fmt % self.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 SpatialBackend.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 = SpatialBackend.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.transformed_srid and ( SpatialBackend.oracle or
|
||||
SpatialBackend.spatialite ):
|
||||
sel_fmt = "'SRID=%d;'||%s" % (self.transformed_srid, sel_fmt)
|
||||
else:
|
||||
sel_fmt = '%s'
|
||||
return sel_fmt
|
||||
return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
|
||||
|
||||
# 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.model._meta.db_table
|
||||
return "%s.%s" % (self.quote_name_unless_alias(table_alias),
|
||||
self.connection.ops.quote_name(column or field.column))
|
||||
|
||||
def _geo_field(self, field_name=None):
|
||||
"""
|
||||
Returns the first Geometry field encountered; or specified via the
|
||||
@ -350,12 +112,3 @@ class GeoQuery(sql.Query):
|
||||
# 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)
|
||||
|
||||
if SpatialBackend.oracle:
|
||||
def unpickle_geoquery():
|
||||
"""
|
||||
Utility function, called by Python's unpickling machinery, that handles
|
||||
unpickling of GeoQuery subclasses of OracleQuery.
|
||||
"""
|
||||
return GeoQuery.__new__(GeoQuery)
|
||||
unpickle_geoquery.__safe_for_unpickling__ = True
|
||||
|
@ -1,39 +1,43 @@
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.db.models.query import insert_query
|
||||
from django.db import connections
|
||||
from django.db.models.sql.subqueries import InsertQuery
|
||||
|
||||
if SpatialBackend.oracle:
|
||||
from django.db import connection
|
||||
from django.db.models.sql.subqueries import InsertQuery
|
||||
class GeoInsertQuery(InsertQuery):
|
||||
def insert_values(self, insert_values, connection, raw_values=False):
|
||||
"""
|
||||
Set up the insert query from the 'insert_values' dictionary. The
|
||||
dictionary gives the model field names and their target values.
|
||||
|
||||
class OracleGeoInsertQuery(InsertQuery):
|
||||
def insert_values(self, insert_values, raw_values=False):
|
||||
"""
|
||||
This routine is overloaded from InsertQuery so that no parameter is
|
||||
passed into cx_Oracle for NULL geometries. The reason is that
|
||||
cx_Oracle has no way to bind Oracle object values (like
|
||||
MDSYS.SDO_GEOMETRY).
|
||||
"""
|
||||
placeholders, values = [], []
|
||||
for field, val in insert_values:
|
||||
if hasattr(field, 'get_placeholder'):
|
||||
ph = field.get_placeholder(val)
|
||||
else:
|
||||
ph = '%s'
|
||||
|
||||
placeholders.append(ph)
|
||||
self.columns.append(field.column)
|
||||
|
||||
# If 'NULL' for the placeholder, omit appending None
|
||||
# to the values list (which is used for db params).
|
||||
if not ph == 'NULL':
|
||||
values.append(val)
|
||||
if raw_values:
|
||||
self.values.extend(values)
|
||||
If 'raw_values' is True, the values in the 'insert_values' dictionary
|
||||
are inserted directly into the query, rather than passed as SQL
|
||||
parameters. This provides a way to insert NULL and DEFAULT keywords
|
||||
into the query, for example.
|
||||
"""
|
||||
placeholders, values = [], []
|
||||
for field, val in insert_values:
|
||||
if hasattr(field, 'get_placeholder'):
|
||||
# Some fields (e.g. geo fields) need special munging before
|
||||
# they can be inserted.
|
||||
placeholders.append(field.get_placeholder(val, connection))
|
||||
else:
|
||||
self.params += tuple(values)
|
||||
self.values.extend(placeholders)
|
||||
placeholders.append('%s')
|
||||
|
||||
def insert_query(model, values, return_id=False, raw_values=False):
|
||||
query = OracleGeoInsertQuery(model, connection)
|
||||
query.insert_values(values, raw_values)
|
||||
return query.execute_sql(return_id)
|
||||
self.columns.append(field.column)
|
||||
|
||||
if not placeholders[-1] == 'NULL':
|
||||
values.append(val)
|
||||
if raw_values:
|
||||
self.values.extend(values)
|
||||
else:
|
||||
self.params += tuple(values)
|
||||
self.values.extend(placeholders)
|
||||
|
||||
def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
||||
"""
|
||||
Inserts a new record for the given model. This provides an interface to
|
||||
the InsertQuery class and is how Model.save() is implemented. It is not
|
||||
part of the public API.
|
||||
"""
|
||||
query = GeoInsertQuery(model)
|
||||
compiler = query.get_compiler(using=using)
|
||||
query.insert_values(values, compiler.connection, raw_values)
|
||||
return compiler.execute_sql(return_id)
|
||||
|
@ -1,23 +1,31 @@
|
||||
from django.db import connection
|
||||
from django.db.models.fields import Field, FieldDoesNotExist
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import WhereNode
|
||||
from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
|
||||
from django.db.models.sql.where import Constraint, WhereNode
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
qn = connection.ops.quote_name
|
||||
|
||||
class GeoAnnotation(object):
|
||||
class GeoConstraint(Constraint):
|
||||
"""
|
||||
The annotation used for GeometryFields; basically a placeholder
|
||||
for metadata needed by the `get_geo_where_clause` of the spatial
|
||||
backend.
|
||||
This subclass overrides `process` to better handle geographic SQL
|
||||
construction.
|
||||
"""
|
||||
def __init__(self, field, value, where):
|
||||
self.geodetic = field.geodetic
|
||||
self.geom_type = field.geom_type
|
||||
self.value = value
|
||||
self.where = tuple(where)
|
||||
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):
|
||||
"""
|
||||
@ -25,75 +33,20 @@ class GeoWhereNode(WhereNode):
|
||||
these are tied to the GeoQuery class that created it.
|
||||
"""
|
||||
def add(self, data, connector):
|
||||
"""
|
||||
This is overridden from the regular WhereNode to handle the
|
||||
peculiarties of GeometryFields, because they need a special
|
||||
annotation object that contains the spatial metadata from the
|
||||
field to generate the spatial SQL.
|
||||
"""
|
||||
if not isinstance(data, (list, tuple)):
|
||||
return super(WhereNode, self).add(data, connector)
|
||||
|
||||
obj, lookup_type, value = data
|
||||
col, field = obj.col, obj.field
|
||||
|
||||
if not hasattr(field, "geom_type"):
|
||||
# Not a geographic field, so call `WhereNode.add`.
|
||||
return super(GeoWhereNode, self).add(data, connector)
|
||||
else:
|
||||
if isinstance(value, SQLEvaluator):
|
||||
# Getting the geographic field to compare with from the expression.
|
||||
geo_fld = self._check_geo_field(value.opts, value.expression.name)
|
||||
if not geo_fld:
|
||||
raise ValueError('No geographic field found in expression.')
|
||||
|
||||
# Get the SRID of the geometry field that the expression was meant
|
||||
# to operate on -- it's needed to determine whether transformation
|
||||
# SQL is necessary.
|
||||
srid = geo_fld.srid
|
||||
|
||||
# Getting the quoted representation of the geometry column that
|
||||
# the expression is operating on.
|
||||
geo_col = '%s.%s' % tuple(map(qn, value.cols[value.expression]))
|
||||
|
||||
# If it's in a different SRID, we'll need to wrap in
|
||||
# transformation SQL.
|
||||
if not srid is None and srid != field.srid and SpatialBackend.transform:
|
||||
placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
|
||||
# Setting these up as if we had called `field.get_db_prep_lookup()`.
|
||||
where = [placeholder % geo_col]
|
||||
params = ()
|
||||
else:
|
||||
# `GeometryField.get_db_prep_lookup` returns a where clause
|
||||
# substitution array in addition to the parameters.
|
||||
where, params = field.get_db_prep_lookup(lookup_type, value)
|
||||
|
||||
# The annotation will be a `GeoAnnotation` object that
|
||||
# will contain the necessary geometry field metadata for
|
||||
# the `get_geo_where_clause` to construct the appropriate
|
||||
# spatial SQL when `make_atom` is called.
|
||||
annotation = GeoAnnotation(field, value, where)
|
||||
return super(WhereNode, self).add(((obj.alias, col, field.db_type()), lookup_type, annotation, params), connector)
|
||||
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)
|
||||
super(GeoWhereNode, self).add(data, connector)
|
||||
|
||||
def make_atom(self, child, qn, connection):
|
||||
obj, lookup_type, value_annot, params = child
|
||||
|
||||
if isinstance(value_annot, GeoAnnotation):
|
||||
if lookup_type in SpatialBackend.gis_terms:
|
||||
# Getting the geographic where clause; substitution parameters
|
||||
# will be populated in the GeoFieldSQL object returned by the
|
||||
# GeometryField.
|
||||
alias, col, db_type = obj
|
||||
gwc = get_geo_where_clause(alias, col, lookup_type, value_annot)
|
||||
return gwc % value_annot.where, params
|
||||
else:
|
||||
raise TypeError('Invalid lookup type: %r' % lookup_type)
|
||||
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 = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field)
|
||||
return spatial_sql, params
|
||||
else:
|
||||
# If not a GeometryField, call the `make_atom` from the
|
||||
# base class.
|
||||
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
||||
|
||||
@classmethod
|
||||
|
9
django/contrib/gis/geometry/__init__.py
Normal file
9
django/contrib/gis/geometry/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.conf import settings
|
||||
|
||||
__all__ = ['Geometry', 'GeometryException']
|
||||
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
|
||||
Geometry = GEOSGeometry
|
||||
GeometryException = GEOSException
|
||||
|
@ -1,233 +0,0 @@
|
||||
"""
|
||||
Imports the SpatialRefSys and GeometryColumns models dependent on the
|
||||
spatial database backend.
|
||||
"""
|
||||
import re
|
||||
from django.conf import settings
|
||||
|
||||
# Checking for the presence of GDAL (needed for the SpatialReference object)
|
||||
from django.contrib.gis.gdal import HAS_GDAL, PYTHON23
|
||||
if HAS_GDAL:
|
||||
from django.contrib.gis.gdal import SpatialReference
|
||||
|
||||
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+)"\])?\]$')
|
||||
|
||||
def srs(self):
|
||||
"""
|
||||
Returns a GDAL SpatialReference object, if GDAL is installed.
|
||||
"""
|
||||
if 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 = SpatialReference(self.wkt)
|
||||
return self.srs
|
||||
except Exception, msg:
|
||||
pass
|
||||
|
||||
try:
|
||||
self._srs = SpatialReference(self.proj4text)
|
||||
return self.srs
|
||||
except Exception, 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.')
|
||||
srs = property(srs)
|
||||
|
||||
def ellipsoid(self):
|
||||
"""
|
||||
Returns a tuple of the ellipsoid parameters:
|
||||
(semimajor axis, semiminor axis, and inverse flattening).
|
||||
"""
|
||||
if 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
|
||||
ellipsoid = property(ellipsoid)
|
||||
|
||||
def name(self):
|
||||
"Returns the projection name."
|
||||
return self.srs.name
|
||||
name = property(name)
|
||||
|
||||
def spheroid(self):
|
||||
"Returns the spheroid name for this spatial reference."
|
||||
return self.srs['spheroid']
|
||||
spheroid = property(spheroid)
|
||||
|
||||
def datum(self):
|
||||
"Returns the datum for this spatial reference."
|
||||
return self.srs['datum']
|
||||
datum = property(datum)
|
||||
|
||||
def projected(self):
|
||||
"Is this Spatial Reference projected?"
|
||||
if HAS_GDAL:
|
||||
return self.srs.projected
|
||||
else:
|
||||
return self.wkt.startswith('PROJCS')
|
||||
projected = property(projected)
|
||||
|
||||
def local(self):
|
||||
"Is this Spatial Reference local?"
|
||||
if HAS_GDAL:
|
||||
return self.srs.local
|
||||
else:
|
||||
return self.wkt.startswith('LOCAL_CS')
|
||||
local = property(local)
|
||||
|
||||
def geographic(self):
|
||||
"Is this Spatial Reference geographic?"
|
||||
if HAS_GDAL:
|
||||
return self.srs.geographic
|
||||
else:
|
||||
return self.wkt.startswith('GEOGCS')
|
||||
geographic = property(geographic)
|
||||
|
||||
def linear_name(self):
|
||||
"Returns the linear units name."
|
||||
if 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')
|
||||
linear_name = property(linear_name)
|
||||
|
||||
def linear_units(self):
|
||||
"Returns the linear units."
|
||||
if HAS_GDAL:
|
||||
return self.srs.linear_units
|
||||
elif self.geographic:
|
||||
return None
|
||||
else:
|
||||
m = self.units_regex.match(self.wkt)
|
||||
return m.group('unit')
|
||||
linear_units = property(linear_units)
|
||||
|
||||
def angular_name(self):
|
||||
"Returns the name of the angular units."
|
||||
if 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')
|
||||
angular_name = property(angular_name)
|
||||
|
||||
def angular_units(self):
|
||||
"Returns the angular units."
|
||||
if HAS_GDAL:
|
||||
return self.srs.angular_units
|
||||
elif self.projected:
|
||||
return None
|
||||
else:
|
||||
m = self.units_regex.match(self.wkt)
|
||||
return m.group('unit')
|
||||
angular_units = property(angular_units)
|
||||
|
||||
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)
|
||||
units = property(units)
|
||||
|
||||
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 HAS_GDAL:
|
||||
return SpatialReference(wkt).units
|
||||
else:
|
||||
m = cls.units_regex.match(wkt)
|
||||
return m.group('unit'), m.group('unit_name')
|
||||
get_units = classmethod(get_units)
|
||||
|
||||
def get_spheroid(cls, wkt, string=True):
|
||||
"""
|
||||
Class method used by GeometryField on initialization to
|
||||
retrieve the `SPHEROID[..]` parameters from the given WKT.
|
||||
"""
|
||||
if HAS_GDAL:
|
||||
srs = 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)
|
||||
get_spheroid = classmethod(get_spheroid)
|
||||
|
||||
def __unicode__(self):
|
||||
"""
|
||||
Returns the string representation. If GDAL is installed,
|
||||
it will be 'pretty' OGC WKT.
|
||||
"""
|
||||
try:
|
||||
return unicode(self.srs)
|
||||
except:
|
||||
return unicode(self.wkt)
|
||||
|
||||
# Django test suite on 2.3 platforms will choke on code inside this
|
||||
# conditional.
|
||||
if not PYTHON23:
|
||||
try:
|
||||
# try/except'ing the importation of SpatialBackend. Have to fail
|
||||
# silently because this module may be inadvertently invoked by
|
||||
# non-GeoDjango users (e.g., when the Django test suite executes
|
||||
# the models.py of all contrib apps).
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
if SpatialBackend.mysql: raise Exception
|
||||
|
||||
# Exposing the SpatialRefSys and GeometryColumns models.
|
||||
class SpatialRefSys(SpatialBackend.SpatialRefSys, SpatialRefSysMixin):
|
||||
pass
|
||||
GeometryColumns = SpatialBackend.GeometryColumns
|
||||
except:
|
||||
pass
|
@ -1,11 +1,11 @@
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.template import loader
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core import urlresolvers
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
||||
from django.db.models import get_model
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.db.models import get_model
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz
|
||||
@ -59,7 +59,7 @@ def sitemap(request, sitemaps, section=None):
|
||||
xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
|
||||
return HttpResponse(xml, mimetype='application/xml')
|
||||
|
||||
def kml(request, label, model, field_name=None, compress=False):
|
||||
def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
This view generates KML for the given app label, model, and field name.
|
||||
|
||||
@ -79,14 +79,16 @@ def kml(request, label, model, field_name=None, compress=False):
|
||||
except:
|
||||
raise Http404('Invalid geometry field.')
|
||||
|
||||
if SpatialBackend.postgis:
|
||||
connection = connections[using]
|
||||
|
||||
if connection.ops.postgis:
|
||||
# PostGIS will take care of transformation.
|
||||
placemarks = klass._default_manager.kml(field_name=field_name)
|
||||
else:
|
||||
# There's no KML method on Oracle or MySQL, so we use the `kml`
|
||||
# attribute of the lazy geometry instead.
|
||||
placemarks = []
|
||||
if SpatialBackend.oracle:
|
||||
if connection.ops.oracle:
|
||||
qs = klass._default_manager.transform(4326, field_name=field_name)
|
||||
else:
|
||||
qs = klass._default_manager.all()
|
||||
@ -101,8 +103,8 @@ def kml(request, label, model, field_name=None, compress=False):
|
||||
render = render_to_kml
|
||||
return render('gis/kml/placemarks.kml', {'places' : placemarks})
|
||||
|
||||
def kmz(request, label, model, field_name=None):
|
||||
def kmz(request, label, model, field_name=None, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
This view returns KMZ for the given app label, model, and field name.
|
||||
"""
|
||||
return kml(request, label, model, field_name, True)
|
||||
return kml(request, label, model, field_name, compress=True, using=using)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import sys, unittest
|
||||
from django.test.simple import run_tests
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
def geo_suite():
|
||||
@ -14,12 +15,11 @@ def geo_suite():
|
||||
from django.contrib.gis.utils import HAS_GEOIP
|
||||
from django.contrib.gis.tests.utils import postgis, mysql
|
||||
|
||||
# The test suite.
|
||||
s = unittest.TestSuite()
|
||||
gis_tests = []
|
||||
|
||||
# Adding the GEOS tests.
|
||||
from django.contrib.gis.geos import tests as geos_tests
|
||||
s.addTest(geos_tests.suite())
|
||||
gis_tests.append(geos_tests.suite())
|
||||
|
||||
# Tests that require use of a spatial database (e.g., creation of models)
|
||||
test_apps = ['geoapp', 'relatedapp']
|
||||
@ -44,7 +44,7 @@ def geo_suite():
|
||||
|
||||
# Adding the GDAL tests.
|
||||
from django.contrib.gis.gdal import tests as gdal_tests
|
||||
s.addTest(gdal_tests.suite())
|
||||
gis_tests.append(gdal_tests.suite())
|
||||
else:
|
||||
print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run."
|
||||
|
||||
@ -55,9 +55,9 @@ def geo_suite():
|
||||
# in the `test_suite_names`.
|
||||
for suite_name in test_suite_names:
|
||||
tsuite = import_module('django.contrib.gis.tests.' + suite_name)
|
||||
s.addTest(tsuite.suite())
|
||||
gis_tests.append(tsuite.suite())
|
||||
|
||||
return s, test_apps
|
||||
return gis_tests, test_apps
|
||||
|
||||
def run_gis_tests(test_labels, **kwargs):
|
||||
"""
|
||||
@ -83,21 +83,13 @@ def run_gis_tests(test_labels, **kwargs):
|
||||
# Setting the URLs.
|
||||
settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
|
||||
|
||||
# Creating the test suite, adding the test models to INSTALLED_APPS, and
|
||||
# adding the model test suites to our suite package.
|
||||
gis_suite, test_apps = geo_suite()
|
||||
# Creating the test suite, adding the test models to INSTALLED_APPS
|
||||
# so they will be tested.
|
||||
gis_tests, test_apps = geo_suite()
|
||||
for test_model in test_apps:
|
||||
module_name = 'django.contrib.gis.tests.%s' % test_model
|
||||
if mysql:
|
||||
test_module = 'tests_mysql'
|
||||
else:
|
||||
test_module = 'tests'
|
||||
new_installed.append(module_name)
|
||||
|
||||
# Getting the model test suite
|
||||
tsuite = import_module(module_name + '.' + test_module)
|
||||
gis_suite.addTest(tsuite.suite())
|
||||
|
||||
# Resetting the loaded flag to take into account what we appended to
|
||||
# the INSTALLED_APPS (since this routine is invoked through
|
||||
# django/core/management, it caches the apps; this ensures that syncdb
|
||||
@ -105,67 +97,13 @@ def run_gis_tests(test_labels, **kwargs):
|
||||
settings.INSTALLED_APPS = new_installed
|
||||
loading.cache.loaded = False
|
||||
|
||||
kwargs['extra_tests'] = gis_tests
|
||||
|
||||
# Running the tests using the GIS test runner.
|
||||
result = run_tests(test_labels, suite=gis_suite, **kwargs)
|
||||
result = run_tests(test_labels, **kwargs)
|
||||
|
||||
# Restoring modified settings.
|
||||
settings.INSTALLED_APPS = old_installed
|
||||
settings.ROOT_URLCONF = old_root_urlconf
|
||||
|
||||
return result
|
||||
|
||||
def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=None):
|
||||
"""
|
||||
Set `TEST_RUNNER` in your settings with this routine in order to
|
||||
scaffold test spatial databases correctly for your GeoDjango models.
|
||||
For more documentation, please consult the following URL:
|
||||
http://geodjango.org/docs/testing.html.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.db.models import get_app, get_apps
|
||||
from django.test.simple import build_suite, build_test, reorder_suite, TestCase
|
||||
from django.test.utils import setup_test_environment, teardown_test_environment
|
||||
|
||||
# The `create_test_spatial_db` routine abstracts away all the steps needed
|
||||
# to properly construct a spatial database for the backend.
|
||||
from django.contrib.gis.db.backend import create_test_spatial_db
|
||||
|
||||
# Setting up for testing.
|
||||
setup_test_environment()
|
||||
settings.DEBUG = False
|
||||
old_name = settings.DATABASE_NAME
|
||||
|
||||
# Creating the test spatial database.
|
||||
create_test_spatial_db(verbosity=verbosity, autoclobber=not interactive)
|
||||
|
||||
# The suite may be passed in manually, e.g., when we run the GeoDjango test,
|
||||
# we want to build it and pass it in due to some customizations. Otherwise,
|
||||
# the normal test suite creation process from `django.test.simple.run_tests`
|
||||
# is used to create the test suite.
|
||||
if suite is None:
|
||||
suite = unittest.TestSuite()
|
||||
if test_labels:
|
||||
for label in test_labels:
|
||||
if '.' in label:
|
||||
suite.addTest(build_test(label))
|
||||
else:
|
||||
app = get_app(label)
|
||||
suite.addTest(build_suite(app))
|
||||
else:
|
||||
for app in get_apps():
|
||||
suite.addTest(build_suite(app))
|
||||
|
||||
for test in extra_tests:
|
||||
suite.addTest(test)
|
||||
|
||||
suite = reorder_suite(suite, (TestCase,))
|
||||
|
||||
# Executing the tests (including the model tests), and destorying the
|
||||
# test database after the tests have completed.
|
||||
result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
|
||||
connection.creation.destroy_test_db(old_name, verbosity)
|
||||
teardown_test_environment()
|
||||
|
||||
# Returning the total failures and errors
|
||||
return len(result.failures) + len(result.errors)
|
||||
|
@ -96,9 +96,9 @@ class DistanceTest(unittest.TestCase):
|
||||
# Creating the query set.
|
||||
qs = AustraliaCity.objects.order_by('name')
|
||||
if type_error:
|
||||
# A TypeError should be raised on PostGIS when trying to pass
|
||||
# A ValueError should be raised on PostGIS when trying to pass
|
||||
# Distance objects into a DWithin query using a geodetic field.
|
||||
self.assertRaises(TypeError, AustraliaCity.objects.filter, point__dwithin=(self.au_pnt, dist))
|
||||
self.assertRaises(ValueError, AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)).count)
|
||||
else:
|
||||
self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
|
||||
|
||||
@ -237,15 +237,15 @@ class DistanceTest(unittest.TestCase):
|
||||
# Oracle doesn't have this limitation -- PostGIS only allows geodetic
|
||||
# distance queries from Points to PointFields.
|
||||
mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
|
||||
self.assertRaises(TypeError,
|
||||
self.assertRaises(ValueError, len,
|
||||
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
|
||||
# Too many params (4 in this case) should raise a ValueError.
|
||||
self.assertRaises(ValueError,
|
||||
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
|
||||
self.assertRaises(ValueError, len,
|
||||
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
|
||||
|
||||
# Not enough params should raise a ValueError.
|
||||
self.assertRaises(ValueError,
|
||||
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)',))
|
||||
self.assertRaises(ValueError, len,
|
||||
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)))
|
||||
|
||||
# Getting all cities w/in 550 miles of Hobart.
|
||||
hobart = AustraliaCity.objects.get(name='Hobart')
|
||||
|
97
django/contrib/gis/tests/geoapp/fixtures/initial_data.json
Normal file
97
django/contrib/gis/tests/geoapp/fixtures/initial_data.json
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +0,0 @@
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Houston', GeomFromText('POINT (-95.363151 29.763374)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Dallas', GeomFromText('POINT (-96.801611 32.782057)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Oklahoma City', GeomFromText('POINT (-97.521157 34.464642)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Wellington', GeomFromText('POINT (174.783117 -41.315268)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Pueblo', GeomFromText('POINT (-104.609252 38.255001)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Lawrence', GeomFromText('POINT (-95.235060 38.971823)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Chicago', GeomFromText('POINT (-87.650175 41.850385)'));
|
||||
INSERT INTO geoapp_city (`name`, `point`) VALUES ('Victoria', GeomFromText('POINT (-123.305196 48.462611)'));
|
@ -1,8 +0,0 @@
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Houston', SDO_GEOMETRY('POINT (-95.363151 29.763374)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Dallas', SDO_GEOMETRY('POINT (-96.801611 32.782057)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Oklahoma City', SDO_GEOMETRY('POINT (-97.521157 34.464642)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Wellington', SDO_GEOMETRY('POINT (174.783117 -41.315268)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Pueblo', SDO_GEOMETRY('POINT (-104.609252 38.255001)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Lawrence', SDO_GEOMETRY('POINT (-95.235060 38.971823)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Chicago', SDO_GEOMETRY('POINT (-87.650175 41.850385)', 4326));
|
||||
INSERT INTO GEOAPP_CITY ("NAME", "POINT") VALUES ('Victoria', SDO_GEOMETRY('POINT (-123.305196 48.462611)', 4326));
|
@ -1,8 +0,0 @@
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Houston', 'SRID=4326;POINT (-95.363151 29.763374)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', 'SRID=4326;POINT (-96.801611 32.782057)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', 'SRID=4326;POINT (-97.521157 34.464642)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', 'SRID=4326;POINT (174.783117 -41.315268)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', 'SRID=4326;POINT (-104.609252 38.255001)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Lawrence', 'SRID=4326;POINT (-95.235060 38.971823)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Chicago', 'SRID=4326;POINT (-87.650175 41.850385)');
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Victoria', 'SRID=4326;POINT (-123.305196 48.462611)');
|
@ -1,8 +0,0 @@
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Houston', GeomFromText('POINT (-95.363151 29.763374)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Dallas', GeomFromText('POINT (-96.801611 32.782057)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Oklahoma City', GeomFromText('POINT (-97.521157 34.464642)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Wellington', GeomFromText('POINT (174.783117 -41.315268)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Pueblo', GeomFromText('POINT (-104.609252 38.255001)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Lawrence', GeomFromText('POINT (-95.235060 38.971823)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Chicago', GeomFromText('POINT (-87.650175 41.850385)', 4326));
|
||||
INSERT INTO geoapp_city ("name", "point") VALUES ('Victoria', GeomFromText('POINT (-123.305196 48.462611)', 4326));
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,4 @@
|
||||
import os, unittest
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
|
||||
from django.contrib.gis.shortcuts import render_to_kmz
|
||||
from models import City
|
||||
|
@ -1,49 +1,29 @@
|
||||
import os, unittest
|
||||
import re, os, unittest
|
||||
from django.db import connection
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.measure import Distance
|
||||
from django.contrib.gis.tests.utils import no_oracle, no_postgis, no_spatialite
|
||||
from django.contrib.gis.tests.utils import \
|
||||
no_mysql, no_oracle, no_postgis, no_spatialite, \
|
||||
mysql, oracle, postgis, spatialite
|
||||
from django.test import TestCase
|
||||
|
||||
from models import Country, City, PennsylvaniaCity, State
|
||||
|
||||
if not SpatialBackend.spatialite:
|
||||
if not spatialite:
|
||||
from models import Feature, MinusOneSRID
|
||||
|
||||
# TODO: Some tests depend on the success/failure of previous tests, these should
|
||||
# be decoupled. This flag is an artifact of this problem, and makes debugging easier;
|
||||
# specifically, the DISABLE flag will disables all tests, allowing problem tests to
|
||||
# be examined individually.
|
||||
DISABLE = False
|
||||
class GeoModelTest(TestCase):
|
||||
|
||||
class GeoModelTest(unittest.TestCase):
|
||||
|
||||
def test01_initial_sql(self):
|
||||
"Testing geographic initial SQL."
|
||||
if DISABLE: return
|
||||
if SpatialBackend.oracle:
|
||||
# Oracle doesn't allow strings longer than 4000 characters
|
||||
# in SQL files, and I'm stumped on how to use Oracle BFILE's
|
||||
# in PLSQL, so we set up the larger geometries manually, rather
|
||||
# than relying on the initial SQL.
|
||||
|
||||
# Routine for returning the path to the data files.
|
||||
data_dir = os.path.join(os.path.dirname(__file__), 'sql')
|
||||
def get_file(wkt_file):
|
||||
return os.path.join(data_dir, wkt_file)
|
||||
State(name='Puerto Rico', poly=None).save()
|
||||
State(name='Colorado', poly=fromfile(get_file('co.wkt'))).save()
|
||||
State(name='Kansas', poly=fromfile(get_file('ks.wkt'))).save()
|
||||
Country(name='Texas', mpoly=fromfile(get_file('tx.wkt'))).save()
|
||||
Country(name='New Zealand', mpoly=fromfile(get_file('nz.wkt'))).save()
|
||||
|
||||
# Ensuring that data was loaded from initial SQL.
|
||||
def test01_fixtures(self):
|
||||
"Testing geographic model initialization from fixtures."
|
||||
# Ensuring that data was loaded from initial data fixtures.
|
||||
self.assertEqual(2, Country.objects.count())
|
||||
self.assertEqual(8, City.objects.count())
|
||||
self.assertEqual(3, State.objects.count())
|
||||
self.assertEqual(2, State.objects.count())
|
||||
|
||||
def test02_proxy(self):
|
||||
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
||||
if DISABLE: return
|
||||
## Testing on a Point
|
||||
pnt = Point(0, 0)
|
||||
nullcity = City(name='NullCity', point=pnt)
|
||||
@ -110,11 +90,13 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
||||
ns.delete()
|
||||
|
||||
@no_oracle # Oracle does not support KML.
|
||||
@no_spatialite # SpatiaLite does not support KML.
|
||||
def test03a_kml(self):
|
||||
"Testing KML output from the database using GeoQuerySet.kml()."
|
||||
if DISABLE: return
|
||||
# Only PostGIS supports KML serialization
|
||||
if not postgis:
|
||||
self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly')
|
||||
return
|
||||
|
||||
# Should throw a TypeError when trying to obtain KML from a
|
||||
# non-geometry field.
|
||||
qs = City.objects.all()
|
||||
@ -122,14 +104,10 @@ class GeoModelTest(unittest.TestCase):
|
||||
|
||||
# The reference KML depends on the version of PostGIS used
|
||||
# (the output stopped including altitude in 1.3.3).
|
||||
major, minor1, minor2 = SpatialBackend.version
|
||||
ref_kml1 = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
|
||||
ref_kml2 = '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
|
||||
if major == 1:
|
||||
if minor1 > 3 or (minor1 == 3 and minor2 >= 3): ref_kml = ref_kml2
|
||||
else: ref_kml = ref_kml1
|
||||
if connection.ops.spatial_version >= (1, 3, 3):
|
||||
ref_kml = '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
|
||||
else:
|
||||
ref_kml = ref_kml2
|
||||
ref_kml = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
|
||||
|
||||
# Ensuring the KML is as expected.
|
||||
ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
|
||||
@ -137,19 +115,20 @@ class GeoModelTest(unittest.TestCase):
|
||||
for ptown in [ptown1, ptown2]:
|
||||
self.assertEqual(ref_kml, ptown.kml)
|
||||
|
||||
@no_spatialite # SpatiaLite does not support GML.
|
||||
def test03b_gml(self):
|
||||
"Testing GML output from the database using GeoQuerySet.gml()."
|
||||
if DISABLE: return
|
||||
if mysql or spatialite:
|
||||
self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly')
|
||||
return
|
||||
|
||||
# Should throw a TypeError when tyring to obtain GML from a
|
||||
# non-geometry field.
|
||||
# non-geometry field.
|
||||
qs = City.objects.all()
|
||||
self.assertRaises(TypeError, qs.gml, field_name='name')
|
||||
ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
|
||||
ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
|
||||
|
||||
import re
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
# No precision parameter for Oracle :-/
|
||||
gml_regex = re.compile(r'^<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ </gml:coordinates></gml:Point>')
|
||||
for ptown in [ptown1, ptown2]:
|
||||
@ -159,17 +138,14 @@ class GeoModelTest(unittest.TestCase):
|
||||
for ptown in [ptown1, ptown2]:
|
||||
self.failUnless(gml_regex.match(ptown.gml))
|
||||
|
||||
@no_spatialite
|
||||
@no_oracle
|
||||
def test03c_geojson(self):
|
||||
"Testing GeoJSON output from the database using GeoQuerySet.geojson()."
|
||||
if DISABLE: return
|
||||
# PostGIS only supports GeoJSON on 1.3.4+
|
||||
if not SpatialBackend.geojson:
|
||||
# Only PostGIS 1.3.4+ supports GeoJSON.
|
||||
if not connection.ops.geojson:
|
||||
self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly')
|
||||
return
|
||||
|
||||
major, minor1, minor2 = SpatialBackend.version
|
||||
if major >=1 and minor1 >= 4:
|
||||
if connection.ops.spatial_version >= (1, 4, 0):
|
||||
pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
|
||||
houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
|
||||
victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}'
|
||||
@ -201,10 +177,12 @@ class GeoModelTest(unittest.TestCase):
|
||||
# Finally, we set every available keyword.
|
||||
self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
|
||||
|
||||
@no_oracle
|
||||
def test03d_svg(self):
|
||||
"Testing SVG output using GeoQuerySet.svg()."
|
||||
if DISABLE: return
|
||||
if mysql or oracle:
|
||||
self.assertRaises(NotImplementedError, City.objects.svg)
|
||||
return
|
||||
|
||||
self.assertRaises(TypeError, City.objects.svg, precision='foo')
|
||||
# SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
|
||||
svg1 = 'cx="-104.609252" cy="-38.255001"'
|
||||
@ -214,9 +192,9 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg)
|
||||
self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
|
||||
|
||||
@no_mysql
|
||||
def test04_transform(self):
|
||||
"Testing the transform() GeoManager method."
|
||||
if DISABLE: return
|
||||
# Pre-transformed points for Houston and Pueblo.
|
||||
htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
|
||||
ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
|
||||
@ -224,7 +202,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
|
||||
# Asserting the result of the transform operation with the values in
|
||||
# the pre-transformed points. Oracle does not have the 3084 SRID.
|
||||
if not SpatialBackend.oracle:
|
||||
if not oracle:
|
||||
h = City.objects.transform(htown.srid).get(name='Houston')
|
||||
self.assertEqual(3084, h.point.srid)
|
||||
self.assertAlmostEqual(htown.x, h.point.x, prec)
|
||||
@ -237,10 +215,10 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(ptown.x, p.point.x, prec)
|
||||
self.assertAlmostEqual(ptown.y, p.point.y, prec)
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite # SpatiaLite does not have an Extent function
|
||||
def test05_extent(self):
|
||||
"Testing the `extent` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
# Reference query:
|
||||
# `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
|
||||
# => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
|
||||
@ -252,11 +230,12 @@ class GeoModelTest(unittest.TestCase):
|
||||
for val, exp in zip(extent, expected):
|
||||
self.assertAlmostEqual(exp, val, 4)
|
||||
|
||||
# Only PostGIS has support for the MakeLine aggregate.
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite # SpatiaLite does not have a MakeLine function
|
||||
@no_spatialite
|
||||
def test06_make_line(self):
|
||||
"Testing the `make_line` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
# Ensuring that a `TypeError` is raised on models without PointFields.
|
||||
self.assertRaises(TypeError, State.objects.make_line)
|
||||
self.assertRaises(TypeError, Country.objects.make_line)
|
||||
@ -265,34 +244,26 @@ class GeoModelTest(unittest.TestCase):
|
||||
ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326)
|
||||
self.assertEqual(ref_line, City.objects.make_line())
|
||||
|
||||
@no_mysql
|
||||
def test09_disjoint(self):
|
||||
"Testing the `disjoint` lookup type."
|
||||
if DISABLE: return
|
||||
ptown = City.objects.get(name='Pueblo')
|
||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||
self.assertEqual(7, qs1.count())
|
||||
|
||||
if not (SpatialBackend.postgis or SpatialBackend.spatialite):
|
||||
# TODO: Do NULL columns bork queries on PostGIS? The following
|
||||
# error is encountered:
|
||||
# psycopg2.ProgrammingError: invalid memory alloc request size 4294957297
|
||||
#
|
||||
# Similarly, on SpatiaLite Puerto Rico is also returned (could be a
|
||||
# manifestation of
|
||||
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||
self.assertEqual(1, qs2.count())
|
||||
self.assertEqual('Kansas', qs2[0].name)
|
||||
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||
self.assertEqual(1, qs2.count())
|
||||
self.assertEqual('Kansas', qs2[0].name)
|
||||
|
||||
def test10_contains_contained(self):
|
||||
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
||||
if DISABLE: return
|
||||
# Getting Texas, yes we were a country -- once ;)
|
||||
texas = Country.objects.get(name='Texas')
|
||||
|
||||
# Seeing what cities are in Texas, should get Houston and Dallas,
|
||||
# and Oklahoma City because 'contained' only checks on the
|
||||
# _bounding box_ of the Geometries.
|
||||
if not SpatialBackend.oracle:
|
||||
if not oracle:
|
||||
qs = City.objects.filter(point__contained=texas.mpoly)
|
||||
self.assertEqual(3, qs.count())
|
||||
cities = ['Houston', 'Dallas', 'Oklahoma City']
|
||||
@ -313,30 +284,31 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual('New Zealand', nz.name)
|
||||
|
||||
# Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
|
||||
if not SpatialBackend.spatialite:
|
||||
if not spatialite:
|
||||
ks = State.objects.get(poly__contains=lawrence.point)
|
||||
self.assertEqual('Kansas', ks.name)
|
||||
|
||||
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
||||
# are not contained in Texas or New Zealand.
|
||||
# are not contained in Texas or New Zealand.
|
||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
|
||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
|
||||
self.assertEqual((mysql and 1) or 0,
|
||||
len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
|
||||
|
||||
# OK City is contained w/in bounding box of Texas.
|
||||
if not SpatialBackend.oracle:
|
||||
if not oracle:
|
||||
qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('Texas', qs[0].name)
|
||||
|
||||
@no_mysql
|
||||
def test11_lookup_insert_transform(self):
|
||||
"Testing automatic transform for lookups and inserts."
|
||||
if DISABLE: return
|
||||
# San Antonio in 'WGS84' (SRID 4326)
|
||||
sa_4326 = 'POINT (-98.493183 29.424170)'
|
||||
wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
|
||||
|
||||
# Oracle doesn't have SRID 3084, using 41157.
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
# San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
|
||||
# Used the following Oracle SQL to get this value:
|
||||
# SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL;
|
||||
@ -351,15 +323,14 @@ class GeoModelTest(unittest.TestCase):
|
||||
# `SDO_OVERLAPBDYINTERSECT` operates differently from
|
||||
# `ST_Intersects`, so contains is used instead.
|
||||
nad_pnt = fromstr(nad_wkt, srid=nad_srid)
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
tx = Country.objects.get(mpoly__contains=nad_pnt)
|
||||
else:
|
||||
tx = Country.objects.get(mpoly__intersects=nad_pnt)
|
||||
self.assertEqual('Texas', tx.name)
|
||||
|
||||
# Creating San Antonio. Remember the Alamo.
|
||||
sa = City(name='San Antonio', point=nad_pnt)
|
||||
sa.save()
|
||||
sa = City.objects.create(name='San Antonio', point=nad_pnt)
|
||||
|
||||
# Now verifying that San Antonio was transformed correctly
|
||||
sa = City.objects.get(name='San Antonio')
|
||||
@ -369,14 +340,17 @@ class GeoModelTest(unittest.TestCase):
|
||||
# If the GeometryField SRID is -1, then we shouldn't perform any
|
||||
# transformation if the SRID of the input geometry is different.
|
||||
# SpatiaLite does not support missing SRID values.
|
||||
if not SpatialBackend.spatialite:
|
||||
if not spatialite:
|
||||
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
|
||||
m1.save()
|
||||
self.assertEqual(-1, m1.geom.srid)
|
||||
|
||||
@no_mysql
|
||||
def test12_null_geometries(self):
|
||||
"Testing NULL geometry support, and the `isnull` lookup type."
|
||||
if DISABLE: return
|
||||
# Creating a state with a NULL boundary.
|
||||
State.objects.create(name='Puerto Rico')
|
||||
|
||||
# Querying for both NULL and Non-NULL values.
|
||||
nullqs = State.objects.filter(poly__isnull=True)
|
||||
validqs = State.objects.filter(poly__isnull=False)
|
||||
@ -401,27 +375,28 @@ class GeoModelTest(unittest.TestCase):
|
||||
State.objects.filter(name='Northern Mariana Islands').update(poly=None)
|
||||
self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
|
||||
|
||||
@no_oracle # No specific `left` or `right` operators in Oracle.
|
||||
@no_spatialite # No `left` or `right` operators in SpatiaLite.
|
||||
# Only PostGIS has `left` and `right` lookup types.
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
def test13_left_right(self):
|
||||
"Testing the 'left' and 'right' lookup types."
|
||||
if DISABLE: return
|
||||
# Left: A << B => true if xmax(A) < xmin(B)
|
||||
# Right: A >> B => true if xmin(A) > xmax(B)
|
||||
# See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
|
||||
# See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
|
||||
|
||||
# Getting the borders for Colorado & Kansas
|
||||
co_border = State.objects.get(name='Colorado').poly
|
||||
ks_border = State.objects.get(name='Kansas').poly
|
||||
|
||||
# Note: Wellington has an 'X' value of 174, so it will not be considered
|
||||
# to the left of CO.
|
||||
# to the left of CO.
|
||||
|
||||
# These cities should be strictly to the right of the CO border.
|
||||
cities = ['Houston', 'Dallas', 'San Antonio', 'Oklahoma City',
|
||||
cities = ['Houston', 'Dallas', 'Oklahoma City',
|
||||
'Lawrence', 'Chicago', 'Wellington']
|
||||
qs = City.objects.filter(point__right=co_border)
|
||||
self.assertEqual(7, len(qs))
|
||||
self.assertEqual(6, len(qs))
|
||||
for c in qs: self.assertEqual(True, c.name in cities)
|
||||
|
||||
# These cities should be strictly to the right of the KS border.
|
||||
@ -442,16 +417,16 @@ class GeoModelTest(unittest.TestCase):
|
||||
|
||||
def test14_equals(self):
|
||||
"Testing the 'same_as' and 'equals' lookup types."
|
||||
if DISABLE: return
|
||||
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
||||
c1 = City.objects.get(point=pnt)
|
||||
c2 = City.objects.get(point__same_as=pnt)
|
||||
c3 = City.objects.get(point__equals=pnt)
|
||||
for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
|
||||
|
||||
@no_mysql
|
||||
def test15_relate(self):
|
||||
"Testing the 'relate' lookup type."
|
||||
if DISABLE: return
|
||||
return
|
||||
# To make things more interesting, we will have our Texas reference point in
|
||||
# different SRIDs.
|
||||
pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
|
||||
@ -459,19 +434,20 @@ class GeoModelTest(unittest.TestCase):
|
||||
|
||||
# Not passing in a geometry as first param shoud
|
||||
# raise a type error when initializing the GeoQuerySet
|
||||
self.assertRaises(TypeError, Country.objects.filter, mpoly__relate=(23, 'foo'))
|
||||
self.assertRaises(ValueError, Country.objects.filter(mpoly__relate=(23, 'foo')).count)
|
||||
|
||||
# Making sure the right exception is raised for the given
|
||||
# bad arguments.
|
||||
for bad_args, e in [((pnt1, 0), TypeError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
|
||||
for bad_args, e in [((pnt1, 0), ValueError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
|
||||
qs = Country.objects.filter(mpoly__relate=bad_args)
|
||||
self.assertRaises(e, qs.count)
|
||||
|
||||
# Relate works differently for the different backends.
|
||||
if SpatialBackend.postgis or SpatialBackend.spatialite:
|
||||
if postgis or spatialite:
|
||||
contains_mask = 'T*T***FF*'
|
||||
within_mask = 'T*F**F***'
|
||||
intersects_mask = 'T********'
|
||||
elif SpatialBackend.oracle:
|
||||
elif oracle:
|
||||
contains_mask = 'contains'
|
||||
within_mask = 'inside'
|
||||
# TODO: This is not quite the same as the PostGIS mask above
|
||||
@ -486,24 +462,23 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
|
||||
|
||||
# Testing intersection relation mask.
|
||||
if not SpatialBackend.oracle:
|
||||
if not oracle:
|
||||
self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name)
|
||||
self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
|
||||
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
|
||||
|
||||
def test16_createnull(self):
|
||||
"Testing creating a model instance and the geometry being None"
|
||||
if DISABLE: return
|
||||
c = City()
|
||||
self.assertEqual(c.point, None)
|
||||
|
||||
@no_mysql
|
||||
def test17_unionagg(self):
|
||||
"Testing the `unionagg` (aggregate union) GeoManager method."
|
||||
if DISABLE: return
|
||||
tx = Country.objects.get(name='Texas').mpoly
|
||||
# Houston, Dallas, San Antonio -- Oracle has different order.
|
||||
union1 = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 32.782057,-95.363151 29.763374)')
|
||||
union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374,-98.493183 29.424170)')
|
||||
# Houston, Dallas -- Oracle has different order.
|
||||
union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||
union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||
qs = City.objects.filter(point__within=tx)
|
||||
self.assertRaises(TypeError, qs.unionagg, 'name')
|
||||
# Using `field_name` keyword argument in one query and specifying an
|
||||
@ -512,7 +487,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
u1 = qs.unionagg(field_name='point')
|
||||
u2 = qs.order_by('name').unionagg()
|
||||
tol = 0.00001
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
union = union2
|
||||
else:
|
||||
union = union1
|
||||
@ -523,8 +498,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
|
||||
@no_spatialite # SpatiaLite does not support abstract geometry columns
|
||||
def test18_geometryfield(self):
|
||||
"Testing GeometryField."
|
||||
if DISABLE: return
|
||||
"Testing the general GeometryField."
|
||||
Feature(name='Point', geom=Point(1, 1)).save()
|
||||
Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save()
|
||||
Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save()
|
||||
@ -545,60 +519,62 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
|
||||
self.assertEqual(f_3.geom, f_4.geom[2])
|
||||
|
||||
@no_mysql
|
||||
def test19_centroid(self):
|
||||
"Testing the `centroid` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
qs = State.objects.exclude(poly__isnull=True).centroid()
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
tol = 0.1
|
||||
elif SpatialBackend.spatialite:
|
||||
elif spatialite:
|
||||
tol = 0.000001
|
||||
else:
|
||||
tol = 0.000000001
|
||||
for s in qs:
|
||||
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
|
||||
|
||||
@no_mysql
|
||||
def test20_pointonsurface(self):
|
||||
"Testing the `point_on_surface` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
# Reference values.
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
# SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
|
||||
ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
|
||||
'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326),
|
||||
}
|
||||
|
||||
elif SpatialBackend.postgis or SpatialBackend.spatialite:
|
||||
elif postgis or spatialite:
|
||||
# Using GEOSGeometry to compute the reference point on surface values
|
||||
# -- since PostGIS also uses GEOS these should be the same.
|
||||
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
|
||||
'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
|
||||
}
|
||||
for cntry in Country.objects.point_on_surface():
|
||||
if SpatialBackend.spatialite:
|
||||
|
||||
for c in Country.objects.point_on_surface():
|
||||
if spatialite:
|
||||
# XXX This seems to be a WKT-translation-related precision issue?
|
||||
tol = 0.00001
|
||||
else: tol = 0.000000001
|
||||
self.assertEqual(True, ref[cntry.name].equals_exact(cntry.point_on_surface, tol))
|
||||
else:
|
||||
tol = 0.000000001
|
||||
self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol))
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
def test21_scale(self):
|
||||
"Testing the `scale` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
xfac, yfac = 2, 3
|
||||
tol = 5 # XXX The low precision tolerance is for SpatiaLite
|
||||
qs = Country.objects.scale(xfac, yfac, model_att='scaled')
|
||||
for c in qs:
|
||||
for p1, p2 in zip(c.mpoly, c.scaled):
|
||||
for r1, r2 in zip(p1, p2):
|
||||
for c1, c2 in zip(r1.coords, r2.coords):
|
||||
# XXX The low precision is for SpatiaLite
|
||||
self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
|
||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
|
||||
self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
|
||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
def test22_translate(self):
|
||||
"Testing the `translate` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
xfac, yfac = 5, -23
|
||||
qs = Country.objects.translate(xfac, yfac, model_att='translated')
|
||||
for c in qs:
|
||||
@ -609,57 +585,60 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
|
||||
self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
|
||||
|
||||
@no_mysql
|
||||
def test23_numgeom(self):
|
||||
"Testing the `num_geom` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
# Both 'countries' only have two geometries.
|
||||
for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
|
||||
for c in City.objects.filter(point__isnull=False).num_geom():
|
||||
# Oracle will return 1 for the number of geometries on non-collections,
|
||||
# whereas PostGIS will return None.
|
||||
if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
|
||||
else: self.assertEqual(1, c.num_geom)
|
||||
if postgis:
|
||||
self.assertEqual(None, c.num_geom)
|
||||
else:
|
||||
self.assertEqual(1, c.num_geom)
|
||||
|
||||
@no_mysql
|
||||
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
||||
def test24_numpoints(self):
|
||||
"Testing the `num_points` GeoQuerySet method."
|
||||
if DISABLE: return
|
||||
for c in Country.objects.num_points():
|
||||
self.assertEqual(c.mpoly.num_points, c.num_points)
|
||||
if not SpatialBackend.oracle:
|
||||
|
||||
if not oracle:
|
||||
# Oracle cannot count vertices in Point geometries.
|
||||
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
||||
|
||||
@no_mysql
|
||||
def test25_geoset(self):
|
||||
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
||||
if DISABLE: return
|
||||
geom = Point(5, 23)
|
||||
tol = 1
|
||||
qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
|
||||
|
||||
# XXX For some reason SpatiaLite does something screwey with the Texas geometry here. Also,
|
||||
# XXX it doesn't like the null intersection.
|
||||
if SpatialBackend.spatialite:
|
||||
if spatialite:
|
||||
qs = qs.exclude(name='Texas')
|
||||
else:
|
||||
qs = qs.intersection(geom)
|
||||
|
||||
for c in qs:
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
# Should be able to execute the queries; however, they won't be the same
|
||||
# as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
|
||||
# SpatiaLite).
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(c.mpoly.difference(geom), c.difference)
|
||||
if not SpatialBackend.spatialite:
|
||||
if not spatialite:
|
||||
self.assertEqual(c.mpoly.intersection(geom), c.intersection)
|
||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||
|
||||
@no_mysql
|
||||
def test26_inherited_geofields(self):
|
||||
"Test GeoQuerySet methods on inherited Geometry fields."
|
||||
if DISABLE: return
|
||||
# Creating a Pennsylvanian city.
|
||||
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
||||
|
||||
@ -670,12 +649,11 @@ class GeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(1, qs.count())
|
||||
for pc in qs: self.assertEqual(32128, pc.point.srid)
|
||||
|
||||
@no_spatialite
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
def test27_snap_to_grid(self):
|
||||
"Testing GeoQuerySet.snap_to_grid()."
|
||||
if DISABLE: return
|
||||
|
||||
# Let's try and break snap_to_grid() with bad combinations of arguments.
|
||||
for bad_args in ((), range(3), range(5)):
|
||||
self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
|
||||
|
@ -1,186 +0,0 @@
|
||||
"""
|
||||
A limited test module is used for a limited spatial database.
|
||||
"""
|
||||
import os, unittest
|
||||
from models import Country, City, State, Feature
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.geos import *
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
class GeoModelTest(unittest.TestCase):
|
||||
|
||||
def test01_initial_sql(self):
|
||||
"Testing geographic initial SQL."
|
||||
# Ensuring that data was loaded from initial SQL.
|
||||
self.assertEqual(2, Country.objects.count())
|
||||
self.assertEqual(8, City.objects.count())
|
||||
self.assertEqual(2, State.objects.count())
|
||||
|
||||
def test02_proxy(self):
|
||||
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
||||
#### Testing on a Point
|
||||
pnt = Point(0, 0)
|
||||
nullcity = City(name='NullCity', point=pnt)
|
||||
nullcity.save()
|
||||
|
||||
# Making sure TypeError is thrown when trying to set with an
|
||||
# incompatible type.
|
||||
for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
|
||||
try:
|
||||
nullcity.point = bad
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
self.fail('Should throw a TypeError')
|
||||
|
||||
# Now setting with a compatible GEOS Geometry, saving, and ensuring
|
||||
# the save took, notice no SRID is explicitly set.
|
||||
new = Point(5, 23)
|
||||
nullcity.point = new
|
||||
|
||||
# Ensuring that the SRID is automatically set to that of the
|
||||
# field after assignment, but before saving.
|
||||
self.assertEqual(4326, nullcity.point.srid)
|
||||
nullcity.save()
|
||||
|
||||
# Ensuring the point was saved correctly after saving
|
||||
self.assertEqual(new, City.objects.get(name='NullCity').point)
|
||||
|
||||
# Setting the X and Y of the Point
|
||||
nullcity.point.x = 23
|
||||
nullcity.point.y = 5
|
||||
# Checking assignments pre & post-save.
|
||||
self.assertNotEqual(Point(23, 5), City.objects.get(name='NullCity').point)
|
||||
nullcity.save()
|
||||
self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point)
|
||||
nullcity.delete()
|
||||
|
||||
#### Testing on a Polygon
|
||||
shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0))
|
||||
inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
|
||||
|
||||
# Creating a State object using a built Polygon
|
||||
ply = Polygon(shell, inner)
|
||||
nullstate = State(name='NullState', poly=ply)
|
||||
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
|
||||
nullstate.save()
|
||||
|
||||
ns = State.objects.get(name='NullState')
|
||||
self.assertEqual(ply, ns.poly)
|
||||
|
||||
# Testing the `ogr` and `srs` lazy-geometry properties.
|
||||
if gdal.HAS_GDAL:
|
||||
self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry))
|
||||
self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
|
||||
self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference))
|
||||
self.assertEqual('WGS 84', ns.poly.srs.name)
|
||||
|
||||
# Changing the interior ring on the poly attribute.
|
||||
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
|
||||
ns.poly[1] = new_inner
|
||||
ply[1] = new_inner
|
||||
self.assertEqual(4326, ns.poly.srid)
|
||||
ns.save()
|
||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
||||
ns.delete()
|
||||
|
||||
def test03_contains_contained(self):
|
||||
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
||||
# Getting Texas, yes we were a country -- once ;)
|
||||
texas = Country.objects.get(name='Texas')
|
||||
|
||||
# Seeing what cities are in Texas, should get Houston and Dallas,
|
||||
# and Oklahoma City because MySQL 'within' only checks on the
|
||||
# _bounding box_ of the Geometries.
|
||||
qs = City.objects.filter(point__within=texas.mpoly)
|
||||
self.assertEqual(3, qs.count())
|
||||
cities = ['Houston', 'Dallas', 'Oklahoma City']
|
||||
for c in qs: self.assertEqual(True, c.name in cities)
|
||||
|
||||
# Pulling out some cities.
|
||||
houston = City.objects.get(name='Houston')
|
||||
wellington = City.objects.get(name='Wellington')
|
||||
pueblo = City.objects.get(name='Pueblo')
|
||||
okcity = City.objects.get(name='Oklahoma City')
|
||||
lawrence = City.objects.get(name='Lawrence')
|
||||
|
||||
# Now testing contains on the countries using the points for
|
||||
# Houston and Wellington.
|
||||
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
|
||||
nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
|
||||
ks = State.objects.get(poly__contains=lawrence.point)
|
||||
self.assertEqual('Texas', tx.name)
|
||||
self.assertEqual('New Zealand', nz.name)
|
||||
self.assertEqual('Kansas', ks.name)
|
||||
|
||||
# Pueblo is not contained in Texas or New Zealand.
|
||||
self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
|
||||
|
||||
# OK City is contained w/in bounding box of Texas.
|
||||
qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('Texas', qs[0].name)
|
||||
|
||||
def test04_disjoint(self):
|
||||
"Testing the `disjoint` lookup type."
|
||||
ptown = City.objects.get(name='Pueblo')
|
||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||
self.assertEqual(7, qs1.count())
|
||||
# TODO: This query should work in MySQL, but it appears the
|
||||
# `MBRDisjoint` function doesn't work properly (I went down
|
||||
# to the SQL level for debugging and still got bogus answers).
|
||||
#qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||
#self.assertEqual(1, qs2.count())
|
||||
#self.assertEqual('Kansas', qs2[0].name)
|
||||
|
||||
def test05_equals(self):
|
||||
"Testing the 'same_as' and 'equals' lookup types."
|
||||
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
||||
c1 = City.objects.get(point=pnt)
|
||||
c2 = City.objects.get(point__same_as=pnt)
|
||||
c3 = City.objects.get(point__equals=pnt)
|
||||
for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
|
||||
|
||||
def test06_geometryfield(self):
|
||||
"Testing GeometryField."
|
||||
f1 = Feature(name='Point', geom=Point(1, 1))
|
||||
f2 = Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5)))
|
||||
f3 = Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))
|
||||
f4 = Feature(name='GeometryCollection',
|
||||
geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
|
||||
Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))))
|
||||
f1.save()
|
||||
f2.save()
|
||||
f3.save()
|
||||
f4.save()
|
||||
|
||||
f_1 = Feature.objects.get(name='Point')
|
||||
self.assertEqual(True, isinstance(f_1.geom, Point))
|
||||
self.assertEqual((1.0, 1.0), f_1.geom.tuple)
|
||||
f_2 = Feature.objects.get(name='LineString')
|
||||
self.assertEqual(True, isinstance(f_2.geom, LineString))
|
||||
self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
|
||||
|
||||
f_3 = Feature.objects.get(name='Polygon')
|
||||
self.assertEqual(True, isinstance(f_3.geom, Polygon))
|
||||
f_4 = Feature.objects.get(name='GeometryCollection')
|
||||
self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
|
||||
self.assertEqual(f_3.geom, f_4.geom[2])
|
||||
|
||||
def test07_mysql_limitations(self):
|
||||
"Testing that union(), kml(), gml() raise exceptions."
|
||||
self.assertRaises(ImproperlyConfigured, City.objects.union, Point(5, 23), field_name='point')
|
||||
self.assertRaises(ImproperlyConfigured, State.objects.all().kml, field_name='poly')
|
||||
self.assertRaises(ImproperlyConfigured, Country.objects.all().gml, field_name='mpoly')
|
||||
|
||||
from test_feeds import GeoFeedTest
|
||||
from test_regress import GeoRegressionTests
|
||||
from test_sitemaps import GeoSitemapTest
|
||||
|
||||
def suite():
|
||||
s = unittest.TestSuite()
|
||||
s.addTest(unittest.makeSuite(GeoModelTest))
|
||||
s.addTest(unittest.makeSuite(GeoFeedTest))
|
||||
s.addTest(unittest.makeSuite(GeoSitemapTest))
|
||||
s.addTest(unittest.makeSuite(GeoRegressionTests))
|
||||
return s
|
@ -2,14 +2,14 @@ import os, unittest
|
||||
from copy import copy
|
||||
from decimal import Decimal
|
||||
from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
|
||||
from django.contrib.gis.gdal import DataSource
|
||||
from django.contrib.gis.tests.utils import mysql
|
||||
|
||||
shp_path = os.path.dirname(__file__)
|
||||
city_shp = os.path.join(shp_path, '../data/cities/cities.shp')
|
||||
co_shp = os.path.join(shp_path, '../data/counties/counties.shp')
|
||||
inter_shp = os.path.join(shp_path, '../data/interstates/interstates.shp')
|
||||
shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
|
||||
city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
|
||||
co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
|
||||
inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
|
||||
|
||||
# Dictionaries to hold what's expected in the county shapefile.
|
||||
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||
@ -84,7 +84,7 @@ class LayerMapTest(unittest.TestCase):
|
||||
lm.save(silent=True, strict=True)
|
||||
except InvalidDecimal:
|
||||
# No transactions for geoms on MySQL; delete added features.
|
||||
if SpatialBackend.mysql: Interstate.objects.all().delete()
|
||||
if mysql: Interstate.objects.all().delete()
|
||||
else:
|
||||
self.fail('Should have failed on strict import with invalid decimal values.')
|
||||
|
||||
@ -149,7 +149,7 @@ class LayerMapTest(unittest.TestCase):
|
||||
self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
|
||||
|
||||
# No source reference system defined in the shapefile, should raise an error.
|
||||
if not SpatialBackend.mysql:
|
||||
if not mysql:
|
||||
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
|
||||
|
||||
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary
|
||||
|
@ -1 +0,0 @@
|
||||
from tests import *
|
@ -1,8 +1,8 @@
|
||||
import os, unittest
|
||||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
|
||||
from django.contrib.gis.geometry import Geometry
|
||||
from django.contrib.gis.tests.utils import mysql, oracle, postgis, spatialite, no_mysql, no_oracle, no_spatialite
|
||||
from django.conf import settings
|
||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
||||
|
||||
@ -95,7 +95,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
# geometries than PostGIS. The second union aggregate is for a union
|
||||
# query that includes limiting information in the WHERE clause (in other
|
||||
# words a `.filter()` precedes the call to `.unionagg()`).
|
||||
if SpatialBackend.oracle:
|
||||
if oracle:
|
||||
ref_u1 = MultiPoint(p3, p1, p2, srid=4326)
|
||||
ref_u2 = MultiPoint(p3, p2, srid=4326)
|
||||
else:
|
||||
@ -144,7 +144,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P2', qs[0].name)
|
||||
|
||||
if not SpatialBackend.mysql:
|
||||
if not mysql:
|
||||
# This time center2 is in a different coordinate system and needs
|
||||
# to be wrapped in transformation SQL.
|
||||
qs = Parcel.objects.filter(center2__within=F('border1'))
|
||||
@ -157,7 +157,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P1', qs[0].name)
|
||||
|
||||
if not SpatialBackend.mysql:
|
||||
if not mysql:
|
||||
# This time the city column should be wrapped in transformation SQL.
|
||||
qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
|
||||
self.assertEqual(1, len(qs))
|
||||
@ -175,8 +175,8 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
for m, d, t in zip(gqs, gvqs, gvlqs):
|
||||
# The values should be Geometry objects and not raw strings returned
|
||||
# by the spatial database.
|
||||
self.failUnless(isinstance(d['point'], SpatialBackend.Geometry))
|
||||
self.failUnless(isinstance(t[1], SpatialBackend.Geometry))
|
||||
self.failUnless(isinstance(d['point'], Geometry))
|
||||
self.failUnless(isinstance(t[1], Geometry))
|
||||
self.assertEqual(m.point, d['point'])
|
||||
self.assertEqual(m.point, t[1])
|
||||
|
||||
@ -279,19 +279,11 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
def test14_collect(self):
|
||||
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
|
||||
# Reference query:
|
||||
<<<<<<< HEAD:django/contrib/gis/tests/relatedapp/tests.py
|
||||
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
|
||||
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
||||
# WHERE "relatedapp_city"."state" = 'TX';
|
||||
ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
|
||||
|
||||
=======
|
||||
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
|
||||
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
||||
# WHERE "relatedapp_city"."state" = 'TX';
|
||||
ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
|
||||
|
||||
>>>>>>> master:django/contrib/gis/tests/relatedapp/tests.py
|
||||
c1 = City.objects.filter(state='TX').collect(field_name='location__point')
|
||||
c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
|
||||
|
||||
@ -300,10 +292,6 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
# consolidate -- that's why 4 points in MultiPoint.
|
||||
self.assertEqual(4, len(coll))
|
||||
self.assertEqual(ref_geom, coll)
|
||||
<<<<<<< HEAD:django/contrib/gis/tests/relatedapp/tests.py
|
||||
|
||||
=======
|
||||
>>>>>>> master:django/contrib/gis/tests/relatedapp/tests.py
|
||||
|
||||
# TODO: Related tests for KML, GML, and distance lookups.
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
from tests import *
|
@ -1,8 +1,7 @@
|
||||
import unittest
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
|
||||
from django.db import connection
|
||||
from django.contrib.gis.tests.utils import mysql, no_mysql, oracle, postgis, spatialite
|
||||
if not mysql:
|
||||
from django.contrib.gis.models import SpatialRefSys
|
||||
|
||||
test_srs = ({'srid' : 4326,
|
||||
'auth_name' : ('EPSG', True),
|
||||
@ -28,9 +27,12 @@ test_srs = ({'srid' : 4326,
|
||||
},
|
||||
)
|
||||
|
||||
if SpatialBackend.postgis:
|
||||
major, minor1, minor2 = SpatialBackend.version
|
||||
POSTGIS_14 = major >=1 and minor1 >= 4
|
||||
if oracle:
|
||||
from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
|
||||
elif postgis:
|
||||
from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
|
||||
elif spatialite:
|
||||
from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
|
||||
|
||||
class SpatialRefSysTest(unittest.TestCase):
|
||||
|
||||
@ -52,7 +54,7 @@ class SpatialRefSysTest(unittest.TestCase):
|
||||
|
||||
# No proj.4 and different srtext on oracle backends :(
|
||||
if postgis:
|
||||
if POSTGIS_14:
|
||||
if connection.ops.spatial_version >= (1, 4, 0):
|
||||
srtext = sd['srtext14']
|
||||
else:
|
||||
srtext = sd['srtext']
|
||||
@ -79,7 +81,7 @@ class SpatialRefSysTest(unittest.TestCase):
|
||||
self.assertEqual(sd['proj4'], srs.proj4)
|
||||
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
|
||||
if not spatialite:
|
||||
if POSTGIS_14:
|
||||
if connection.ops.spatial_version >= (1, 4, 0):
|
||||
srtext = sd['srtext14']
|
||||
else:
|
||||
srtext = sd['srtext']
|
||||
|
@ -1,11 +1,12 @@
|
||||
from django.conf import settings
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
|
||||
# function that will pass a test.
|
||||
def pass_test(*args): return
|
||||
|
||||
def no_backend(test_func, backend):
|
||||
"Use this decorator to disable test on specified backend."
|
||||
if settings.DATABASE_ENGINE == backend:
|
||||
if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1] == backend:
|
||||
return pass_test
|
||||
else:
|
||||
return test_func
|
||||
@ -13,12 +14,13 @@ def no_backend(test_func, backend):
|
||||
# Decorators to disable entire test functions for specific
|
||||
# spatial backends.
|
||||
def no_oracle(func): return no_backend(func, 'oracle')
|
||||
def no_postgis(func): return no_backend(func, 'postgresql_psycopg2')
|
||||
def no_postgis(func): return no_backend(func, 'postgis')
|
||||
def no_mysql(func): return no_backend(func, 'mysql')
|
||||
def no_spatialite(func): return no_backend(func, 'sqlite3')
|
||||
def no_spatialite(func): return no_backend(func, 'spatialite')
|
||||
|
||||
# Shortcut booleans to omit only portions of tests.
|
||||
oracle = settings.DATABASE_ENGINE == 'oracle'
|
||||
postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2'
|
||||
mysql = settings.DATABASE_ENGINE == 'mysql'
|
||||
spatialite = settings.DATABASE_ENGINE == 'sqlite3'
|
||||
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
|
||||
oracle = _default_db == 'oracle'
|
||||
postgis = _default_db == 'postgis'
|
||||
mysql = _default_db == 'mysql'
|
||||
spatialite = _default_db == 'spatialite'
|
||||
|
@ -3,115 +3,15 @@
|
||||
The LayerMapping class provides a way to map the contents of OGR
|
||||
vector files (e.g. SHP files) to Geographic-enabled Django models.
|
||||
|
||||
This grew out of my personal needs, specifically the code repetition
|
||||
that went into pulling geometries and fields out of an OGR layer,
|
||||
converting to another coordinate system (e.g. WGS84), and then inserting
|
||||
into a GeoDjango model.
|
||||
|
||||
Please report any bugs encountered using this utility.
|
||||
|
||||
Requirements: OGR C Library (from GDAL) required.
|
||||
|
||||
Usage:
|
||||
lm = LayerMapping(model, source_file, mapping) where,
|
||||
|
||||
model:
|
||||
GeoDjango model (not an instance)
|
||||
|
||||
data:
|
||||
OGR-supported data source file (e.g. a shapefile) or
|
||||
gdal.DataSource instance
|
||||
|
||||
mapping:
|
||||
A python dictionary, keys are strings corresponding
|
||||
to the GeoDjango model field, and values correspond to
|
||||
string field names for the OGR feature, or if the model field
|
||||
is a geographic then it should correspond to the OGR
|
||||
geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
|
||||
|
||||
Keyword Args:
|
||||
layer:
|
||||
The index of the layer to use from the Data Source (defaults to 0)
|
||||
|
||||
source_srs:
|
||||
Use this to specify the source SRS manually (for example,
|
||||
some shapefiles don't come with a '.prj' file). An integer SRID,
|
||||
a string WKT, and SpatialReference objects are valid parameters.
|
||||
|
||||
encoding:
|
||||
Specifies the encoding of the string in the OGR data source.
|
||||
For example, 'latin-1', 'utf-8', and 'cp437' are all valid
|
||||
encoding parameters.
|
||||
|
||||
transaction_mode:
|
||||
May be 'commit_on_success' (default) or 'autocommit'.
|
||||
|
||||
transform:
|
||||
Setting this to False will disable all coordinate transformations.
|
||||
|
||||
unique:
|
||||
Setting this to the name, or a tuple of names, from the given
|
||||
model will create models unique only to the given name(s).
|
||||
Geometries will from each feature will be added into the collection
|
||||
associated with the unique model. Forces transaction mode to
|
||||
be 'autocommit'.
|
||||
|
||||
Example:
|
||||
|
||||
1. You need a GDAL-supported data source, like a shapefile.
|
||||
|
||||
Assume we're using the test_poly SHP file:
|
||||
>>> from django.contrib.gis.gdal import DataSource
|
||||
>>> ds = DataSource('test_poly.shp')
|
||||
>>> layer = ds[0]
|
||||
>>> print layer.fields # Exploring the fields in the layer, we only want the 'str' field.
|
||||
['float', 'int', 'str']
|
||||
>>> print len(layer) # getting the number of features in the layer (should be 3)
|
||||
3
|
||||
>>> print layer.geom_type # Should be 3 (a Polygon)
|
||||
3
|
||||
>>> print layer.srs # WGS84
|
||||
GEOGCS["GCS_WGS_1984",
|
||||
DATUM["WGS_1984",
|
||||
SPHEROID["WGS_1984",6378137,298.257223563]],
|
||||
PRIMEM["Greenwich",0],
|
||||
UNIT["Degree",0.017453292519943295]]
|
||||
|
||||
2. Now we define our corresponding Django model (make sure to use syncdb):
|
||||
|
||||
from django.contrib.gis.db import models
|
||||
class TestGeo(models.Model, models.GeoMixin):
|
||||
name = models.CharField(maxlength=25) # corresponds to the 'str' field
|
||||
poly = models.PolygonField(srid=4269) # we want our model in a different SRID
|
||||
objects = models.GeoManager()
|
||||
def __str__(self):
|
||||
return 'Name: %s' % self.name
|
||||
|
||||
3. Use LayerMapping to extract all the features and place them in the database:
|
||||
|
||||
>>> from django.contrib.gis.utils import LayerMapping
|
||||
>>> from geoapp.models import TestGeo
|
||||
>>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' layer field.
|
||||
'poly' : 'POLYGON', # For geometry fields use OGC name.
|
||||
} # The mapping is a dictionary
|
||||
>>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
|
||||
>>> lm.save(verbose=True) # Save the layermap, imports the data.
|
||||
Saved: Name: 1
|
||||
Saved: Name: 2
|
||||
Saved: Name: 3
|
||||
|
||||
LayerMapping just transformed the three geometries from the SHP file from their
|
||||
source spatial reference system (WGS84) to the spatial reference system of
|
||||
the GeoDjango model (NAD83). If no spatial reference system is defined for
|
||||
the layer, use the `source_srs` keyword with a SpatialReference object to
|
||||
specify one.
|
||||
For more information, please consult the GeoDjango documentation:
|
||||
http://geodjango.org/docs/layermapping.html
|
||||
"""
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
from django.contrib.gis.db.models import GeometryField
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.gdal import CoordTransform, DataSource, \
|
||||
OGRException, OGRGeometry, OGRGeomType, SpatialReference
|
||||
from django.contrib.gis.gdal.field import \
|
||||
@ -167,7 +67,7 @@ class LayerMapping(object):
|
||||
def __init__(self, model, data, mapping, layer=0,
|
||||
source_srs=None, encoding=None,
|
||||
transaction_mode='commit_on_success',
|
||||
transform=True, unique=None):
|
||||
transform=True, unique=None, using=DEFAULT_DB_ALIAS):
|
||||
"""
|
||||
A LayerMapping object is initialized using the given Model (not an instance),
|
||||
a DataSource (or string path to an OGR-supported data file), and a mapping
|
||||
@ -181,6 +81,9 @@ class LayerMapping(object):
|
||||
self.ds = data
|
||||
self.layer = self.ds[layer]
|
||||
|
||||
self.using = using
|
||||
self.spatial_backend = connections[using].ops
|
||||
|
||||
# Setting the mapping & model attributes.
|
||||
self.mapping = mapping
|
||||
self.model = model
|
||||
@ -191,7 +94,7 @@ class LayerMapping(object):
|
||||
|
||||
# Getting the geometry column associated with the model (an
|
||||
# exception will be raised if there is no geometry column).
|
||||
if SpatialBackend.mysql:
|
||||
if self.spatial_backend.mysql:
|
||||
transform = False
|
||||
else:
|
||||
self.geo_col = self.geometry_column()
|
||||
@ -230,6 +133,9 @@ class LayerMapping(object):
|
||||
else:
|
||||
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
|
||||
|
||||
if using is None:
|
||||
pass
|
||||
|
||||
#### Checking routines used during initialization ####
|
||||
def check_fid_range(self, fid_range):
|
||||
"This checks the `fid_range` keyword."
|
||||
@ -341,10 +247,10 @@ class LayerMapping(object):
|
||||
|
||||
def check_srs(self, source_srs):
|
||||
"Checks the compatibility of the given spatial reference object."
|
||||
from django.contrib.gis.models import SpatialRefSys
|
||||
|
||||
if isinstance(source_srs, SpatialReference):
|
||||
sr = source_srs
|
||||
elif isinstance(source_srs, SpatialRefSys):
|
||||
elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()):
|
||||
sr = source_srs.srs
|
||||
elif isinstance(source_srs, (int, basestring)):
|
||||
sr = SpatialReference(source_srs)
|
||||
@ -517,7 +423,7 @@ class LayerMapping(object):
|
||||
#### Other model methods ####
|
||||
def coord_transform(self):
|
||||
"Returns the coordinate transformation object."
|
||||
from django.contrib.gis.models import SpatialRefSys
|
||||
SpatialRefSys = self.spatial_backend.spatial_ref_sys()
|
||||
try:
|
||||
# Getting the target spatial reference system
|
||||
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
|
||||
@ -529,7 +435,6 @@ class LayerMapping(object):
|
||||
|
||||
def geometry_column(self):
|
||||
"Returns the GeometryColumn model associated with the geographic column."
|
||||
from django.contrib.gis.models import GeometryColumns
|
||||
# Use the `get_field_by_name` on the model's options so that we
|
||||
# get the correct model if there's model inheritance -- otherwise
|
||||
# the returned model is None.
|
||||
@ -543,11 +448,13 @@ class LayerMapping(object):
|
||||
db_table = model._meta.db_table
|
||||
geo_col = fld.column
|
||||
|
||||
if SpatialBackend.oracle:
|
||||
if self.spatial_backend.oracle:
|
||||
# Making upper case for Oracle.
|
||||
db_table = db_table.upper()
|
||||
geo_col = geo_col.upper()
|
||||
|
||||
GeometryColumns = self.spatial_backend.geometry_columns()
|
||||
|
||||
gc_kwargs = { GeometryColumns.table_name_col() : db_table,
|
||||
GeometryColumns.geom_col_name() : geo_col,
|
||||
}
|
||||
@ -643,7 +550,7 @@ class LayerMapping(object):
|
||||
# Getting the keyword arguments and retrieving
|
||||
# the unique model.
|
||||
u_kwargs = self.unique_kwargs(kwargs)
|
||||
m = self.model.objects.get(**u_kwargs)
|
||||
m = self.model.objects.using(self.using).get(**u_kwargs)
|
||||
is_update = True
|
||||
|
||||
# Getting the geometry (in OGR form), creating
|
||||
@ -662,7 +569,7 @@ class LayerMapping(object):
|
||||
|
||||
try:
|
||||
# Attempting to save.
|
||||
m.save()
|
||||
m.save(using=self.using)
|
||||
num_saved += 1
|
||||
if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
|
||||
except SystemExit:
|
||||
|
@ -82,14 +82,9 @@ class Command(BaseCommand):
|
||||
model_list = get_models(app)
|
||||
|
||||
for model in model_list:
|
||||
<<<<<<< HEAD:django/core/management/commands/dumpdata.py
|
||||
# Don't serialize proxy models, or models that haven't been synchronized
|
||||
if not model._meta.proxy and model._meta.db_table in tables:
|
||||
objects.extend(model._default_manager.using(using).all())
|
||||
=======
|
||||
if not model._meta.proxy:
|
||||
objects.extend(model._default_manager.all())
|
||||
>>>>>>> master:django/core/management/commands/dumpdata.py
|
||||
|
||||
try:
|
||||
return serializers.serialize(format, objects, indent=indent)
|
||||
|
@ -1,20 +1,5 @@
|
||||
try:
|
||||
# Only exists in Python 2.4+
|
||||
from threading import local
|
||||
except ImportError:
|
||||
# Import copy of _thread_local.py from Python 2.4
|
||||
from django.utils._threading_local import local
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
# Python 2.3 compat
|
||||
from sets import Set as set
|
||||
|
||||
try:
|
||||
import decimal
|
||||
except ImportError:
|
||||
# Python 2.3 fallback
|
||||
from django.utils import _decimal as decimal
|
||||
import decimal
|
||||
from threading import local
|
||||
|
||||
from django.db import DEFAULT_DB_ALIAS
|
||||
from django.db.backends import util
|
||||
@ -286,9 +271,9 @@ class BaseDatabaseOperations(object):
|
||||
|
||||
def compiler(self, compiler_name):
|
||||
"""
|
||||
Given the default Query class, returns a custom Query class
|
||||
to use for this backend. Returns the Query class unmodified if the
|
||||
backend doesn't need a custom Query clsas.
|
||||
Returns the SQLCompiler class corresponding to the given name,
|
||||
in the namespace corresponding to the `compiler_module` attribute
|
||||
on this backend.
|
||||
"""
|
||||
if compiler_name not in self._cache:
|
||||
self._cache[compiler_name] = getattr(
|
||||
|
@ -357,11 +357,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||
cursor = None
|
||||
if not self._valid_connection():
|
||||
conn_string = convert_unicode(self._connect_string())
|
||||
<<<<<<< HEAD:django/db/backends/oracle/base.py
|
||||
self.connection = Database.connect(conn_string, **self.settings_dict['OPTIONS'])
|
||||
=======
|
||||
self.connection = Database.connect(conn_string, **self.settings_dict['DATABASE_OPTIONS'])
|
||||
>>>>>>> master:django/db/backends/oracle/base.py
|
||||
cursor = FormatStylePlaceholderCursor(self.connection)
|
||||
# Set oracle date to ansi date format. This only needs to execute
|
||||
# once when we create a new connection. We also set the Territory
|
||||
|
@ -29,10 +29,6 @@ except ImportError, exc:
|
||||
module = 'either pysqlite2 or sqlite3 modules (tried in that order)'
|
||||
raise ImproperlyConfigured, "Error loading %s: %s" % (module, exc)
|
||||
|
||||
try:
|
||||
import decimal
|
||||
except ImportError:
|
||||
from django.utils import _decimal as decimal # for Python 2.3
|
||||
|
||||
DatabaseError = Database.DatabaseError
|
||||
IntegrityError = Database.IntegrityError
|
||||
|
@ -230,7 +230,6 @@ class ModelBase(type):
|
||||
|
||||
signals.class_prepared.send(sender=cls)
|
||||
|
||||
<<<<<<< HEAD:django/db/models/base.py
|
||||
class ModelState(object):
|
||||
"""
|
||||
A class for storing instance state
|
||||
@ -238,8 +237,6 @@ class ModelState(object):
|
||||
def __init__(self, db=None):
|
||||
self.db = db
|
||||
|
||||
=======
|
||||
>>>>>>> master:django/db/models/base.py
|
||||
class Model(object):
|
||||
__metaclass__ = ModelBase
|
||||
_deferred = False
|
||||
@ -491,11 +488,7 @@ class Model(object):
|
||||
if pk_set:
|
||||
# Determine whether a record with the primary key already exists.
|
||||
if (force_update or (not force_insert and
|
||||
<<<<<<< HEAD:django/db/models/base.py
|
||||
manager.using(using).filter(pk=pk_val).exists())):
|
||||
=======
|
||||
manager.filter(pk=pk_val).exists())):
|
||||
>>>>>>> master:django/db/models/base.py
|
||||
# It does already exist, so do an UPDATE.
|
||||
if force_update or non_pks:
|
||||
values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
|
||||
@ -534,10 +527,7 @@ class Model(object):
|
||||
# Store the database on which the object was saved
|
||||
self._state.db = using
|
||||
|
||||
<<<<<<< HEAD:django/db/models/base.py
|
||||
# Signal that the save is complete
|
||||
=======
|
||||
>>>>>>> master:django/db/models/base.py
|
||||
if origin and not meta.auto_created:
|
||||
signals.post_save.send(sender=origin, instance=self,
|
||||
created=(not record_exists), raw=raw)
|
||||
|
@ -474,11 +474,7 @@ def create_many_related_manager(superclass, rel=False):
|
||||
if not rel.through._meta.auto_created:
|
||||
opts = through._meta
|
||||
raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s.%s's Manager instead." % (opts.app_label, opts.object_name)
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
new_obj = super(ManyRelatedManager, self).using(self.instance._state.db).create(**kwargs)
|
||||
=======
|
||||
new_obj = super(ManyRelatedManager, self).create(**kwargs)
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
self.add(new_obj)
|
||||
return new_obj
|
||||
create.alters_data = True
|
||||
@ -505,22 +501,15 @@ def create_many_related_manager(superclass, rel=False):
|
||||
new_ids = set()
|
||||
for obj in objs:
|
||||
if isinstance(obj, self.model):
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
if obj._state.db != self.instance._state.db:
|
||||
raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' %
|
||||
(obj, self.instance._state.db, obj._state.db))
|
||||
=======
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
new_ids.add(obj.pk)
|
||||
elif isinstance(obj, Model):
|
||||
raise TypeError, "'%s' instance expected" % self.model._meta.object_name
|
||||
else:
|
||||
new_ids.add(obj)
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
vals = self.through._default_manager.using(self.instance._state.db).values_list(target_field_name, flat=True)
|
||||
=======
|
||||
vals = self.through._default_manager.values_list(target_field_name, flat=True)
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
vals = vals.filter(**{
|
||||
source_field_name: self._pk_val,
|
||||
'%s__in' % target_field_name: new_ids,
|
||||
@ -529,11 +518,7 @@ def create_many_related_manager(superclass, rel=False):
|
||||
|
||||
# Add the ones that aren't there already
|
||||
for obj_id in (new_ids - vals):
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
self.through._default_manager.using(self.instance._state.db).create(**{
|
||||
=======
|
||||
self.through._default_manager.create(**{
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
'%s_id' % source_field_name: self._pk_val,
|
||||
'%s_id' % target_field_name: obj_id,
|
||||
})
|
||||
@ -553,22 +538,14 @@ def create_many_related_manager(superclass, rel=False):
|
||||
else:
|
||||
old_ids.add(obj)
|
||||
# Remove the specified objects from the join table
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
self.through._default_manager.using(self.instance._state.db).filter(**{
|
||||
=======
|
||||
self.through._default_manager.filter(**{
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
source_field_name: self._pk_val,
|
||||
'%s__in' % target_field_name: old_ids
|
||||
}).delete()
|
||||
|
||||
def _clear_items(self, source_field_name):
|
||||
# source_col_name: the PK colname in join_table for the source object
|
||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
||||
self.through._default_manager.using(self.instance._state.db).filter(**{
|
||||
=======
|
||||
self.through._default_manager.filter(**{
|
||||
>>>>>>> master:django/db/models/fields/related.py
|
||||
source_field_name: self._pk_val
|
||||
}).delete()
|
||||
|
||||
|
@ -172,12 +172,9 @@ class Manager(object):
|
||||
def only(self, *args, **kwargs):
|
||||
return self.get_query_set().only(*args, **kwargs)
|
||||
|
||||
<<<<<<< HEAD:django/db/models/manager.py
|
||||
def using(self, *args, **kwargs):
|
||||
return self.get_query_set().using(*args, **kwargs)
|
||||
|
||||
=======
|
||||
>>>>>>> master:django/db/models/manager.py
|
||||
def exists(self, *args, **kwargs):
|
||||
return self.get_query_set().exists(*args, **kwargs)
|
||||
|
||||
|
@ -3,12 +3,8 @@ The main QuerySet implementation. This provides the public API for the ORM.
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
<<<<<<< HEAD:django/db/models/query.py
|
||||
|
||||
from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
|
||||
=======
|
||||
from django.db import connection, transaction, IntegrityError
|
||||
>>>>>>> master:django/db/models/query.py
|
||||
from django.db.models.aggregates import Aggregate
|
||||
from django.db.models.fields import DateField
|
||||
from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
|
||||
@ -480,11 +476,7 @@ class QuerySet(object):
|
||||
|
||||
def exists(self):
|
||||
if self._result_cache is None:
|
||||
<<<<<<< HEAD:django/db/models/query.py
|
||||
return self.query.has_results(using=self.db)
|
||||
=======
|
||||
return self.query.has_results()
|
||||
>>>>>>> master:django/db/models/query.py
|
||||
return bool(self._result_cache)
|
||||
|
||||
##################################################
|
||||
@ -1157,6 +1149,6 @@ def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
||||
part of the public API.
|
||||
"""
|
||||
query = sql.InsertQuery(model)
|
||||
query.insert_values(values, raw_values)
|
||||
compiler = query.get_compiler(using=using)
|
||||
query.insert_values(values, compiler.connection, raw_values)
|
||||
return compiler.execute_sql(return_id)
|
||||
|
@ -755,7 +755,7 @@ class SQLUpdateCompiler(SQLCompiler):
|
||||
|
||||
# Getting the placeholder for the field.
|
||||
if hasattr(field, 'get_placeholder'):
|
||||
placeholder = field.get_placeholder(val)
|
||||
placeholder = field.get_placeholder(val, self.connection)
|
||||
else:
|
||||
placeholder = '%s'
|
||||
|
||||
|
@ -22,12 +22,7 @@ from django.db.models.sql.expressions import SQLEvaluator
|
||||
from django.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
|
||||
from django.core.exceptions import FieldError
|
||||
|
||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
||||
|
||||
__all__ = ['Query']
|
||||
=======
|
||||
__all__ = ['Query', 'BaseQuery']
|
||||
>>>>>>> master:django/db/models/sql/query.py
|
||||
|
||||
class Query(object):
|
||||
"""
|
||||
@ -341,11 +336,7 @@ class Query(object):
|
||||
|
||||
return number
|
||||
|
||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
||||
def has_results(self, using):
|
||||
=======
|
||||
def has_results(self):
|
||||
>>>>>>> master:django/db/models/sql/query.py
|
||||
q = self.clone()
|
||||
q.add_extra({'a': 1}, None, None, None, None, None)
|
||||
q.add_fields(())
|
||||
@ -353,104 +344,8 @@ class Query(object):
|
||||
q.set_aggregate_mask(())
|
||||
q.clear_ordering()
|
||||
q.set_limits(high=1)
|
||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
||||
compiler = q.get_compiler(using=using)
|
||||
return bool(compiler.execute_sql(SINGLE))
|
||||
=======
|
||||
return bool(q.execute_sql(SINGLE))
|
||||
|
||||
def as_sql(self, with_limits=True, with_col_aliases=False):
|
||||
"""
|
||||
Creates the SQL for this query. Returns the SQL string and list of
|
||||
parameters.
|
||||
|
||||
If 'with_limits' is False, any limit/offset information is not included
|
||||
in the query.
|
||||
"""
|
||||
self.pre_sql_setup()
|
||||
out_cols = self.get_columns(with_col_aliases)
|
||||
ordering, ordering_group_by = self.get_ordering()
|
||||
|
||||
# This must come after 'select' and 'ordering' -- see docstring of
|
||||
# get_from_clause() for details.
|
||||
from_, f_params = self.get_from_clause()
|
||||
|
||||
qn = self.quote_name_unless_alias
|
||||
where, w_params = self.where.as_sql(qn=qn)
|
||||
having, h_params = self.having.as_sql(qn=qn)
|
||||
params = []
|
||||
for val in self.extra_select.itervalues():
|
||||
params.extend(val[1])
|
||||
|
||||
result = ['SELECT']
|
||||
if self.distinct:
|
||||
result.append('DISTINCT')
|
||||
result.append(', '.join(out_cols + self.ordering_aliases))
|
||||
|
||||
result.append('FROM')
|
||||
result.extend(from_)
|
||||
params.extend(f_params)
|
||||
|
||||
if where:
|
||||
result.append('WHERE %s' % where)
|
||||
params.extend(w_params)
|
||||
if self.extra_where:
|
||||
if not where:
|
||||
result.append('WHERE')
|
||||
else:
|
||||
result.append('AND')
|
||||
result.append(' AND '.join(self.extra_where))
|
||||
|
||||
grouping, gb_params = self.get_grouping()
|
||||
if grouping:
|
||||
if ordering:
|
||||
# If the backend can't group by PK (i.e., any database
|
||||
# other than MySQL), then any fields mentioned in the
|
||||
# ordering clause needs to be in the group by clause.
|
||||
if not self.connection.features.allows_group_by_pk:
|
||||
for col, col_params in ordering_group_by:
|
||||
if col not in grouping:
|
||||
grouping.append(str(col))
|
||||
gb_params.extend(col_params)
|
||||
else:
|
||||
ordering = self.connection.ops.force_no_ordering()
|
||||
result.append('GROUP BY %s' % ', '.join(grouping))
|
||||
params.extend(gb_params)
|
||||
|
||||
if having:
|
||||
result.append('HAVING %s' % having)
|
||||
params.extend(h_params)
|
||||
|
||||
if ordering:
|
||||
result.append('ORDER BY %s' % ', '.join(ordering))
|
||||
|
||||
if with_limits:
|
||||
if self.high_mark is not None:
|
||||
result.append('LIMIT %d' % (self.high_mark - self.low_mark))
|
||||
if self.low_mark:
|
||||
if self.high_mark is None:
|
||||
val = self.connection.ops.no_limit_value()
|
||||
if val:
|
||||
result.append('LIMIT %d' % val)
|
||||
result.append('OFFSET %d' % self.low_mark)
|
||||
|
||||
params.extend(self.extra_params)
|
||||
return ' '.join(result), tuple(params)
|
||||
|
||||
def as_nested_sql(self):
|
||||
"""
|
||||
Perform the same functionality as the as_sql() method, returning an
|
||||
SQL string and parameters. However, the alias prefixes are bumped
|
||||
beforehand (in a copy -- the current query isn't changed) and any
|
||||
ordering is removed.
|
||||
|
||||
Used when nesting this query inside another.
|
||||
"""
|
||||
obj = self.clone()
|
||||
obj.clear_ordering(True)
|
||||
obj.bump_prefix()
|
||||
return obj.as_sql()
|
||||
>>>>>>> master:django/db/models/sql/query.py
|
||||
|
||||
def combine(self, rhs, connector):
|
||||
"""
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user