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

通常不需要特殊设置,因此您可以像任何其他项目一样创建一个数据库。 我们为选定的数据库提供一些提示:

Create a New Project

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

$ django-admin startproject geodjango

这将初始化一个新项目。 现在,在world项目中创建geodjango 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文件中。 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中的地理数据的空间参考信息。

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: (2.0) 表示FIPS字符字段的最大长度为2。 同样的, LON: 真实 (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。

运行migrate

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

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

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

$ 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

导入空间数据

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

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

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

GDAL接口

之前,您使用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']

您可以迭代图层中的每个要素,并从要素几何(通过get()属性访问)以及要素的属性字段(访问其)中提取信息通过geom方法):

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

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

$ python manage.py shell

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

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

试试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。
  • ogrinspect选项指示--mapping也生成用于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)

# 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表中找到国家/地区。 首先,启动管理shell:

$ python manage.py shell

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

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

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

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

在这里,您只检索了一个QuerySet,只有一个模型:美国的边界(正是你所期望的)。

同样,您也可以使用GEOS geometry object 在这里,您可以将WorldBorder空间查找与get方法组合,仅检索San Marino的intersects实例,而不是查询集:

>>> from django.contrib.gis.geos import Point
>>> pnt = Point(12.4604, 43.9420)
>>> WorldBorder.objects.get(mpoly__intersects=pnt)
<WorldBorder: San Marino>

intersectscontains查找只是可用查询的一个子集 - 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构造,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
<QuerySet [<WorldBorder: United States>]>

原始查询

当使用raw queries时,通常应使用ST_AsText SQL函数(或对于PostGIS为asText())包装几何字段,价值将被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还提供一组地理注解来计算距离和其他几个操作(交点,差异等)。 请参阅Geographic Database Functions文档。

把你的数据放在地图上

地理管理

GeoDjango扩展了Django的管理应用程序,支持编辑几何字段。

Basics

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

让我们直接进来。 使用以下代码在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在管理中使用开放街道地图图层。 与使用OSGeo托管的GeoModelAdmin(使用矢量图0级 WMS数据集)相比,这提供了更多的上下文(包括街道和街道详细信息) )。

必须安装PROJ.4原点移动文件(有关详细信息,请参阅PROJ.4 installation instructions)。

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

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简单功能规范