QGIS是一套开源的跨平台地理信息系统,支持的操作系统包括Windows、Mac、Linux和BSD,也即将支持Android。当前软件的最新版本为2.18,文档版本为2.14(2.16版正在更新,2.18遥遥无期)。
QGIS支持栅格(raster)和矢量(vector)两种图像,前者主要包括GeoTIFF、JPEG、PNG等文件格式,后者以点、折线和多边形三种要素(feature)的形式进行存储,既可以是文件,也可以是数据库。
栅格图即是点阵图,用于展示卫星遥感、土地利用、温度分布等直观影像。矢量图通常用于展示人为划分的功能性逻辑区域,比如用点表示城市、机场、加油站等不同级别的地点信息,用折线表示道路、河流等线型信息,用多边形表示湖泊、各类用地等区域信息。
在QGIS中导入矢量图后,可以通过鼠标操作对其直接进行修改(比如增加点或线段、拖动多边形顶点等),也可以用相同的方式根据项目需要创建自己的矢量图。
矢量图的展示效果有很大的调整空间,可以设置颜色、透明度、纹理、宽度、大小等属性,可以显示说明性文字或标签(如路名),更强大的是,矢量图中的每一个要素除了坐标信息以外,还可以具有任意多的属性值,用于记录如道路类别、建筑用途、区域面积等诸多额外信息,通过编写条件脚本,可以利用这些属性值实现如不同缩放程度下过滤显示不同级别的要素、以不同的颜色显示不同功能的区域等灵活的展示形式。
QGIS支持将编辑好的地图输出为各种格式的图片、PDF或SVG文件,可以添加网格、指北针、图例、比例尺等多种装饰信息,以制作有模有样的实用地图。
QGIS提供了强大的空间分析能力,除了自带的工具以外,还可以通过插件的形式进行扩展,同时集成了GRASS(Geographic Resources Analysis Support System)的400多项功能模块。
空间分析旨在挖掘地图中的有用信息,以得到特定问题的求解。比如针对房地产开发,需要计算及分析区域面积、与各大交通要道的距离、地势、居民密度等诸多影响因素,借助现成功能或自己编写脚本可以筛选出所需的最佳区域。
一个比较直观的例子是最短路径的计算,利用QGIS自带的插件「Road graph plugin」可以实现该功能。将代表道路的图层设置为计算依据,在地图上选择任意两点作为起点和终点,所得的最短路径将以红线标出,同时显示公里数和耗时。
通过条件设置,还可以根据道路类别(高速、小路等)、限速等信息进行不同使用场景下的道路规划——这让我想起了百度地图导航时「用时较少」、「距离较短」、「高速优先」之类的选项。
栅格图一个重要的作用是表示包含高程信息的地形图——数字高程模型DEM(Digital Elevation Model)。
QGIS有多种途径可以优化DEM的显示效果。
使用QGIS自带的插件「Raster Terrain Analysis plugin」可以更便捷地实现DEM的三维渲染。
GRASS作为一套始于1984年的GIS,是妥妥的前辈,为QGIS所集成,其诸多工具中也有一件可以用于DEM渲染。
当然DEM除了提供直观的三维展示,同样可以用于空间分析,比如通过条件脚本突出显示一定坡度范围的区域,为房地产开发(要求地势平坦)提供参考。
与栅格图相比,矢量图在诸多方面都比较灵活,在逻辑上可以将其视作包含坐标信息和其他信息的表格。因此矢量图除了可以以文件形式存储,还可以存储在数据库中。存储地理空间数据的数据库,称为空间数据库(spatial database)。QGIS支持与Oracle Spatial、SQL Server、MySQL、PostgreSQL等多种主流数据库连接,其中PostgreSQL为QGIS的主打数据库。
PostgreSQL是一种对象-关系数据库,在传统的关系数据库中引入了面向对象技术,以存储非结构化的空间数据——引入一种叫geometry的键类型。空间数据的操作与普通数据有所不同,于是出现了PostGIS,作为PostgreSQL的一个扩展,它提供了许多便捷的空间数据处理函数。
开放地理空间信息联盟OGC(Open Geospatial Consortium)制定了空间信息在数据库中的存储规格——SFS模型(Simple Feature for SQL Model),该模型中,三种空间要素的坐标信息有WKT(well-known text)和WKB(well-known binary)两种表示格式。其中WKT易于人类阅读,WKB是用于存储和传输的二进制。
比如在PostGIS环境下定义一个坐标为(1,1)的点:select st_pointfromtext(’POINT(1 1)’);
这里st_pointfromtext
就是PostGIS提供的函数,POINT(1 1)
就是点的WKT表示。
所得结果(一长串十六进制码就是点的WKB表示):
st_pointfromtext
--------------------------------------------
0101000000000000000000F03F000000000000F03F
(1 row)
如果希望结果显示为WKT形式,则需要用到转换函数st_astext
:select st_astext(st_pointfromtext(’POINT(1 1)’));
结果:
st_astext
------------
POINT(1 1)
(1 row)
在实际使用中,通常一个图层在数据库中用一张表表示,其中一个键设为geometry类型,且限定其只能赋值为「ST_Point」、「ST_LineString」或「ST_Polygon」,即一个图层中的要素要么都是点,要么都是折线,要么都是多边形。
一个插入点的示例(其中「4326」为坐标参考系的代号):
insert into people (name,house_no, street_id, phone_no, the_geom)
values (’Fault Towers’,34,3,’072 812 31 28’,’SRID=4326;POINT(33 -33)’);
一个更新折线的示例:
update streets set the_geom = ’SRID=4326;LINESTRING(20 -33, 21 -34, 24 -33)’
where streets.id=2;
一个插入多边形的示例:
insert into cities (name, the_geom)
values (’Tokyo Outer Wards’, ’SRID=4326;POLYGON((20 10, 20 20, 35 20, 20 10),(-10 -30, -5 0, -15 -15, -10 -30))’);
可见,无论何种类型的要素,都是以点为基本单位构建的,多边形要求首末两点重合。
PostGIS和QGIS提供了多种工具,以实现矢量图在文件与数据库之间的相互转换,使得处理GIS数据时,可以兼得QGIS图形界面之便捷与数据库ODBC之灵活。
除了传统数据库的C/S模式,还有一种将整套数据库系统存储在一个独立文件中的做法——SQLite,号称世界上最小的数据库。对SQLite进行空间数据处理能力的扩展,得到SpatiaLite。QGIS支持SpatiaLite,兼得文件之便携与数据库之灵活。
GPS数据分为路点(waypoint)、路线(route)、轨迹(track)三种,路点表示离散的位置,路线表示计划要走的一系列位置,轨迹表示已走过的一系列位置。在GIS中,路点由点表示,路线和轨迹由折线表示。
使用插件「GPS Tools」可实现与GPS设备的数据交互,也可以从GPX(GPS Exchange Format)文件中导入GPS数据(GPX是一种用于记录GPS数据的轻量级XML格式)。QGIS本身也支持将点和折线的图层保存为GPX文件。
但上述操作的数据都是静态的,QGIS还提供了实时显示GPS信号的功能,在「GPS Information Panel」操作面板中,还可以看到经纬度值、GPS信号强度、附近卫星分布情况等诸多信息。
QGIS除了可以操作本地数据,还提供了C/S模式。QGIS客户端向公共的或自己搭建的服务器请求地图数据,QGIS服务器则将地图项目发布出去。
常见的地图数据服务有:
WFS-T(Web Feature Service - Transactional):WFS支持从服务器上获取矢量要素,而WFS-T,即带事务的WFS还支持客户端对服务器上的要素进行增删改。
WCS(Web Coverage Service):WCS提供涉及时空信息的多维数据,如卫星影像、土地覆盖数据、DEM等。
QGIS服务器是需要与web服务器配合使用的FastCGI/CGI程序,上述这些服务本质上响应的都是HTTP请求。对于WMS,将请求的URL放到web浏览器里同样可以刷出地图。
服务器通过接收HTTP请求参数的形式,支持GetCapabilities、GetMap(针对WMS)、GetFeature(针对WFS)、GetCoverage(针对WCS)等多种请求指令,还通过其他附带参数,实现灵活的请求操作,例如用参数「MAP」指定服务器上QGIS项目文件的路径,用参数「DPI」指定所得图像的分辨率:
http://server/path/qgis_mapserv.fcgi?REQUEST=GetMap&MAP=/home/qgis/mymap.qgs&DPI=300&...
基于SIP和PyQT4,QGIS支持多种形式的Python二次开发——PyQGIS,对于QGIS桌面软件或QGIS客户端:
QGIS启动时自动运行的脚本:编辑一个指定的.py文件,用于在QGIS启动时执行如清理变量「sys.path」等准备工作。
在QGIS内嵌的控制台中执行的语句:QGIS已经为用户做好了导入PyQGIS模块等初始化工作,我们可以在控制台里直接操作画布、菜单、工具栏等。
插件开发
应用程序开发:通过导入PyQGIS的模块,如「qgis.core」和「qgis.gui」,就可以在我们自己的Python程序中进行GIS数据处理。我根据《PyQGIS Developer Cookbook》中的例子拼了一个简单的小程序——在窗口中显示栅格图,支持拖拽缩放和矩形涂鸦。
from qgis.gui import *
from PyQt4.QtGui import QAction, QMainWindow
from PyQt4.QtCore import SIGNAL, Qt, QFileInfo
# 自定义矩形绘制工具
class RectangleMapTool(QgsMapToolEmitPoint):
def __init__(self, canvas):
self.canvas = canvas
QgsMapToolEmitPoint.__init__(self, self.canvas)
self.rubberBand = QgsRubberBand(self.canvas, QGis.Polygon)
self.rubberBand.setColor(Qt.red)
self.rubberBand.setWidth(1)
self.reset()
def reset(self):
self.startPoint = self.endPoint = None
self.isEmittingPoint = False
self.rubberBand.reset(QGis.Polygon)
def canvasPressEvent(self, e):
self.startPoint = self.toMapCoordinates(e.pos())
self.endPoint = self.startPoint
self.isEmittingPoint = True
self.showRect(self.startPoint, self.endPoint)
def canvasReleaseEvent(self, e):
self.isEmittingPoint = False
r = self.rectangle()
if r is not None:
print "Rectangle:", r.xMinimum(), r.yMinimum(), r.xMaximum(), r.yMaximum()
def canvasMoveEvent(self, e):
if not self.isEmittingPoint:
return
self.endPoint = self.toMapCoordinates(e.pos())
self.showRect(self.startPoint, self.endPoint)
def showRect(self, startPoint, endPoint):
self.rubberBand.reset(QGis.Polygon)
if startPoint.x() == endPoint.x() or startPoint.y() == endPoint.y():
return
point1 = QgsPoint(startPoint.x(), startPoint.y())
point2 = QgsPoint(startPoint.x(), endPoint.y())
point3 = QgsPoint(endPoint.x(), endPoint.y())
point4 = QgsPoint(endPoint.x(), startPoint.y())
self.rubberBand.addPoint(point1, False)
self.rubberBand.addPoint(point2, False)
self.rubberBand.addPoint(point3, False)
self.rubberBand.addPoint(point4, True) # true to update canvas
self.rubberBand.show()
def rectangle(self):
if self.startPoint is None or self.endPoint is None:
return None
elif self.startPoint.x() == self.endPoint.x() or self.startPoint.y() == self.endPoint.y():
return None
return QgsRectangle(self.startPoint, self.endPoint)
def deactivate(self):
super(RectangleMapTool, self).deactivate()
self.emit(SIGNAL("deactivated()"))
# 定义主窗口
class MyWnd(QMainWindow):
def __init__(self, layer):
QMainWindow.__init__(self)
self.canvas = QgsMapCanvas()
self.canvas.setCanvasColor(Qt.white)
self.canvas.setExtent(layer.extent())
self.canvas.setLayerSet([QgsMapCanvasLayer(layer)])
self.setCentralWidget(self.canvas)
actionZoomIn = QAction("Zoom in", self)
actionZoomOut = QAction("Zoom out", self)
actionPan = QAction("Pan", self)
actionRectangle = QAction("Rectangle", self)
actionZoomIn.setCheckable(True)
actionZoomOut.setCheckable(True)
actionPan.setCheckable(True)
actionRectangle.setCheckable(True)
self.connect(actionZoomIn, SIGNAL("triggered()"), self.zoomIn)
self.connect(actionZoomOut, SIGNAL("triggered()"), self.zoomOut)
self.connect(actionPan, SIGNAL("triggered()"), self.pan)
self.connect(actionRectangle, SIGNAL("triggered()"), self.rectangle)
self.toolbar = self.addToolBar("Canvas actions")
self.toolbar.addAction(actionZoomIn)
self.toolbar.addAction(actionZoomOut)
self.toolbar.addAction(actionPan)
self.toolbar.addAction(actionRectangle)
# create the map tools
self.toolPan = QgsMapToolPan(self.canvas)
self.toolPan.setAction(actionPan)
self.toolZoomIn = QgsMapToolZoom(self.canvas, False) # false = in
self.toolZoomIn.setAction(actionZoomIn)
self.toolZoomOut = QgsMapToolZoom(self.canvas, True) # true = out
self.toolZoomOut.setAction(actionZoomOut)
self.toolRectangle = RectangleMapTool(self.canvas)
self.toolRectangle.setAction(actionRectangle)
self.pan()
def zoomIn(self):
self.canvas.setMapTool(self.toolZoomIn)
def zoomOut(self):
self.canvas.setMapTool(self.toolZoomOut)
def pan(self):
self.canvas.setMapTool(self.toolPan)
def rectangle(self):
self.canvas.setMapTool(self.toolRectangle)
# 将美国阿拉斯加州的地图加载为图层,并显示到窗口中
fileName = "D:/qgis_sample_data/raster/landcover.img"
fileInfo = QFileInfo(fileName)
baseName = fileInfo.baseName()
rlayer = QgsRasterLayer(fileName, baseName)
if not rlayer.isValid():
print "Layer failed to load!"
QgsMapLayerRegistry.instance().addMapLayer(rlayer)
w = MyWnd(rlayer)
w.show()
对于QGIS服务器,也可以用PyQGIS进行插件和独立程序的开发。通过插件开发,可以实现修改现有服务(WMS、WFS等)、根据请求进行额外处理(如身份验证)甚至修改请求参数、修改响应结果(如给图片增加水印)、新建自定义服务、设置访问权限等灵活的功能。
QGIS Client和QGIS Server都可以替换为自己开发的PyQGIS程序。或者出于瘦客户端的考虑,QGIS还支持配置出简洁的用户界面。
2017年2月1日~6日 苏州&无锡