Учебник GeoDjango
Введение
GeoDjango — это модуль contrib для Django, который превращает его в полнофункциональный географический веб-фреймворк. GeoDjango стремится максимально упростить создание географических веб-приложений, таких как сервисы, основанные на местоположении. Его возможности включают:
- Поля модели Django для геометрий OGC и растровых данных.
- Расширения ORM Django для запросов и манипуляций пространственными данными.
- Слабосвязанные, высокоуровневые Python-интерфейсы для операций с геометрией и растровыми данными GIS, а также манипуляции данными в различных форматах.
- Редактирование полей геометрии в админке.
Этот учебник предполагает знакомство с 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 ../..
Архив ZIP с границами стран содержит набор файлов данных, объединённых под общим названием 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):
return self.name
Обратите внимание, что модуль models импортирован из django.contrib.gis.db.
По умолчанию пространственная система координат для полей геометрии — WGS84 (то есть SRID равен 4326) — другими словами, координаты поля — это пары долгота, широта в градусах. Чтобы использовать другую систему координат, установите SRID поля геометрии с аргументом srid. Используйте целое число, представляющее код EPSG системы координат.
Выполнить migrate
После определения модели её необходимо синхронизировать с базой данных. Сначала создайте миграцию базы данных:
$ python manage.py makemigrations
Migrations for 'world':
world/migrations/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, auth, contenttypes, sessions, world 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'))
Теперь откройте шейп-файл границ стран, используя интерфейс DataSource GeoDjango:
>>> 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 из каталога проекта %%%CODE_BLOCK_85%%:
$ 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 >>> WorldBorder.objects.filter(mpoly__contains=pnt_wkt) <QuerySet [<WorldBorder: United States>]>
Здесь вы получили QuerySet с одной моделью: граница Соединенных Штатов (точно то, что вы ожидали).
Аналогично, вы также можете использовать объект геометрии GEOS. Здесь вы можете объединить пространственный поиск intersects с методом get для извлечения только экземпляра WorldBorder для Сан-Марино вместо набора результатов:
>>> from django.contrib.gis.geos import Point >>> pnt = Point(12.4604, 43.9420) >>> WorldBorder.objects.get(mpoly__intersects=pnt) <WorldBorder: San Marino>
Поиски contains и intersects — лишь часть доступных запросов — в документации API базы данных GeoDjango содержится больше информации.
Автоматические пространственные преобразования
При выполнении пространственных запросов GeoDjango автоматически преобразует геометрии, если они находятся в другой системе координат. В следующем примере координаты будут выражены в системе координат EPSG SRID 32140 только для южной части Техаса и в единицах метров, а не градусов:
>>> from django.contrib.gis.geos import GEOSGeometry, Point >>> 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
<QuerySet [<WorldBorder: United States>]>
Прямые запросы
При использовании прямых запросов необходимо обернуть ваши поля геометрии, чтобы значение поля можно было распознать GEOS:
from django.db import connection
# or if you're querying a non-default database:
from django.db import connections
connection = connections['your_gis_db_alias']
City.objects.raw('SELECT id, name, %s as point from myapp_city' % (connection.ops.select % 'point'))
Прямые запросы следует использовать только в том случае, если вы точно знаете, что делаете.
Ленивые геометрии
GeoDjango загружает геометрии в стандартном текстовом представлении. Когда к полю геометрии впервые обращаются, GeoDjango создаёт объект GEOSGeometry, предоставляя мощную функциональность, такую как свойства сериализации для популярных форматов геопространственных данных:
>>> 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
'{ "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.contrib.gis import admin
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
]
Создайте пользователя-администратора:
$ python manage.py createsuperuser
Далее, запустите сервер разработки Django:
$ python manage.py runserver
Наконец, перейдите к http://localhost:8000/admin/, и войдите в систему с только что созданным пользователем. Перейдите к любой из записей WorldBorder — границы можно изменять, щелкнув по многоугольнику и перетащив вершины в нужное положение.
OSMGeoAdmin
С помощью OSMGeoAdmin, GeoDjango использует слой OpenStreetMap в админке. Это предоставляет больше контекста (включая детали улиц и проездов), чем доступно с помощью GeoModelAdmin (который использует набор данных WMS Vector Map Level 0, размещённый в OSGeo).
Файлы сдвига датумов 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 для SQL. |
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/2.0/ref/contrib/gis/tutorial/