GeoDjango Tutorial

Introduction

GeoDjango是Django的一个包含的contrib模块,它将其变成一个世界级的地理Web框架。GeoDjango努力使它尽可能简单地创建地理Web应用程序,如基于位置的服务。其特点包括:

  • OGC几何的Django模型字段。
  • 扩展到Django的ORM,用于查询和处理空间数据。
  • 松散耦合的高级Python接口,用于GIS几何操作和数据格式。
  • 从管理员编辑几何字段。

本教程假设您熟悉Django;因此,如果您是Django的全新用户,请先阅读regular tutorial,以便首先熟悉Django。

注意

GeoDjango除了Django需要的额外要求 - 有关详细信息,请参阅installation documentation

本教程将指导您创建地理网络应用程序,以查看世界边框[1]本教程中使用的一些代码取自和/或受到GeoDjango基本应用程序项目的启发。[2]

注意

按顺序继续阅读教程部分,以获取分步说明。

Setting Up

Create a Spatial Database

注意

MySQL和Oracle用户可以跳过此部分,因为空间类型已内置到数据库中。

首先,为您的项目创建一个空间数据库。

如果使用PostGIS,请从spatial database template创建数据库:

$ createdb -T template_postgis geodjango

注意

此命令必须由具有创建数据库的足够特权的数据库用户颁发。要在PostgreSQL中使用CREATE DATABASE权限创建用户,请使用以下命令:

$ sudo su - postgres
$ createuser --createdb geo
$ exit

geo替换为Postgres数据库用户的用户名。(在PostgreSQL中,此用户也将是操作系统级别的用户。)

如果您使用的是SQLite和SpatiaLite,请参考如何创建SpatiaLite database的说明。

Create a New Project

使用标准django-admin脚本创建名为geodjango的项目:

$ django-admin startproject geodjango

这将初始化一个新项目。现在,在geodjango项目中创建world Django应用程序:

$ cd geodjango
$ python manage.py startapp world

Configure settings.py

geodjango项目设置存储在geodjango/settings.py文件中。编辑数据库连接设置以匹配您的设置:

DATABASES = {
    'default': {
         'ENGINE': 'django.contrib.gis.db.backends.postgis',
         'NAME': 'geodjango',
         'USER': 'geo',
     }
}

此外,修改INSTALLED_APPS设置以包括django.contrib.admindjango.contrib.gisworld

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'world'
)

Geographic Data

World Borders

世界边界数据位于此zip文件中。world应用程序中创建data目录,下载世界边界数据,然后解压缩。在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中的地理数据的空间参考信息。

Use ogrinfo to examine spatial data

GDAL ogrinfo实用程序允许检查shapefile或其他矢量数据源的元数据:

$ 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)是一个浮点字段,到三个小数位。

Geographic Models

Defining a Geographic Model

现在,您已使用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), and
    # overriding the default manager with a GeoManager instance.
    mpoly = models.MultiPolygonField()
    objects = models.GeoManager()

    # Returns the string representation of the model.
    def __str__(self):              # __unicode__ on Python 2
        return self.name

请注意两个重要的事情:

  1. models模块从django.contrib.gis.db导入。
  2. 您必须使用GeoManager覆盖模型的默认管理器才能执行空间查询。

几何字段的默认空间参考系统是WGS84(意味着SRID是4326) - 换句话说,字段坐标是经度,纬度对,以度为单位。要使用不同的坐标系,请使用srid参数设置几何字段的SRID。

Run migrate

定义模型后,需要将其与数据库同步。首先,创建数据库迁移:

$ python manage.py makemigrations
Migrations for 'world':
  0001_initial.py:
    - Create model WorldBorder

让我们来看看将为WorldBorder模型生成表的SQL:

$ python manage.py sqlmigrate world 0001

此命令应产生以下输出:

BEGIN;
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;

注意

用PostGIS通过单独的SELECT AddGeometryColumn(...)语句添加mpoly几何列。

如果这看起来正确,请运行migrate在数据库中创建此表:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, world, contenttypes, auth, sessions
Running migrations:
  ...
  Applying world.0001_initial... OK

Importing Spatial Data

本节将介绍如何使用LayerMapping data import utility通过GeoDjango模型将世界边界shapefile导入数据库。

有很多不同的方法将数据导入空间数据库 - 除了包含在GeoDjango中的工具,您还可以使用以下:

  • ogr2ogr:GDAL包含的命令行实用程序,可以将许多矢量数据格式导入PostGIS,MySQL和Oracle数据库。
  • shp2pgsql:PostGIS附带的此实用程序将ESRI shapefile导入PostGIS。

GDAL Interface

之前,您使用ogrinfo检查世界边界shapefile的内容。GeoDjango还包括一个Pythonic接口到GDAL的强大的OGR库,可以与OGR支持的所有矢量数据源一起使用。

首先,调用Django shell:

$ python manage.py shell

如果您在教程中下载了World Borders数据,那么您可以使用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界面打开世界边界shapefile:

>>> from django.contrib.gis.gdal import DataSource
>>> ds = DataSource(world_shp)
>>> print(ds)
/ ... /geodjango/world/data/TM_WORLD_BORDERS-0.3.shp (ESRI Shapefile)

数据源对象可以具有不同的地理空间特征层;然而,shapefile只允许有一层:

>>> 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几何体,而不是多边形。It’s important to use a more general field type in models: a GeoDjango MultiPolygonField will accept a Polygon geometry, but a PolygonField will not accept a MultiPolygon type geometry. 这就是为什么上面定义的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空间参考系统 - 换句话说,数据使用经度,纬度对以度为单位。

此外,shapefile还支持可以包含附加数据的属性字段。这里是世界边界层上的字段:

>>> 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>]

并且可以通过其特征ID检索单个特征:

>>> 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

要导入数据,请在Python脚本中使用LayerMapping。使用以下代码在world应用程序中创建一个名为load.py的文件:

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字段的名称。
  • 几何字段的键mpolyMULTIPOLYGON,几何类型GeoDjango将导入字段。即使在shapefile中的简单多边形也会在插入数据库之前自动转换为集合。
  • shapefile的路径不是绝对路径 - 换句话说,如果将world应用程序(使用data子目录)移动到其他位置,脚本仍然可以工作。
  • transform关键字设置为False,因为shapefile中的数据不需要转换 - 它已经在WGS84(SRID = 4326)中。
  • encoding关键字设置为shapefile中字符串值的字符编码。这确保字符串值从其原始编码系统正确读取和保存。

然后,从geodjango项目目录调用Django shell:

$ python manage.py shell

接下来,导入load模块,调用run例程,并观察LayerMapping

>>> from world import load
>>> load.run()

Try ogrinspect

现在您已经了解了如何使用LayerMapping data import utility定义地理模型和导入数据,可以使用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

该命令产生以下输出,可以直接复制到GeoDjango应用程序的models.py中:

# 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)
    objects = models.GeoManager()

# 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',
}

Spatial Queries

Spatial Lookups

GeoDjango向Django ORM添加空间查找。例如,您可以在包含特定点的WorldBorder表中找到国家/地区。首先,启动管理shell:

$ python manage.py shell

现在,定义一个兴趣点[3]

>>> pnt_wkt = 'POINT(-95.3385 29.7245)'

pnt_wkt字符串表示在-95.3385度经度,29.7245度纬度处的点。几何形状是称为井知文本(WKT)的格式,由开放地理空间联盟(OGC)发布的标准。[4]导入WorldBorder模型,并使用pnt_wkt作为参数执行contains

>>> from world.models import WorldBorder
>>> qs = WorldBorder.objects.filter(mpoly__contains=pnt_wkt)
>>> qs
[<WorldBorder: United States>]

在这里,您只使用一个模型检索了GeoQuerySet:美国的边框(正是您期望的)。

同样,您也可以使用GEOS geometry object在这里,您可以将intersects空间查找与get方法组合,仅检索San Marino的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>

containsintersects查找只是可用查询的一个子集 - GeoDjango Database API文档有更多。

Automatic Spatial Transformations

当进行空间查询时,GeoDjango会自动变换几何,如果它们在不同的坐标系中。在以下示例中,坐标将以EPSG SRID 32140表示,这是仅针对南德克萨斯度:

>>> from django.contrib.gis.geos import Point, GEOSGeometry
>>> pnt = Point(954158.1, 4215137.1, srid=32140)

注意,pnt也可以用EWKT构造,EWKT是包括SRID的WKT的“扩展”形式:

>>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)')

GeoDjango的ORM将在转换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>]

原始查询

当使用raw queries时,通常应使用asText() SQL函数(或对于PostGIS为ST_AsText)包装几何字段,价值将被GEOS认可:

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

这不是绝对需要PostGIS,但通常你应该只使用原始查询,当你知道你在做什么。

Lazy Geometries

GeoDjango以标准化文本表示方式加载几何。首次访问geometry字段时,GeoDjango会创建一个GEOS geometry对象&lt; ref-geos&gt;,显示强大的功能,例如流行的地理空间格式的序列化属性:

>>> 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

GeoQuerySet Methods

Putting your data on the map

Geographic Admin

GeoDjango扩展了Django’s admin application,支持编辑几何字段。

Basics

GeoDjango还通过允许用户在JavaScript滑动地图(由OpenLayers提供支持)上创建和修改几何图形来补充Django管理员。

让我们直接进来。使用以下代码在world应用程序中创建名为admin.py的文件:

from django.contrib.gis import admin
from models import WorldBorder

admin.site.register(WorldBorder, admin.GeoModelAdmin)

接下来,在geodjango应用程序文件夹中修改urls.py,如下所示:

from django.conf.urls import url, include
from django.contrib.gis import admin

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
]

创建管理员用户:

$ python manage.py createsuperuser

接下来,启动Django开发服务器:

$ python manage.py runserver

最后,浏览至http://localhost:8000/admin/,并使用您刚刚创建的用户登录。浏览到任何WorldBorder条目 - 可以通过单击多边形并将顶点拖动到所需位置来编辑边框。

OSMGeoAdmin

使用OSMGeoAdmin,GeoDjango在管理中使用开放街道地图图层。与使用OSGeo托管的GeoModelAdmin(使用矢量图0级 WMS数据集)相比,这提供了更多的上下文(包括街道和街道详细信息) )。

首先,有一些重要的要求:

如果您满足此要求,则只需替换admin.py文件中的OSMGeoAdmin选项类:

admin.site.register(WorldBorder, admin.OSMGeoAdmin)

脚注

[1]特别感谢BjørnSandvik的thematicmapping.org提供和维护此数据集。
[2]GeoDjango基本应用程序由Dane Springmeyer,Josh Livni和Christopher Schmidt编写。
[3]这一点是休斯顿大学法律中心
[4]Open Geospatial Consortium,Inc.,OpenGIS SQL简单功能规范