1.前言

阅读本文需要知道什么是shapfile,什么是路径分析,什么是GIS。相比Arcgis的路径分析功能,本文介绍的方法稍微复杂,需要注意的细节更多,但却是完全免费的。PostGis+QGIS+Geoserver开源Gis三剑客用起来真的很舒服。

2.软件环境


Postgresql+Postgis+pgRouting+QGIS。

2.数据准备

1.将道路Shp数据导入Postgis

打开PostGIS Shapefile Import/Export Manager,导入线路数据,你的数据只要是单线段类型即可,在导入过程中注意填写SRID、字符编码、并勾选上单体图形,如果导入过程中报错无法生成单体图形,则需要将图形处理成单体图形,可以借助ArcMap的FeatureToLine功能。

2.数据表修改

现在为刚刚导入的道路数据添加路径分析所需的字段,其中source指该路段的源头(道路节点),target指该路段的终点(道路节点):

ALTER TABLE daolu_simp
ADD COLUMN source integer,
ADD COLUMN target integer,
ADD COLUMN cost_len double precision,
ADD COLUMN cost_time double precision,
ADD COLUMN rcost_len double precision,
ADD COLUMN rcost_time double precision,
ADD COLUMN x1 double precision,
ADD COLUMN y1 double precision,
ADD COLUMN x2 double precision,
ADD COLUMN y2 double precision,
ADD COLUMN to_cost double precision,
ADD COLUMN rule text,
ADD COLUMN isolated integer;

接下来我们把新增的内容填上,这里主要填道路往返的路程花费和起点终点坐标,时间花费和其他暂时不填:

UPDATE daolu_simp SET x1 = st_x(st_startpoint(geom)),
y1 = st_y(st_startpoint(geom)),
x2 = st_x(st_endpoint(geom)),
y2 = st_y(st_endpoint(geom)),
cost_len = st_length_spheroid(geom, 'SPHEROID["WGS84",6378137,298.25728]'),
rcost_len = st_length_spheroid(geom, 'SPHEROID["WGS84",6378137,298.25728]')

这里有一份可直接用的路网数据(不需要从上一步导入):

CREATE TABLE edge_table (
id BIGSERIAL,
dir character varying,
source BIGINT,
target BIGINT,
cost_lenFLOAT,
rcost_len FLOAT,
capacity BIGINT,
reverse_capacity BIGINT,
category_id INTEGER,
reverse_category_id INTEGER,
x1 FLOAT,
y1 FLOAT,
x2 FLOAT,
y2 FLOAT,
the_geom geometry
);INSERT INTO edge_table (
category_id, reverse_category_id,
cost_len, rcost_len,
capacity, reverse_capacity,
x1, y1,
x2, y2) VALUES
(3, 1, 1, 1, 80, 130, 2, 0, 2, 1),
(3, 2, -1, 1, -1, 100, 2, 1, 3, 1),
(2, 1, -1, 1, -1, 130, 3, 1, 4, 1),
(2, 4, 1, 1, 100, 50, 2, 1, 2, 2),
(1, 4, 1, -1, 130, -1, 3, 1, 3, 2),
(4, 2, 1, 1, 50, 100, 0, 2, 1, 2),
(4, 1, 1, 1, 50, 130, 1, 2, 2, 2),
(2, 1, 1, 1, 100, 130, 2, 2, 3, 2),
(1, 3, 1, 1, 130, 80, 3, 2, 4, 2),
(1, 4, 1, 1, 130, 50, 2, 2, 2, 3),
(1, 2, 1, -1, 130, -1, 3, 2, 3, 3),
(2, 3, 1, -1, 100, -1, 2, 3, 3, 3),
(2, 4, 1, -1, 100, -1, 3, 3, 4, 3),
(3, 1, 1, 1, 80, 130, 2, 3, 2, 4),
(3, 4, 1, 1, 80, 50, 4, 2, 4, 3),
(3, 3, 1, 1, 80, 80, 4, 1, 4, 2),
(1, 2, 1, 1, 130, 100, 0.5, 3.5, 1.999999999999,3.5),
(4, 1, 1, 1, 50, 130, 3.5, 2.3, 3.5,4);

3.道路拓扑计算

拓扑分析函数的签名如下:

varchar pgr_createTopology(text edge_table, double precision
tolerance, text the_geom:=‘the_geom’, text id:=‘id’, text
source:=‘source’,text target:=‘target’, text rows_where:=‘true’,
boolean clean:=false)

手册中对各个参数的说明:

edge_table text 路网表的名字. (也可以包含Schema) tolerance float8 决定路段不连通的阈值.
(投影坐标的单位)> the_geom text 路网数据中道路的图形数据. 默认是the_geom. id text 路网的Primary
key.默认为id. source text 路段的起点节点. 默认为 source. target text 路段的终点节点.
默认为target. rows_where text Condition to SELECT a subset or rows.
Default value is true to indicate all rows that where source or target
have a null value, otherwise the condition is used. clean boolean
是否清除上一次该路网的拓扑数据.默认false.

此函数对路网进行拓扑分析,会生成一个路网节点表,并对路网表中的source和target字段填充入节点编号。这些参数中需要特别注意tolerance参数的填写,过小可能造成道路不连通,过大可能造成把不连通的道路给连通上。下面对两个不同的tolerance取值进行对比,在拓扑检查和路径分析的时候会获得不同的结果。


SELECT pgr_createTopology(edge_table:='daolu_simp', tolerance:=0.0000001, the_geom:='geom', id:='gid', clean:=true);

结果为OK说明成功运行。

4.拓扑检查

拓扑分析检查函数签名:

varchar pgr_analyzeGraph(text edge_table, double precision tolerance,
text the_geom:=‘the_geom’, text id:=‘id’, text source:=‘source’,text
target:=‘target’,text rows_where:=‘true’)

本例我们这样填写:

select pgr_analyzeGraph('daolu_simp', 0.0001, 'geom', 'gid')

检查结果如下:

3.路径分析

1.pgr_withPoints函数

pgr_dijkstra是pgRouting官方教程中第一个介绍的最短路径规划方法,pgr_withPoints和 pgr_dijkstra一样也是最短路径规划方法,输出结果也基本一致,但是两者的输入不一样。pgr_dijkstra需要输入的是路网节点编号,也就是说只能从道路的端点算起,而pgr_withPoints可以从道路的中间位置选择起点和终点。
来看看最简单使用方法的函数签名:

r_withPoints(edges_sql, points_sql, start_vid, end_vid)

参数说明:edges_sql是从路网表选择出需要参与计算的路径的Sql字符串,points_sql是从POI表(下文会说明)选择出途径点的Sql字符串,start_vid是起点的节点id,正值为路网节点,负值为POI表数据,end_vid为终点节点,同样正值为路网节点,负值为POI表数据。注意在edges_sql中需要指定cost和reverse_cost(去的花费和来的花费),函数通过这两个值的正负情况来获知道路的通过性(单行、双向、不通)

这里我们举一个例子:(一个起点一个终点)

SELECT * FROM pgr_withPoints(
'SELECT id, source, target, cost, reverse_cost FROM edge_table ORDER BY id',
'SELECT pid, edge_id, fraction, side from pointsOfInterest',
-1, -3);seq | path_seq | node | edge | cost | agg_cost
-----+----------+------+------+------+----------
1 | 1 | -1 | 1 | 0.6 | 0
2 | 2 | 2 | 4 | 1 | 0.6
3 | 3 | 5 | 10 | 1 | 1.6
4 | 4 | 10 | 12 | 0.6 | 2.6
5 | 5 | -3 | -1 | 0 | 3.2
(5 rows)

输出结果中path_seq为路径(点位)顺序,node为途经节点(正值为路网节点,负值为POI节点),edge为路网的路段,cost为该路段的花费。需要注意的是所有的edge加起来可能会多于实际经过路径(如果从POI节点出发),这时需要将一头一尾(结果为-1的edge排除在外)两个路段单独取出来根据fraction计算实际经过部分,其他路段则为完整实际经过路段。

2.POI表

POI表在路径分析功能中的角色是,保存用户选择的途径点(起点和终点)并进行必要的处理,表结构sql:

CREATE TABLE pointsOfInterest(
pid BIGSERIAL,
x FLOAT,
y FLOAT,
edge_id BIGINT,
side CHAR,
fraction FLOAT,
the_geom geometry,
newPoint geometry
);

需要注意三个字段:edge_id表示该POI在路网中距离最近(或者其他条件)的路径编号,fraction表示该路径上距离POI最近的位置,the_geom表示POI的图形,newPoint表示POI在路网中对应的位置(fraction表示的位置)。

通过下面的sql来填写POI表,其中106.72545, 26.56554(sql中出现了4对)经纬度为POI的原始位置,ST_DWithin函数用于选择距离POI最近的路径边,参数1000表示在1000米内搜索;ST_LineLocatePoint函数的作用是计算该路径边上距离POI最近的位置,得到的结果更新表中的fracion字段;ST_LineInterpolatePoint函数的作用则是根据fraction将路径上实际的这个点位获取出来,得到的结果更新表中newPoint字段。

INSERT INTO pointsOfInterest(x, y, edge_id, side, fraction,the_geom,newPoint)
(
SELECT 106.72545, 26.56554,gid,'b',ST_LineLocatePoint(geom, concat('SRID=4326;POINT(106.72545 26.56554)')::geometry) as fraction,st_makePoint(106.72545,26.56554),ST_LineInterpolatePoint(geom, ST_LineLocatePoint(geom, concat('SRID=4326;POINT(106.72545 26.56554)')::geometry))
FROM daolu_simp
WHERE ST_DWithin(geom, concat('SRID=4326;POINT(106.72545 26.56554)')::geometry, 1000)
order by geom <-> concat('SRID=4326;POINT(106.72545 26.56554)')::geometry
limit 1)

POI点导入后可以看到有POI点的原始点位(绿色)和对应在路径上的点位(紫色)

3.开始路径分析

为了方便,我们使用QGIS调试路径分析效果,打开QGIS,连接PostGis数据库,打开数据




路径查询Sql如下所示,采用的展示方案是将途径路段的首尾连接起来,核心函数pgr_withPoints的用法上文已经介绍,注意在使用st_makeline生成线段的时候需要对节点按照seq或seq_path进行排序。

select st_makeline(point_geom)
from
(SELECT seq, node ,newpoint as point_geom FROM pgr_withPoints(
'SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid',
'SELECT pid as pid, edge_id, fraction, side from pointsOfInterest',
-1, -2) as wp
join pointsofinterest poi
on -wp.node= poi.pid
union all
SELECT seq, node ,the_geom as point_geom FROM pgr_withPoints(
'SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid',
'SELECT pid as pid, edge_id, fraction, side from pointsOfInterest',
-1, -2) as wp
join daolu_simp_vertices_pgr et
on wp.node= et.idorder by seq)as a

计算结果(绿色线条):

4.最后

到此为止,我们已经能够计算出路径了(实际上完整的路径还需要另写Sql获取),至于是不是最短路径,需要保证路网数据质量,例如路网连通性、路径花费的合理性等,那些都是数据层面的问题,在程序方面我们已经实现了最基础的版本。若路网存在转弯限制、单行道限制(本文中提到了相应字段,但是未使用)等,还需要我们进一步探讨。
我们将上一次路径绘制的坑给填了,经过一番sql的洗礼我们终于能够获取完整的规划路径(上一次只是取得每一段路的起点和终点)了:

并且我们将路径规划做成一个Sql函数,要规划路线的话只需要调用函数即可。

select whz_getPathBeta(106.7267,26.5690,106.8046,26.56904,1000);

废话不多说,上sql。
首先是生成路径的sql,参数填写起点终点经纬度和poi搜索范围:

-- 路网表daolu_simp
-- POI表pointsOfInterest2CREATE OR REPLACE FUNCTION whz_getPathBeta(x1 float8, y1 float8, x2 float8, y2 float8, roadWithin numeric) RETURNS setof geometry AS $$declare poiId1 integer;
declare poiId2 integer;
declare segCount integer;
begin
poiId1=whz_updatePoi(x1,y1,roadWithin);poiId2=whz_updatePoi(x2,y2,roadWithin);select count(*) into segCount from pgr_withPoints(
'SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid',
'SELECT pid as pid, edge_id, fraction, side from pointsOfInterest2',
-poiId1, -poiId2);return query SELECT whz_getSegGeom(wp.seq,wp.node::integer,wp.edge::integer, segCount,-poiId1,-poiId2) as the_geom FROM pgr_withPoints(
'SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid',
'SELECT pid as pid, edge_id, fraction, side from pointsOfInterest2',
-poiId1, -poiId2) as wp;
end;$$LANGUAGE plpgsql;
;

然后是往里面插入POI的sql:

-- POI表pointsOfInterest2
-- 指定SRID=4326
-- 指定改点在路段的两侧CREATE OR REPLACE FUNCTION whz_updatePoi(xo float8, yo float8, roadWithin numeric) RETURNS integer AS $$declare idRet numeric;beginINSERT INTO pointsOfInterest2(x, y, edge_id, side, fraction, the_geom, newPoint)(SELECT xo, yo,gid,'b',ST_LineLocatePoint(geom, concat('SRID=4326;POINT(',xo,' ',yo,')')::geometry) as fraction,
st_makePoint(xo,yo),
ST_LineInterpolatePoint(geom, ST_LineLocatePoint(geom, concat('SRID=4326;POINT(',xo,' ',yo,')')::geometry))
FROM daolu_simp
WHERE ST_DWithin(geom, concat('SRID=4326;POINT(',xo,' ',yo,')')::geometry, roadWithin)
order by geom <-> concat('SRID=4326;POINT(',xo,' ',yo,')')::geometry
limit 1)RETURNING pid into idRet;
return idRet;
end;$$
LANGUAGE plpgsql;
;

最后是获取每一段子路径的完整路径的sql:

-- POI表pointsofinterest2
-- 路网节点表daolu_simp_vertices_pgr
-- 路网表daolu_simp
-- 本方法试用于起点终点都在poi,且一起点一终点CREATE OR REPLACE FUNCTION whz_getSegGeom(myseq integer, mynode integer, myedge integer, node_num integer,poiId1 integer,poiId2 integer) RETURNS geometry AS $$declare geomRet geometry;
declare startFraction float8;
declare endFraction float8;
declare edgeidForPoi integer;
declare pointTmp geometry;
declare nodeTmp integer;
declare edgeTmp geometry;beginIF(mynode<0 AND myseq=1)THENSELECT node into nodeTmp FROM pgr_withPoints('SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid','SELECT pid as pid, edge_id, fraction, side from pointsOfInterest2',poiId1, poiId2) as wp1where wp1.seq= 2;select pointsofinterest2.fraction into endFractionfrom pointsofinterest2where pointsofinterest2.pid=-mynodelimit 1;select pointsofinterest2.edge_id into edgeidForPoifrom pointsofinterest2where pointsofinterest2.pid=-mynodelimit 1;select the_geom into pointTmpfrom daolu_simp_vertices_pgrwhere daolu_simp_vertices_pgr.id= nodeTmplimit 1;select geom into edgeTmp from daolu_simpwhere daolu_simp.gid=edgeidForPoilimit 1;startFraction=st_linelocatepoint(edgeTmp, pointTmp);if(startFraction>=endFraction) THENgeomRet=ST_LineSubstring(edgeTmp, endFraction,startFraction);ELSE geomRet=ST_LineSubstring(edgeTmp, startFraction,endFraction);end if;ELSIF(mynode<0 AND myseq=node_num)THENSELECT node into nodeTmp FROM pgr_withPoints('SELECT gid as id, source, target, cost_len as cost, rcost_len as reverse_cost FROM daolu_simp ORDER BY gid','SELECT pid as pid, edge_id, fraction, side from pointsOfInterest2',poiId1, poiId2) as wp2where wp2.seq= myseq-1;select edge_id into edgeidForPoifrom pointsofinterest2where pointsofinterest2.pid=-mynodelimit 1;select fraction into endFractionfrom pointsofinterest2where pointsofinterest2.pid=-mynodelimit 1;select the_geom into pointTmpfrom daolu_simp_vertices_pgrwhere daolu_simp_vertices_pgr.id= nodeTmplimit 1;select geom into edgeTmp from daolu_simpwhere daolu_simp.gid=edgeidForPoilimit 1;startFraction=st_linelocatepoint(edgeTmp, pointTmp);if(startFraction>=endFraction) THENgeomRet=ST_LineSubstring(edgeTmp, endFraction,startFraction);ELSE geomRet=ST_LineSubstring(edgeTmp, startFraction,endFraction);end if;ELSIF(mynode>0 AND myseq<node_num-1)thenselect geom into geomRetfrom daolu_simpwhere daolu_simp.gid=myedge;END IF;return geomRet;
end;$$LANGUAGE plpgsql;

注意,这个sql里面的表名和相关参数是写死的,实际使用的时候需要根据情况调整。

用Postgis算最短路径(在任意位置选择起点终点)相关推荐

  1. java gis 最短路径_用Postgis算最短路径(在任意位置选择起点终点)(下)

    今天将上一次路径绘制的坑(上次绘制的是子路径端点的连线)给填了,本文中我将实现一个输入起点和终点经纬度,输出完整路径(包含子路径端点间的真实弯曲情况,完美贴合真实路径)的函数,能够计算准确的路径长度. ...

  2. postgis+geoserver最短路径

    postgis+geoserver最短路径 1 安装软件 2 数据预处理 3 操作postgres 4 操作Geoserver 5 计算最短路径 6 问题记录 1 安装软件 安装PostgreSQL与 ...

  3. linux 命令行管理员身份运行,任意位置以管理员身份打开CMD(命令提示符)

    关于CMD这个东西,windows和linux还是有很大差别的,linux系统下可以在任意位置右键选择在当前文件夹打开终端,而windows没有这个功能,每次打开都是默认的位置,然后不停的cd.为了提 ...

  4. Excel删除文本中任意位置所有空格的3种方法比较

    今天小编要分享的是删除文本任意位置的空格的3种方法,如下图文本的左中右都有空格 一. 1.为了对比先复制一列出来 2.然后呢直接按Ctrl+H打开替换对话框 ​ 3.再然后我们输入查找内容空格,然后全 ...

  5. 如何在多个视频画面的任意位置上添加上同一张图片

    现在大家都会做视频,在视频画面上添加一张图片,会让作品一半是视频一半是图片,那这种效果的视频要如何快速的制作呢?下面就随小编一起用视频剪辑高手来操作试试. 准备几个相同格式的视频及尺寸相应的图片保存在 ...

  6. JAVA I/O之神奇的RandomAccessFile(快速定位文件任意位置,修改或插入)

    一.简述 这个是JDK上的截图,我们可以看到它的父类是Object,没有继承字节流.字符流家族中任何一个类.并且它实现了DataInput.DataOutput这两个接口,也就意味着这个类既可以读也可 ...

  7. uniapp 自定义图片水印插件(任意位置) Ba-Watermark

    自定义图片水印 Ba-Watermark 简介(下载地址) Ba-Watermark 是一款uniapp给图片自定义水印的插件. 支持添加多个.多种样式水印 支持在任意位置添加 支持按实际像素或图片高 ...

  8. 教你给视频画面任意位置插入GIF图

    视频怎么添加GIF图片呢?如何在视频任意位置添加的呢?其实很简单.教你这个简单的剪辑方法.一起来试试吧. 准备工具: 视频素材及动图 下载一个视频剪辑高手 开始操作: 运行软件登录上,在多种功能上选择 ...

  9. MFC模拟AutoCAD 在单文本视图窗口任意位置输入文字

    本文介绍如果通过MFC编程实现模拟AutoCAD 在单文本视图窗口任意位置输入文字. 先在VS2017中建一个名为FormatDemo单文档工程,在FormatDemoView.h中声明如下变量: p ...

最新文章

  1. 【Harvest源码分析】GetFilteredSignal函数
  2. Android开发者指南(18) —— Web Apps Overview
  3. 使用windows activeX 在Webclient UI 中打开word文档
  4. neo4j cypher_优化Neo4j Cypher查询
  5. TortoiseGit上传代码报错error:1407742E
  6. 源代码安装apache遇到的问题解决
  7. netty使用(5)client_server一发一回阐释ByteBuffer的使用
  8. C语言printf语法
  9. 佳能MP145 /140故障代码大全
  10. C# SQL拼接字符串
  11. EXCEL功能之Excel表格边框设置
  12. 一元云购系统对接短信功能图文教程—【V4版】
  13. erp系统云端服务器,erp系统软件云服务器
  14. 考研数学笔记1-常数项级数的审敛法思路
  15. javaweb(基础二)
  16. 阿里云Web应用防火墙-WAF
  17. 【每日新闻】2017年亚马逊研发投入排世界第一,超过华为、BAT 总和 | 数人云宣布与UMCloud合并
  18. LoCCS专访:后量子密码技术让Hcash走得更远
  19. GJB 标准化工作报告(模板)
  20. Java中GSON的使用

热门文章

  1. 暴力破解周边Wi-Fi密码
  2. 迪士尼照片_如何更改您的迪士尼+个人资料图片
  3. Niushop微信支付配置、微信退款配置、微信转账配置操作流程
  4. 移动端滚动穿透与滚动溢出解决方案
  5. 计算机原理-存储系统
  6. 有哪些值得推荐的好用视频剪辑软件?
  7. 共模电感适用的频率_共模电感
  8. MAC下配置JAVAWEB环境(原创,写的很详细)
  9. PS学习记录6--html5 canvas+js实现ps钢笔抠图
  10. 关于android架构的英文资料,第十五期:英语流利说 Android 架构演进