网络分析库¶

Docs in progress for 'QGIS testing'. Visit https://docs.qgis.org/3.4 for QGIS 3.4 docs and translations.

警告

Despite our constant efforts, information beyond this line may not be updated for QGIS 3. Refer to https://qgis.org/pyqgis/master for the python API documentation or, give a hand to update the chapters you know about. Thanks.

从修订开始 ee19294562 (qgis>=1.8)新的网络分析库被添加到qgis核心分析库中。类库:

网络分析库是通过从roadgraph核心插件导出基本功能创建的,现在您可以在插件中使用它的方法,也可以直接从python控制台使用它的方法。

一般信息

简单地说,一个典型的用例可以描述为:

  1. 从地理数据创建图形(通常是多段线矢量层)
  2. 运行图分析
  3. 使用分析结果(例如,可视化结果)

构建图形

你需要做的第一件事就是准备输入数据,也就是把一个向量层转换成一个图形。所有进一步的操作都将使用这个图表,而不是图层。

作为源,我们可以使用任何折线向量层。多段线的节点成为图形顶点,多段线的段是图形边缘。如果几个节点具有相同的坐标,则它们是相同的图形顶点。所以有一个公共节点的两条线相互连接。

此外,在图形创建过程中,可以“固定”(“tie”)到输入矢量层任何数量的附加点。对于每个额外的点,将找到一个匹配点---最近的图形顶点或最近的图形边缘。在后一种情况下,将分割边并添加新顶点。

矢量层属性和边的长度可以用作边的属性。

使用 Builder 编程模式。图是使用所谓的Director构造的。目前只有一位董事: QgsLineVectorLayerDirector . 控制器设置将用于从线向量层构建图形的基本设置,生成器使用这些设置创建图形。目前,与主管一样,只有一个生成器存在: QgsGraphBuilder ,创造 QgsGraph 物体。您可能希望实现自己的构建器,该构建器将构建与以下库兼容的图: BGLNetworkX .

计算边缘属性的编程模式 strategy 使用。目前只 QgsDistanceArcProperter 战略是可行的,考虑到路线的长度。您可以实现自己的策略,使用所有必要的参数。例如,Roadgraph插件使用一种策略,该策略使用边缘长度和属性中的速度值计算行程时间。

是时候深入研究这个过程了。

首先,要使用这个库,我们应该导入networkanalysis模块

from qgis.networkanalysis import *

再举几个创建导演的例子

# don't use information about road direction from layer attributes,
# all roads are treated as two-way
director = QgsLineVectorLayerDirector(vLayer, -1, '', '', '', 3)

# use field with index 5 as source of information about road direction.
# one-way roads with direct direction have attribute value "yes",
# one-way roads with reverse direction have the value "1", and accordingly
# bidirectional roads have "no". By default roads are treated as two-way.
# This scheme can be used with OpenStreetMap data
director = QgsLineVectorLayerDirector(vLayer, 5, 'yes', '1', 'no', 3)

为了构造一个导向器,我们应该通过一个向量层,它将被用作每个路段上允许移动的图形结构和信息的来源(单向或双向移动,直接或反向)。电话看起来像这样

director = QgsLineVectorLayerDirector(vl, directionFieldId,
                                      directDirectionValue,
                                      reverseDirectionValue,
                                      bothDirectionValue,
                                      defaultDirection)

以下是这些参数的全部含义:

然后有必要创建一个计算边缘特性的策略

properter = QgsDistanceArcProperter()

告诉主任这个策略

director.addProperter(properter)

现在我们可以使用构建器,它将创建图表。qgsgraphbuilder类构造函数接受几个参数:

# only CRS is set, all other values are defaults
builder = QgsGraphBuilder(myCRS)

我们还可以定义几个点,这些点将在分析中使用。例如

startPoint = QgsPoint(82.7112, 55.1672)
endPoint = QgsPoint(83.1879, 54.7079)

现在一切都就绪了,所以我们可以构建图表,并将这些点与之“绑定”。

tiedPoints = director.makeGraph(builder, [startPoint, endPoint])

构建图形可能需要一些时间(这取决于一个图层中的特征数量和图层大小)。 tiedPoints 是一个带有“绑定”点坐标的列表。当构建操作完成后,我们可以获取图表并将其用于分析

graph = builder.graph()

通过下一个代码,我们可以得到点的顶点索引。

startId = graph.findVertex(tiedPoints[0])
endId = graph.findVertex(tiedPoints[1])

图分析

网络分析用于找出两个问题的答案:哪些顶点连接,以及如何找到最短路径。为了解决这些问题,网络分析库提供了Dijkstra的算法。

Dijkstra算法找到了从图的一个顶点到所有其他顶点的最短路径以及优化参数的值。结果可以表示为最短路径树。

最短路径树是一个有向加权图(或更精确地说是树),具有以下特性:

要获得最短路径树,请使用以下方法 shortestTree()dijkstra() 属于 QgsGraphAnalyzer class. It is recommended to use method dijkstra() 因为它工作更快,而且使用内存更高效。

这个 shortestTree() 当您想绕着最短路径树行走时,方法很有用。它总是创建一个新的图形对象(qgsgraph)并接受三个变量:

tree = QgsGraphAnalyzer.shortestTree(graph, startId, 0)

这个 dijkstra() 方法具有相同的参数,但返回两个数组。在第一个数组元素中,我包含传入边缘的索引,如果没有传入边缘,则为-1。在第二个数组元素中,我包含从树的根到顶点i的距离,或者如果顶点i无法从根到达,则包含双_max。

(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, startId, 0)

下面是一些非常简单的代码,用创建的图形显示最短路径树。 shortestTree() 方法(在TOC中选择LineString层,并用自己的坐标替换坐标)。 警告: 仅将此代码用作示例,它会创建大量 QgsRubberBand 对象,在大型数据集上速度可能较慢。

from qgis.core import *
from qgis.gui import *
from qgis.networkanalysis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *

vl = qgis.utils.iface.mapCanvas().currentLayer()
director = QgsLineVectorLayerDirector(vl, -1, '', '', '', 3)
properter = QgsDistanceArcProperter()
director.addProperter(properter)
crs = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs()
builder = QgsGraphBuilder(crs)

pStart = QgsPoint(-0.743804, 0.22954)
tiedPoint = director.makeGraph(builder, [pStart])
pStart = tiedPoint[0]

graph = builder.graph()

idStart = graph.findVertex(pStart)

tree = QgsGraphAnalyzer.shortestTree(graph, idStart, 0)

i = 0;
while (i < tree.arcCount()):
  rb = QgsRubberBand(qgis.utils.iface.mapCanvas())
  rb.setColor (Qt.red)
  rb.addPoint (tree.vertex(tree.arc(i).inVertex()).point())
  rb.addPoint (tree.vertex(tree.arc(i).outVertex()).point())
  i = i + 1

相同的东西,但使用 dijkstra() 方法

from qgis.core import *
from qgis.gui import *
from qgis.networkanalysis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *

vl = qgis.utils.iface.mapCanvas().currentLayer()
director = QgsLineVectorLayerDirector(vl, -1, '', '', '', 3)
properter = QgsDistanceArcProperter()
director.addProperter(properter)
crs = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs()
builder = QgsGraphBuilder(crs)

pStart = QgsPoint(-1.37144, 0.543836)
tiedPoint = director.makeGraph(builder, [pStart])
pStart = tiedPoint[0]

graph = builder.graph()

idStart = graph.findVertex(pStart)

(tree, costs) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)

for edgeId in tree:
  if edgeId == -1:
    continue
  rb = QgsRubberBand(qgis.utils.iface.mapCanvas())
  rb.setColor (Qt.red)
  rb.addPoint (graph.vertex(graph.arc(edgeId).inVertex()).point())
  rb.addPoint (graph.vertex(graph.arc(edgeId).outVertex()).point())

寻找最短路径

为了找到两点之间的最佳路径,采用以下方法。两个点(起点A和终点B)在构建时都“绑定”到图形上。然后使用这些方法 shortestTree()dijkstra() 我们建立了一个最短路径树,其根在起点A,在同一棵树中,我们也找到终点B,并开始从起点B到终点A的遍历树,整个算法可以写成

assign Т = B
while Т != A
    add point Т to path
    get incoming edge for point Т
    look for point ТТ, that is start point of this edge
    assign Т = ТТ
add point А to path

在这一点上,我们有一条路径,其形式是倒排的顶点列表(顶点按从终点到起点的相反顺序列出),在通过这条路径旅行时将访问这些顶点。

下面是使用方法的qgis python控制台的示例代码(您需要在toc中选择linestring层并用您的代码替换代码中的坐标)。 shortestTree()

from qgis.core import *
from qgis.gui import *
from qgis.networkanalysis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *

vl = qgis.utils.iface.mapCanvas().currentLayer()
director = QgsLineVectorLayerDirector(vl, -1, '', '', '', 3)
properter = QgsDistanceArcProperter()
director.addProperter(properter)
crs = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs()
builder = QgsGraphBuilder(crs)

pStart = QgsPoint(-0.835953, 0.15679)
pStop = QgsPoint(-1.1027, 0.699986)

tiedPoints = director.makeGraph(builder, [pStart, pStop])
graph = builder.graph()

tStart = tiedPoints[0]
tStop = tiedPoints[1]

idStart = graph.findVertex(tStart)
tree = QgsGraphAnalyzer.shortestTree(graph, idStart, 0)

idStart = tree.findVertex(tStart)
idStop = tree.findVertex(tStop)

if idStop == -1:
  print("Path not found")
else:
  p = []
  while (idStart != idStop):
    l = tree.vertex(idStop).inArc()
    if len(l) == 0:
      break
    e = tree.arc(l[0])
    p.insert(0, tree.vertex(e.inVertex()).point())
    idStop = e.outVertex()

  p.insert(0, tStart)
  rb = QgsRubberBand(qgis.utils.iface.mapCanvas())
  rb.setColor(Qt.red)

  for pnt in p:
    rb.addPoint(pnt)

这里有相同的样品,但是使用 dijkstra() 方法

from qgis.core import *
from qgis.gui import *
from qgis.networkanalysis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *

vl = qgis.utils.iface.mapCanvas().currentLayer()
director = QgsLineVectorLayerDirector(vl, -1, '', '', '', 3)
properter = QgsDistanceArcProperter()
director.addProperter(properter)
crs = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs()
builder = QgsGraphBuilder(crs)

pStart = QgsPoint(-0.835953, 0.15679)
pStop = QgsPoint(-1.1027, 0.699986)

tiedPoints = director.makeGraph(builder, [pStart, pStop])
graph = builder.graph()

tStart = tiedPoints[0]
tStop = tiedPoints[1]

idStart = graph.findVertex(tStart)
idStop = graph.findVertex(tStop)

(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)

if tree[idStop] == -1:
  print("Path not found")
else:
  p = []
  curPos = idStop
  while curPos != idStart:
    p.append(graph.vertex(graph.arc(tree[curPos]).inVertex()).point())
    curPos = graph.arc(tree[curPos]).outVertex();

  p.append(tStart)

  rb = QgsRubberBand(qgis.utils.iface.mapCanvas())
  rb.setColor(Qt.red)

  for pnt in p:
    rb.addPoint(pnt)

可用性领域

顶点A的可用性区域是可从顶点A访问的图形顶点的子集,并且从顶点A到这些顶点的路径成本不大于某个值。

更清楚地说,这可以用下面的例子来表示:“有一个消防站。消防车能在5分钟内到达城市的哪些地方?10分钟?15分钟?”.这些问题的答案是消防站的可用范围。

为了找到可用性方面,我们可以使用方法 dijkstra()QgsGraphAnalyzer 班级。将成本数组的元素与预先定义的值进行比较就足够了。如果成本[i]小于或等于预定义值,则顶点i在可用性区域内,否则它在可用性区域外。

一个更困难的问题是获得可用性领域的边界。底部边框是一组仍可以访问的顶点,顶部边框是一组不可访问的顶点。事实上,这很简单:它是基于最短路径树的边缘的可用性边界,对于该树,边缘的源顶点是可访问的,而边缘的目标顶点不是。

下面是一个例子

from qgis.core import *
from qgis.gui import *
from qgis.networkanalysis import *
from qgis.PyQt.QtCore import *
from qgis.PyQt.QtGui import *

vl = qgis.utils.iface.mapCanvas().currentLayer()
director = QgsLineVectorLayerDirector(vl, -1, '', '', '', 3)
properter = QgsDistanceArcProperter()
director.addProperter(properter)
crs = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs()
builder = QgsGraphBuilder(crs)

pStart = QgsPoint(65.5462, 57.1509)
delta = qgis.utils.iface.mapCanvas().getCoordinateTransform().mapUnitsPerPixel() * 1

rb = QgsRubberBand(qgis.utils.iface.mapCanvas(), True)
rb.setColor(Qt.green)
rb.addPoint(QgsPoint(pStart.x() - delta, pStart.y() - delta))
rb.addPoint(QgsPoint(pStart.x() + delta, pStart.y() - delta))
rb.addPoint(QgsPoint(pStart.x() + delta, pStart.y() + delta))
rb.addPoint(QgsPoint(pStart.x() - delta, pStart.y() + delta))

tiedPoints = director.makeGraph(builder, [pStart])
graph = builder.graph()
tStart = tiedPoints[0]

idStart = graph.findVertex(tStart)

(tree, cost) = QgsGraphAnalyzer.dijkstra(graph, idStart, 0)

upperBound = []
r = 2000.0
i = 0
while i < len(cost):
  if cost[i] > r and tree[i] != -1:
    outVertexId = graph.arc(tree [i]).outVertex()
    if cost[outVertexId] < r:
      upperBound.append(i)
  i = i + 1

for i in upperBound:
  centerPoint = graph.vertex(i).point()
  rb = QgsRubberBand(qgis.utils.iface.mapCanvas(), True)
  rb.setColor(Qt.red)
  rb.addPoint(QgsPoint(centerPoint.x() - delta, centerPoint.y() - delta))
  rb.addPoint(QgsPoint(centerPoint.x() + delta, centerPoint.y() - delta))
  rb.addPoint(QgsPoint(centerPoint.x() + delta, centerPoint.y() + delta))
  rb.addPoint(QgsPoint(centerPoint.x() - delta, centerPoint.y() + delta))