mirror of
https://github.com/django/django.git
synced 2025-07-05 02:09: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_PORT = '' # Set to empty string for default. Not used with sqlite3.
|
||||||
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
|
DATABASE_OPTIONS = {} # Set to empty dictionary for default.
|
||||||
|
|
||||||
<<<<<<< HEAD:django/conf/global_settings.py
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> master:django/conf/global_settings.py
|
|
||||||
# The email backend to use. For possible shortcuts see django.core.mail.
|
# The email backend to use. For possible shortcuts see django.core.mail.
|
||||||
# The default is to use the SMTP backend.
|
# The default is to use the SMTP backend.
|
||||||
# Third-party backends can be specified by providing a Python path
|
# Third-party backends can be specified by providing a Python path
|
||||||
|
Binary file not shown.
@ -5,11 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Django\n"
|
"Project-Id-Version: Django\n"
|
||||||
"Report-Msgid-Bugs-To: \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"
|
"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"
|
"PO-Revision-Date: 2008-02-25 15:53+0100\n"
|
||||||
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
|
"Last-Translator: Jarek Zgoda <jarek.zgoda@gmail.com>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@ -227,11 +223,7 @@ msgstr "chiński tradycyjny"
|
|||||||
msgid "Successfully deleted %(count)d %(items)s."
|
msgid "Successfully deleted %(count)d %(items)s."
|
||||||
msgstr "Usunięto %(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
|
#: contrib/admin/actions.py:67 contrib/admin/options.py:1034
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
msgid "Are you sure?"
|
msgid "Are you sure?"
|
||||||
msgstr "Jesteś pewien?"
|
msgstr "Jesteś pewien?"
|
||||||
|
|
||||||
@ -327,11 +319,7 @@ msgstr "brak"
|
|||||||
msgid "Changed %s."
|
msgid "Changed %s."
|
||||||
msgstr "Zmieniono %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
|
#: 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
|
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
|
||||||
#: forms/models.py:596
|
#: forms/models.py:596
|
||||||
msgid "and"
|
msgid "and"
|
||||||
@ -356,94 +344,53 @@ msgstr "Usunięto %(name)s \"%(object)s\"."
|
|||||||
msgid "No fields changed."
|
msgid "No fields changed."
|
||||||
msgstr "Żadne pole nie zmienione."
|
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
|
#: contrib/admin/options.py:602 contrib/auth/admin.py:68
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s \"%(obj)s\" was added successfully."
|
msgid "The %(name)s \"%(obj)s\" was added successfully."
|
||||||
msgstr "%(name)s \"%(obj)s\" dodany pomyślnie."
|
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/admin/options.py:606 contrib/admin/options.py:639
|
||||||
#: contrib/auth/admin.py:77
|
#: contrib/auth/admin.py:77
|
||||||
msgid "You may edit it again below."
|
msgid "You may edit it again below."
|
||||||
msgstr "Możesz ponownie edytować wpis poniżej."
|
msgstr "Możesz ponownie edytować wpis poniżej."
|
||||||
|
|
||||||
#: contrib/admin/options.py:616 contrib/admin/options.py:649
|
#: contrib/admin/options.py:616 contrib/admin/options.py:649
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "You may add another %s below."
|
msgid "You may add another %s below."
|
||||||
msgstr "Możesz dodać nowy wpis %s poniżej."
|
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
|
#: contrib/admin/options.py:637
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s \"%(obj)s\" was changed successfully."
|
msgid "The %(name)s \"%(obj)s\" was changed successfully."
|
||||||
msgstr "%(name)s \"%(obj)s\" zostało pomyślnie zmienione."
|
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
|
#: contrib/admin/options.py:645
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
|
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"%(name)s \"%(obj)s\" dodane pomyślnie. Możesz edytować ponownie wpis poniżej."
|
"%(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
|
#: contrib/admin/options.py:778
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Add %s"
|
msgid "Add %s"
|
||||||
msgstr "Dodaj %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
|
#: contrib/admin/options.py:810 contrib/admin/options.py:1012
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(name)s object with primary key %(key)r does not exist."
|
msgid "%(name)s object with primary key %(key)r does not exist."
|
||||||
msgstr "Obiekt %(name)s o kluczu głównym %(key)r nie istnieje."
|
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
|
#: contrib/admin/options.py:867
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Change %s"
|
msgid "Change %s"
|
||||||
msgstr "Zmień %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
|
#: contrib/admin/options.py:911
|
||||||
msgid "Database error"
|
msgid "Database error"
|
||||||
msgstr "Błąd bazy danych"
|
msgstr "Błąd bazy danych"
|
||||||
|
|
||||||
#: contrib/admin/options.py:947
|
#: contrib/admin/options.py:947
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(count)s %(name)s was changed successfully."
|
msgid "%(count)s %(name)s was changed successfully."
|
||||||
msgid_plural "%(count)s %(name)s were 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[1] "%(count)s %(name)s zostały pomyślnie zmienione."
|
||||||
msgstr[2] "%(count)s %(name)s zostało pomyślnie zmienionych."
|
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
|
#: contrib/admin/options.py:1027
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
|
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
|
||||||
msgstr "%(name)s \"%(obj)s\" usunięty pomyślnie."
|
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
|
#: contrib/admin/options.py:1064
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Change history: %s"
|
msgid "Change history: %s"
|
||||||
msgstr "Historia zmian: %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
|
#: 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
|
#: contrib/auth/forms.py:80
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please enter a correct username and password. Note that both fields are case-"
|
"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 "
|
"Proszę wpisać poprawną nazwę użytkownika i hasło. Uwaga: wielkość liter ma "
|
||||||
"znaczenie."
|
"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
|
#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
|
||||||
msgid "Please log in again, because your session has expired."
|
msgid "Please log in again, because your session has expired."
|
||||||
msgstr "Twoja sesja wygasła, zaloguj się ponownie."
|
msgstr "Twoja sesja wygasła, zaloguj się ponownie."
|
||||||
|
|
||||||
#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
|
#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Looks like your browser isn't configured to accept cookies. Please enable "
|
"Looks like your browser isn't configured to accept cookies. Please enable "
|
||||||
"cookies, reload this page, and try again."
|
"cookies, reload this page, and try again."
|
||||||
@ -502,47 +429,27 @@ msgstr ""
|
|||||||
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
|
"Twoja przeglądarka nie chce akceptować ciasteczek. Zmień jej ustawienia i "
|
||||||
"spróbuj ponownie."
|
"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
|
#: 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
|
#: contrib/admin/views/decorators.py:66
|
||||||
msgid "Usernames cannot contain the '@' character."
|
msgid "Usernames cannot contain the '@' character."
|
||||||
msgstr "Nazwy użytkowników nie mogą zawierać znaku '@'."
|
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
|
#: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Your e-mail address is not your username. Try '%s' instead."
|
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'."
|
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
|
#: contrib/admin/sites.py:374
|
||||||
msgid "Site administration"
|
msgid "Site administration"
|
||||||
msgstr "Administracja stroną"
|
msgstr "Administracja stroną"
|
||||||
|
|
||||||
#: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
|
#: 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/templates/registration/password_reset_complete.html:14
|
||||||
#: contrib/admin/views/decorators.py:20
|
#: contrib/admin/views/decorators.py:20
|
||||||
msgid "Log in"
|
msgid "Log in"
|
||||||
msgstr "Zaloguj się"
|
msgstr "Zaloguj się"
|
||||||
|
|
||||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#: contrib/admin/sites.py:429
|
|
||||||
=======
|
|
||||||
#: contrib/admin/sites.py:433
|
#: contrib/admin/sites.py:433
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%s administration"
|
msgid "%s administration"
|
||||||
msgstr "%s - administracja"
|
msgstr "%s - administracja"
|
||||||
@ -763,13 +670,8 @@ msgid ""
|
|||||||
"Are you sure you want to delete the selected %(object_name)s objects? All of "
|
"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:"
|
"the following objects and their related items will be deleted:"
|
||||||
msgstr ""
|
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 "
|
"Czy chcesz skasować wybrane %(object_name)s? Następujące obiekty i zależne "
|
||||||
"od nich zostaną skasowane:"
|
"od nich zostaną skasowane:"
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
|
|
||||||
#: contrib/admin/templates/admin/filter.html:2
|
#: contrib/admin/templates/admin/filter.html:2
|
||||||
#, python-format
|
#, python-format
|
||||||
@ -1524,11 +1426,7 @@ msgstr "użytkownicy"
|
|||||||
msgid "message"
|
msgid "message"
|
||||||
msgstr "wiadomość"
|
msgstr "wiadomość"
|
||||||
|
|
||||||
<<<<<<< HEAD:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#: contrib/auth/views.py:58
|
|
||||||
=======
|
|
||||||
#: contrib/auth/views.py:60
|
#: contrib/auth/views.py:60
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
msgid "Logged out"
|
msgid "Logged out"
|
||||||
msgstr "Wylogowany"
|
msgstr "Wylogowany"
|
||||||
|
|
||||||
@ -4025,22 +3923,14 @@ msgstr ""
|
|||||||
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
|
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
|
||||||
msgstr "Proszę wpisać poprawną godzinę w formacie HH:MM[:ss[.uuuuuu]]."
|
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
|
#: db/models/fields/related.py:869
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
|
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby "
|
"Przytrzymaj wciśnięty klawisz \"Ctrl\" lub \"Command\" na Mac'u aby "
|
||||||
"zaznaczyć więcej niż jeden wybór."
|
"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
|
#: db/models/fields/related.py:930
|
||||||
>>>>>>> master:django/conf/locale/pl/LC_MESSAGES/django.po
|
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
|
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
|
||||||
msgid_plural ""
|
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
|
This provides an adaptor for Geometries sent to the
|
||||||
MySQL and Oracle database backends.
|
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
|
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.
|
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):
|
class GeometryColumns(models.Model):
|
||||||
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
"Maps to the Oracle USER_SDO_GEOM_METADATA table."
|
||||||
@ -39,17 +41,20 @@ class GeometryColumns(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
|
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."
|
"Maps to the Oracle MDSYS.CS_SRS table."
|
||||||
cs_name = models.CharField(max_length=68)
|
cs_name = models.CharField(max_length=68)
|
||||||
srid = models.IntegerField(primary_key=True)
|
srid = models.IntegerField(primary_key=True)
|
||||||
auth_srid = models.IntegerField()
|
auth_srid = models.IntegerField()
|
||||||
auth_name = models.CharField(max_length=256)
|
auth_name = models.CharField(max_length=256)
|
||||||
wktext = models.CharField(max_length=2046)
|
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:
|
class Meta:
|
||||||
abstract = True
|
app_label = 'gis'
|
||||||
db_table = 'CS_SRS'
|
db_table = 'CS_SRS'
|
||||||
managed = False
|
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.
|
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 import Binary
|
||||||
from psycopg2.extensions import ISQLQuote
|
from psycopg2.extensions import ISQLQuote
|
||||||
|
|
||||||
class PostGISAdaptor(object):
|
class PostGISAdapter(object):
|
||||||
def __init__(self, geom):
|
def __init__(self, geom):
|
||||||
"Initializes on the geometry."
|
"Initializes on the geometry."
|
||||||
# Getting the WKB (in string form, to allow easy pickling of
|
# 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?')
|
raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
|
||||||
|
|
||||||
def __eq__(self, other):
|
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):
|
def __str__(self):
|
||||||
return self.getquoted()
|
return self.getquoted()
|
||||||
@ -30,7 +29,7 @@ class PostGISAdaptor(object):
|
|||||||
def getquoted(self):
|
def getquoted(self):
|
||||||
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
|
||||||
# Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
|
# 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):
|
def prepare_database_save(self, unused):
|
||||||
return self
|
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.
|
The GeometryColumns and SpatialRefSys models for the PostGIS backend.
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
|
||||||
|
|
||||||
class GeometryColumns(models.Model):
|
class GeometryColumns(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -42,7 +43,7 @@ class GeometryColumns(models.Model):
|
|||||||
(self.f_table_name, self.f_geometry_column,
|
(self.f_table_name, self.f_geometry_column,
|
||||||
self.coord_dimension, self.type, self.srid)
|
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
|
The 'spatial_ref_sys' table from PostGIS. See the PostGIS
|
||||||
documentaiton at Ch. 4.2.1.
|
documentaiton at Ch. 4.2.1.
|
||||||
@ -54,7 +55,7 @@ class SpatialRefSys(models.Model):
|
|||||||
proj4text = models.CharField(max_length=2048)
|
proj4text = models.CharField(max_length=2048)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
app_label = 'gis'
|
||||||
db_table = 'spatial_ref_sys'
|
db_table = 'spatial_ref_sys'
|
||||||
managed = False
|
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.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."
|
"SQLite adaptor for geometry objects."
|
||||||
def __conform__(self, protocol):
|
def __conform__(self, protocol):
|
||||||
if protocol is Database.PrepareProtocol:
|
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.
|
The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.gis.db.backends.base import SpatialRefSysMixin
|
||||||
|
|
||||||
class GeometryColumns(models.Model):
|
class GeometryColumns(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -40,7 +41,7 @@ class GeometryColumns(models.Model):
|
|||||||
(self.f_table_name, self.f_geometry_column,
|
(self.f_table_name, self.f_geometry_column,
|
||||||
self.coord_dimension, self.type, self.srid)
|
self.coord_dimension, self.type, self.srid)
|
||||||
|
|
||||||
class SpatialRefSys(models.Model):
|
class SpatialRefSys(models.Model, SpatialRefSysMixin):
|
||||||
"""
|
"""
|
||||||
The 'spatial_ref_sys' table from SpatiaLite.
|
The 'spatial_ref_sys' table from SpatiaLite.
|
||||||
"""
|
"""
|
||||||
@ -56,6 +57,6 @@ class SpatialRefSys(models.Model):
|
|||||||
return SpatialReference(self.proj4text).wkt
|
return SpatialReference(self.proj4text).wkt
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
app_label = 'gis'
|
||||||
db_table = 'spatial_ref_sys'
|
db_table = 'spatial_ref_sys'
|
||||||
managed = False
|
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.
|
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.function = function
|
||||||
self.operator = operator
|
self.operator = operator
|
||||||
self.result = result
|
self.result = result
|
||||||
self.beg_subst = beg_subst
|
self.extra = kwargs
|
||||||
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
|
|
||||||
|
|
||||||
@property
|
def as_sql(self, geo_col, geometry='%s'):
|
||||||
def sql_subst(self):
|
return self.sql_template % self.params(geo_col, geometry)
|
||||||
return ''.join([self.beg_subst, self.end_subst])
|
|
||||||
|
|
||||||
def as_sql(self, geo_col):
|
def params(self, geo_col, geometry):
|
||||||
return self.sql_subst % self.params(geo_col)
|
params = {'function' : self.function,
|
||||||
|
'geo_col' : geo_col,
|
||||||
def params(self, geo_col):
|
'geometry' : geometry,
|
||||||
return (geo_col, self.operator)
|
'operator' : self.operator,
|
||||||
|
'result' : self.result,
|
||||||
|
}
|
||||||
|
params.update(self.extra)
|
||||||
|
return params
|
||||||
|
|
||||||
class SpatialFunction(SpatialOperation):
|
class SpatialFunction(SpatialOperation):
|
||||||
"""
|
"""
|
||||||
Base class for generating spatial SQL related to a function.
|
Base class for generating spatial SQL related to a function.
|
||||||
"""
|
"""
|
||||||
def __init__(self, func, beg_subst='%s(%s, %%s', end_subst=')', result='', operator=''):
|
sql_template = '%(function)s(%(geo_col)s, %(geometry)s)'
|
||||||
# Getting the function prefix.
|
|
||||||
kwargs = {'function' : func, 'operator' : operator, 'result' : result,
|
|
||||||
'beg_subst' : beg_subst, 'end_subst' : end_subst,}
|
|
||||||
super(SpatialFunction, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
def params(self, geo_col):
|
def __init__(self, func, result='', operator='', **kwargs):
|
||||||
return (self.function, geo_col)
|
# 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.db.models import Aggregate
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
|
||||||
from django.contrib.gis.db.models.sql import GeomField
|
from django.contrib.gis.db.models.sql import GeomField
|
||||||
|
|
||||||
class GeoAggregate(Aggregate):
|
class Collect(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):
|
|
||||||
name = 'Collect'
|
name = 'Collect'
|
||||||
|
|
||||||
class Extent(GeoAggregate):
|
class Extent(Aggregate):
|
||||||
name = 'Extent'
|
name = 'Extent'
|
||||||
|
|
||||||
class Extent3D(GeoAggregate):
|
class Extent3D(Aggregate):
|
||||||
name = 'Extent3D'
|
name = 'Extent3D'
|
||||||
|
|
||||||
class MakeLine(GeoAggregate):
|
class MakeLine(Aggregate):
|
||||||
name = 'MakeLine'
|
name = 'MakeLine'
|
||||||
|
|
||||||
class Union(GeoAggregate):
|
class Union(Aggregate):
|
||||||
name = 'Union'
|
name = 'Union'
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
|
from django.db.models.fields import Field
|
||||||
from django.contrib.gis import forms
|
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.db.models.proxy import GeometryProxy
|
||||||
|
from django.contrib.gis.geometry import Geometry, GeometryException
|
||||||
from django.contrib.gis.measure import Distance
|
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.
|
# 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.
|
# This exists so that we don't have to hit the database each time
|
||||||
_srid_cache = {}
|
# 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
|
Returns the units, unit name, and spheroid WKT associated with the
|
||||||
given SRID from the `spatial_ref_sys` (or equivalent) spatial database
|
given SRID from the `spatial_ref_sys` (or equivalent) spatial database
|
||||||
@ -17,19 +21,26 @@ def get_srid_info(srid):
|
|||||||
"""
|
"""
|
||||||
global _srid_cache
|
global _srid_cache
|
||||||
|
|
||||||
if SpatialBackend.mysql:
|
# No `spatial_ref_sys` table in MySQL.
|
||||||
|
if connection.ops.mysql:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
if not srid in _srid_cache:
|
name = connection.ops.name
|
||||||
from django.contrib.gis.models import SpatialRefSys
|
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)
|
sr = SpatialRefSys.objects.get(srid=srid)
|
||||||
units, units_name = sr.units
|
units, units_name = sr.units
|
||||||
spheroid = SpatialRefSys.get_spheroid(sr.wkt)
|
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 base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||||
|
|
||||||
# The OpenGIS Geometry name.
|
# The OpenGIS Geometry name.
|
||||||
@ -38,7 +49,8 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
# Geodetic units.
|
# Geodetic units.
|
||||||
geodetic_units = ('Decimal Degree', 'degree')
|
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
|
The initialization function for geometry fields. Takes the following
|
||||||
as keyword arguments:
|
as keyword arguments:
|
||||||
@ -54,6 +66,9 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
|
|
||||||
dim:
|
dim:
|
||||||
The number of dimensions for this geometry. Defaults to 2.
|
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.
|
# Setting the index flag with the value of the `spatial_index` keyword.
|
||||||
@ -70,120 +85,116 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
# first parameter, so this works like normal fields.
|
# first parameter, so this works like normal fields.
|
||||||
kwargs['verbose_name'] = verbose_name
|
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.
|
# 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`.
|
# 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, connection):
|
||||||
def spheroid(self):
|
|
||||||
if not hasattr(self, '_spheroid'):
|
if not hasattr(self, '_spheroid'):
|
||||||
self._get_srid_info()
|
self._get_srid_info(connection)
|
||||||
return self._spheroid
|
return self._spheroid
|
||||||
|
|
||||||
@property
|
def units(self, connection):
|
||||||
def units(self):
|
|
||||||
if not hasattr(self, '_units'):
|
if not hasattr(self, '_units'):
|
||||||
self._get_srid_info()
|
self._get_srid_info(connection)
|
||||||
return self._units
|
return self._units
|
||||||
|
|
||||||
@property
|
def units_name(self, connection):
|
||||||
def units_name(self):
|
|
||||||
if not hasattr(self, '_units_name'):
|
if not hasattr(self, '_units_name'):
|
||||||
self._get_srid_info()
|
self._get_srid_info(connection)
|
||||||
return self._units_name
|
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 ###
|
### Routines specific to GeometryField ###
|
||||||
@property
|
def geodetic(self, connection):
|
||||||
def geodetic(self):
|
|
||||||
"""
|
"""
|
||||||
Returns true if this field's SRID corresponds with a coordinate
|
Returns true if this field's SRID corresponds with a coordinate
|
||||||
system that uses non-projected units (e.g., latitude/longitude).
|
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
|
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,
|
`D(km=1)` was passed in and the units of the field were in meters,
|
||||||
then 1000 would be returned.
|
then 1000 would be returned.
|
||||||
"""
|
"""
|
||||||
# Getting the distance parameter and any options.
|
# Getting the distance parameter and any options.
|
||||||
if len(dist_val) == 1: dist, option = dist_val[0], None
|
if len(dist_val) == 1:
|
||||||
else: dist, option = dist_val
|
dist, option = dist_val[0], None
|
||||||
|
else:
|
||||||
|
dist, option = dist_val
|
||||||
|
|
||||||
if isinstance(dist, Distance):
|
if isinstance(dist, Distance):
|
||||||
if self.geodetic:
|
if self.geodetic(connection):
|
||||||
# Won't allow Distance objects w/DWithin lookups on PostGIS.
|
# Won't allow Distance objects w/DWithin lookups on PostGIS.
|
||||||
if SpatialBackend.postgis and lookup_type == 'dwithin':
|
if connection.ops.postgis and lookup_type == 'dwithin':
|
||||||
raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.')
|
raise ValueError('Only numeric values of degree units are allowed on geographic DWithin queries.')
|
||||||
|
|
||||||
# Spherical distance calculation parameter should be in meters.
|
# Spherical distance calculation parameter should be in meters.
|
||||||
dist_param = dist.m
|
dist_param = dist.m
|
||||||
else:
|
else:
|
||||||
dist_param = getattr(dist, Distance.unit_attname(self.units_name))
|
dist_param = getattr(dist, Distance.unit_attname(self.units_name(connection)))
|
||||||
else:
|
else:
|
||||||
# Assuming the distance is in the units of the field.
|
# Assuming the distance is in the units of the field.
|
||||||
dist_param = dist
|
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
|
# On PostGIS, by default `ST_distance_sphere` is used; but if the
|
||||||
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
# accuracy of `ST_distance_spheroid` is needed than the spheroid
|
||||||
# needs to be passed to the SQL stored procedure.
|
# needs to be passed to the SQL stored procedure.
|
||||||
return [gqn(self._spheroid), dist_param]
|
return [self._spheroid, dist_param]
|
||||||
else:
|
else:
|
||||||
return [dist_param]
|
return [dist_param]
|
||||||
|
|
||||||
def get_geometry(self, value):
|
def get_prep_value(self, value):
|
||||||
"""
|
"""
|
||||||
Retrieves the geometry, setting the default SRID from the given
|
Spatial lookup values are either a parameter that is (or may be
|
||||||
lookup parameters.
|
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]
|
geom = value[0]
|
||||||
|
seq_value = True
|
||||||
else:
|
else:
|
||||||
geom = value
|
geom = value
|
||||||
|
seq_value = False
|
||||||
|
|
||||||
# When the input is not a GEOS geometry, attempt to construct one
|
# When the input is not a GEOS geometry, attempt to construct one
|
||||||
# from the given string input.
|
# from the given string input.
|
||||||
if isinstance(geom, SpatialBackend.Geometry):
|
if isinstance(geom, Geometry):
|
||||||
pass
|
pass
|
||||||
elif isinstance(geom, basestring):
|
elif isinstance(geom, basestring):
|
||||||
try:
|
try:
|
||||||
geom = SpatialBackend.Geometry(geom)
|
geom = Geometry(geom)
|
||||||
except SpatialBackend.GeometryException:
|
except GeometryException:
|
||||||
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
|
raise ValueError('Could not create geometry from lookup value: %s' % str(value))
|
||||||
else:
|
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.
|
# Assigning the SRID value.
|
||||||
geom.srid = self.get_srid(geom)
|
geom.srid = self.get_srid(geom)
|
||||||
|
|
||||||
|
if seq_value:
|
||||||
|
lookup_val = [geom]
|
||||||
|
lookup_val.extend(value[1:])
|
||||||
|
return tuple(lookup_val)
|
||||||
|
else:
|
||||||
return geom
|
return geom
|
||||||
|
|
||||||
def get_srid(self, geom):
|
def get_srid(self, geom):
|
||||||
@ -203,7 +214,20 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
super(GeometryField, self).contribute_to_class(cls, name)
|
super(GeometryField, self).contribute_to_class(cls, name)
|
||||||
|
|
||||||
# Setup for lazy-instantiated Geometry object.
|
# 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):
|
def formfield(self, **kwargs):
|
||||||
defaults = {'form_class' : forms.GeometryField,
|
defaults = {'form_class' : forms.GeometryField,
|
||||||
@ -214,46 +238,50 @@ class GeometryField(SpatialBackend.Field):
|
|||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(GeometryField, self).formfield(**defaults)
|
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
|
XXX: Document me.
|
||||||
given lookup type and value. The value will be prepared for database
|
|
||||||
lookup (e.g., spatial transformation SQL will be added if necessary).
|
|
||||||
"""
|
"""
|
||||||
if lookup_type in SpatialBackend.gis_terms:
|
if lookup_type in connection.ops.gis_terms:
|
||||||
# special case for isnull lookup
|
# special case for isnull lookup
|
||||||
if lookup_type == 'isnull': return [], []
|
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)]
|
|
||||||
|
|
||||||
|
# Populating the parameters list, and wrapping the Geometry
|
||||||
|
# with the Adapter of the spatial backend.
|
||||||
if isinstance(value, (tuple, list)):
|
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.
|
# Getting the distance parameter in the units of the field.
|
||||||
where += self.get_distance(value[1:], lookup_type)
|
params += self.get_distance(value[1:], lookup_type, connection)
|
||||||
elif lookup_type in SpatialBackend.limited_where:
|
elif lookup_type in connection.ops.limited_where:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
# Otherwise, making sure any other parameters are properly quoted.
|
params += value[1:]
|
||||||
where += map(gqn, value[1:])
|
elif isinstance(value, SQLEvaluator):
|
||||||
return where, params
|
params = []
|
||||||
|
else:
|
||||||
|
params = [connection.ops.Adapter(value)]
|
||||||
|
|
||||||
|
return params
|
||||||
else:
|
else:
|
||||||
raise TypeError("Field has invalid lookup: %s" % lookup_type)
|
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."
|
"Prepares the value for saving in the database."
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
else:
|
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
|
# The OpenGIS Geometry Type Fields
|
||||||
class PointField(GeometryField):
|
class PointField(GeometryField):
|
@ -1,20 +1,19 @@
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.db import connections
|
||||||
from django.db import connection
|
|
||||||
from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
|
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 import aggregates
|
||||||
from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField
|
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.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
|
||||||
|
from django.contrib.gis.geometry import Geometry
|
||||||
from django.contrib.gis.measure import Area, Distance
|
from django.contrib.gis.measure import Area, Distance
|
||||||
|
|
||||||
class GeoQuerySet(QuerySet):
|
class GeoQuerySet(QuerySet):
|
||||||
"The Geographic QuerySet."
|
"The Geographic QuerySet."
|
||||||
|
|
||||||
### Methods overloaded from 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)
|
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):
|
def values(self, *fields):
|
||||||
return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
|
return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
|
||||||
@ -42,14 +41,16 @@ class GeoQuerySet(QuerySet):
|
|||||||
'geo_field' : geo_field,
|
'geo_field' : geo_field,
|
||||||
'setup' : False,
|
'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_fmt'] = '%(geo_col)s,%(tolerance)s'
|
||||||
s['procedure_args']['tolerance'] = tolerance
|
s['procedure_args']['tolerance'] = tolerance
|
||||||
s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
|
s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
|
||||||
elif SpatialBackend.postgis or SpatialBackend.spatialite:
|
elif backend.postgis or backend.spatialite:
|
||||||
if not geo_field.geodetic:
|
if not geo_field.geodetic(connection):
|
||||||
# Getting the area units of the geographic field.
|
# 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:
|
else:
|
||||||
# TODO: Do we want to support raw number areas for geodetic fields?
|
# TODO: Do we want to support raw number areas for geodetic fields?
|
||||||
raise Exception('Area on geodetic coordinate systems not supported.')
|
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
|
the coordinate reference system and the bounding box to be included
|
||||||
in the GeoJSON representation of the geometry.
|
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.')
|
raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
|
||||||
|
|
||||||
if not isinstance(precision, (int, long)):
|
if not isinstance(precision, (int, long)):
|
||||||
@ -135,8 +137,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
|
|
||||||
# Setting the options flag -- which depends on which version of
|
# Setting the options flag -- which depends on which version of
|
||||||
# PostGIS we're using.
|
# PostGIS we're using.
|
||||||
major, minor1, minor2 = SpatialBackend.version
|
if backend.spatial_version >= (1, 4, 0):
|
||||||
if major >=1 and (minor1 >= 4):
|
|
||||||
options = 0
|
options = 0
|
||||||
if crs and bbox: options = 3
|
if crs and bbox: options = 3
|
||||||
elif bbox: options = 1
|
elif bbox: options = 1
|
||||||
@ -157,12 +158,12 @@ class GeoQuerySet(QuerySet):
|
|||||||
Returns GML representation of the given field in a `gml` attribute
|
Returns GML representation of the given field in a `gml` attribute
|
||||||
on each element of the GeoQuerySet.
|
on each element of the GeoQuerySet.
|
||||||
"""
|
"""
|
||||||
|
backend = connections[self.db].ops
|
||||||
s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
|
s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
|
||||||
if SpatialBackend.postgis:
|
if backend.postgis:
|
||||||
# PostGIS AsGML() aggregate function parameter order depends on the
|
# PostGIS AsGML() aggregate function parameter order depends on the
|
||||||
# version -- uggh.
|
# version -- uggh.
|
||||||
major, minor1, minor2 = SpatialBackend.version
|
if backend.spatial_version > (1, 3, 1):
|
||||||
if major >= 1 and (minor1 > 3 or (minor1 == 3 and minor2 > 1)):
|
|
||||||
procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
|
procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
|
||||||
else:
|
else:
|
||||||
procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
|
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
|
Scales the geometry to a new size by multiplying the ordinates
|
||||||
with the given x,y,z scale factors.
|
with the given x,y,z scale factors.
|
||||||
"""
|
"""
|
||||||
if SpatialBackend.spatialite:
|
if connections[self.db].ops.spatialite:
|
||||||
if z != 0.0:
|
if z != 0.0:
|
||||||
raise NotImplementedError('SpatiaLite does not support 3D scaling.')
|
raise NotImplementedError('SpatiaLite does not support 3D scaling.')
|
||||||
s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
|
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
|
Translates the geometry to a new location using the given numeric
|
||||||
parameters as offsets.
|
parameters as offsets.
|
||||||
"""
|
"""
|
||||||
if SpatialBackend.spatialite:
|
if connections[self.db].ops.spatialite:
|
||||||
if z != 0.0:
|
if z != 0.0:
|
||||||
raise NotImplementedError('SpatiaLite does not support 3D translation.')
|
raise NotImplementedError('SpatiaLite does not support 3D translation.')
|
||||||
s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
|
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
|
# Setting the key for the field's column with the custom SELECT SQL to
|
||||||
# override the geometry column returned from the database.
|
# override the geometry column returned from the database.
|
||||||
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?
|
# TODO: Should we have this as an alias?
|
||||||
# custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
|
# custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
|
||||||
self.query.transformed_srid = srid # So other GeoQuerySet methods
|
self.query.transformed_srid = srid # So other GeoQuerySet methods
|
||||||
@ -396,9 +397,13 @@ class GeoQuerySet(QuerySet):
|
|||||||
Performs set up for executing the spatial function.
|
Performs set up for executing the spatial function.
|
||||||
"""
|
"""
|
||||||
# Does the spatial backend support this?
|
# 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 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.
|
# Initializing the procedure arguments.
|
||||||
procedure_args = {'function' : func}
|
procedure_args = {'function' : func}
|
||||||
@ -442,7 +447,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
# Adding any keyword parameters for the Aggregate object. Oracle backends
|
# Adding any keyword parameters for the Aggregate object. Oracle backends
|
||||||
# in particular need an additional `tolerance` parameter.
|
# in particular need an additional `tolerance` parameter.
|
||||||
agg_kwargs = {}
|
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.
|
# Calling the QuerySet.aggregate, and returning only the value of the aggregate.
|
||||||
return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']
|
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('procedure_fmt', '%(geo_col)s')
|
||||||
settings.setdefault('select_params', [])
|
settings.setdefault('select_params', [])
|
||||||
|
|
||||||
|
connection = connections[self.db]
|
||||||
|
backend = connection.ops
|
||||||
|
|
||||||
# Performing setup for the spatial column, unless told not to.
|
# Performing setup for the spatial column, unless told not to.
|
||||||
if settings.get('setup', True):
|
if settings.get('setup', True):
|
||||||
default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name)
|
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.
|
# Special handling for any argument that is a geometry.
|
||||||
for name in settings['geom_args']:
|
for name in settings['geom_args']:
|
||||||
# Using the field's get_db_prep_lookup() to get any needed
|
# Using the field's get_placeholder() routine to get any needed
|
||||||
# transformation SQL -- we pass in a 'dummy' `contains` lookup.
|
# transformation SQL.
|
||||||
where, params = geo_field.get_db_prep_lookup('contains', settings['procedure_args'][name])
|
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
|
# Replacing the procedure format with that of any needed
|
||||||
# transformation SQL.
|
# transformation SQL.
|
||||||
old_fmt = '%%(%s)s' % name
|
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['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
|
||||||
settings['select_params'].extend(params)
|
settings['select_params'].extend(params)
|
||||||
|
|
||||||
@ -507,8 +518,10 @@ class GeoQuerySet(QuerySet):
|
|||||||
# If the result of this function needs to be converted.
|
# If the result of this function needs to be converted.
|
||||||
if settings.get('select_field', False):
|
if settings.get('select_field', False):
|
||||||
sel_fld = settings['select_field']
|
sel_fld = settings['select_field']
|
||||||
if isinstance(sel_fld, GeomField) and SpatialBackend.select:
|
if isinstance(sel_fld, GeomField) and backend.select:
|
||||||
self.query.custom_select[model_att] = SpatialBackend.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
|
self.query.extra_select_fields[model_att] = sel_fld
|
||||||
|
|
||||||
# Finally, setting the extra selection attribute with
|
# Finally, setting the extra selection attribute with
|
||||||
@ -527,10 +540,13 @@ class GeoQuerySet(QuerySet):
|
|||||||
# If geodetic defaulting distance attribute to meters (Oracle and
|
# If geodetic defaulting distance attribute to meters (Oracle and
|
||||||
# PostGIS spherical distances return meters). Otherwise, use the
|
# PostGIS spherical distances return meters). Otherwise, use the
|
||||||
# units of the geometry field.
|
# units of the geometry field.
|
||||||
if geo_field.geodetic:
|
connection = connections[self.db]
|
||||||
|
geodetic = geo_field.geodetic(connection)
|
||||||
|
|
||||||
|
if geodetic:
|
||||||
dist_att = 'm'
|
dist_att = 'm'
|
||||||
else:
|
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
|
# Shortcut booleans for what distance function we're using and
|
||||||
# whether the geometry field is 3D.
|
# whether the geometry field is 3D.
|
||||||
@ -546,19 +562,23 @@ class GeoQuerySet(QuerySet):
|
|||||||
# parameters that will be passed in to field's function.
|
# parameters that will be passed in to field's function.
|
||||||
lookup_params = [geom or 'POINT (0 0)', 0]
|
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`
|
# If the spheroid calculation is desired, either by the `spheroid`
|
||||||
# keyword or when calculating the length of geodetic field, make
|
# keyword or when calculating the length of geodetic field, make
|
||||||
# sure the 'spheroid' distance setting string is passed in so we
|
# sure the 'spheroid' distance setting string is passed in so we
|
||||||
# get the correct spatial stored procedure.
|
# 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')
|
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
|
# The `geom_args` flag is set to true if a geometry parameter was
|
||||||
# passed in.
|
# passed in.
|
||||||
geom_args = bool(geom)
|
geom_args = bool(geom)
|
||||||
|
|
||||||
if SpatialBackend.oracle:
|
if backend.oracle:
|
||||||
if distance:
|
if distance:
|
||||||
procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
|
procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
|
||||||
elif length or perimeter:
|
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
|
# Getting whether this field is in units of degrees since the field may have
|
||||||
# been transformed via the `transform` GeoQuerySet method.
|
# been transformed via the `transform` GeoQuerySet method.
|
||||||
if self.query.transformed_srid:
|
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
|
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.')
|
raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
|
||||||
|
|
||||||
if distance:
|
if distance:
|
||||||
@ -583,14 +601,14 @@ class GeoQuerySet(QuerySet):
|
|||||||
# (which will transform to the original SRID of the field rather
|
# (which will transform to the original SRID of the field rather
|
||||||
# than to what was transformed to).
|
# than to what was transformed to).
|
||||||
geom_args = False
|
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 geom.srid is None or geom.srid == self.query.transformed_srid:
|
||||||
# If the geom parameter srid is None, it is assumed the coordinates
|
# If the geom parameter srid is None, it is assumed the coordinates
|
||||||
# are in the transformed units. A placeholder is used for the
|
# are in the transformed units. A placeholder is used for the
|
||||||
# geometry parameter. `GeomFromText` constructor is also needed
|
# geometry parameter. `GeomFromText` constructor is also needed
|
||||||
# to wrap geom placeholder for SpatiaLite.
|
# to wrap geom placeholder for SpatiaLite.
|
||||||
if SpatialBackend.spatialite:
|
if backend.spatialite:
|
||||||
procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.from_text, self.query.transformed_srid)
|
procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid)
|
||||||
else:
|
else:
|
||||||
procedure_fmt += ', %%s'
|
procedure_fmt += ', %%s'
|
||||||
else:
|
else:
|
||||||
@ -598,11 +616,11 @@ class GeoQuerySet(QuerySet):
|
|||||||
# so wrapping the geometry placeholder in transformation SQL.
|
# so wrapping the geometry placeholder in transformation SQL.
|
||||||
# SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
|
# SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
|
||||||
# constructor.
|
# constructor.
|
||||||
if SpatialBackend.spatialite:
|
if backend.spatialite:
|
||||||
procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (SpatialBackend.transform, SpatialBackend.from_text,
|
procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (backend.transform, backend.from_text,
|
||||||
geom.srid, self.query.transformed_srid)
|
geom.srid, self.query.transformed_srid)
|
||||||
else:
|
else:
|
||||||
procedure_fmt += ', %s(%%%%s, %s)' % (SpatialBackend.transform, self.query.transformed_srid)
|
procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_srid)
|
||||||
else:
|
else:
|
||||||
# `transform()` was not used on this GeoQuerySet.
|
# `transform()` was not used on this GeoQuerySet.
|
||||||
procedure_fmt = '%(geo_col)s,%(geom)s'
|
procedure_fmt = '%(geo_col)s,%(geom)s'
|
||||||
@ -614,29 +632,29 @@ class GeoQuerySet(QuerySet):
|
|||||||
# some error checking is required.
|
# some error checking is required.
|
||||||
if not isinstance(geo_field, PointField):
|
if not isinstance(geo_field, PointField):
|
||||||
raise ValueError('Spherical distance calculation only supported on PointFields.')
|
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')
|
raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
|
||||||
# The `function` procedure argument needs to be set differently for
|
# The `function` procedure argument needs to be set differently for
|
||||||
# geodetic distance calculations.
|
# geodetic distance calculations.
|
||||||
if spheroid:
|
if spheroid:
|
||||||
# Call to distance_spheroid() requires spheroid param as well.
|
# Call to distance_spheroid() requires spheroid param as well.
|
||||||
procedure_fmt += ',%(spheroid)s'
|
procedure_fmt += ",'%(spheroid)s'"
|
||||||
procedure_args.update({'function' : SpatialBackend.distance_spheroid, 'spheroid' : where[1]})
|
procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]})
|
||||||
else:
|
else:
|
||||||
procedure_args.update({'function' : SpatialBackend.distance_sphere})
|
procedure_args.update({'function' : backend.distance_sphere})
|
||||||
elif length or perimeter:
|
elif length or perimeter:
|
||||||
procedure_fmt = '%(geo_col)s'
|
procedure_fmt = '%(geo_col)s'
|
||||||
if geodetic and length:
|
if geodetic and length:
|
||||||
# There's no `length_sphere`, and `length_spheroid` also
|
# There's no `length_sphere`, and `length_spheroid` also
|
||||||
# works on 3D geometries.
|
# works on 3D geometries.
|
||||||
procedure_fmt += ',%(spheroid)s'
|
procedure_fmt += ",'%(spheroid)s'"
|
||||||
procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
|
procedure_args.update({'function' : backend.length_spheroid, 'spheroid' : params[1]})
|
||||||
elif geom_3d and SpatialBackend.postgis:
|
elif geom_3d and backend.postgis:
|
||||||
# Use 3D variants of perimeter and length routines on PostGIS.
|
# Use 3D variants of perimeter and length routines on PostGIS.
|
||||||
if perimeter:
|
if perimeter:
|
||||||
procedure_args.update({'function' : SpatialBackend.perimeter3d})
|
procedure_args.update({'function' : backend.perimeter3d})
|
||||||
elif length:
|
elif length:
|
||||||
procedure_args.update({'function' : SpatialBackend.length3d})
|
procedure_args.update({'function' : backend.length3d})
|
||||||
|
|
||||||
# Setting up the settings for `_spatial_attribute`.
|
# Setting up the settings for `_spatial_attribute`.
|
||||||
s = {'select_field' : DistanceField(dist_att),
|
s = {'select_field' : DistanceField(dist_att),
|
||||||
@ -651,7 +669,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
elif geom:
|
elif geom:
|
||||||
# The geometry is passed in as a parameter because we handled
|
# The geometry is passed in as a parameter because we handled
|
||||||
# transformation conditions in this routine.
|
# transformation conditions in this routine.
|
||||||
s['select_params'] = [SpatialBackend.Adaptor(geom)]
|
s['select_params'] = [backend.Adapter(geom)]
|
||||||
return self._spatial_attribute(func, s, **kwargs)
|
return self._spatial_attribute(func, s, **kwargs)
|
||||||
|
|
||||||
def _geom_attribute(self, func, tolerance=0.05, **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`).
|
Geometry attribute (e.g., `centroid`, `point_on_surface`).
|
||||||
"""
|
"""
|
||||||
s = {'select_field' : GeomField(),}
|
s = {'select_field' : GeomField(),}
|
||||||
if SpatialBackend.oracle:
|
if connections[self.db].ops.oracle:
|
||||||
s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
|
s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
|
||||||
s['procedure_args'] = {'tolerance' : tolerance}
|
s['procedure_args'] = {'tolerance' : tolerance}
|
||||||
return self._spatial_attribute(func, s, **kwargs)
|
return self._spatial_attribute(func, s, **kwargs)
|
||||||
@ -677,7 +695,7 @@ class GeoQuerySet(QuerySet):
|
|||||||
'procedure_fmt' : '%(geo_col)s,%(geom)s',
|
'procedure_fmt' : '%(geo_col)s,%(geom)s',
|
||||||
'procedure_args' : {'geom' : geom},
|
'procedure_args' : {'geom' : geom},
|
||||||
}
|
}
|
||||||
if SpatialBackend.oracle:
|
if connections[self.db].ops.oracle:
|
||||||
s['procedure_fmt'] += ',%(tolerance)s'
|
s['procedure_fmt'] += ',%(tolerance)s'
|
||||||
s['procedure_args']['tolerance'] = tolerance
|
s['procedure_args']['tolerance'] = tolerance
|
||||||
return self._spatial_attribute(func, s, **kwargs)
|
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
|
# If so, it'll have to be added to the select related information
|
||||||
# (e.g., if 'location__point' was given as the field name).
|
# (e.g., if 'location__point' was given as the field name).
|
||||||
self.query.add_select_related([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)]
|
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:
|
elif not geo_field in opts.local_fields:
|
||||||
# This geographic field is inherited from another model, so we have to
|
# This geographic field is inherited from another model, so we have to
|
||||||
# use the db table for the _parent_ model instead.
|
# use the db table for the _parent_ model instead.
|
||||||
tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
|
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:
|
else:
|
||||||
return self.query._field_column(geo_field)
|
return self.query.get_compiler(self.db)._field_column(geo_field)
|
||||||
|
|
||||||
class GeoValuesQuerySet(ValuesQuerySet):
|
class GeoValuesQuerySet(ValuesQuerySet):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -1,84 +1,10 @@
|
|||||||
from django.db.models.sql.aggregates import *
|
from django.db.models.sql.aggregates import *
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
from django.contrib.gis.db.models.fields import GeometryField
|
||||||
from django.contrib.gis.db.models.sql.conversion import GeomField
|
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):
|
class GeoAggregate(Aggregate):
|
||||||
# Overriding the SQL template with the geographic one.
|
# Default SQL template for spatial aggregates.
|
||||||
sql_template = geo_template
|
sql_template = '%(function)s(%(field)s)'
|
||||||
|
|
||||||
# Conversion class, if necessary.
|
# Conversion class, if necessary.
|
||||||
conversion_class = None
|
conversion_class = None
|
||||||
@ -86,41 +12,50 @@ class GeoAggregate(Aggregate):
|
|||||||
# Flags for indicating the type of the aggregate.
|
# Flags for indicating the type of the aggregate.
|
||||||
is_extent = False
|
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)
|
super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
|
||||||
|
|
||||||
if not self.is_extent and SpatialBackend.oracle:
|
# Required by some Oracle aggregates.
|
||||||
self.extra.setdefault('tolerance', 0.05)
|
self.tolerance = tolerance
|
||||||
|
|
||||||
# Can't use geographic aggregates on non-geometry fields.
|
# Can't use geographic aggregates on non-geometry fields.
|
||||||
if not isinstance(self.source, GeometryField):
|
if not isinstance(self.source, GeometryField):
|
||||||
raise ValueError('Geospatial aggregates only allowed on geometry fields.')
|
raise ValueError('Geospatial aggregates only allowed on geometry fields.')
|
||||||
|
|
||||||
# Making sure the SQL function is available for this spatial backend.
|
def as_sql(self, qn, connection):
|
||||||
if not self.sql_function:
|
"Return the aggregate, rendered as SQL."
|
||||||
raise NotImplementedError('This aggregate functionality not implemented for your spatial backend.')
|
|
||||||
|
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):
|
class Collect(GeoAggregate):
|
||||||
conversion_class = GeomField
|
pass
|
||||||
sql_function = SpatialBackend.collect
|
|
||||||
|
|
||||||
class Extent(GeoAggregate):
|
class Extent(GeoAggregate):
|
||||||
is_extent = '2D'
|
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):
|
class Extent3D(GeoAggregate):
|
||||||
is_extent = '3D'
|
is_extent = '3D'
|
||||||
sql_function = SpatialBackend.extent3d
|
|
||||||
|
|
||||||
class MakeLine(GeoAggregate):
|
class MakeLine(GeoAggregate):
|
||||||
conversion_class = GeomField
|
pass
|
||||||
sql_function = SpatialBackend.make_line
|
|
||||||
|
|
||||||
class Union(GeoAggregate):
|
class Union(GeoAggregate):
|
||||||
conversion_class = GeomField
|
pass
|
||||||
sql_function = SpatialBackend.unionagg
|
|
||||||
|
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
|
This module holds simple classes used by GeoQuery.convert_values
|
||||||
to convert geospatial values from the database.
|
to convert geospatial values from the database.
|
||||||
"""
|
"""
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
|
||||||
|
|
||||||
class BaseField(object):
|
class BaseField(object):
|
||||||
|
empty_strings_allowed = True
|
||||||
def get_internal_type(self):
|
def get_internal_type(self):
|
||||||
"Overloaded method so OracleQuery.convert_values doesn't balk."
|
"Overloaded method so OracleQuery.convert_values doesn't balk."
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if SpatialBackend.oracle: BaseField.empty_strings_allowed = False
|
|
||||||
|
|
||||||
class AreaField(BaseField):
|
class AreaField(BaseField):
|
||||||
"Wrapper for Area values."
|
"Wrapper for Area values."
|
||||||
def __init__(self, area_att):
|
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.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.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.conversion import AreaField, DistanceField, GeomField
|
||||||
from django.contrib.gis.db.models.sql.where import GeoWhereNode
|
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
|
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.
|
ALL_TERMS = dict([(x, None) for x in (
|
||||||
TABLE_NAME = sql.constants.TABLE_NAME
|
'bbcontains', 'bboverlaps', 'contained', 'contains',
|
||||||
get_proxied_model = sql.query.get_proxied_model
|
'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):
|
class GeoQuery(sql.Query):
|
||||||
"""
|
"""
|
||||||
@ -23,11 +27,13 @@ class GeoQuery(sql.Query):
|
|||||||
"""
|
"""
|
||||||
# Overridding the valid query terms.
|
# Overridding the valid query terms.
|
||||||
query_terms = ALL_TERMS
|
query_terms = ALL_TERMS
|
||||||
aggregates_module = gis_aggregates_module
|
aggregates_module = gis_aggregates
|
||||||
|
|
||||||
|
compiler = 'GeoSQLCompiler'
|
||||||
|
|
||||||
#### Methods overridden from the base Query class ####
|
#### Methods overridden from the base Query class ####
|
||||||
def __init__(self, model, conn):
|
def __init__(self, model, where=GeoWhereNode):
|
||||||
super(GeoQuery, self).__init__(model, conn, where=GeoWhereNode)
|
super(GeoQuery, self).__init__(model, where)
|
||||||
# The following attributes are customized for the GeoQuerySet.
|
# The following attributes are customized for the GeoQuerySet.
|
||||||
# The GeoWhereNode and SpatialBackend classes contain backend-specific
|
# The GeoWhereNode and SpatialBackend classes contain backend-specific
|
||||||
# routines and functions.
|
# routines and functions.
|
||||||
@ -35,13 +41,6 @@ class GeoQuery(sql.Query):
|
|||||||
self.transformed_srid = None
|
self.transformed_srid = None
|
||||||
self.extra_select_fields = {}
|
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):
|
def clone(self, *args, **kwargs):
|
||||||
obj = super(GeoQuery, self).clone(*args, **kwargs)
|
obj = super(GeoQuery, self).clone(*args, **kwargs)
|
||||||
# Customized selection dictionary and transformed srid flag have
|
# 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()
|
obj.extra_select_fields = self.extra_select_fields.copy()
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_columns(self, with_aliases=False):
|
def convert_values(self, value, field, connection):
|
||||||
"""
|
""" Using the same routines that Oracle does we can convert our
|
||||||
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
|
|
||||||
extra selection objects into Geometry and Distance objects.
|
extra selection objects into Geometry and Distance objects.
|
||||||
TODO: Make converted objects 'lazy' for less overhead.
|
TODO: Make converted objects 'lazy' for less overhead.
|
||||||
"""
|
"""
|
||||||
if SpatialBackend.oracle:
|
if connection.ops.oracle:
|
||||||
# Running through Oracle's first.
|
# 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):
|
if isinstance(field, DistanceField):
|
||||||
# Using the field's distance attribute, can instantiate
|
# Using the field's distance attribute, can instantiate
|
||||||
@ -252,10 +66,20 @@ class GeoQuery(sql.Query):
|
|||||||
elif isinstance(field, AreaField):
|
elif isinstance(field, AreaField):
|
||||||
value = Area(**{field.area_att : value})
|
value = Area(**{field.area_att : value})
|
||||||
elif isinstance(field, (GeomField, GeometryField)) and value:
|
elif isinstance(field, (GeomField, GeometryField)) and value:
|
||||||
value = SpatialBackend.Geometry(value)
|
value = Geometry(value)
|
||||||
return 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
|
Overridden from GeoQuery's normalize to handle the conversion of
|
||||||
GeoAggregate objects.
|
GeoAggregate objects.
|
||||||
@ -263,77 +87,15 @@ class GeoQuery(sql.Query):
|
|||||||
if isinstance(aggregate, self.aggregates_module.GeoAggregate):
|
if isinstance(aggregate, self.aggregates_module.GeoAggregate):
|
||||||
if aggregate.is_extent:
|
if aggregate.is_extent:
|
||||||
if aggregate.is_extent == '3D':
|
if aggregate.is_extent == '3D':
|
||||||
return self.aggregates_module.convert_extent3d(value)
|
return connection.ops.convert_extent3d(value)
|
||||||
else:
|
else:
|
||||||
return self.aggregates_module.convert_extent(value)
|
return connection.ops.convert_extent(value)
|
||||||
else:
|
else:
|
||||||
return self.aggregates_module.convert_geom(value, aggregate.source)
|
return connection.ops.convert_geom(value, aggregate.source)
|
||||||
else:
|
else:
|
||||||
return super(GeoQuery, self).resolve_aggregate(value, aggregate)
|
return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
|
||||||
|
|
||||||
#### 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
|
|
||||||
|
|
||||||
# Private API utilities, subject to change.
|
# 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):
|
def _geo_field(self, field_name=None):
|
||||||
"""
|
"""
|
||||||
Returns the first Geometry field encountered; or specified via the
|
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
|
# Otherwise, check by the given field name -- which may be
|
||||||
# a lookup to a _related_ geographic field.
|
# a lookup to a _related_ geographic field.
|
||||||
return GeoWhereNode._check_geo_field(self.model._meta, field_name)
|
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,31 +1,29 @@
|
|||||||
from django.contrib.gis.db.backend import SpatialBackend
|
from django.db import connections
|
||||||
from django.db.models.query import insert_query
|
|
||||||
|
|
||||||
if SpatialBackend.oracle:
|
|
||||||
from django.db import connection
|
|
||||||
from django.db.models.sql.subqueries import InsertQuery
|
from django.db.models.sql.subqueries import InsertQuery
|
||||||
|
|
||||||
class OracleGeoInsertQuery(InsertQuery):
|
class GeoInsertQuery(InsertQuery):
|
||||||
def insert_values(self, insert_values, raw_values=False):
|
def insert_values(self, insert_values, connection, raw_values=False):
|
||||||
"""
|
"""
|
||||||
This routine is overloaded from InsertQuery so that no parameter is
|
Set up the insert query from the 'insert_values' dictionary. The
|
||||||
passed into cx_Oracle for NULL geometries. The reason is that
|
dictionary gives the model field names and their target values.
|
||||||
cx_Oracle has no way to bind Oracle object values (like
|
|
||||||
MDSYS.SDO_GEOMETRY).
|
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 = [], []
|
placeholders, values = [], []
|
||||||
for field, val in insert_values:
|
for field, val in insert_values:
|
||||||
if hasattr(field, 'get_placeholder'):
|
if hasattr(field, 'get_placeholder'):
|
||||||
ph = field.get_placeholder(val)
|
# Some fields (e.g. geo fields) need special munging before
|
||||||
|
# they can be inserted.
|
||||||
|
placeholders.append(field.get_placeholder(val, connection))
|
||||||
else:
|
else:
|
||||||
ph = '%s'
|
placeholders.append('%s')
|
||||||
|
|
||||||
placeholders.append(ph)
|
|
||||||
self.columns.append(field.column)
|
self.columns.append(field.column)
|
||||||
|
|
||||||
# If 'NULL' for the placeholder, omit appending None
|
if not placeholders[-1] == 'NULL':
|
||||||
# to the values list (which is used for db params).
|
|
||||||
if not ph == 'NULL':
|
|
||||||
values.append(val)
|
values.append(val)
|
||||||
if raw_values:
|
if raw_values:
|
||||||
self.values.extend(values)
|
self.values.extend(values)
|
||||||
@ -33,7 +31,13 @@ if SpatialBackend.oracle:
|
|||||||
self.params += tuple(values)
|
self.params += tuple(values)
|
||||||
self.values.extend(placeholders)
|
self.values.extend(placeholders)
|
||||||
|
|
||||||
def insert_query(model, values, return_id=False, raw_values=False):
|
def insert_query(model, values, return_id=False, raw_values=False, using=None):
|
||||||
query = OracleGeoInsertQuery(model, connection)
|
"""
|
||||||
query.insert_values(values, raw_values)
|
Inserts a new record for the given model. This provides an interface to
|
||||||
return query.execute_sql(return_id)
|
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.fields import Field, FieldDoesNotExist
|
||||||
from django.db.models.sql.constants import LOOKUP_SEP
|
from django.db.models.sql.constants import LOOKUP_SEP
|
||||||
from django.db.models.sql.expressions import SQLEvaluator
|
from django.db.models.sql.expressions import SQLEvaluator
|
||||||
from django.db.models.sql.where import WhereNode
|
from django.db.models.sql.where import Constraint, WhereNode
|
||||||
from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
|
|
||||||
from django.contrib.gis.db.models.fields import GeometryField
|
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
|
This subclass overrides `process` to better handle geographic SQL
|
||||||
for metadata needed by the `get_geo_where_clause` of the spatial
|
construction.
|
||||||
backend.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, field, value, where):
|
def __init__(self, init_constraint):
|
||||||
self.geodetic = field.geodetic
|
self.alias = init_constraint.alias
|
||||||
self.geom_type = field.geom_type
|
self.col = init_constraint.col
|
||||||
self.value = value
|
self.field = init_constraint.field
|
||||||
self.where = tuple(where)
|
|
||||||
|
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):
|
class GeoWhereNode(WhereNode):
|
||||||
"""
|
"""
|
||||||
@ -25,75 +33,20 @@ class GeoWhereNode(WhereNode):
|
|||||||
these are tied to the GeoQuery class that created it.
|
these are tied to the GeoQuery class that created it.
|
||||||
"""
|
"""
|
||||||
def add(self, data, connector):
|
def add(self, data, connector):
|
||||||
"""
|
if isinstance(data, (list, tuple)):
|
||||||
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
|
obj, lookup_type, value = data
|
||||||
col, field = obj.col, obj.field
|
if ( isinstance(obj, Constraint) and
|
||||||
|
isinstance(obj.field, GeometryField) ):
|
||||||
if not hasattr(field, "geom_type"):
|
data = (GeoConstraint(obj), lookup_type, value)
|
||||||
# Not a geographic field, so call `WhereNode.add`.
|
super(GeoWhereNode, self).add(data, connector)
|
||||||
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)
|
|
||||||
|
|
||||||
def make_atom(self, child, qn, connection):
|
def make_atom(self, child, qn, connection):
|
||||||
obj, lookup_type, value_annot, params = child
|
lvalue, lookup_type, value_annot, params_or_value = child
|
||||||
|
if isinstance(lvalue, GeoConstraint):
|
||||||
if isinstance(value_annot, GeoAnnotation):
|
data, params = lvalue.process(lookup_type, params_or_value, connection)
|
||||||
if lookup_type in SpatialBackend.gis_terms:
|
spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field)
|
||||||
# Getting the geographic where clause; substitution parameters
|
return spatial_sql, params
|
||||||
# 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:
|
else:
|
||||||
raise TypeError('Invalid lookup type: %r' % lookup_type)
|
|
||||||
else:
|
|
||||||
# If not a GeometryField, call the `make_atom` from the
|
|
||||||
# base class.
|
|
||||||
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
return super(GeoWhereNode, self).make_atom(child, qn, connection)
|
||||||
|
|
||||||
@classmethod
|
@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.http import HttpResponse, Http404
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger
|
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.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.utils.encoding import smart_str
|
||||||
|
|
||||||
from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz
|
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}))
|
xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
|
||||||
return HttpResponse(xml, mimetype='application/xml')
|
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.
|
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:
|
except:
|
||||||
raise Http404('Invalid geometry field.')
|
raise Http404('Invalid geometry field.')
|
||||||
|
|
||||||
if SpatialBackend.postgis:
|
connection = connections[using]
|
||||||
|
|
||||||
|
if connection.ops.postgis:
|
||||||
# PostGIS will take care of transformation.
|
# PostGIS will take care of transformation.
|
||||||
placemarks = klass._default_manager.kml(field_name=field_name)
|
placemarks = klass._default_manager.kml(field_name=field_name)
|
||||||
else:
|
else:
|
||||||
# There's no KML method on Oracle or MySQL, so we use the `kml`
|
# There's no KML method on Oracle or MySQL, so we use the `kml`
|
||||||
# attribute of the lazy geometry instead.
|
# attribute of the lazy geometry instead.
|
||||||
placemarks = []
|
placemarks = []
|
||||||
if SpatialBackend.oracle:
|
if connection.ops.oracle:
|
||||||
qs = klass._default_manager.transform(4326, field_name=field_name)
|
qs = klass._default_manager.transform(4326, field_name=field_name)
|
||||||
else:
|
else:
|
||||||
qs = klass._default_manager.all()
|
qs = klass._default_manager.all()
|
||||||
@ -101,8 +103,8 @@ def kml(request, label, model, field_name=None, compress=False):
|
|||||||
render = render_to_kml
|
render = render_to_kml
|
||||||
return render('gis/kml/placemarks.kml', {'places' : placemarks})
|
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.
|
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
|
import sys, unittest
|
||||||
|
from django.test.simple import run_tests
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
def geo_suite():
|
def geo_suite():
|
||||||
@ -14,12 +15,11 @@ def geo_suite():
|
|||||||
from django.contrib.gis.utils import HAS_GEOIP
|
from django.contrib.gis.utils import HAS_GEOIP
|
||||||
from django.contrib.gis.tests.utils import postgis, mysql
|
from django.contrib.gis.tests.utils import postgis, mysql
|
||||||
|
|
||||||
# The test suite.
|
gis_tests = []
|
||||||
s = unittest.TestSuite()
|
|
||||||
|
|
||||||
# Adding the GEOS tests.
|
# Adding the GEOS tests.
|
||||||
from django.contrib.gis.geos import tests as 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)
|
# Tests that require use of a spatial database (e.g., creation of models)
|
||||||
test_apps = ['geoapp', 'relatedapp']
|
test_apps = ['geoapp', 'relatedapp']
|
||||||
@ -44,7 +44,7 @@ def geo_suite():
|
|||||||
|
|
||||||
# Adding the GDAL tests.
|
# Adding the GDAL tests.
|
||||||
from django.contrib.gis.gdal import tests as gdal_tests
|
from django.contrib.gis.gdal import tests as gdal_tests
|
||||||
s.addTest(gdal_tests.suite())
|
gis_tests.append(gdal_tests.suite())
|
||||||
else:
|
else:
|
||||||
print >>sys.stderr, "GDAL not available - no tests requiring GDAL will be run."
|
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`.
|
# in the `test_suite_names`.
|
||||||
for suite_name in test_suite_names:
|
for suite_name in test_suite_names:
|
||||||
tsuite = import_module('django.contrib.gis.tests.' + suite_name)
|
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):
|
def run_gis_tests(test_labels, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -83,21 +83,13 @@ def run_gis_tests(test_labels, **kwargs):
|
|||||||
# Setting the URLs.
|
# Setting the URLs.
|
||||||
settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
|
settings.ROOT_URLCONF = 'django.contrib.gis.tests.urls'
|
||||||
|
|
||||||
# Creating the test suite, adding the test models to INSTALLED_APPS, and
|
# Creating the test suite, adding the test models to INSTALLED_APPS
|
||||||
# adding the model test suites to our suite package.
|
# so they will be tested.
|
||||||
gis_suite, test_apps = geo_suite()
|
gis_tests, test_apps = geo_suite()
|
||||||
for test_model in test_apps:
|
for test_model in test_apps:
|
||||||
module_name = 'django.contrib.gis.tests.%s' % test_model
|
module_name = 'django.contrib.gis.tests.%s' % test_model
|
||||||
if mysql:
|
|
||||||
test_module = 'tests_mysql'
|
|
||||||
else:
|
|
||||||
test_module = 'tests'
|
|
||||||
new_installed.append(module_name)
|
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
|
# Resetting the loaded flag to take into account what we appended to
|
||||||
# the INSTALLED_APPS (since this routine is invoked through
|
# the INSTALLED_APPS (since this routine is invoked through
|
||||||
# django/core/management, it caches the apps; this ensures that syncdb
|
# 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
|
settings.INSTALLED_APPS = new_installed
|
||||||
loading.cache.loaded = False
|
loading.cache.loaded = False
|
||||||
|
|
||||||
|
kwargs['extra_tests'] = gis_tests
|
||||||
|
|
||||||
# Running the tests using the GIS test runner.
|
# 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.
|
# Restoring modified settings.
|
||||||
settings.INSTALLED_APPS = old_installed
|
settings.INSTALLED_APPS = old_installed
|
||||||
settings.ROOT_URLCONF = old_root_urlconf
|
settings.ROOT_URLCONF = old_root_urlconf
|
||||||
|
|
||||||
return result
|
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.
|
# Creating the query set.
|
||||||
qs = AustraliaCity.objects.order_by('name')
|
qs = AustraliaCity.objects.order_by('name')
|
||||||
if type_error:
|
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.
|
# 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:
|
else:
|
||||||
self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
|
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
|
# Oracle doesn't have this limitation -- PostGIS only allows geodetic
|
||||||
# distance queries from Points to PointFields.
|
# distance queries from Points to PointFields.
|
||||||
mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
|
mp = GEOSGeometry('MULTIPOINT(0 0, 5 23)')
|
||||||
self.assertRaises(TypeError,
|
self.assertRaises(ValueError, len,
|
||||||
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
|
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
|
||||||
# Too many params (4 in this case) should raise a ValueError.
|
# Too many params (4 in this case) should raise a ValueError.
|
||||||
self.assertRaises(ValueError,
|
self.assertRaises(ValueError, len,
|
||||||
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
|
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
|
||||||
|
|
||||||
# Not enough params should raise a ValueError.
|
# Not enough params should raise a ValueError.
|
||||||
self.assertRaises(ValueError,
|
self.assertRaises(ValueError, len,
|
||||||
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)',))
|
AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)))
|
||||||
|
|
||||||
# Getting all cities w/in 550 miles of Hobart.
|
# Getting all cities w/in 550 miles of Hobart.
|
||||||
hobart = AustraliaCity.objects.get(name='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
|
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.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
|
||||||
from django.contrib.gis.shortcuts import render_to_kmz
|
from django.contrib.gis.shortcuts import render_to_kmz
|
||||||
from models import City
|
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 import gdal
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
|
||||||
from django.contrib.gis.geos import *
|
from django.contrib.gis.geos import *
|
||||||
from django.contrib.gis.measure import Distance
|
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
|
from models import Country, City, PennsylvaniaCity, State
|
||||||
|
|
||||||
if not SpatialBackend.spatialite:
|
if not spatialite:
|
||||||
from models import Feature, MinusOneSRID
|
from models import Feature, MinusOneSRID
|
||||||
|
|
||||||
# TODO: Some tests depend on the success/failure of previous tests, these should
|
class GeoModelTest(TestCase):
|
||||||
# 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(unittest.TestCase):
|
def test01_fixtures(self):
|
||||||
|
"Testing geographic model initialization from fixtures."
|
||||||
def test01_initial_sql(self):
|
# Ensuring that data was loaded from initial data fixtures.
|
||||||
"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.
|
|
||||||
self.assertEqual(2, Country.objects.count())
|
self.assertEqual(2, Country.objects.count())
|
||||||
self.assertEqual(8, City.objects.count())
|
self.assertEqual(8, City.objects.count())
|
||||||
self.assertEqual(3, State.objects.count())
|
self.assertEqual(2, State.objects.count())
|
||||||
|
|
||||||
def test02_proxy(self):
|
def test02_proxy(self):
|
||||||
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
||||||
if DISABLE: return
|
|
||||||
## Testing on a Point
|
## Testing on a Point
|
||||||
pnt = Point(0, 0)
|
pnt = Point(0, 0)
|
||||||
nullcity = City(name='NullCity', point=pnt)
|
nullcity = City(name='NullCity', point=pnt)
|
||||||
@ -110,11 +90,13 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
self.assertEqual(ply, State.objects.get(name='NullState').poly)
|
||||||
ns.delete()
|
ns.delete()
|
||||||
|
|
||||||
@no_oracle # Oracle does not support KML.
|
|
||||||
@no_spatialite # SpatiaLite does not support KML.
|
|
||||||
def test03a_kml(self):
|
def test03a_kml(self):
|
||||||
"Testing KML output from the database using GeoQuerySet.kml()."
|
"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
|
# Should throw a TypeError when trying to obtain KML from a
|
||||||
# non-geometry field.
|
# non-geometry field.
|
||||||
qs = City.objects.all()
|
qs = City.objects.all()
|
||||||
@ -122,14 +104,10 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
|
|
||||||
# The reference KML depends on the version of PostGIS used
|
# The reference KML depends on the version of PostGIS used
|
||||||
# (the output stopped including altitude in 1.3.3).
|
# (the output stopped including altitude in 1.3.3).
|
||||||
major, minor1, minor2 = SpatialBackend.version
|
if connection.ops.spatial_version >= (1, 3, 3):
|
||||||
ref_kml1 = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
|
ref_kml = '<Point><coordinates>-104.609252,38.255001</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
|
|
||||||
else:
|
else:
|
||||||
ref_kml = ref_kml2
|
ref_kml = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
|
||||||
|
|
||||||
# Ensuring the KML is as expected.
|
# Ensuring the KML is as expected.
|
||||||
ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
|
ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
|
||||||
@ -137,10 +115,12 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
for ptown in [ptown1, ptown2]:
|
for ptown in [ptown1, ptown2]:
|
||||||
self.assertEqual(ref_kml, ptown.kml)
|
self.assertEqual(ref_kml, ptown.kml)
|
||||||
|
|
||||||
@no_spatialite # SpatiaLite does not support GML.
|
|
||||||
def test03b_gml(self):
|
def test03b_gml(self):
|
||||||
"Testing GML output from the database using GeoQuerySet.gml()."
|
"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
|
# Should throw a TypeError when tyring to obtain GML from a
|
||||||
# non-geometry field.
|
# non-geometry field.
|
||||||
qs = City.objects.all()
|
qs = City.objects.all()
|
||||||
@ -148,8 +128,7 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
|
ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
|
||||||
ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
|
ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
|
||||||
|
|
||||||
import re
|
if oracle:
|
||||||
if SpatialBackend.oracle:
|
|
||||||
# No precision parameter for 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>')
|
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]:
|
for ptown in [ptown1, ptown2]:
|
||||||
@ -159,17 +138,14 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
for ptown in [ptown1, ptown2]:
|
for ptown in [ptown1, ptown2]:
|
||||||
self.failUnless(gml_regex.match(ptown.gml))
|
self.failUnless(gml_regex.match(ptown.gml))
|
||||||
|
|
||||||
@no_spatialite
|
|
||||||
@no_oracle
|
|
||||||
def test03c_geojson(self):
|
def test03c_geojson(self):
|
||||||
"Testing GeoJSON output from the database using GeoQuerySet.geojson()."
|
"Testing GeoJSON output from the database using GeoQuerySet.geojson()."
|
||||||
if DISABLE: return
|
# Only PostGIS 1.3.4+ supports GeoJSON.
|
||||||
# PostGIS only supports GeoJSON on 1.3.4+
|
if not connection.ops.geojson:
|
||||||
if not SpatialBackend.geojson:
|
self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly')
|
||||||
return
|
return
|
||||||
|
|
||||||
major, minor1, minor2 = SpatialBackend.version
|
if connection.ops.spatial_version >= (1, 4, 0):
|
||||||
if major >=1 and minor1 >= 4:
|
|
||||||
pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
|
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]}'
|
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]}'
|
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.
|
# Finally, we set every available keyword.
|
||||||
self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
|
self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
|
||||||
|
|
||||||
@no_oracle
|
|
||||||
def test03d_svg(self):
|
def test03d_svg(self):
|
||||||
"Testing SVG output using GeoQuerySet.svg()."
|
"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')
|
self.assertRaises(TypeError, City.objects.svg, precision='foo')
|
||||||
# SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
|
# SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
|
||||||
svg1 = 'cx="-104.609252" cy="-38.255001"'
|
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(svg1, City.objects.svg().get(name='Pueblo').svg)
|
||||||
self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
|
self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test04_transform(self):
|
def test04_transform(self):
|
||||||
"Testing the transform() GeoManager method."
|
"Testing the transform() GeoManager method."
|
||||||
if DISABLE: return
|
|
||||||
# Pre-transformed points for Houston and Pueblo.
|
# Pre-transformed points for Houston and Pueblo.
|
||||||
htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
|
htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
|
||||||
ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
|
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
|
# Asserting the result of the transform operation with the values in
|
||||||
# the pre-transformed points. Oracle does not have the 3084 SRID.
|
# 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')
|
h = City.objects.transform(htown.srid).get(name='Houston')
|
||||||
self.assertEqual(3084, h.point.srid)
|
self.assertEqual(3084, h.point.srid)
|
||||||
self.assertAlmostEqual(htown.x, h.point.x, prec)
|
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.x, p.point.x, prec)
|
||||||
self.assertAlmostEqual(ptown.y, p.point.y, prec)
|
self.assertAlmostEqual(ptown.y, p.point.y, prec)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
@no_spatialite # SpatiaLite does not have an Extent function
|
@no_spatialite # SpatiaLite does not have an Extent function
|
||||||
def test05_extent(self):
|
def test05_extent(self):
|
||||||
"Testing the `extent` GeoQuerySet method."
|
"Testing the `extent` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
# Reference query:
|
# Reference query:
|
||||||
# `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
|
# `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
|
||||||
# => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
|
# => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
|
||||||
@ -252,11 +230,12 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
for val, exp in zip(extent, expected):
|
for val, exp in zip(extent, expected):
|
||||||
self.assertAlmostEqual(exp, val, 4)
|
self.assertAlmostEqual(exp, val, 4)
|
||||||
|
|
||||||
|
# Only PostGIS has support for the MakeLine aggregate.
|
||||||
|
@no_mysql
|
||||||
@no_oracle
|
@no_oracle
|
||||||
@no_spatialite # SpatiaLite does not have a MakeLine function
|
@no_spatialite
|
||||||
def test06_make_line(self):
|
def test06_make_line(self):
|
||||||
"Testing the `make_line` GeoQuerySet method."
|
"Testing the `make_line` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
# Ensuring that a `TypeError` is raised on models without PointFields.
|
# Ensuring that a `TypeError` is raised on models without PointFields.
|
||||||
self.assertRaises(TypeError, State.objects.make_line)
|
self.assertRaises(TypeError, State.objects.make_line)
|
||||||
self.assertRaises(TypeError, Country.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)
|
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())
|
self.assertEqual(ref_line, City.objects.make_line())
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test09_disjoint(self):
|
def test09_disjoint(self):
|
||||||
"Testing the `disjoint` lookup type."
|
"Testing the `disjoint` lookup type."
|
||||||
if DISABLE: return
|
|
||||||
ptown = City.objects.get(name='Pueblo')
|
ptown = City.objects.get(name='Pueblo')
|
||||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||||
self.assertEqual(7, qs1.count())
|
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)
|
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||||
self.assertEqual(1, qs2.count())
|
self.assertEqual(1, qs2.count())
|
||||||
self.assertEqual('Kansas', qs2[0].name)
|
self.assertEqual('Kansas', qs2[0].name)
|
||||||
|
|
||||||
def test10_contains_contained(self):
|
def test10_contains_contained(self):
|
||||||
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
||||||
if DISABLE: return
|
|
||||||
# Getting Texas, yes we were a country -- once ;)
|
# Getting Texas, yes we were a country -- once ;)
|
||||||
texas = Country.objects.get(name='Texas')
|
texas = Country.objects.get(name='Texas')
|
||||||
|
|
||||||
# Seeing what cities are in Texas, should get Houston and Dallas,
|
# Seeing what cities are in Texas, should get Houston and Dallas,
|
||||||
# and Oklahoma City because 'contained' only checks on the
|
# and Oklahoma City because 'contained' only checks on the
|
||||||
# _bounding box_ of the Geometries.
|
# _bounding box_ of the Geometries.
|
||||||
if not SpatialBackend.oracle:
|
if not oracle:
|
||||||
qs = City.objects.filter(point__contained=texas.mpoly)
|
qs = City.objects.filter(point__contained=texas.mpoly)
|
||||||
self.assertEqual(3, qs.count())
|
self.assertEqual(3, qs.count())
|
||||||
cities = ['Houston', 'Dallas', 'Oklahoma City']
|
cities = ['Houston', 'Dallas', 'Oklahoma City']
|
||||||
@ -313,30 +284,31 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
self.assertEqual('New Zealand', nz.name)
|
self.assertEqual('New Zealand', nz.name)
|
||||||
|
|
||||||
# Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
|
# 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)
|
ks = State.objects.get(poly__contains=lawrence.point)
|
||||||
self.assertEqual('Kansas', ks.name)
|
self.assertEqual('Kansas', ks.name)
|
||||||
|
|
||||||
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
# 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=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.
|
# 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)
|
qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
|
||||||
self.assertEqual(1, len(qs))
|
self.assertEqual(1, len(qs))
|
||||||
self.assertEqual('Texas', qs[0].name)
|
self.assertEqual('Texas', qs[0].name)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test11_lookup_insert_transform(self):
|
def test11_lookup_insert_transform(self):
|
||||||
"Testing automatic transform for lookups and inserts."
|
"Testing automatic transform for lookups and inserts."
|
||||||
if DISABLE: return
|
|
||||||
# San Antonio in 'WGS84' (SRID 4326)
|
# San Antonio in 'WGS84' (SRID 4326)
|
||||||
sa_4326 = 'POINT (-98.493183 29.424170)'
|
sa_4326 = 'POINT (-98.493183 29.424170)'
|
||||||
wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
|
wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
|
||||||
|
|
||||||
# Oracle doesn't have SRID 3084, using 41157.
|
# Oracle doesn't have SRID 3084, using 41157.
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
# San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
|
# San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
|
||||||
# Used the following Oracle SQL to get this value:
|
# 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;
|
# 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
|
# `SDO_OVERLAPBDYINTERSECT` operates differently from
|
||||||
# `ST_Intersects`, so contains is used instead.
|
# `ST_Intersects`, so contains is used instead.
|
||||||
nad_pnt = fromstr(nad_wkt, srid=nad_srid)
|
nad_pnt = fromstr(nad_wkt, srid=nad_srid)
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
tx = Country.objects.get(mpoly__contains=nad_pnt)
|
tx = Country.objects.get(mpoly__contains=nad_pnt)
|
||||||
else:
|
else:
|
||||||
tx = Country.objects.get(mpoly__intersects=nad_pnt)
|
tx = Country.objects.get(mpoly__intersects=nad_pnt)
|
||||||
self.assertEqual('Texas', tx.name)
|
self.assertEqual('Texas', tx.name)
|
||||||
|
|
||||||
# Creating San Antonio. Remember the Alamo.
|
# Creating San Antonio. Remember the Alamo.
|
||||||
sa = City(name='San Antonio', point=nad_pnt)
|
sa = City.objects.create(name='San Antonio', point=nad_pnt)
|
||||||
sa.save()
|
|
||||||
|
|
||||||
# Now verifying that San Antonio was transformed correctly
|
# Now verifying that San Antonio was transformed correctly
|
||||||
sa = City.objects.get(name='San Antonio')
|
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
|
# If the GeometryField SRID is -1, then we shouldn't perform any
|
||||||
# transformation if the SRID of the input geometry is different.
|
# transformation if the SRID of the input geometry is different.
|
||||||
# SpatiaLite does not support missing SRID values.
|
# SpatiaLite does not support missing SRID values.
|
||||||
if not SpatialBackend.spatialite:
|
if not spatialite:
|
||||||
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
|
m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
|
||||||
m1.save()
|
m1.save()
|
||||||
self.assertEqual(-1, m1.geom.srid)
|
self.assertEqual(-1, m1.geom.srid)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test12_null_geometries(self):
|
def test12_null_geometries(self):
|
||||||
"Testing NULL geometry support, and the `isnull` lookup type."
|
"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.
|
# Querying for both NULL and Non-NULL values.
|
||||||
nullqs = State.objects.filter(poly__isnull=True)
|
nullqs = State.objects.filter(poly__isnull=True)
|
||||||
validqs = State.objects.filter(poly__isnull=False)
|
validqs = State.objects.filter(poly__isnull=False)
|
||||||
@ -401,11 +375,12 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
State.objects.filter(name='Northern Mariana Islands').update(poly=None)
|
State.objects.filter(name='Northern Mariana Islands').update(poly=None)
|
||||||
self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
|
self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
|
||||||
|
|
||||||
@no_oracle # No specific `left` or `right` operators in Oracle.
|
# Only PostGIS has `left` and `right` lookup types.
|
||||||
@no_spatialite # No `left` or `right` operators in SpatiaLite.
|
@no_mysql
|
||||||
|
@no_oracle
|
||||||
|
@no_spatialite
|
||||||
def test13_left_right(self):
|
def test13_left_right(self):
|
||||||
"Testing the 'left' and 'right' lookup types."
|
"Testing the 'left' and 'right' lookup types."
|
||||||
if DISABLE: return
|
|
||||||
# Left: A << B => true if xmax(A) < xmin(B)
|
# Left: A << B => true if xmax(A) < xmin(B)
|
||||||
# Right: A >> B => true if xmin(A) > xmax(B)
|
# Right: A >> B => true if xmin(A) > xmax(B)
|
||||||
# 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.
|
||||||
@ -418,10 +393,10 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
# to the left of CO.
|
# to the left of CO.
|
||||||
|
|
||||||
# These cities should be strictly to the right of the CO border.
|
# 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']
|
'Lawrence', 'Chicago', 'Wellington']
|
||||||
qs = City.objects.filter(point__right=co_border)
|
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)
|
for c in qs: self.assertEqual(True, c.name in cities)
|
||||||
|
|
||||||
# These cities should be strictly to the right of the KS border.
|
# These cities should be strictly to the right of the KS border.
|
||||||
@ -442,16 +417,16 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test14_equals(self):
|
def test14_equals(self):
|
||||||
"Testing the 'same_as' and 'equals' lookup types."
|
"Testing the 'same_as' and 'equals' lookup types."
|
||||||
if DISABLE: return
|
|
||||||
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
||||||
c1 = City.objects.get(point=pnt)
|
c1 = City.objects.get(point=pnt)
|
||||||
c2 = City.objects.get(point__same_as=pnt)
|
c2 = City.objects.get(point__same_as=pnt)
|
||||||
c3 = City.objects.get(point__equals=pnt)
|
c3 = City.objects.get(point__equals=pnt)
|
||||||
for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
|
for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test15_relate(self):
|
def test15_relate(self):
|
||||||
"Testing the 'relate' lookup type."
|
"Testing the 'relate' lookup type."
|
||||||
if DISABLE: return
|
return
|
||||||
# To make things more interesting, we will have our Texas reference point in
|
# To make things more interesting, we will have our Texas reference point in
|
||||||
# different SRIDs.
|
# different SRIDs.
|
||||||
pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
|
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
|
# Not passing in a geometry as first param shoud
|
||||||
# raise a type error when initializing the GeoQuerySet
|
# 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
|
# Making sure the right exception is raised for the given
|
||||||
# bad arguments.
|
# 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)
|
qs = Country.objects.filter(mpoly__relate=bad_args)
|
||||||
self.assertRaises(e, qs.count)
|
self.assertRaises(e, qs.count)
|
||||||
|
|
||||||
# Relate works differently for the different backends.
|
# Relate works differently for the different backends.
|
||||||
if SpatialBackend.postgis or SpatialBackend.spatialite:
|
if postgis or spatialite:
|
||||||
contains_mask = 'T*T***FF*'
|
contains_mask = 'T*T***FF*'
|
||||||
within_mask = 'T*F**F***'
|
within_mask = 'T*F**F***'
|
||||||
intersects_mask = 'T********'
|
intersects_mask = 'T********'
|
||||||
elif SpatialBackend.oracle:
|
elif oracle:
|
||||||
contains_mask = 'contains'
|
contains_mask = 'contains'
|
||||||
within_mask = 'inside'
|
within_mask = 'inside'
|
||||||
# TODO: This is not quite the same as the PostGIS mask above
|
# 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)
|
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
|
||||||
|
|
||||||
# Testing intersection relation mask.
|
# 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=(pnt1, intersects_mask)).name)
|
||||||
self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, 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)
|
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
|
||||||
|
|
||||||
def test16_createnull(self):
|
def test16_createnull(self):
|
||||||
"Testing creating a model instance and the geometry being None"
|
"Testing creating a model instance and the geometry being None"
|
||||||
if DISABLE: return
|
|
||||||
c = City()
|
c = City()
|
||||||
self.assertEqual(c.point, None)
|
self.assertEqual(c.point, None)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test17_unionagg(self):
|
def test17_unionagg(self):
|
||||||
"Testing the `unionagg` (aggregate union) GeoManager method."
|
"Testing the `unionagg` (aggregate union) GeoManager method."
|
||||||
if DISABLE: return
|
|
||||||
tx = Country.objects.get(name='Texas').mpoly
|
tx = Country.objects.get(name='Texas').mpoly
|
||||||
# Houston, Dallas, San Antonio -- Oracle has different order.
|
# Houston, Dallas -- Oracle has different order.
|
||||||
union1 = fromstr('MULTIPOINT(-98.493183 29.424170,-96.801611 32.782057,-95.363151 29.763374)')
|
union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||||
union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374,-98.493183 29.424170)')
|
union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||||
qs = City.objects.filter(point__within=tx)
|
qs = City.objects.filter(point__within=tx)
|
||||||
self.assertRaises(TypeError, qs.unionagg, 'name')
|
self.assertRaises(TypeError, qs.unionagg, 'name')
|
||||||
# Using `field_name` keyword argument in one query and specifying an
|
# 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')
|
u1 = qs.unionagg(field_name='point')
|
||||||
u2 = qs.order_by('name').unionagg()
|
u2 = qs.order_by('name').unionagg()
|
||||||
tol = 0.00001
|
tol = 0.00001
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
union = union2
|
union = union2
|
||||||
else:
|
else:
|
||||||
union = union1
|
union = union1
|
||||||
@ -523,8 +498,7 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
|
|
||||||
@no_spatialite # SpatiaLite does not support abstract geometry columns
|
@no_spatialite # SpatiaLite does not support abstract geometry columns
|
||||||
def test18_geometryfield(self):
|
def test18_geometryfield(self):
|
||||||
"Testing GeometryField."
|
"Testing the general GeometryField."
|
||||||
if DISABLE: return
|
|
||||||
Feature(name='Point', geom=Point(1, 1)).save()
|
Feature(name='Point', geom=Point(1, 1)).save()
|
||||||
Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).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()
|
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(True, isinstance(f_4.geom, GeometryCollection))
|
||||||
self.assertEqual(f_3.geom, f_4.geom[2])
|
self.assertEqual(f_3.geom, f_4.geom[2])
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test19_centroid(self):
|
def test19_centroid(self):
|
||||||
"Testing the `centroid` GeoQuerySet method."
|
"Testing the `centroid` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
qs = State.objects.exclude(poly__isnull=True).centroid()
|
qs = State.objects.exclude(poly__isnull=True).centroid()
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
tol = 0.1
|
tol = 0.1
|
||||||
elif SpatialBackend.spatialite:
|
elif spatialite:
|
||||||
tol = 0.000001
|
tol = 0.000001
|
||||||
else:
|
else:
|
||||||
tol = 0.000000001
|
tol = 0.000000001
|
||||||
for s in qs:
|
for s in qs:
|
||||||
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
|
self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test20_pointonsurface(self):
|
def test20_pointonsurface(self):
|
||||||
"Testing the `point_on_surface` GeoQuerySet method."
|
"Testing the `point_on_surface` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
# Reference values.
|
# Reference values.
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
# SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
|
# 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),
|
ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
|
||||||
'Texas' : fromstr('POINT (-103.002434 36.500397)', 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
|
# Using GEOSGeometry to compute the reference point on surface values
|
||||||
# -- since PostGIS also uses GEOS these should be the same.
|
# -- since PostGIS also uses GEOS these should be the same.
|
||||||
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
|
ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
|
||||||
'Texas' : Country.objects.get(name='Texas').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?
|
# XXX This seems to be a WKT-translation-related precision issue?
|
||||||
tol = 0.00001
|
tol = 0.00001
|
||||||
else: tol = 0.000000001
|
else:
|
||||||
self.assertEqual(True, ref[cntry.name].equals_exact(cntry.point_on_surface, tol))
|
tol = 0.000000001
|
||||||
|
self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol))
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
@no_oracle
|
@no_oracle
|
||||||
def test21_scale(self):
|
def test21_scale(self):
|
||||||
"Testing the `scale` GeoQuerySet method."
|
"Testing the `scale` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
xfac, yfac = 2, 3
|
xfac, yfac = 2, 3
|
||||||
|
tol = 5 # XXX The low precision tolerance is for SpatiaLite
|
||||||
qs = Country.objects.scale(xfac, yfac, model_att='scaled')
|
qs = Country.objects.scale(xfac, yfac, model_att='scaled')
|
||||||
for c in qs:
|
for c in qs:
|
||||||
for p1, p2 in zip(c.mpoly, c.scaled):
|
for p1, p2 in zip(c.mpoly, c.scaled):
|
||||||
for r1, r2 in zip(p1, p2):
|
for r1, r2 in zip(p1, p2):
|
||||||
for c1, c2 in zip(r1.coords, r2.coords):
|
for c1, c2 in zip(r1.coords, r2.coords):
|
||||||
# XXX The low precision is for SpatiaLite
|
self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
|
||||||
self.assertAlmostEqual(c1[0] * xfac, c2[0], 5)
|
self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
|
||||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], 5)
|
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
@no_oracle
|
@no_oracle
|
||||||
def test22_translate(self):
|
def test22_translate(self):
|
||||||
"Testing the `translate` GeoQuerySet method."
|
"Testing the `translate` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
xfac, yfac = 5, -23
|
xfac, yfac = 5, -23
|
||||||
qs = Country.objects.translate(xfac, yfac, model_att='translated')
|
qs = Country.objects.translate(xfac, yfac, model_att='translated')
|
||||||
for c in qs:
|
for c in qs:
|
||||||
@ -609,57 +585,60 @@ class GeoModelTest(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
|
self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
|
||||||
self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
|
self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test23_numgeom(self):
|
def test23_numgeom(self):
|
||||||
"Testing the `num_geom` GeoQuerySet method."
|
"Testing the `num_geom` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
# Both 'countries' only have two geometries.
|
# Both 'countries' only have two geometries.
|
||||||
for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
|
for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
|
||||||
for c in City.objects.filter(point__isnull=False).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,
|
# Oracle will return 1 for the number of geometries on non-collections,
|
||||||
# whereas PostGIS will return None.
|
# whereas PostGIS will return None.
|
||||||
if SpatialBackend.postgis: self.assertEqual(None, c.num_geom)
|
if postgis:
|
||||||
else: self.assertEqual(1, c.num_geom)
|
self.assertEqual(None, c.num_geom)
|
||||||
|
else:
|
||||||
|
self.assertEqual(1, c.num_geom)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
@no_spatialite # SpatiaLite can only count vertices in LineStrings
|
||||||
def test24_numpoints(self):
|
def test24_numpoints(self):
|
||||||
"Testing the `num_points` GeoQuerySet method."
|
"Testing the `num_points` GeoQuerySet method."
|
||||||
if DISABLE: return
|
|
||||||
for c in Country.objects.num_points():
|
for c in Country.objects.num_points():
|
||||||
self.assertEqual(c.mpoly.num_points, c.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.
|
# Oracle cannot count vertices in Point geometries.
|
||||||
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test25_geoset(self):
|
def test25_geoset(self):
|
||||||
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
"Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
|
||||||
if DISABLE: return
|
|
||||||
geom = Point(5, 23)
|
geom = Point(5, 23)
|
||||||
tol = 1
|
tol = 1
|
||||||
qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
|
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 For some reason SpatiaLite does something screwey with the Texas geometry here. Also,
|
||||||
# XXX it doesn't like the null intersection.
|
# XXX it doesn't like the null intersection.
|
||||||
if SpatialBackend.spatialite:
|
if spatialite:
|
||||||
qs = qs.exclude(name='Texas')
|
qs = qs.exclude(name='Texas')
|
||||||
else:
|
else:
|
||||||
qs = qs.intersection(geom)
|
qs = qs.intersection(geom)
|
||||||
|
|
||||||
for c in qs:
|
for c in qs:
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
# Should be able to execute the queries; however, they won't be the same
|
# 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
|
# as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
|
||||||
# SpatiaLite).
|
# SpatiaLite).
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.assertEqual(c.mpoly.difference(geom), c.difference)
|
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.intersection(geom), c.intersection)
|
||||||
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
|
||||||
self.assertEqual(c.mpoly.union(geom), c.union)
|
self.assertEqual(c.mpoly.union(geom), c.union)
|
||||||
|
|
||||||
|
@no_mysql
|
||||||
def test26_inherited_geofields(self):
|
def test26_inherited_geofields(self):
|
||||||
"Test GeoQuerySet methods on inherited Geometry fields."
|
"Test GeoQuerySet methods on inherited Geometry fields."
|
||||||
if DISABLE: return
|
|
||||||
# Creating a Pennsylvanian city.
|
# Creating a Pennsylvanian city.
|
||||||
mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
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())
|
self.assertEqual(1, qs.count())
|
||||||
for pc in qs: self.assertEqual(32128, pc.point.srid)
|
for pc in qs: self.assertEqual(32128, pc.point.srid)
|
||||||
|
|
||||||
@no_spatialite
|
@no_mysql
|
||||||
@no_oracle
|
@no_oracle
|
||||||
|
@no_spatialite
|
||||||
def test27_snap_to_grid(self):
|
def test27_snap_to_grid(self):
|
||||||
"Testing GeoQuerySet.snap_to_grid()."
|
"Testing GeoQuerySet.snap_to_grid()."
|
||||||
if DISABLE: return
|
|
||||||
|
|
||||||
# Let's try and break snap_to_grid() with bad combinations of arguments.
|
# Let's try and break snap_to_grid() with bad combinations of arguments.
|
||||||
for bad_args in ((), range(3), range(5)):
|
for bad_args in ((), range(3), range(5)):
|
||||||
self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
|
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 copy import copy
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
|
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.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
|
||||||
from django.contrib.gis.gdal import DataSource
|
from django.contrib.gis.gdal import DataSource
|
||||||
|
from django.contrib.gis.tests.utils import mysql
|
||||||
|
|
||||||
shp_path = os.path.dirname(__file__)
|
shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
|
||||||
city_shp = os.path.join(shp_path, '../data/cities/cities.shp')
|
city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
|
||||||
co_shp = os.path.join(shp_path, '../data/counties/counties.shp')
|
co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
|
||||||
inter_shp = os.path.join(shp_path, '../data/interstates/interstates.shp')
|
inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
|
||||||
|
|
||||||
# Dictionaries to hold what's expected in the county shapefile.
|
# Dictionaries to hold what's expected in the county shapefile.
|
||||||
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||||
@ -84,7 +84,7 @@ class LayerMapTest(unittest.TestCase):
|
|||||||
lm.save(silent=True, strict=True)
|
lm.save(silent=True, strict=True)
|
||||||
except InvalidDecimal:
|
except InvalidDecimal:
|
||||||
# No transactions for geoms on MySQL; delete added features.
|
# No transactions for geoms on MySQL; delete added features.
|
||||||
if SpatialBackend.mysql: Interstate.objects.all().delete()
|
if mysql: Interstate.objects.all().delete()
|
||||||
else:
|
else:
|
||||||
self.fail('Should have failed on strict import with invalid decimal values.')
|
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)
|
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.
|
# 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)
|
self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
|
||||||
|
|
||||||
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary
|
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from tests import *
|
|
@ -1,8 +1,8 @@
|
|||||||
import os, unittest
|
import os, unittest
|
||||||
from django.contrib.gis.geos import *
|
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.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 django.conf import settings
|
||||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
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
|
# geometries than PostGIS. The second union aggregate is for a union
|
||||||
# query that includes limiting information in the WHERE clause (in other
|
# query that includes limiting information in the WHERE clause (in other
|
||||||
# words a `.filter()` precedes the call to `.unionagg()`).
|
# words a `.filter()` precedes the call to `.unionagg()`).
|
||||||
if SpatialBackend.oracle:
|
if oracle:
|
||||||
ref_u1 = MultiPoint(p3, p1, p2, srid=4326)
|
ref_u1 = MultiPoint(p3, p1, p2, srid=4326)
|
||||||
ref_u2 = MultiPoint(p3, p2, srid=4326)
|
ref_u2 = MultiPoint(p3, p2, srid=4326)
|
||||||
else:
|
else:
|
||||||
@ -144,7 +144,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||||||
self.assertEqual(1, len(qs))
|
self.assertEqual(1, len(qs))
|
||||||
self.assertEqual('P2', qs[0].name)
|
self.assertEqual('P2', qs[0].name)
|
||||||
|
|
||||||
if not SpatialBackend.mysql:
|
if not mysql:
|
||||||
# This time center2 is in a different coordinate system and needs
|
# This time center2 is in a different coordinate system and needs
|
||||||
# to be wrapped in transformation SQL.
|
# to be wrapped in transformation SQL.
|
||||||
qs = Parcel.objects.filter(center2__within=F('border1'))
|
qs = Parcel.objects.filter(center2__within=F('border1'))
|
||||||
@ -157,7 +157,7 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||||||
self.assertEqual(1, len(qs))
|
self.assertEqual(1, len(qs))
|
||||||
self.assertEqual('P1', qs[0].name)
|
self.assertEqual('P1', qs[0].name)
|
||||||
|
|
||||||
if not SpatialBackend.mysql:
|
if not mysql:
|
||||||
# This time the city column should be wrapped in transformation SQL.
|
# This time the city column should be wrapped in transformation SQL.
|
||||||
qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
|
qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
|
||||||
self.assertEqual(1, len(qs))
|
self.assertEqual(1, len(qs))
|
||||||
@ -175,8 +175,8 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||||||
for m, d, t in zip(gqs, gvqs, gvlqs):
|
for m, d, t in zip(gqs, gvqs, gvlqs):
|
||||||
# The values should be Geometry objects and not raw strings returned
|
# The values should be Geometry objects and not raw strings returned
|
||||||
# by the spatial database.
|
# by the spatial database.
|
||||||
self.failUnless(isinstance(d['point'], SpatialBackend.Geometry))
|
self.failUnless(isinstance(d['point'], Geometry))
|
||||||
self.failUnless(isinstance(t[1], SpatialBackend.Geometry))
|
self.failUnless(isinstance(t[1], Geometry))
|
||||||
self.assertEqual(m.point, d['point'])
|
self.assertEqual(m.point, d['point'])
|
||||||
self.assertEqual(m.point, t[1])
|
self.assertEqual(m.point, t[1])
|
||||||
|
|
||||||
@ -279,19 +279,11 @@ class RelatedGeoModelTest(unittest.TestCase):
|
|||||||
def test14_collect(self):
|
def test14_collect(self):
|
||||||
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
|
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
|
||||||
# Reference query:
|
# Reference query:
|
||||||
<<<<<<< HEAD:django/contrib/gis/tests/relatedapp/tests.py
|
|
||||||
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
|
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
|
||||||
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
||||||
# WHERE "relatedapp_city"."state" = 'TX';
|
# 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)')
|
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')
|
c1 = City.objects.filter(state='TX').collect(field_name='location__point')
|
||||||
c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
|
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.
|
# consolidate -- that's why 4 points in MultiPoint.
|
||||||
self.assertEqual(4, len(coll))
|
self.assertEqual(4, len(coll))
|
||||||
self.assertEqual(ref_geom, 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.
|
# TODO: Related tests for KML, GML, and distance lookups.
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
from tests import *
|
|
@ -1,8 +1,7 @@
|
|||||||
import unittest
|
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
|
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,
|
test_srs = ({'srid' : 4326,
|
||||||
'auth_name' : ('EPSG', True),
|
'auth_name' : ('EPSG', True),
|
||||||
@ -28,9 +27,12 @@ test_srs = ({'srid' : 4326,
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if SpatialBackend.postgis:
|
if oracle:
|
||||||
major, minor1, minor2 = SpatialBackend.version
|
from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
|
||||||
POSTGIS_14 = major >=1 and minor1 >= 4
|
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):
|
class SpatialRefSysTest(unittest.TestCase):
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ class SpatialRefSysTest(unittest.TestCase):
|
|||||||
|
|
||||||
# No proj.4 and different srtext on oracle backends :(
|
# No proj.4 and different srtext on oracle backends :(
|
||||||
if postgis:
|
if postgis:
|
||||||
if POSTGIS_14:
|
if connection.ops.spatial_version >= (1, 4, 0):
|
||||||
srtext = sd['srtext14']
|
srtext = sd['srtext14']
|
||||||
else:
|
else:
|
||||||
srtext = sd['srtext']
|
srtext = sd['srtext']
|
||||||
@ -79,7 +81,7 @@ class SpatialRefSysTest(unittest.TestCase):
|
|||||||
self.assertEqual(sd['proj4'], srs.proj4)
|
self.assertEqual(sd['proj4'], srs.proj4)
|
||||||
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
|
# No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
|
||||||
if not spatialite:
|
if not spatialite:
|
||||||
if POSTGIS_14:
|
if connection.ops.spatial_version >= (1, 4, 0):
|
||||||
srtext = sd['srtext14']
|
srtext = sd['srtext14']
|
||||||
else:
|
else:
|
||||||
srtext = sd['srtext']
|
srtext = sd['srtext']
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
|
|
||||||
# function that will pass a test.
|
# function that will pass a test.
|
||||||
def pass_test(*args): return
|
def pass_test(*args): return
|
||||||
|
|
||||||
def no_backend(test_func, backend):
|
def no_backend(test_func, backend):
|
||||||
"Use this decorator to disable test on specified 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
|
return pass_test
|
||||||
else:
|
else:
|
||||||
return test_func
|
return test_func
|
||||||
@ -13,12 +14,13 @@ def no_backend(test_func, backend):
|
|||||||
# Decorators to disable entire test functions for specific
|
# Decorators to disable entire test functions for specific
|
||||||
# spatial backends.
|
# spatial backends.
|
||||||
def no_oracle(func): return no_backend(func, 'oracle')
|
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_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.
|
# Shortcut booleans to omit only portions of tests.
|
||||||
oracle = settings.DATABASE_ENGINE == 'oracle'
|
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
|
||||||
postgis = settings.DATABASE_ENGINE == 'postgresql_psycopg2'
|
oracle = _default_db == 'oracle'
|
||||||
mysql = settings.DATABASE_ENGINE == 'mysql'
|
postgis = _default_db == 'postgis'
|
||||||
spatialite = settings.DATABASE_ENGINE == 'sqlite3'
|
mysql = _default_db == 'mysql'
|
||||||
|
spatialite = _default_db == 'spatialite'
|
||||||
|
@ -3,115 +3,15 @@
|
|||||||
The LayerMapping class provides a way to map the contents of OGR
|
The LayerMapping class provides a way to map the contents of OGR
|
||||||
vector files (e.g. SHP files) to Geographic-enabled Django models.
|
vector files (e.g. SHP files) to Geographic-enabled Django models.
|
||||||
|
|
||||||
This grew out of my personal needs, specifically the code repetition
|
For more information, please consult the GeoDjango documentation:
|
||||||
that went into pulling geometries and fields out of an OGR layer,
|
http://geodjango.org/docs/layermapping.html
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
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.models import GeometryField
|
||||||
from django.contrib.gis.db.backend import SpatialBackend
|
|
||||||
from django.contrib.gis.gdal import CoordTransform, DataSource, \
|
from django.contrib.gis.gdal import CoordTransform, DataSource, \
|
||||||
OGRException, OGRGeometry, OGRGeomType, SpatialReference
|
OGRException, OGRGeometry, OGRGeomType, SpatialReference
|
||||||
from django.contrib.gis.gdal.field import \
|
from django.contrib.gis.gdal.field import \
|
||||||
@ -167,7 +67,7 @@ class LayerMapping(object):
|
|||||||
def __init__(self, model, data, mapping, layer=0,
|
def __init__(self, model, data, mapping, layer=0,
|
||||||
source_srs=None, encoding=None,
|
source_srs=None, encoding=None,
|
||||||
transaction_mode='commit_on_success',
|
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 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
|
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.ds = data
|
||||||
self.layer = self.ds[layer]
|
self.layer = self.ds[layer]
|
||||||
|
|
||||||
|
self.using = using
|
||||||
|
self.spatial_backend = connections[using].ops
|
||||||
|
|
||||||
# Setting the mapping & model attributes.
|
# Setting the mapping & model attributes.
|
||||||
self.mapping = mapping
|
self.mapping = mapping
|
||||||
self.model = model
|
self.model = model
|
||||||
@ -191,7 +94,7 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
# Getting the geometry column associated with the model (an
|
# Getting the geometry column associated with the model (an
|
||||||
# exception will be raised if there is no geometry column).
|
# exception will be raised if there is no geometry column).
|
||||||
if SpatialBackend.mysql:
|
if self.spatial_backend.mysql:
|
||||||
transform = False
|
transform = False
|
||||||
else:
|
else:
|
||||||
self.geo_col = self.geometry_column()
|
self.geo_col = self.geometry_column()
|
||||||
@ -230,6 +133,9 @@ class LayerMapping(object):
|
|||||||
else:
|
else:
|
||||||
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
|
raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
|
||||||
|
|
||||||
|
if using is None:
|
||||||
|
pass
|
||||||
|
|
||||||
#### Checking routines used during initialization ####
|
#### Checking routines used during initialization ####
|
||||||
def check_fid_range(self, fid_range):
|
def check_fid_range(self, fid_range):
|
||||||
"This checks the `fid_range` keyword."
|
"This checks the `fid_range` keyword."
|
||||||
@ -341,10 +247,10 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
def check_srs(self, source_srs):
|
def check_srs(self, source_srs):
|
||||||
"Checks the compatibility of the given spatial reference object."
|
"Checks the compatibility of the given spatial reference object."
|
||||||
from django.contrib.gis.models import SpatialRefSys
|
|
||||||
if isinstance(source_srs, SpatialReference):
|
if isinstance(source_srs, SpatialReference):
|
||||||
sr = source_srs
|
sr = source_srs
|
||||||
elif isinstance(source_srs, SpatialRefSys):
|
elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()):
|
||||||
sr = source_srs.srs
|
sr = source_srs.srs
|
||||||
elif isinstance(source_srs, (int, basestring)):
|
elif isinstance(source_srs, (int, basestring)):
|
||||||
sr = SpatialReference(source_srs)
|
sr = SpatialReference(source_srs)
|
||||||
@ -517,7 +423,7 @@ class LayerMapping(object):
|
|||||||
#### Other model methods ####
|
#### Other model methods ####
|
||||||
def coord_transform(self):
|
def coord_transform(self):
|
||||||
"Returns the coordinate transformation object."
|
"Returns the coordinate transformation object."
|
||||||
from django.contrib.gis.models import SpatialRefSys
|
SpatialRefSys = self.spatial_backend.spatial_ref_sys()
|
||||||
try:
|
try:
|
||||||
# Getting the target spatial reference system
|
# Getting the target spatial reference system
|
||||||
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
|
target_srs = SpatialRefSys.objects.get(srid=self.geo_col.srid).srs
|
||||||
@ -529,7 +435,6 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
def geometry_column(self):
|
def geometry_column(self):
|
||||||
"Returns the GeometryColumn model associated with the geographic column."
|
"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
|
# Use the `get_field_by_name` on the model's options so that we
|
||||||
# get the correct model if there's model inheritance -- otherwise
|
# get the correct model if there's model inheritance -- otherwise
|
||||||
# the returned model is None.
|
# the returned model is None.
|
||||||
@ -543,11 +448,13 @@ class LayerMapping(object):
|
|||||||
db_table = model._meta.db_table
|
db_table = model._meta.db_table
|
||||||
geo_col = fld.column
|
geo_col = fld.column
|
||||||
|
|
||||||
if SpatialBackend.oracle:
|
if self.spatial_backend.oracle:
|
||||||
# Making upper case for Oracle.
|
# Making upper case for Oracle.
|
||||||
db_table = db_table.upper()
|
db_table = db_table.upper()
|
||||||
geo_col = geo_col.upper()
|
geo_col = geo_col.upper()
|
||||||
|
|
||||||
|
GeometryColumns = self.spatial_backend.geometry_columns()
|
||||||
|
|
||||||
gc_kwargs = { GeometryColumns.table_name_col() : db_table,
|
gc_kwargs = { GeometryColumns.table_name_col() : db_table,
|
||||||
GeometryColumns.geom_col_name() : geo_col,
|
GeometryColumns.geom_col_name() : geo_col,
|
||||||
}
|
}
|
||||||
@ -643,7 +550,7 @@ class LayerMapping(object):
|
|||||||
# Getting the keyword arguments and retrieving
|
# Getting the keyword arguments and retrieving
|
||||||
# the unique model.
|
# the unique model.
|
||||||
u_kwargs = self.unique_kwargs(kwargs)
|
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
|
is_update = True
|
||||||
|
|
||||||
# Getting the geometry (in OGR form), creating
|
# Getting the geometry (in OGR form), creating
|
||||||
@ -662,7 +569,7 @@ class LayerMapping(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Attempting to save.
|
# Attempting to save.
|
||||||
m.save()
|
m.save(using=self.using)
|
||||||
num_saved += 1
|
num_saved += 1
|
||||||
if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
|
if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
|
@ -82,14 +82,9 @@ class Command(BaseCommand):
|
|||||||
model_list = get_models(app)
|
model_list = get_models(app)
|
||||||
|
|
||||||
for model in model_list:
|
for model in model_list:
|
||||||
<<<<<<< HEAD:django/core/management/commands/dumpdata.py
|
|
||||||
# Don't serialize proxy models, or models that haven't been synchronized
|
# Don't serialize proxy models, or models that haven't been synchronized
|
||||||
if not model._meta.proxy and model._meta.db_table in tables:
|
if not model._meta.proxy and model._meta.db_table in tables:
|
||||||
objects.extend(model._default_manager.using(using).all())
|
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:
|
try:
|
||||||
return serializers.serialize(format, objects, indent=indent)
|
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
|
import decimal
|
||||||
except ImportError:
|
from threading import local
|
||||||
# Python 2.3 fallback
|
|
||||||
from django.utils import _decimal as decimal
|
|
||||||
|
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
from django.db.backends import util
|
from django.db.backends import util
|
||||||
@ -286,9 +271,9 @@ class BaseDatabaseOperations(object):
|
|||||||
|
|
||||||
def compiler(self, compiler_name):
|
def compiler(self, compiler_name):
|
||||||
"""
|
"""
|
||||||
Given the default Query class, returns a custom Query class
|
Returns the SQLCompiler class corresponding to the given name,
|
||||||
to use for this backend. Returns the Query class unmodified if the
|
in the namespace corresponding to the `compiler_module` attribute
|
||||||
backend doesn't need a custom Query clsas.
|
on this backend.
|
||||||
"""
|
"""
|
||||||
if compiler_name not in self._cache:
|
if compiler_name not in self._cache:
|
||||||
self._cache[compiler_name] = getattr(
|
self._cache[compiler_name] = getattr(
|
||||||
|
@ -357,11 +357,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||||||
cursor = None
|
cursor = None
|
||||||
if not self._valid_connection():
|
if not self._valid_connection():
|
||||||
conn_string = convert_unicode(self._connect_string())
|
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['OPTIONS'])
|
||||||
=======
|
|
||||||
self.connection = Database.connect(conn_string, **self.settings_dict['DATABASE_OPTIONS'])
|
|
||||||
>>>>>>> master:django/db/backends/oracle/base.py
|
|
||||||
cursor = FormatStylePlaceholderCursor(self.connection)
|
cursor = FormatStylePlaceholderCursor(self.connection)
|
||||||
# Set oracle date to ansi date format. This only needs to execute
|
# Set oracle date to ansi date format. This only needs to execute
|
||||||
# once when we create a new connection. We also set the Territory
|
# 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)'
|
module = 'either pysqlite2 or sqlite3 modules (tried in that order)'
|
||||||
raise ImproperlyConfigured, "Error loading %s: %s" % (module, exc)
|
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
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
IntegrityError = Database.IntegrityError
|
||||||
|
@ -230,7 +230,6 @@ class ModelBase(type):
|
|||||||
|
|
||||||
signals.class_prepared.send(sender=cls)
|
signals.class_prepared.send(sender=cls)
|
||||||
|
|
||||||
<<<<<<< HEAD:django/db/models/base.py
|
|
||||||
class ModelState(object):
|
class ModelState(object):
|
||||||
"""
|
"""
|
||||||
A class for storing instance state
|
A class for storing instance state
|
||||||
@ -238,8 +237,6 @@ class ModelState(object):
|
|||||||
def __init__(self, db=None):
|
def __init__(self, db=None):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> master:django/db/models/base.py
|
|
||||||
class Model(object):
|
class Model(object):
|
||||||
__metaclass__ = ModelBase
|
__metaclass__ = ModelBase
|
||||||
_deferred = False
|
_deferred = False
|
||||||
@ -491,11 +488,7 @@ class Model(object):
|
|||||||
if pk_set:
|
if pk_set:
|
||||||
# Determine whether a record with the primary key already exists.
|
# Determine whether a record with the primary key already exists.
|
||||||
if (force_update or (not force_insert and
|
if (force_update or (not force_insert and
|
||||||
<<<<<<< HEAD:django/db/models/base.py
|
|
||||||
manager.using(using).filter(pk=pk_val).exists())):
|
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.
|
# It does already exist, so do an UPDATE.
|
||||||
if force_update or non_pks:
|
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]
|
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
|
# Store the database on which the object was saved
|
||||||
self._state.db = using
|
self._state.db = using
|
||||||
|
|
||||||
<<<<<<< HEAD:django/db/models/base.py
|
|
||||||
# Signal that the save is complete
|
# Signal that the save is complete
|
||||||
=======
|
|
||||||
>>>>>>> master:django/db/models/base.py
|
|
||||||
if origin and not meta.auto_created:
|
if origin and not meta.auto_created:
|
||||||
signals.post_save.send(sender=origin, instance=self,
|
signals.post_save.send(sender=origin, instance=self,
|
||||||
created=(not record_exists), raw=raw)
|
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:
|
if not rel.through._meta.auto_created:
|
||||||
opts = through._meta
|
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)
|
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).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)
|
self.add(new_obj)
|
||||||
return new_obj
|
return new_obj
|
||||||
create.alters_data = True
|
create.alters_data = True
|
||||||
@ -505,22 +501,15 @@ def create_many_related_manager(superclass, rel=False):
|
|||||||
new_ids = set()
|
new_ids = set()
|
||||||
for obj in objs:
|
for obj in objs:
|
||||||
if isinstance(obj, self.model):
|
if isinstance(obj, self.model):
|
||||||
<<<<<<< HEAD:django/db/models/fields/related.py
|
|
||||||
if obj._state.db != self.instance._state.db:
|
if obj._state.db != self.instance._state.db:
|
||||||
raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' %
|
raise ValueError('Cannot add "%r": instance is on database "%s", value is is on database "%s"' %
|
||||||
(obj, self.instance._state.db, obj._state.db))
|
(obj, self.instance._state.db, obj._state.db))
|
||||||
=======
|
|
||||||
>>>>>>> master:django/db/models/fields/related.py
|
|
||||||
new_ids.add(obj.pk)
|
new_ids.add(obj.pk)
|
||||||
elif isinstance(obj, Model):
|
elif isinstance(obj, Model):
|
||||||
raise TypeError, "'%s' instance expected" % self.model._meta.object_name
|
raise TypeError, "'%s' instance expected" % self.model._meta.object_name
|
||||||
else:
|
else:
|
||||||
new_ids.add(obj)
|
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.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(**{
|
vals = vals.filter(**{
|
||||||
source_field_name: self._pk_val,
|
source_field_name: self._pk_val,
|
||||||
'%s__in' % target_field_name: new_ids,
|
'%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
|
# Add the ones that aren't there already
|
||||||
for obj_id in (new_ids - vals):
|
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.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' % source_field_name: self._pk_val,
|
||||||
'%s_id' % target_field_name: obj_id,
|
'%s_id' % target_field_name: obj_id,
|
||||||
})
|
})
|
||||||
@ -553,22 +538,14 @@ def create_many_related_manager(superclass, rel=False):
|
|||||||
else:
|
else:
|
||||||
old_ids.add(obj)
|
old_ids.add(obj)
|
||||||
# Remove the specified objects from the join table
|
# 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.using(self.instance._state.db).filter(**{
|
||||||
=======
|
|
||||||
self.through._default_manager.filter(**{
|
|
||||||
>>>>>>> master:django/db/models/fields/related.py
|
|
||||||
source_field_name: self._pk_val,
|
source_field_name: self._pk_val,
|
||||||
'%s__in' % target_field_name: old_ids
|
'%s__in' % target_field_name: old_ids
|
||||||
}).delete()
|
}).delete()
|
||||||
|
|
||||||
def _clear_items(self, source_field_name):
|
def _clear_items(self, source_field_name):
|
||||||
# source_col_name: the PK colname in join_table for the source object
|
# 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.using(self.instance._state.db).filter(**{
|
||||||
=======
|
|
||||||
self.through._default_manager.filter(**{
|
|
||||||
>>>>>>> master:django/db/models/fields/related.py
|
|
||||||
source_field_name: self._pk_val
|
source_field_name: self._pk_val
|
||||||
}).delete()
|
}).delete()
|
||||||
|
|
||||||
|
@ -172,12 +172,9 @@ class Manager(object):
|
|||||||
def only(self, *args, **kwargs):
|
def only(self, *args, **kwargs):
|
||||||
return self.get_query_set().only(*args, **kwargs)
|
return self.get_query_set().only(*args, **kwargs)
|
||||||
|
|
||||||
<<<<<<< HEAD:django/db/models/manager.py
|
|
||||||
def using(self, *args, **kwargs):
|
def using(self, *args, **kwargs):
|
||||||
return self.get_query_set().using(*args, **kwargs)
|
return self.get_query_set().using(*args, **kwargs)
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> master:django/db/models/manager.py
|
|
||||||
def exists(self, *args, **kwargs):
|
def exists(self, *args, **kwargs):
|
||||||
return self.get_query_set().exists(*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
|
from copy import deepcopy
|
||||||
<<<<<<< HEAD:django/db/models/query.py
|
|
||||||
|
|
||||||
from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
|
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.aggregates import Aggregate
|
||||||
from django.db.models.fields import DateField
|
from django.db.models.fields import DateField
|
||||||
from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
|
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):
|
def exists(self):
|
||||||
if self._result_cache is None:
|
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(using=self.db)
|
||||||
=======
|
|
||||||
return self.query.has_results()
|
|
||||||
>>>>>>> master:django/db/models/query.py
|
|
||||||
return bool(self._result_cache)
|
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.
|
part of the public API.
|
||||||
"""
|
"""
|
||||||
query = sql.InsertQuery(model)
|
query = sql.InsertQuery(model)
|
||||||
query.insert_values(values, raw_values)
|
|
||||||
compiler = query.get_compiler(using=using)
|
compiler = query.get_compiler(using=using)
|
||||||
|
query.insert_values(values, compiler.connection, raw_values)
|
||||||
return compiler.execute_sql(return_id)
|
return compiler.execute_sql(return_id)
|
||||||
|
@ -755,7 +755,7 @@ class SQLUpdateCompiler(SQLCompiler):
|
|||||||
|
|
||||||
# Getting the placeholder for the field.
|
# Getting the placeholder for the field.
|
||||||
if hasattr(field, 'get_placeholder'):
|
if hasattr(field, 'get_placeholder'):
|
||||||
placeholder = field.get_placeholder(val)
|
placeholder = field.get_placeholder(val, self.connection)
|
||||||
else:
|
else:
|
||||||
placeholder = '%s'
|
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.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
|
||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
|
|
||||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
|
||||||
|
|
||||||
__all__ = ['Query']
|
__all__ = ['Query']
|
||||||
=======
|
|
||||||
__all__ = ['Query', 'BaseQuery']
|
|
||||||
>>>>>>> master:django/db/models/sql/query.py
|
|
||||||
|
|
||||||
class Query(object):
|
class Query(object):
|
||||||
"""
|
"""
|
||||||
@ -341,11 +336,7 @@ class Query(object):
|
|||||||
|
|
||||||
return number
|
return number
|
||||||
|
|
||||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
|
||||||
def has_results(self, using):
|
def has_results(self, using):
|
||||||
=======
|
|
||||||
def has_results(self):
|
|
||||||
>>>>>>> master:django/db/models/sql/query.py
|
|
||||||
q = self.clone()
|
q = self.clone()
|
||||||
q.add_extra({'a': 1}, None, None, None, None, None)
|
q.add_extra({'a': 1}, None, None, None, None, None)
|
||||||
q.add_fields(())
|
q.add_fields(())
|
||||||
@ -353,104 +344,8 @@ class Query(object):
|
|||||||
q.set_aggregate_mask(())
|
q.set_aggregate_mask(())
|
||||||
q.clear_ordering()
|
q.clear_ordering()
|
||||||
q.set_limits(high=1)
|
q.set_limits(high=1)
|
||||||
<<<<<<< HEAD:django/db/models/sql/query.py
|
|
||||||
compiler = q.get_compiler(using=using)
|
compiler = q.get_compiler(using=using)
|
||||||
return bool(compiler.execute_sql(SINGLE))
|
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):
|
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