强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 9 章:路径规划

第 9 章:路径规划

9.1 pgRouting 概述

pgRouting 是 PostGIS 的路径规划扩展,它在 PostgreSQL 空间数据库之上提供了图论算法,用于解决最短路径、旅行商问题(TSP)、车辆路径问题(VRP)等。

核心算法

算法函数用途
Dijkstrapgr_dijkstra最短路径(经典)
A*pgr_astar启发式最短路径
白色骑士pgr_kspK 条最短路径
TSPpgr_tsp旅行商问题
容量限制 VRPpgr_vrpOneDepot车辆路径问题
转弯限制pgr_trsp带转向约束的路径
全源最短路pgr_johnson / pgr_floydWarshall所有节点对的最短距离

9.2 安装 pgRouting

-- 安装扩展
CREATE EXTENSION IF NOT EXISTS pgrouting;

-- 验证
SELECT pgr_version();
# Ubuntu 安装
sudo apt install postgresql-16-pgrouting

# Docker 中使用含 pgRouting 的镜像
docker run -d --name postgis-routing \
  -e POSTGRES_PASSWORD=routing123 \
  -p 5432:5432 \
  camptocamp/pgrouting:16-3.4

9.3 构建路网数据

路网表结构

pgRouting 要求路网数据满足以下结构:

字段类型说明
idINTEGER边的唯一标识
sourceINTEGER起点节点 ID
targetINTEGER终点节点 ID
costDOUBLE PRECISION正向通行成本(如距离、时间)
reverse_costDOUBLE PRECISION反向通行成本(单行道使用)
geomGEOMETRY边的几何

创建路网

-- 创建路网表
CREATE TABLE ways (
    gid SERIAL PRIMARY KEY,
    name TEXT,
    road_class TEXT,
    maxspeed INTEGER,
    oneway INTEGER DEFAULT 0,  -- 0=双向, 1=正向, -1=反向
    source INTEGER,
    target INTEGER,
    cost DOUBLE PRECISION,
    reverse_cost DOUBLE PRECISION,
    geom GEOMETRY(LineString, 4326)
);

-- 插入道路数据
INSERT INTO ways (name, road_class, maxspeed, oneway, geom) VALUES
('长安街', '主干道', 60, 0,
 ST_GeomFromText('LINESTRING(116.3470 39.9042, 116.3745 39.9042, 116.4074 39.9042, 116.4612 39.9042)', 4326)),
('建国路', '主干道', 60, 0,
 ST_GeomFromText('LINESTRING(116.4612 39.9042, 116.4612 39.9200, 116.4800 39.9400)', 4326)),
('二环路', '快速路', 80, 0,
 ST_GeomFromText('LINESTRING(116.3576 39.9340, 116.3745 39.9530, 116.4200 39.9530, 116.4450 39.9340)', 4326));

-- 创建拓扑(自动填充 source、target、cost 字段)
-- 步骤 1: 添加拓扑字段
ALTER TABLE ways ADD COLUMN source INTEGER;
ALTER TABLE ways ADD COLUMN target INTEGER;

-- 步骤 2: 创建拓扑
SELECT pgr_createTopology('ways', 0.00001, 'geom', 'gid');

-- 步骤 3: 计算通行成本(使用地理距离,单位米)
UPDATE ways SET
    cost = ST_Length(geom::geography),
    reverse_cost = ST_Length(geom::geography);

-- 单行道处理
UPDATE ways SET
    reverse_cost = -1  -- -1 表示不可通行
WHERE oneway = 1;

UPDATE ways SET
    cost = -1
WHERE oneway = -1;

-- 步骤 4: 创建索引
CREATE INDEX idx_ways_source ON ways(source);
CREATE INDEX idx_ways_target ON ways(target);
CREATE INDEX idx_ways_geom ON ways USING GIST(geom);

9.4 Dijkstra 最短路径

基本最短路径查询

-- 查询从节点 1 到节点 10 的最短路径
SELECT * FROM pgr_dijkstra(
    'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
    1,    -- 起始节点
    10,   -- 终止节点
    directed := true
);

返回结果

字段说明
seq路径序号
path_seq路径段序号
node经过的节点
edge经过的边
cost当前段成本
agg_cost累计成本

带几何输出的最短路径

-- 获取带几何的路径
SELECT
    d.seq,
    d.node,
    d.edge,
    d.cost,
    d.agg_cost,
    w.name,
    w.geom
FROM pgr_dijkstra(
    'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
    1, 10, directed := true
) d
JOIN ways w ON d.edge = w.gid
ORDER BY d.seq;

业务场景:导航路径查询

-- 封装为函数
CREATE OR REPLACE FUNCTION find_route(
    start_lng DOUBLE PRECISION,
    start_lat DOUBLE PRECISION,
    end_lng DOUBLE PRECISION,
    end_lat DOUBLE PRECISION
) RETURNS TABLE(
    seq INTEGER,
    road_name TEXT,
    segment_cost DOUBLE PRECISION,
    total_cost DOUBLE PRECISION,
    geom GEOMETRY
) AS $$
DECLARE
    start_node INTEGER;
    end_node INTEGER;
BEGIN
    -- 找到最近的路网节点
    SELECT source INTO start_node
    FROM ways
    ORDER BY geom <-> ST_SetSRID(ST_MakePoint(start_lng, start_lat), 4326)
    LIMIT 1;

    SELECT source INTO end_node
    FROM ways
    ORDER BY geom <-> ST_SetSRID(ST_MakePoint(end_lng, end_lat), 4326)
    LIMIT 1;

    -- 查询最短路径
    RETURN QUERY
    SELECT
        d.seq::INTEGER,
        COALESCE(w.name, '未命名道路')::TEXT,
        d.cost,
        d.agg_cost,
        w.geom
    FROM pgr_dijkstra(
        'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
        start_node, end_node, directed := true
    ) d
    JOIN ways w ON d.edge = w.gid
    ORDER BY d.seq;
END;
$$ LANGUAGE plpgsql;

-- 使用
SELECT * FROM find_route(116.3470, 39.9042, 116.4612, 39.9042);

9.5 A* 算法

A* 算法是 Dijkstra 的启发式版本,使用直线距离作为启发函数,搜索速度更快。

-- A* 需要节点的坐标信息
-- 创建包含节点坐标的辅助视图
CREATE OR REPLACE VIEW ways_vertices AS
SELECT id, ST_X(the_geom) AS x, ST_Y(the_geom) AS y
FROM ways_vertices_pgr;

-- 使用 A* 算法
SELECT * FROM pgr_astar(
    'SELECT gid AS id, source, target, cost, reverse_cost,
            ST_X(ST_StartPoint(geom)) AS x1, ST_Y(ST_StartPoint(geom)) AS y1,
            ST_X(ST_EndPoint(geom)) AS x2, ST_Y(ST_EndPoint(geom)) AS y2
     FROM ways',
    1, 10,
    directed := true,
    heuristic := 2  -- 2 = 欧几里得距离
);

A* vs Dijkstra

特性DijkstraA*
搜索策略广度优先(均匀扩展)目标导向(启发式)
时间复杂度O(V²) 或 O((V+E)logV)通常更优
适用场景通用有坐标信息时
最优性保证最优保证最优(启发函数可接受时)

9.6 K 最短路径

-- 查找 3 条最短路径
SELECT * FROM pgr_ksp(
    'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
    1, 10,
    3,            -- K = 3 条路径
    directed := true
);

业务场景:备选路线

-- 为导航提供 3 条备选路线
WITH routes AS (
    SELECT
        path_id,
        seq,
        edge,
        cost,
        agg_cost
    FROM pgr_ksp(
        'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
        1, 10, 3, directed := true
    )
)
SELECT
    r.path_id,
    ST_LineMerge(ST_Collect(w.geom)) AS route_geom,
    SUM(r.cost) AS total_distance,
    MAX(r.agg_cost) AS cumulative_distance
FROM routes r
JOIN ways w ON r.edge = w.gid
GROUP BY r.path_id
ORDER BY total_distance;

9.7 旅行商问题 (TSP)

TSP 问题:给定一系列城市,找到访问每个城市恰好一次并返回起点的最短路径。

基本 TSP

-- 准备距离矩阵
-- 假设有 5 个配送点
WITH delivery_points AS (
    SELECT id, name, geom FROM stores WHERE id IN (1, 2, 3, 4, 5)
),
distance_matrix AS (
    SELECT
        a.id AS start_id,
        b.id AS end_id,
        ST_Distance(a.geom::geography, b.geom::geography) AS distance
    FROM delivery_points a, delivery_points b
    WHERE a.id != b.id
)
SELECT * FROM pgr_tsp(
    $$
    WITH dm AS (
        SELECT
            a.id AS start_id, b.id AS end_id,
            ST_Distance(a.geom::geography, b.geom::geography) AS dist
        FROM (SELECT id, geom FROM stores WHERE id IN (1,2,3,4,5)) a,
             (SELECT id, geom FROM stores WHERE id IN (1,2,3,4,5)) b
        WHERE a.id != b.id
    )
    SELECT * FROM dm
    $$,
    1  -- 起始节点
);

TSP 结果优化

-- 获取 TSP 路径的几何
WITH tsp_result AS (
    SELECT * FROM pgr_tsp(
        'SELECT * FROM distance_matrix_table',
        1
    )
)
SELECT
    t.seq,
    t.node,
    s.name,
    s.geom,
    t.cost AS leg_distance,
    t.agg_cost AS cumulative_distance
FROM tsp_result t
JOIN stores s ON t.node = s.id
ORDER BY t.seq;

9.8 带容量限制的 VRP

-- 简化的车辆路径规划示例
-- 使用 pgr_dijkstra 的多起终点版本

-- 为多个客户找到最近的配送中心
WITH depots AS (
    SELECT id, name, geom FROM facilities WHERE type = '配送中心'
),
customers AS (
    SELECT id, name, geom FROM customers WHERE status = 'pending'
),
assignments AS (
    SELECT
        c.id AS customer_id,
        c.name AS customer_name,
        d.id AS depot_id,
        d.name AS depot_name,
        ST_Distance(c.geom::geography, d.geom::geography) AS distance_m,
        ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY ST_Distance(c.geom::geography, d.geom::geography)) AS rn
    FROM customers c
    CROSS JOIN depots d
)
SELECT customer_id, customer_name, depot_id, depot_name, distance_m
FROM assignments
WHERE rn = 1
ORDER BY depot_id, distance_m;

9.9 转弯限制

-- 创建转弯限制表
CREATE TABLE restrictions (
    id SERIAL PRIMARY KEY,
    cost DOUBLE PRECISION,
    path INTEGER[]  -- 受限的边序列
);

-- 插入限制(例如:禁止从边 1 直接转到边 3)
INSERT INTO restrictions (cost, path) VALUES
(-1, ARRAY[1, 3]),  -- -1 表示禁止
(-1, ARRAY[5, 7]);

-- 使用带转弯限制的路径查询
SELECT * FROM pgr_trsp(
    'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
    'SELECT id, cost, path FROM restrictions',
    1, 10,
    directed := true
);

9.10 等时圈分析

等时圈(Isochrone)表示从某点出发,在指定时间内能到达的区域。

-- 创建等时圈函数
CREATE OR REPLACE FUNCTION isochrone(
    start_lng DOUBLE PRECISION,
    start_lat DOUBLE PRECISION,
    max_cost DOUBLE PRECISION,  -- 最大通行成本(米)
    step_cost DOUBLE PRECISION DEFAULT 500  -- 步长(米)
) RETURNS TABLE(
    cost_level DOUBLE PRECISION,
    geom GEOMETRY
) AS $$
DECLARE
    start_node INTEGER;
BEGIN
    -- 找到最近节点
    SELECT source INTO start_node
    FROM ways
    ORDER BY geom <-> ST_SetSRID(ST_MakePoint(start_lng, start_lat), 4326)
    LIMIT 1;

    -- 使用 drivingDistance 计算可达范围
    RETURN QUERY
    WITH reachable AS (
        SELECT node, agg_cost
        FROM pgr_drivingDistance(
            'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
            start_node,
            max_cost,
            directed := true
        )
    ),
    grouped AS (
        SELECT
            CEIL(r.agg_cost / step_cost) * step_cost AS cost_level,
            array_agg(r.node) AS nodes
        FROM reachable r
        GROUP BY CEIL(r.agg_cost / step_cost)
    )
    SELECT
        g.cost_level,
        ST_ConvexHull(ST_Collect(v.the_geom)) AS geom
    FROM grouped g
    JOIN ways_vertices_pgr v ON v.id = ANY(g.nodes)
    GROUP BY g.cost_level
    ORDER BY g.cost_level;
END;
$$ LANGUAGE plpgsql;

-- 使用:15 分钟步行等时圈(步行速度 1.2m/s,15分钟 = 1080m)
SELECT cost_level, geom
FROM isochrone(116.4074, 39.9042, 1080, 200);

9.11 性能优化

路网预处理

-- 1. 只加载需要的路网子集
SELECT pgr_createTopology('ways', 0.00001, 'geom', 'gid',
    rows_where := 'road_class IN (''主干道'', ''快速路'', ''高速'')');

-- 2. 添加路网分区
ALTER TABLE ways ADD COLUMN partition_id INTEGER;

-- 3. 使用分区查询加速
SELECT * FROM pgr_dijkstra(
    'SELECT gid AS id, source, target, cost, reverse_cost
     FROM ways WHERE partition_id = ANY(ARRAY[1, 2, 3])',
    1, 10
);

缓存热门路线

-- 创建路线缓存表
CREATE TABLE route_cache (
    id SERIAL PRIMARY KEY,
    start_node INTEGER,
    end_node INTEGER,
    route_geom GEOMETRY,
    total_cost DOUBLE PRECISION,
    created_at TIMESTAMP DEFAULT now()
);

CREATE INDEX idx_route_cache_nodes ON route_cache(start_node, end_node);

-- 查询前先检查缓存
CREATE OR REPLACE FUNCTION get_cached_route(
    p_start INTEGER, p_end INTEGER
) RETURNS TABLE(route_geom GEOMETRY, total_cost DOUBLE PRECISION) AS $$
BEGIN
    -- 检查缓存
    RETURN QUERY
    SELECT rc.route_geom, rc.total_cost
    FROM route_cache rc
    WHERE rc.start_node = p_start AND rc.end_node = p_end
      AND rc.created_at > now() - INTERVAL '1 day';

    IF NOT FOUND THEN
        -- 缓存未命中,计算新路线并缓存
        INSERT INTO route_cache (start_node, end_node, route_geom, total_cost)
        SELECT p_start, p_end,
               ST_LineMerge(ST_Collect(w.geom)),
               MAX(d.agg_cost)
        FROM pgr_dijkstra(
            'SELECT gid AS id, source, target, cost, reverse_cost FROM ways',
            p_start, p_end
        ) d
        JOIN ways w ON d.edge = w.gid;

        RETURN QUERY
        SELECT rc.route_geom, rc.total_cost
        FROM route_cache rc
        WHERE rc.start_node = p_start AND rc.end_node = p_end;
    END IF;
END;
$$ LANGUAGE plpgsql;

9.12 本章小结

要点说明
pgRoutingPostgreSQL/PostGIS 的路径规划扩展
路网拓扑pgr_createTopology 自动生成
Dijkstra最通用的最短路径算法
A*有坐标时更快的最短路径
TSP旅行商问题,配送路线优化
等时圈pgr_drivingDistance + ST_ConvexHull

扩展阅读