GeoDjango是Django的一个包含的contrib模块,它将其变成一个世界级的地理Web框架。 GeoDjango努力使它尽可能简单地创建地理Web应用程序,如基于位置的服务。 其特点包括:
本教程假设您熟悉Django;因此,如果您是Django的全新用户,请先阅读regular tutorial,以便首先熟悉Django。
注
GeoDjango除了Django需要的额外要求 - 有关详细信息,请参阅installation documentation。
本教程将指导您创建地理网络应用程序,以查看世界边框。 [1]本教程中使用的一些代码取自和/或受到GeoDjango基本应用程序项目的启发。 [2]
注
按顺序继续阅读教程部分,以获取分步说明。
使用标准django-admin
脚本创建名为geodjango
的项目:
$ django-admin startproject geodjango
这将初始化一个新项目。 现在,在world
项目中创建geodjango
Django应用程序:
$ 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
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中的工具,您还可以使用以下:
之前,您使用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字段的名称。mpoly
为MULTIPOLYGON
,几何类型GeoDjango将导入字段。 即使在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>
intersects
和contains
查找只是可用查询的一个子集 - 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的管理应用程序,支持编辑几何字段。
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简单功能规范。 |
2017年9月6日