电动汽车路线规划

1. 简介

在不断发展的汽车制造和物流领域,优化电动汽车 (EV) 的路线对于实现效率、可持续性和成本管理至关重要。电动汽车路线规划用例解决了关键的业务挑战:即如何在供应链中通过声明式方法找到兼顾能源限制、充电需求和时间限制的最优路径。通过利用 Neo4j 的 Cypher 25,组织可以对复杂的图遍历进行建模,为运输零部件的电动车队规划路线,确保能源消耗最小化并符合运营限制。这些洞察有助于确定高效的物流策略、减少停机时间并降低制造供应链中的风险。

2. 应用场景

为了理解电动汽车路线规划用例的价值,请考虑汽车制造业中路线效率低下严重影响运营的现实场景。以下三个关键领域重点介绍了这些挑战:

  1. 供应链物流优化

    • 由于未考虑能源消耗或充电站的可用性,运输零部件的电动车队可能会面临路线规划低效的问题。

    • 如果没有对路径依赖性的深刻洞察,意外延误可能会破坏准时制 (JIT) 制造流程。

    • 往往缺乏对路线的整体视图,从而忽略了节能绕行或大宗运输的机会。

  2. 能源与风险管理

    • 能源状态映射不足,导致难以评估整个车队面临的电池耗尽或时间超支风险。

    • 过度依赖特定的充电基础设施可能在高峰需求或停电期间导致连锁故障。

    • 传统的路线规划系统难以应对动态限制,使供应链中系统性风险的预测变得复杂。

  3. 可持续性与合规性

    • 日益严格的法规要求对节能实践进行透明报告,以达到环境标准。

    • 如果没有统一的工具,手动路线规划既容易出错又耗时。

    • 如果组织无法向监管机构证明其物流经过优化且具有可持续性,则可能面临罚款和声誉受损的风险。这些场景强调了采用 Neo4j 电动汽车路线规划(配合 Cypher 25)等先进解决方案的必要性,该方案利用图技术对动态路线进行建模、分析和可视化,为制造业的业务和技术用户提供关键洞察。

3. 解决方案

像 Neo4j 这样的先进图数据库对于处理汽车制造业中相互关联的物流数据至关重要。它们擅长管理动态关系,使得对路线、约束和状态进行建模变得简单直接。通过将数据表示为图,组织可以发现最优路径、模拟场景并得出可操作的洞察——从而增强决策能力、可持续性和供应链韧性。

3.1. 图数据库如何提供帮助?

图数据库为汽车制造业中的电动汽车路线规划挑战提供了强有力的解决方案。以下是图数据库不可或缺的五个关键原因:

  1. 动态关系建模:图数据库天然适合处理道路和充电回路等复杂连接,能够捕获关系数据库无法高效表示的依赖关系。

  2. 实时状态感知剪枝:它们能够即时评估状态(例如电池 SOC、时间),瞬间剔除无效路线,实现更快、更准确的规划。

  3. 全面的路线可视化:图提供物流网络的完整视图,揭示电动汽车零部件运输中隐藏的效率问题和风险。

  4. 灵活的场景模拟:利用可重复元素等特性,图支持建模循环(例如多次充电),有助于主动调整制造计划。

  5. 简化的合规性与优化:图简化了能源使用和路线报告流程,确保在符合可持续发展法规的同时最小化成本。这些能力使得图数据库成为汽车组织获取洞察并解决电动汽车路线规划中多方面问题的核心。

4. 建模

本节演示了示例图上的 Cypher 查询。目的是展示查询结构并指导生产环境中的数据建模。我们将基于下方的数据模型,使用一个包含若干节点的小型图

4.1. 数据模型

ev routing model
图 1. 数据模型

4.1.1 所需数据字段

以下是开始所需的字段

  • Geo 节点

    • name:可读的位置名称(例如城市或交叉路口)

    • geo:用于距离计算的地理空间点(例如 point({srid:4326, x:lon, y:lat}))

  • ChargingStation 节点(Geo 的子标签)

    • power_kw:充电功率(千瓦)

  • Car 节点

    • id:唯一的车辆标识符

    • battery_capacity_kwh:总电池容量

    • efficiency_kwh_per_km:每公里能效

    • current_soc_percent:起始充电状态百分比

  • ROAD 关系

    • distance_km:位置间的距离

    • speed_limit_kph:最高限速

    • hourly_expected_speed_kph:24 小时预期速度列表

  • CHARGE 关系(ChargingStation 上的自循环)

    • time_in_minutes:充电持续时间

    • power_kw:此充电段的功率

    • station_id:关联的充电站 ID

4.1.2 所需参数

  • 从巴黎到马赛,使用车辆 1

    :params {
      max_mins: 700, // maximum allowed duration of the trip in minutes
      car_id: "Car1",
      source_geo_name: "Paris", // place of departure place
      target_geo_name: "Marseille", // place of arrival
      detour_ratio: 1.2, // You need to be at any step on traversal at most at detour_ratio * distance(source, target) from source or target
      min_soc: 1, // state of charge can't be below min_soc percents
      max_soc: 100, // state of charge can't be above max_soc percents
      departure_datetime: datetime("2025-10-15T17:46:16.114000000Z")
    }
  • 从勒阿弗尔到尼斯,使用车辆 1

    :params {
      max_mins: 1000,
      car_id: "Car1",
      source_geo_name: "Le Havre",
      target_geo_name: "Nice",
      detour_ratio: 1.2,
      min_soc: 1,
      max_soc: 100,
      departure_datetime: datetime("2025-10-15T17:46:16.114000000Z")
    }
  • 从勒阿弗尔到尼斯,使用车辆 2

    :params {
      max_mins: 1000,
      car_id: "Car2",
      source_geo_name: "Le Havre",
      target_geo_name: "Nice",
      detour_ratio: 1.2,
      min_soc: 1,
      max_soc: 100,
      departure_datetime: datetime("2025-10-15T17:46:16.114000000Z")
    }

4.2. 演示数据

以下 Cypher 语句将在 Neo4j 数据库中创建示例图:

MATCH (n) DETACH DELETE n;
CREATE (paris:City {lat: 48.8566, lon: 2.3522, name: 'Paris'}),
 (lyon:City {lat: 45.7640, lon: 4.8357, name: 'Lyon'}),
 (marseille:City {lat: 43.2965, lon: 5.3698, name: 'Marseille'}),
 (bordeaux:City {lat: 44.8378, lon: -0.5792, name: 'Bordeaux'}),
 (strasbourg:City {lat: 48.5734, lon: 7.7521, name: 'Strasbourg'}),
 (lille:City {lat: 50.6292, lon: 3.0573, name: 'Lille'}),
 (toulouse:City {lat: 43.6047, lon: 1.4442, name: 'Toulouse'}),
 (nice:City {lat: 43.7102, lon: 7.2620, name: 'Nice'}),
 (nantes:City {lat: 47.2184, lon: -1.5536, name: 'Nantes'}),
 (montpellier:City {lat: 43.6108, lon: 3.8767, name: 'Montpellier'}),
 (rennes:City {lat: 48.1173, lon: -1.6778, name: 'Rennes'}),
 (reims:City {lat: 49.2583, lon: 4.0317, name: 'Reims'}),
 (grenoble:City {lat: 45.1885, lon: 5.7245, name: 'Grenoble'}),
 (dijon:City {lat: 47.3220, lon: 5.0415, name: 'Dijon'}),
 (lehavre:City {lat: 49.4938, lon: 0.1079, name: 'Le Havre'});

CREATE (cs1:ChargingStation {lat: 48.7566, lon: 2.4522, id: 'CS1', name: 'CS1', power_kw: 150}),
 (cs2:ChargingStation {lat: 45.8640, lon: 4.7357, id: 'CS2', name: 'CS2', power_kw: 200}),
 (cs3:ChargingStation {lat: 43.3965, lon: 5.4698, id: 'CS3', name: 'CS3', power_kw: 350}),
 (cs4:ChargingStation {lat: 44.7378, lon: -0.4792, id: 'CS4', name: 'CS4', power_kw: 100}),
 (cs5:ChargingStation {lat: 48.4734, lon: 7.8521, id: 'CS5', name: 'CS5', power_kw: 250}),
 (cs6:ChargingStation {lat: 50.5292, lon: 3.1573, id: 'CS6', name: 'CS6', power_kw: 150}),
 (cs7:ChargingStation {lat: 43.5047, lon: 1.5442, id: 'CS7', name: 'CS7', power_kw: 200}),
 (cs8:ChargingStation {lat: 43.6102, lon: 7.3620, id: 'CS8', name: 'CS8', power_kw: 300}),
 (cs9:ChargingStation {lat: 47.1184, lon: -1.4536, id: 'CS9', name: 'CS9', power_kw: 120}),
 (cs10:ChargingStation {lat: 43.5108, lon: 3.9767, id: 'CS10', name: 'CS10', power_kw: 180});

CREATE (c0:Car {id: 'Car0', battery_capacity_kwh: 56, efficiency_kwh_per_km: 0.19, current_soc_percent: 39});
CREATE (c1:Car {id: 'Car1', battery_capacity_kwh: 100, efficiency_kwh_per_km: 0.1, current_soc_percent: 75});
CREATE (c2:Car {id: 'Car2', battery_capacity_kwh: 100, efficiency_kwh_per_km: 0.07, current_soc_percent: 50});

MATCH (a:City {name: 'Paris'}), (b:ChargingStation {id: 'CS1'}) CREATE (a)-[:ROAD {distance_km: 10.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Paris'}), (b:City {name: 'Lyon'}) CREATE (a)-[:ROAD {distance_km: 460.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Lyon'}), (b:ChargingStation {id: 'CS2'}) CREATE (a)-[:ROAD {distance_km: 15.0, speed_limit_kph: 60}]->(b);
MATCH (a:City {name: 'Lyon'}), (b:City {name: 'Marseille'}) CREATE (a)-[:ROAD {distance_km: 310.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Marseille'}), (b:ChargingStation {id: 'CS3'}) CREATE (a)-[:ROAD {distance_km: 12.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Marseille'}), (b:City {name: 'Nice'}) CREATE (a)-[:ROAD {distance_km: 190.0, speed_limit_kph: 100}]->(b);
MATCH (a:City {name: 'Bordeaux'}), (b:ChargingStation {id: 'CS4'}) CREATE (a)-[:ROAD {distance_km: 8.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Bordeaux'}), (b:City {name: 'Nantes'}) CREATE (a)-[:ROAD {distance_km: 320.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Strasbourg'}), (b:ChargingStation {id: 'CS5'}) CREATE (a)-[:ROAD {distance_km: 10.0, speed_limit_kph: 60}]->(b);
MATCH (a:City {name: 'Strasbourg'}), (b:City {name: 'Reims'}) CREATE (a)-[:ROAD {distance_km: 370.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Lille'}), (b:ChargingStation {id: 'CS6'}) CREATE (a)-[:ROAD {distance_km: 7.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Lille'}), (b:City {name: 'Paris'}) CREATE (a)-[:ROAD {distance_km: 220.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Toulouse'}), (b:ChargingStation {id: 'CS7'}) CREATE (a)-[:ROAD {distance_km: 9.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Toulouse'}), (b:City {name: 'Montpellier'}) CREATE (a)-[:ROAD {distance_km: 340.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Nice'}), (b:ChargingStation {id: 'CS8'}) CREATE (a)-[:ROAD {distance_km: 11.0, speed_limit_kph: 60}]->(b);
MATCH (a:City {name: 'Nice'}), (b:City {name: 'Marseille'}) CREATE (a)-[:ROAD {distance_km: 190.0, speed_limit_kph: 100}]->(b);
MATCH (a:City {name: 'Nantes'}), (b:ChargingStation {id: 'CS9'}) CREATE (a)-[:ROAD {distance_km: 6.0, speed_limit_kph: 50}]->(b);
MATCH (a:City {name: 'Nantes'}), (b:City {name: 'Rennes'}) CREATE (a)-[:ROAD {distance_km: 110.0, speed_limit_kph: 100}]->(b);
MATCH (a:City {name: 'Montpellier'}), (b:ChargingStation {id: 'CS10'}) CREATE (a)-[:ROAD {distance_km: 13.0, speed_limit_kph: 60}]->(b);
MATCH (a:City {name: 'Montpellier'}), (b:City {name: 'Toulouse'}) CREATE (a)-[:ROAD {distance_km: 340.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Rennes'}), (b:City {name: 'Le Havre'}) CREATE (a)-[:ROAD {distance_km: 250.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Reims'}), (b:City {name: 'Paris'}) CREATE (a)-[:ROAD {distance_km: 140.0, speed_limit_kph: 100}]->(b);
MATCH (a:City {name: 'Grenoble'}), (b:City {name: 'Lyon'}) CREATE (a)-[:ROAD {distance_km: 100.0, speed_limit_kph: 100}]->(b);
MATCH (a:City {name: 'Dijon'}), (b:City {name: 'Strasbourg'}) CREATE (a)-[:ROAD {distance_km: 300.0, speed_limit_kph: 110}]->(b);
MATCH (a:City {name: 'Le Havre'}), (b:City {name: 'Lille'}) CREATE (a)-[:ROAD {distance_km: 230.0, speed_limit_kph: 110}]->(b);

// create geo point
MATCH (x: ChargingStation|City)
SET x.geo = point({longitude:x.lon, latitude:x.lat}), x:Geo;

// create geo index
CREATE POINT INDEX point_index_geo
IF NOT EXISTS
FOR (n:Geo) ON (n.geo);

// create charging loops (5 and 15 minutes)
MATCH (cs:ChargingStation)
MERGE (cs)-[:CHARGE {station_id:cs.id, power_kw: cs.power_kw, time_in_minutes: 5}]->(cs)
MERGE (cs)-[:CHARGE {station_id:cs.id, power_kw: cs.power_kw, time_in_minutes: 15}]->(cs);

// create max speed expected hourly time
MATCH ()-[r:ROAD]-()
SET r.hourly_expected_speed_kph =
  [h IN range(0,23) | r.speed_limit_kph];

// Lyon-->Marseille with rush hours
MATCH (x:Geo {name:"Lyon"})-[r {speed_limit_kph: 110}]-(y:Geo {name:"Marseille"})
SET r.hourly_expected_speed_kph =
  [80,80,80,80,80,110,110,110,
  110,110,110,110,110,110,110,110,
  110,80,80,80,80,80,80,80];

5. Cypher 查询

这些 Cypher 查询兼容 Neo4j 2025.08+ 版本和 Cypher 25。

5.1. 应用空间预剪枝到模式以避免绕路

此模式结合了空间过滤,将路径限制在合理的绕行范围内

MATCH REPEATABLE ELEMENTS p = (a:Geo {name: $source_geo_name})
  (() -[rels:ROAD|CHARGE]- (x:Geo
     // Spatial pruning to avoid excessive detours
     WHERE point.distance(x.geo, b.geo) < $detour_ratio * point.distance(a.geo, b.geo)
    AND point.distance(x.geo, a.geo) < $detour_ratio * point.distance(a.geo, b.geo)
  )){1,12}
  (b:Geo {name: $target_geo_name})

5.2. 使用 Cypher 25 进行完整的状态感知路线规划

利用 Cypher 25 进行声明式、状态感知的遍历,通过剪枝生成所有有效路径

// MATCH A QUANTIFIED PATH PATTERN

// CHYPHER version >= 25 and parallel runtime
CYPHER 25 runtime=parallel // Match the vehicle
MATCH (c:Car {id: $car_id})
// Define the path with repeatable elements
MATCH REPEATABLE ELEMENTS p = (a:Geo {name: $source_geo_name})
  (() -[rels:ROAD|CHARGE]- (x:Geo
     // Spatial pruning to avoid excessive detours
     WHERE point.distance(x.geo, b.geo) < $detour_ratio * point.distance(a.geo, b.geo)
    AND point.distance(x.geo, a.geo) < $detour_ratio * point.distance(a.geo, b.geo)
  )){1,12}
  (b:Geo {name: $target_geo_name})

// COMPUTE CURRENT STATE AND PRUNE

// Apply stateful pruning with allReduce
WHERE allReduce(
  // initial state
  current = {soc: c.current_soc_percent, time_in_min: 0.0},  // Initialize state
  r IN rels |  // Accumulate per relationship at traversal time
    CASE
      WHEN r:ROAD
        // state of charge goes down
        THEN {soc: current.soc - (r.distance_km*c.efficiency_kwh_per_km*100) / c.battery_capacity_kwh,
            time_in_min: current.time_in_min
                        + 60.0 *(r.distance_km / r.hourly_expected_speed_kph[
                ($departure_datetime+duration({minutes:current.time_in_min})).hour
                                  ]) }

      WHEN r:CHARGE
        // state of charge goes up
        THEN {soc: current.soc + (r.power_kw*(r.time_in_minutes/60.0)*100) / c.battery_capacity_kwh,
            time_in_min: current.time_in_min + r.time_in_minutes }
    END,
  // Prune if constraints are violated
  // The boolean value needs to be True at each hop
  // Battery should be in an acceptable state
  $min_soc <= current.soc <= $max_soc
    // Travel duration should be under the defined threshold
  AND current.time_in_min <= $max_mins
  )
// Return for next stage
RETURN c, p

5.3. 计算时间和能源消耗以选择最佳路径

此查询与上一个查询通过 NEXT 链接,计算给定路径的总持续时间和能源消耗

// SCORE, ORDER AND SELECT
NEXT

// Score and order paths
RETURN c, p, reduce(current = {soc: c.current_soc_percent, time_in_min: 0.0, energy_kwh: 0.0},
  r IN relationships(p) | CASE
    WHEN r:ROAD
      THEN {soc: current.soc - (r.distance_km*c.efficiency_kwh_per_km*100) / c.battery_capacity_kwh,
          time_in_min: current.time_in_min
                        + 60.0 *(r.distance_km / r.hourly_expected_speed_kph[
                ($departure_datetime+duration({minutes:current.time_in_min})).hour
                                  ]),
          energy_kwh: current.energy_kwh + (r.distance_km * c.efficiency_kwh_per_km)}
    WHEN r:CHARGE
      THEN {soc: current.soc + (r.power_kw*(r.time_in_minutes/60.0)*100) / c.battery_capacity_kwh,
          time_in_min: current.time_in_min + r.time_in_minutes,
          energy_kwh: current.energy_kwh}
  END) AS final_values

ORDER BY final_values.time_in_min ASC,
         final_values.energy_kwh ASC,
         size(relationships(p)) ASC
LIMIT 1
ev routing path
图 2. 车辆 2 从勒阿弗尔到尼斯的最佳路线

5.4. 进一步优化:图重构与高级剪枝

为了增强图以在定向场景中实现更高效的查询,重构道路关系以确保明确的双向性。这可以通过在不存在反向关系的地方添加反向关系,并从原始关系中复制属性来实现

MATCH (a)-[r:ROAD]->(b)
MERGE (b)-[rev_r:ROAD]->(a)
SET rev_r += r{.*}

通过此次重构,道路现在完全双向化(建模为两个方向的定向边),从而在需要时保持方向性的同时允许灵活遍历。

这使得改进后的查询能够结合高级启发式剪枝,以消除循环和不向目标推进的 4 跳段。通过在状态中追踪最近四次到目标的距离(previous_dists)并确保进度(state.previous_dists[0] >= state.previous_dists[-1]),可以剪除不产生实质进展的路径。此外,roads_taken 追踪已使用的道路,以防止在同一方向重复使用同一条道路,从而避免循环。

该剪枝效果显著,足以支持高达 100 万跳的路径长度上限而不会降低性能。以下是更新后的查询

CYPHER 25 runtime=parallel
MATCH (c:Car {id: $car_id})
MATCH (a:Geo {name: $source_geo_name})
MATCH (b:Geo {name: $target_geo_name})
WITH c, a, b, a AS source, b AS target
MATCH REPEATABLE ELEMENTS p =  (source)
  (() -[rels:ROAD|CHARGE]-> (x:Geo
     WHERE point.distance(x.geo, b.geo) < $detour_ratio * point.distance(a.geo, b.geo)
     AND point.distance(x.geo, a.geo) < $detour_ratio * point.distance(a.geo, b.geo)
  )){1,1000000}
  (target)
WHERE allReduce(
  state = {soc: c.current_soc_percent, time_in_min: 0.0, previous_dists: [ 3_000_000_000, 2_000_000_000, 1_000_000_000, point.distance(a.geo, b.geo)], roads_taken: []},
  r IN rels|
    CASE
      WHEN r:ROAD
        THEN {
          soc: state.soc - (r.distance_km*c.efficiency_kwh_per_km*100) / c.battery_capacity_kwh,
          time_in_min: state.time_in_min
                        + 60.0 *(r.distance_km / r.hourly_expected_speed_kph[
                      ($departure_datetime+duration({minutes:state.time_in_min})).hour
                            ]),
          previous_dists: state.previous_dists[1..]+[point.distance(endNode(r).geo, b.geo)],
          roads_taken: state.roads_taken + [elementId(r)]
            }

      WHEN r:CHARGE
        THEN {
          soc: state.soc + (r.power_kw*(r.time_in_minutes/60.0)*100) / c.battery_capacity_kwh,
          time_in_min: state.time_in_min + r.time_in_minutes,
          previous_dists: state.previous_dists,
          roads_taken: state.roads_taken
            }
    END,
  $min_soc <= state.soc <= $max_soc
    AND state.time_in_min <= $max_mins
    AND state.previous_dists[0] >= state.previous_dists[-1]
    AND NOT state.roads_taken[-1] IN state.roads_taken[..-1]
  )
RETURN c, p
NEXT
RETURN c, p, reduce(state = {soc: c.current_soc_percent, time_in_min: 0.0, energy_kwh: 0.0},
  r IN relationships(p) | CASE
    WHEN r:ROAD
      THEN {soc: state.soc - (r.distance_km*c.efficiency_kwh_per_km*100) / c.battery_capacity_kwh,
          time_in_min: state.time_in_min
                        + 60.0 *(r.distance_km / r.hourly_expected_speed_kph[
                ($departure_datetime+duration({minutes:state.time_in_min})).hour
                                  ]),
          energy_kwh: state.energy_kwh + (r.distance_km * c.efficiency_kwh_per_km)}
    WHEN r:CHARGE
      THEN {soc: state.soc + (r.power_kw*(r.time_in_minutes/60.0)*100) / c.battery_capacity_kwh,
          time_in_min: state.time_in_min + r.time_in_minutes,
          energy_kwh: state.energy_kwh}
  END) AS final_values
ORDER BY final_values.time_in_min ASC,
         final_values.energy_kwh ASC,
         size(relationships(p)) ASC
LIMIT 1

此版本提供了卓越的性能。例如,车辆 2 从勒阿弗尔到尼斯的路线在 Neo4j 实例(例如 24 CPU / 128 GB RAM)上执行仅需约 85 毫秒,证明了 Cypher 25 状态感知剪枝对于复杂图遍历的高效性。

© . This site is unofficial and not affiliated with Neo4j, Inc.