Spec-Zone.ru › Django 1.11

Учебник по GeoDjango

Введение

GeoDjango — это включенный модуль contrib для Django, который превращает его в полнофункциональный географический веб-фреймворк. GeoDjango стремится максимально упростить создание географических веб-приложений, таких как сервисы на основе местоположения. Его возможности включают:

  • Поля модели Django для геометрий OGC и растровых данных.
  • Расширения ORM Django для запросов и манипулирования пространственными данными.
  • Слабосвязанные, высокоуровневые Python-интерфейсы для операций с геометрией и растровыми данными GIS, а также для работы с данными в различных форматах.
  • Редактирование полей геометрии в админ-панели.

В этом учебнике предполагается знакомство с Django; поэтому, если вы новичок в Django, пожалуйста, сначала ознакомьтесь с основным учебником.

Примечание

GeoDjango имеет дополнительные требования, помимо тех, которые требуются Django. Подробности см. в документации по установке.

В этом учебнике вы узнаете, как создать географическое веб-приложение для просмотра границ стран мира. [1] Некоторые части кода, используемые в этом учебнике, взяты из проекта GeoDjango basic apps или вдохновлены им. [2]

Примечание

Для пошаговых инструкций следуйте разделам учебника последовательно.

Настройка

Создание пространственной базы данных

Обычно специальная настройка не требуется, поэтому вы можете создать базу данных, как и для любого другого проекта. Мы предоставляем некоторые рекомендации для выбранных баз данных:

  • Установка PostGIS
  • Установка SpatiaLite

Создание нового проекта

Используйте стандартную django-admin команду для создания проекта с именем geodjango.

$ django-admin startproject geodjango

Это инициализирует новый проект. Теперь создайте world приложение Django в рамках проекта 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: Содержит информацию о пространственной системе координат для географических данных, хранящихся в shapefile.

Использование ogrinfo для проверки пространственных данных

Утилита GDAL ogrinfo позволяет просмотреть метаданные shapefiles или других источников векторных данных:

$ 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 сообщает нам, что shapefile имеет один слой, и этот слой содержит полигональные данные. Для получения дополнительной информации укажите имя слоя и используйте параметр -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':
  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

Импорт пространственных данных

В этом разделе вы узнаете, как импортировать shapefile границ стран мира в базу данных через модели GeoDjango, используя утилиту импорта данных LayerMapping.

Существует множество способов импорта данных в пространственную базу данных. Помимо инструментов, включенных в GeoDjango, вы также можете использовать следующие:

  • ogr2ogr: Утилита командной строки, включённая в GDAL, которая может импортировать множество форматов векторных данных в PostGIS, MySQL и Oracle базы данных.
  • shp2pgsql: Эта утилита, включенная в PostGIS, импортирует ESRI shapefiles в PostGIS.

Интерфейс GDAL

Ранее вы использовали ogrinfo для просмотра содержимого shapefile границ стран мира. GeoDjango также включает Python-интерфейс к мощной библиотеке 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'))

Теперь откройте shapefile границ стран мира, используя интерфейс 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)

Объекты источника данных могут иметь разные слои географических объектов; однако shapefiles могут иметь только один слой:

>>> print(len(ds))
1
>>> lyr = ds[0]
>>> print(lyr)
TM_WORLD_BORDERS-0.3

Вы можете увидеть тип геометрии слоя и количество объектов в нём:

>>> print(lyr.geom_type)
Polygon
>>> print(len(lyr))
246

Примечание

К сожалению, формат данных shapefile не позволяет более точно определить типы геометрии. Этот shapefile, как и многие другие, фактически включает 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 '

Этот shapefile использует популярную пространственную систему координат WGS84 — другими словами, данные используют пары долгота, широта в градусах.

Кроме того, shapefiles также поддерживают атрибутивные поля, которые могут содержать дополнительные данные. Вот поля в слое «Границы стран мира»:

>>> 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
>>> 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 — лишь подмножество доступных запросов. Дополнительные сведения содержатся в документации GeoDjango Database API.

Автоматические пространственные преобразования

При выполнении пространственных запросов GeoDjango автоматически преобразует геометрии, если они находятся в другой системе координат. В следующем примере координаты будут выражены в EPSG SRID 32140, системе координат, специфичной только для Южного Техаса, и в единицах измерения метров, а не градусов:

>>> 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
<QuerySet [<WorldBorder: United States>]>

Прямые запросы

При использовании прямых запросов следует обычно обернуть поля геометрии функцией SQL asText() (или ST_AsText для PostGIS), чтобы значение поля распознавалось GEOS:

City.objects.raw('SELECT id, name, asText(point) from myapp_city')

Это не является абсолютно необходимым для PostGIS, но, как правило, следует использовать прямые запросы только в том случае, если вы точно знаете, что делаете.

Ленивые геометрии

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.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 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] Консорциум открытых геопространственных данных, Inc., Спецификация OpenGIS простых функций для SQL.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.11/ref/contrib/gis/tutorial/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API