Учебник по GeoDjango
Введение
GeoDjango — это модуль contrib для Django, превращающий его в полнофункциональный географический веб-фреймворк. GeoDjango призван максимально упростить создание географических веб-приложений, таких как сервисы на основе местоположения. Его возможности включают:
- Поля модели Django для геометрий OGC и растровых данных.
- Расширения ORM Django для запросов и манипуляций с пространственными данными.
- Слабосвязанные, высокоуровневые Python-интерфейсы для геометрии и растровых операций ГИС, а также обработки данных в разных форматах.
- Редактирование полей геометрии в админке.
Этот учебник предполагает знание Django; поэтому, если вы новичок в Django, пожалуйста, сначала прочитайте основной учебник, чтобы ознакомиться с Django.
Примечание
GeoDjango имеет дополнительные требования помимо требований к Django — для получения подробной информации обратитесь к документации по установке.
Этот учебник проведет вас через создание географического веб-приложения для отображения границ стран мира. [1] Некоторые из кодов, используемых в этом учебнике, взяты из проекта GeoDjango basic apps или вдохновлены им. [2]
Примечание
Для пошаговых инструкций пройдите разделы учебника последовательно.
Настройка
Создание пространственной базы данных
Обычно дополнительная настройка не требуется, поэтому вы можете создать базу данных, как и для любого другого проекта. Мы предоставляем некоторые рекомендации для выбранных баз данных:
Создание нового проекта
Используйте стандартный django-admin скрипт для создания проекта под названием geodjango:
$ django-admin startproject geodjango
Это инициализирует новый проект. Теперь создайте приложение Django world внутри проекта geodjango:
$ cd geodjango $ python manage.py startapp world
Настройка settings.py
Параметры проекта geodjango хранятся в файле geodjango/settings.py. Измените параметры подключения к базе данных, чтобы соответствовать вашей установке:
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'geodjango',
'USER': 'geo',
},
}
Кроме того, измените настройку INSTALLED_APPS для включения django.contrib.admin, django.contrib.gis и world (ваше недавно созданное приложение):
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.gis',
'world',
]
Географические данные
Границы стран
Данные границ стран доступны в этом архиве zip. Создайте директорию data в приложении world, загрузите данные границ стран и распакуйте архив. На платформах GNU/Linux используйте следующие команды:
$ mkdir world/data $ cd world/data $ wget http://thematicmapping.org/downloads/TM_WORLD_BORDERS-0.3.zip $ unzip TM_WORLD_BORDERS-0.3.zip $ cd ../..
Архив границ стран содержит набор файлов данных, известный как ESRI Shapefile — один из наиболее популярных форматов пространственных данных. После распаковки набор данных границ стран включает файлы со следующими расширениями:
-
.shp: Содержит векторные данные геометрий границ стран. -
.shx: Файл пространственного индекса для геометрий, хранящихся в.shp. -
.dbf: Файл базы данных для хранения негеометрических атрибутивных данных (например, целочисленных и символьных полей). -
.prj: Содержит информацию о системе пространственной привязки для географических данных, хранящихся в шейп-файле.
Использование ogrinfo для проверки пространственных данных
Утилита GDAL ogrinfo позволяет просматривать метаданные шейп-файлов или других векторных источников данных:
$ ogrinfo world/data/TM_WORLD_BORDERS-0.3.shp
INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp'
using driver `ESRI Shapefile' successful.
1: TM_WORLD_BORDERS-0.3 (Polygon)
ogrinfo показывает, что шейп-файл имеет один слой, и что этот слой содержит полигональные данные. Для получения более подробной информации укажите имя слоя и используйте параметр -so, чтобы получить только важную сводную информацию:
$ ogrinfo -so world/data/TM_WORLD_BORDERS-0.3.shp TM_WORLD_BORDERS-0.3
INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp'
using driver `ESRI Shapefile' successful.
Layer name: TM_WORLD_BORDERS-0.3
Geometry: Polygon
Feature Count: 246
Extent: (-180.000000, -90.000000) - (180.000000, 83.623596)
Layer SRS WKT:
GEOGCS["GCS_WGS_1984",
DATUM["WGS_1984",
SPHEROID["WGS_1984",6378137.0,298.257223563]],
PRIMEM["Greenwich",0.0],
UNIT["Degree",0.0174532925199433]]
FIPS: String (2.0)
ISO2: String (2.0)
ISO3: String (3.0)
UN: Integer (3.0)
NAME: String (50.0)
AREA: Integer (7.0)
POP2005: Integer (10.0)
REGION: Integer (3.0)
SUBREGION: Integer (3.0)
LON: Real (8.3)
LAT: Real (7.3)
Эта подробная сводная информация сообщает нам о количестве объектов в слое (246), географических границах данных, системе пространственной привязки («SRS WKT»), а также о типе каждого атрибутивного поля. Например, FIPS: String (2.0) указывает, что символьное поле FIPS имеет максимальную длину 2. Аналогично, LON: Real (8.3) — это числовое поле с плавающей запятой, которое может хранить максимум 8 цифр до трех десятичных знаков.
Географические модели
Определение географической модели
После проверки набора данных с помощью ogrinfo создайте модель GeoDjango для представления этих данных:
from django.contrib.gis.db import models
class WorldBorder(models.Model):
# Regular Django fields corresponding to the attributes in the
# world borders shapefile.
name = models.CharField(max_length=50)
area = models.IntegerField()
pop2005 = models.IntegerField('Population 2005')
fips = models.CharField('FIPS Code', max_length=2)
iso2 = models.CharField('2 Digit ISO', max_length=2)
iso3 = models.CharField('3 Digit ISO', max_length=3)
un = models.IntegerField('United Nations Code')
region = models.IntegerField('Region Code')
subregion = models.IntegerField('Sub-Region Code')
lon = models.FloatField()
lat = models.FloatField()
# GeoDjango-specific: a geometry field (MultiPolygonField)
mpoly = models.MultiPolygonField()
# Returns the string representation of the model.
def __str__(self): # __unicode__ on Python 2
return self.name
Обратите внимание, что модуль models импортируется из django.contrib.gis.db.
По умолчанию система пространственной привязки для полей геометрии — WGS84 (то есть, SRID равен 4326) — другими словами, координаты поля — это пары долгота, широта в градусах. Для использования другой системы координат установите SRID поля геометрии с помощью аргумента srid. Используйте целое число, представляющее код EPSG системы координат.
Запуск migrate
После определения модели вам необходимо синхронизировать ее с базой данных. Сначала создайте миграцию базы данных:
$ python manage.py makemigrations
Migrations for 'world':
0001_initial.py:
- Create model WorldBorder
Давайте посмотрим на SQL-запрос, который сгенерирует таблицу для модели WorldBorder:
$ python manage.py sqlmigrate world 0001
Эта команда должна выдать следующий результат:
BEGIN;
--
-- Create model WorldBorder
--
CREATE TABLE "world_worldborder" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(50) NOT NULL,
"area" integer NOT NULL,
"pop2005" integer NOT NULL,
"fips" varchar(2) NOT NULL,
"iso2" varchar(2) NOT NULL,
"iso3" varchar(3) NOT NULL,
"un" integer NOT NULL,
"region" integer NOT NULL,
"subregion" integer NOT NULL,
"lon" double precision NOT NULL,
"lat" double precision NOT NULL
"mpoly" geometry(MULTIPOLYGON,4326) NOT NULL
)
;
CREATE INDEX "world_worldborder_mpoly_id" ON "world_worldborder" USING GIST ( "mpoly" );
COMMIT;
Если все правильно, запустите migrate, чтобы создать эту таблицу в базе данных:
$ python manage.py migrate Operations to perform: Apply all migrations: admin, world, contenttypes, auth, sessions Running migrations: ... Applying world.0001_initial... OK
Импорт пространственных данных
В этом разделе показано, как импортировать шейп-файл границ стран в базу данных через модели GeoDjango с помощью утилиты импорта данных LayerMapping.
Существует множество способов импорта данных в пространственную базу данных — помимо инструментов, включенных в GeoDjango, вы также можете использовать следующие:
- ogr2ogr: Утилита командной строки, входящая в состав GDAL, которая может импортировать многие форматы векторных данных в базы данных PostGIS, MySQL и Oracle.
- shp2pgsql: Эта утилита, входящая в состав PostGIS, импортирует ESRI shapefiles в PostGIS.
Интерфейс GDAL
Ранее вы использовали ogrinfo для проверки содержимого шейп-файла границ стран. GeoDjango также включает питоновский интерфейс к мощной библиотеке OGR GDAL, который может работать со всеми источниками векторных данных, поддерживаемыми OGR.
Сначала вызовите оболочку Django:
$ python manage.py shell
Если вы загрузили данные границ стран ранее в этом учебнике, вы можете определить его путь с помощью встроенного модуля Python os:
>>> import os >>> import world >>> world_shp = os.path.abspath(os.path.join(os.path.dirname(world.__file__), ... 'data', 'TM_WORLD_BORDERS-0.3.shp'))
Теперь откройте шейп-файл границ стран с помощью интерфейса GeoDjango DataSource:
>>> from django.contrib.gis.gdal import DataSource >>> ds = DataSource(world_shp) >>> print(ds) / ... /geodjango/world/data/TM_WORLD_BORDERS-0.3.shp (ESRI Shapefile)
Объекты источника данных могут иметь разные слои географических объектов; однако шейп-файлы могут иметь только один слой:
>>> print(len(ds)) 1 >>> lyr = ds[0] >>> print(lyr) TM_WORLD_BORDERS-0.3
Вы можете увидеть тип геометрии слоя и количество объектов:
>>> print(lyr.geom_type) Polygon >>> print(len(lyr)) 246
Примечание
К сожалению, формат данных шейп-файла не позволяет более подробно определять типы геометрий. Этот шейп-файл, как и многие другие, на самом деле содержит MultiPolygon геометрии, а не Polygons. Важно использовать более общий тип поля в моделях: GeoDjango MultiPolygonField будет принимать Polygon геометрию, но PolygonField не будет принимать MultiPolygon тип геометрии. Вот почему модель WorldBorder, определенная выше, использует MultiPolygonField.
Объект Layer также может иметь связанную с ним систему пространственной привязки. Если это так, атрибут srs вернет объект SpatialReference:
>>> srs = lyr.srs
>>> print(srs)
GEOGCS["GCS_WGS_1984",
DATUM["WGS_1984",
SPHEROID["WGS_1984",6378137.0,298.257223563]],
PRIMEM["Greenwich",0.0],
UNIT["Degree",0.0174532925199433]]
>>> srs.proj4 # PROJ.4 representation
'+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs '
Этот шейп-файл использует популярную систему пространственной привязки WGS84 — другими словами, данные используют пары долгота, широта в градусах.
Кроме того, шейп-файлы также поддерживают атрибутивные поля, которые могут содержать дополнительные данные. Вот поля в слое «Границы стран»:
>>> print(lyr.fields) ['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT']
Следующий код позволит вам просмотреть типы OGR (например, целые числа или строки), связанные с каждым из полей:
>>> [fld.__name__ for fld in lyr.field_types] ['OFTString', 'OFTString', 'OFTString', 'OFTInteger', 'OFTString', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTReal', 'OFTReal']
Вы можете перебирать каждый объект в слое и извлекать информацию как из геометрии объекта (доступ к которой осуществляется через атрибут geom), так и из атрибутивных полей объекта (значения которых доступны через метод get()):
>>> for feat in lyr:
... print(feat.get('NAME'), feat.geom.num_points)
...
Guernsey 18
Jersey 26
South Georgia South Sandwich Islands 338
Taiwan 363
Layer объекты могут быть нарезаны:
>>> lyr[0:2] [<django.contrib.gis.gdal.feature.Feature object at 0x2f47690>, <django.contrib.gis.gdal.feature.Feature object at 0x2f47650>]
И отдельные объекты могут быть получены по их идентификатору:
>>> feat = lyr[234]
>>> print(feat.get('NAME'))
San Marino
Геометрии границ могут быть экспортированы как WKT и GeoJSON:
>>> geom = feat.geom
>>> print(geom.wkt)
POLYGON ((12.415798 43.957954,12.450554 ...
>>> print(geom.json)
{ "type": "Polygon", "coordinates": [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ...
LayerMapping
Для импорта данных используйте LayerMapping в скрипте Python. Создайте файл с именем load.py в приложении world со следующим кодом:
import os
from django.contrib.gis.utils import LayerMapping
from .models import WorldBorder
world_mapping = {
'fips' : 'FIPS',
'iso2' : 'ISO2',
'iso3' : 'ISO3',
'un' : 'UN',
'name' : 'NAME',
'area' : 'AREA',
'pop2005' : 'POP2005',
'region' : 'REGION',
'subregion' : 'SUBREGION',
'lon' : 'LON',
'lat' : 'LAT',
'mpoly' : 'MULTIPOLYGON',
}
world_shp = os.path.abspath(
os.path.join(os.path.dirname(__file__), 'data', 'TM_WORLD_BORDERS-0.3.shp'),
)
def run(verbose=True):
lm = LayerMapping(
WorldBorder, world_shp, world_mapping,
transform=False, encoding='iso-8859-1',
)
lm.save(strict=True, verbose=verbose)
Несколько замечаний о происходящем:
- Каждый ключ в словаре
world_mappingсоответствует полю в моделиWorldBorder. Значение — имя поля shapefile, из которого будут загружены данные. - Ключ
mpolyдля геометрического поля —MULTIPOLYGON, тип геометрии, который GeoDjango импортирует как поле. Даже простые полигоны в shapefile автоматически преобразуются в коллекции перед вставкой в базу данных. - Путь к shapefile не является абсолютным — другими словами, если вы переместите приложение
world(с подкаталогомdata) в другое место, скрипт по-прежнему будет работать. - Ключевое слово
transformустановлено вFalse, так как данные в shapefile не нуждаются в преобразовании — они уже в WGS84 (SRID=4326). - Ключевое слово
encodingустановлено в кодировку символов строковых значений в shapefile. Это гарантирует, что строковые значения читаются и сохраняются правильно из их исходной кодировки.
Затем вызовите Django shell из каталога проекта geodjango:
$ python manage.py shell
Далее, импортируйте модуль load, вызовите процедуру run и наблюдайте, как LayerMapping выполняет работу:
>>> from world import load >>> load.run()
Попробуйте ogrinspect
Теперь, когда вы увидели, как определять географические модели и импортировать данные с помощью утилиты импорта данных LayerMapping, можно дополнительно автоматизировать этот процесс с помощью командной утилиты ogrinspect. Команда ogrinspect инспектирует источник векторных данных, поддерживаемых GDAL (например, shapefile), и автоматически генерирует определение модели и словарь LayerMapping.
Общая форма использования команды следующая:
$ python manage.py ogrinspect [options] <data_source> <model_name> [options]
data_source — путь к источнику данных, поддерживаемому GDAL, а model_name — имя, используемое для модели. Можно использовать параметры командной строки для дальнейшего определения способа генерации модели.
Например, следующая команда почти точно воспроизводит модель WorldBorder и словарь отображения, созданный выше, автоматически:
$ python manage.py ogrinspect world/data/TM_WORLD_BORDERS-0.3.shp WorldBorder \
--srid=4326 --mapping --multi
Несколько замечаний о параметрах командной строки, приведенных выше:
- Параметр
--srid=4326устанавливает SRID для географического поля. - Параметр
--mappingсообщаетogrinspectтакже сгенерировать словарь отображения для использования сLayerMapping. - Параметр
--multiзадан таким образом, что географическое поле являетсяMultiPolygonField, а не простоPolygonField.
Команда выводит следующий результат, который можно скопировать непосредственно в models.py приложения GeoDjango:
# This is an auto-generated Django model module created by ogrinspect.
from django.contrib.gis.db import models
class WorldBorder(models.Model):
fips = models.CharField(max_length=2)
iso2 = models.CharField(max_length=2)
iso3 = models.CharField(max_length=3)
un = models.IntegerField()
name = models.CharField(max_length=50)
area = models.IntegerField()
pop2005 = models.IntegerField()
region = models.IntegerField()
subregion = models.IntegerField()
lon = models.FloatField()
lat = models.FloatField()
geom = models.MultiPolygonField(srid=4326)
# Auto-generated `LayerMapping` dictionary for WorldBorder model
worldborders_mapping = {
'fips' : 'FIPS',
'iso2' : 'ISO2',
'iso3' : 'ISO3',
'un' : 'UN',
'name' : 'NAME',
'area' : 'AREA',
'pop2005' : 'POP2005',
'region' : 'REGION',
'subregion' : 'SUBREGION',
'lon' : 'LON',
'lat' : 'LAT',
'geom' : 'MULTIPOLYGON',
}
Пространственные запросы
Пространственные поиски
GeoDjango добавляет пространственные поиски в Django ORM. Например, вы можете найти страну в таблице WorldBorder, содержащую определенную точку. Сначала запустите оболочку управления:
$ python manage.py shell
Теперь определите точку интереса [3]:
>>> pnt_wkt = 'POINT(-95.3385 29.7245)'
Строка pnt_wkt представляет точку в -95.3385 градусах долготы, 29.7245 градусах широты. Геометрия представлена в формате Well Known Text (WKT), стандарт, выпущенный Open Geospatial Consortium (OGC). [4] Импортируйте модель WorldBorder и выполните поиск contains, используя pnt_wkt в качестве параметра:
>>> from world.models import WorldBorder >>> qs = WorldBorder.objects.filter(mpoly__contains=pnt_wkt) >>> qs [<WorldBorder: United States>]
Здесь вы извлекли запись QuerySet только с одной моделью: границей Соединенных Штатов (именно то, что вы ожидали).
Аналогично, вы также можете использовать объект геометрии GEOS. Здесь можно комбинировать пространственный поиск intersects с методом get, чтобы получить только запись WorldBorder для Сан-Марино, а не набор результатов:
>>> from django.contrib.gis.geos import Point >>> pnt = Point(12.4604, 43.9420) >>> sm = WorldBorder.objects.get(mpoly__intersects=pnt) >>> sm <WorldBorder: San Marino>
Поиски contains и intersects — это лишь подмножество доступных запросов. Дополнительная информация содержится в документации API базы данных GeoDjango GeoDjango Database API.
Автоматические пространственные преобразования
При выполнении пространственных запросов GeoDjango автоматически преобразует геометрии, если они находятся в другой системе координат. В следующем примере координаты будут выражены в SRID 32140 EPSG, системе координат, специфичной только для южной Техаса, и в единицах метров, а не градусов:
>>> from django.contrib.gis.geos import Point, GEOSGeometry >>> pnt = Point(954158.1, 4215137.1, srid=32140)
Обратите внимание, что pnt также может быть создан с EWKT, «расширенной» формой WKT, которая включает SRID:
>>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)')
ORM GeoDjango автоматически обернёт значения геометрии в SQL преобразования, позволяя разработчику работать на более высоком уровне абстракции:
>>> qs = WorldBorder.objects.filter(mpoly__intersects=pnt)
>>> print(qs.query) # Generating the SQL
SELECT "world_worldborder"."id", "world_worldborder"."name", "world_worldborder"."area",
"world_worldborder"."pop2005", "world_worldborder"."fips", "world_worldborder"."iso2",
"world_worldborder"."iso3", "world_worldborder"."un", "world_worldborder"."region",
"world_worldborder"."subregion", "world_worldborder"."lon", "world_worldborder"."lat",
"world_worldborder"."mpoly" FROM "world_worldborder"
WHERE ST_Intersects("world_worldborder"."mpoly", ST_Transform(%s, 4326))
>>> qs # printing evaluates the queryset
[<WorldBorder: United States>]
Прямые запросы
При использовании прямых запросов, вы обычно должны обернуть поля геометрии с помощью функции SQL asText() (или ST_AsText для PostGIS), чтобы значение поля распознавалось GEOS:
City.objects.raw('SELECT id, name, asText(point) from myapp_city')
Это не является абсолютно необходимым для PostGIS, но, как правило, используйте прямые запросы только тогда, когда вы точно знаете, что делаете.
Ленивые геометрии
GeoDjango загружает геометрии в стандартном текстовом представлении. При первом доступе к полю геометрии GeoDjango создаёт GEOS geometry object <ref-geos>, предоставляя мощные возможности, такие как свойства сериализации для популярных геопространственных форматов:
>>> sm = WorldBorder.objects.get(name='San Marino')
>>> sm.mpoly
<MultiPolygon object at 0x24c6798>
>>> sm.mpoly.wkt # WKT
MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ...
>>> sm.mpoly.wkb # WKB (as Python binary buffer)
<read-only buffer for 0x1fe2c70, size -1, offset 0 at 0x2564c40>
>>> sm.mpoly.geojson # GeoJSON (requires GDAL)
'{ "type": "MultiPolygon", "coordinates": [ [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ...
Это включает доступ ко всем расширенным геометрическим операциям, предоставляемым библиотекой GEOS:
>>> pnt = Point(12.4604, 43.9420) >>> sm.mpoly.contains(pnt) True >>> pnt.contains(sm.mpoly) False
Географические аннотации
GeoDjango также предоставляет набор географических аннотаций для вычисления расстояний и нескольких других операций (пересечение, разность и т. д.). См. документацию Географические функции базы данных.
Выведение ваших данных на карту
Географический администратор
GeoDjango расширяет приложение администратора Django с поддержкой редактирования полей геометрии.
Основы
GeoDjango также дополняет Django администратор, позволяя пользователям создавать и изменять геометрии на JavaScript карте (на базе OpenLayers).
Давайте сразу приступим. Создайте файл с именем admin.py в приложении world со следующим кодом:
from django.contrib.gis import admin from .models import WorldBorder admin.site.register(WorldBorder, admin.GeoModelAdmin)
Далее, отредактируйте свой urls.py в папке приложения geodjango следующим образом:
from django.conf.urls import url, include
from django.contrib.gis import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
Создайте пользователя администратора:
$ python manage.py createsuperuser
Далее, запустите сервер разработки Django:
$ python manage.py runserver
Наконец, перейдите по адресу http://localhost:8000/admin/ и войдите в систему с пользователем, которого вы только что создали. Перейдите к любой из записей WorldBorder — границы можно изменить, нажав на полигон и перетащив вершины в нужное положение.
OSMGeoAdmin
С OSMGeoAdmin, GeoDjango использует слой Open Street Map в администраторе. Это обеспечивает больший контекст (включая улицы и подробности о дорогах) по сравнению с GeoModelAdmin (который использует набор данных WMS уровня 0 векторной карты Vector Map Level 0 от OSGeo).
Сначала есть некоторые важные требования:
OSMGeoAdminтребует, чтобы GDAL был установлен.- Необходимо установить файлы сдвига данных PROJ.4 (подробности см. в инструкциях по установке PROJ.4).
Если вы удовлетворяете этому требованию, просто замените класс опции OSMGeoAdmin в файле admin.py:
admin.site.register(WorldBorder, admin.OSMGeoAdmin)
Примечания
| [1] | Особая благодарность Bjørn Sandvik из thematicmapping.org за предоставление и поддержку этого набора данных. |
| [2] | Основные приложения GeoDjango были написаны Dane Springmeyer, Josh Livni и Christopher Schmidt. |
| [3] | Эта точка — Юридический центр университета Хьюстона. |
| [4] | Open Geospatial Consortium, Inc., OpenGIS Simple Feature Specification For SQL. |
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/ref/contrib/gis/tutorial/