简介这是一份围绕电动汽车充电站选址优化问题的学术论文聚焦新能源汽车配套设施规划中的关键问题适合交通规划、电气工程及新能源领域的研究人员、相关专业学生和从业者参考。文档从充电需求点分布、服务半径与最大服务数量等角度出发梳理影响选址布局的因素与原则提出以充电站收益最大化为目标的非线性函数并利用遗传算法进行求解优化最后通过仿真得出充电站位置和服务范围案例与建模过程可为同类选址项目提供方法借鉴。资源为单个PDF文件压缩包大小约327KB内容包含摘要、引言、模型构建、算法设计与仿真结果等完整期刊论文结构便于直接阅读与引用。目前已有215人学习/下载对于正在开展电动汽车充电设施规划、选址建模或相关课程设计的研究者而言是一份较紧凑的参考文献。1. 充电站选址优化不是拍脑袋认清这是设施选址问题再动手充电站选址这件事外行看是“哪里车多建哪里”内行知道它是一个几十万到上千万投资额的决策问题站建偏了利用率上不去回本周期被无限拉长站建密了自家几个场站先互相抢客流。电动汽车充电站选址优化研究本质上是把“在哪些候选地块建站、建几个、服务哪些需求点”变成数学模型用需求分布、建设成本、服务半径和容量约束算出最优建站组合。适合正在做充电网络规划的运营工程师、做投资测算的咨询人员以及写相关课题但缺落地思路的学生。这篇文章我不打算复述论文式的抽象推导而是按我做过方案的路子来讲先把模型立住再给最小可跑的代码和参数表最后把最容易翻车的几个坑一个个拆开。2. 选址建模三类经典模型、目标函数与参数标定2.1 三类经典选址模型P-中位、最大覆盖与 P-中心怎么选先别急着写代码选址优化最怕的就是模型选错。同一个城市数据P-中位模型会把站往市中心挤最大覆盖模型则会优先填满外围空白区结果能差出几条街。P-中位模型追求的是所有需求点到最近站点的加权距离总和最小。它衡量的是“平均出行成本”适合城市里的快充网络做均摊覆盖因为你的用户大多是通勤顺路补电和日常随机充电不希望在某个方向绕太远。代价是它允许个别偏远需求点距离很差只要总加权距离小就行所以纯用这个模型郊区可能被牺牲。最大覆盖模型则是在给定建站数量内覆盖尽可能多的需求权重。它适合早期扩张阶段比如运营商第一年铺网络预算只够建 10 个站目标就是把城市的主要居住区和商圈先盖住至于已经被覆盖的需求点到站多远模型不太关心。这个模型产出的方案往往更“好看”因为覆盖率指标容易汇报。P-中心模型和前面两个都不一样它要把“最远那个需求点到最近站的距离”压到最小是典型的 min-max 问题。适合高速服务区、城际走廊这类“最坏情况不能太远”的场景。城市充电站如果直接套 P-中心结果会让你多建很多不必要的站因为只是为了照顾某一个极端偏远点。选型我一般看两个问题业务目标是效率还是公平是预算内覆盖还是兜底保障。城市快充网络我首选最大覆盖或 P-中位两者结合也行——第一轮用最大覆盖定骨架第二轮在已覆盖区域内用 P-中位微调具体点位。2.2 一个可落地的混合整数规划变量、约束与目标函数拿到实际问题后我习惯把它建模成混合整数规划MILP因为这类问题有成熟的求解器可以直接跑不需要一开始就上启发式算法。模型变量应包含两类一是候选站是否建设的 0-1 变量二是需求点是否分配给某个候选站的 0-1 变量。目标函数至少拆成两段建设成本和用户行驶成本。建设成本来自候选站列表里的土地、变压器、充电桩和施工费用用户行驶成本用需求点的权重乘到最近站点的距离再求和权重越大代表这个片区越需要被认真对待。约束条件至少有四条每个需求点最多由一个站服务未建设的站不能分配任何需求超出覆盖半径的分配必须被禁止总建站数量不超过规划上限。如果想让模型更接近真实运营还需要再加一条容量约束某个站服务的总需求权重不能超过它的实际服务能力比如 8 个 120kW 桩一天最多服务多少车次。加了容量约束之后问题的求解难度会明显上一个台阶这个在第四章会展开讲。提示目标函数里如果只有成本而没有覆盖率约束很多情况下最优解是一个站都不建。必须额外加“被覆盖的需求权重不低于总需求的一定比例”这类硬约束模型才会给出有业务意义的方案。2.3 参数标定覆盖半径、需求权重与容量上限的初值参数写得太随意模型再漂亮也算不出可用的站址。我的习惯是先按业务经验定初值再用敏感性分析调整而不是一上来就搞复杂的标定算法。覆盖半径 R 是最关键的一个参数它的取值直接决定一个站“管多宽”。城市内公共快充站我一般取 2 到 3 公里郊区取 5 到 8 公里高速服务区场景不叫半径而是沿着走廊每隔 30 到 50 公里布局一个节点。这个半径不是拍脑袋定的它跟用户到站后愿意接受的绕行时间有关城市里 3 公里大约对应 10 分钟车程再远用户就宁可用慢充了。需求权重 w 的来源常见是三类兴趣点热度、人口居住密度、已有充电订单分布。三类数据各自归一化之后按 0.3、0.3、0.4 加权合并成一个综合权重。有历史订单时权重直接用订单量标定最可靠而不是用熵权法对 POI 做纯数学加权。容量参数按“桩数 × 日均服务车次 × 高峰集中系数”估算例如一个站 8 个桩、每桩日均服务 8 车次、高峰集中系数取 1.5那这个站一天的实际服务能力就是 96 车次。成本参数直接来自候选地块报价和电网接入勘察没有勘察数据时用同城市已建站点的决算平均值估算不要拍脑袋。3. 最小可行求解用 Python PuLP 跑通 50 个候选站的选址实验3.1 准备四类数据从 POI、人口到路网与电网接入点选址模型需要的数据没那么玄关键是四种数据要对齐坐标系、对齐时间范围。第一类是需求侧数据包括兴趣点、小区人口分布、已有充电订单这是生成需求栅格的原料第二类是候选站数据包括可用地块经纬度、预估建设成本、可容纳桩数可以由规划同事提供初筛名单第三类是距离数据候选站到每个需求点的行驶距离或时间量大优先用地图 API 批量计算预算有限时先用米制坐标下的欧氏距离乘 1.3 的绕路系数近似第四类是边界条件包括规划建站上限、可用总投资、电网接入容量。数据格式上我建议全部整理成 CSV 或 GeoJSON坐标系统一到 GCJ-02避免因为坐标系错位导致站址落到马路上。表格字段至少包含候选站的 id、lng、lat、build_cost、capacity需求点的 gid、demand_w、center_lng、center_lat。距离矩阵单独存成一个稀疏关系表cand_id、gid、dist_m。3.2 用 Python 生成需求栅格500 米网格如何聚合权重需求侧原始数据是一堆散点不能直接当需求点进模型否则变量数量爆炸。我一般先把城市按 500 米 × 500 米网格切分把散点聚合成网格中心点作为需求点。网格大小影响求解规模500 米适合主城区1 公里适合全市范围粗选。先把经纬度转成米制坐标再切网格这样格子才是正方形。import pandas as pd import numpy as np # 读取兴趣点数据字段至少包含 lng、lat、weight poi pd.read_csv(poi_points.csv) # 转成 Web Mercator 米制坐标避免直接用经纬度相减算距离 def web_mercator(lng, lat): x lng * 20037583.34 / 180.0 y np.log(np.tan((90.0 lat) * np.pi / 360.0)) / (np.pi / 180.0) y y * 20037583.34 / 180.0 return x, y poi[x], poi[y] zip(*poi.apply(lambda r: web_mercator(r[lng], r[lat]), axis1)) # 500 米网格聚合成需求栅格 GRID_SIZE 500 poi[gx] (poi[x] // GRID_SIZE).astype(int) poi[gy] (poi[y] // GRID_SIZE).astype(int) # 同一个格子内的权重累加得到需求点的需求权重 demand poi.groupby([gx, gy]).agg( demand_w(weight, sum), center_lng(lng, median), center_lat(lat, median) ).reset_index() # 剔除权重过低的空格子减少后续求解规模 demand demand[demand[demand_w] 1.0] print(f需求栅格数: {len(demand)}) demand.to_csv(demand_grid.csv, indexFalse)这段代码的核心是把散点兴趣点聚合成可以直接参与选址模型的需求点。web_mercator函数解决的是坐标系问题经纬度的 1 度在不同纬度代表的实际距离不一样直接相减算距离会产生系统性偏差。GRID_SIZE是网格边长取值越小需求点越密集求解变量越多如果数据覆盖的是全市而非主城区我建议改成 1000 再跑一次对比。最后过滤掉demand_w 1.0的格子是为了去掉偶然出现的孤立点避免模型为了覆盖一个权重极低的需求点而多建一个站。3.3 用 PuLP 求解选址实例覆盖率约束与候选站上限数据就绪后用 PuLP 加内置 CBC 求解器就能处理小规模实例。下面的代码假设候选站 50 个以内、需求点 300 个以内变量规模在可控范围内。import pandas as pd import pulp # 读取三张表 candidates pd.read_csv(candidate_sites.csv) # id, lng, lat, build_cost, capacity demand pd.read_csv(demand_grid.csv) # gid, demand_w, center_lng, center_lat dist pd.read_csv(distance_matrix.csv) # cand_id, gid, dist_m R 3000 # 覆盖半径单位米按市区快充经验值取 3km P 10 # 规划最多建站数量 MIN_COVER 0.8 # 需求覆盖率下限 I candidates[id].tolist() J demand[gid].tolist() c dict(zip(candidates[id], candidates[build_cost])) cap dict(zip(candidates[id], candidates[capacity])) w dict(zip(demand[gid], demand[demand_w])) d {(r[cand_id], r[gid]): r[dist_m] for _, r in dist.iterrows()} prob pulp.LpProblem(ev_charging_site_selection, pulp.LpMinimize) # 决策变量候选站是否建设、需求点是否分配给某个站、是否被覆盖 y pulp.LpVariable.dicts(build, I, catBinary) x pulp.LpVariable.dicts(assign, (I, J), catBinary) cov pulp.LpVariable.dicts(covered, J, catBinary) # 目标建设成本 加权距离成本0.001 是元/米尺度系数避免距离项反向压倒成本项 prob ( pulp.lpSum(c[i] * y[i] for i in I) pulp.lpSum(0.001 * w[j] * d[(i, j)] * x[i][j] for i in I for j in J) ) # 每个需求点最多分配到一个候选站且只有建站才能分配 for j in J: prob pulp.lpSum(x[i][j] for i in I) 1 prob cov[j] pulp.lpSum(x[i][j] for i in I) for i in I: for j in J: prob x[i][j] y[i] # 超出覆盖半径的分配直接禁止 for i in I: for j in J: if d[(i, j)] R: prob x[i][j] 0 # 容量约束站服务的需求权重不超过其日服务能力 for i in I: prob pulp.lpSum(w[j] * x[i][j] for j in J) cap[i] * y[i] # 覆盖率约束被覆盖需求权重不低于总权重的一定比例 prob pulp.lpSum(w[j] * cov[j] for j in J) MIN_COVER * pulp.lpSum(w[j] for j in J) # 建站数量上限 prob pulp.lpSum(y[i] for i in I) P solver pulp.PULP_CBC_CMD(msgFalse) prob.solve(solver) print(求解状态:, pulp.LpStatus[prob.status]) selected [i for i in I if y[i].value() and y[i].value() 0.5] print(选中站点:, selected) # 计算并输出实际覆盖率方便和约束目标对比 covered_w sum(w[j] * cov[j].value() for j in J) total_w sum(w.values()) print(f实际覆盖率: {covered_w / total_w:.2%})这段代码里cov[j] pulp.lpSum(x[i][j] for i in I)是关键它把一个需求点是否被覆盖定义成“是否被任意一个站分配”避免覆盖率和分配脱节。目标函数里的0.001经常被忽略但非常重要建设成本单位是万元距离成本单位是元如果直接用原始数值相加距离项会小到可以忽略模型就退化成纯成本模型。覆盖率 0.8、最多建 10 个站、容量约束三条共同防止模型给出“只建一个最便宜站”的极端解。如果求解状态是 Infeasible优先检查是不是覆盖半径太小或容量上限太低。3.4 结果不理想时先调哪个参数模型跑完不等于工作结束。我拿到结果第一眼先看三样东西选中站点是否集中在少数几个热点片区、实际覆盖率是贴着 0.8 还是超出很多、有没有某个站的容量利用率超过 90%。如果站点扎堆通常是距离成本系数 0.001 设得太小导致模型宁愿扎堆也不愿意照顾外围需求这时候把系数上调到 0.003 再跑。如果覆盖率贴着 0.8 不动说明 MIN_COVER 成了起作用的约束可以把这个值提高到 0.85 或 0.9观察成本增加多少。如果个别站容量爆表则说明候选站名单本身有问题比如某个地块虽然成本低但它恰好在一圈高需求点的正中心需要人工权衡是否把它拆成两个小站。4. 候选点一多就换启发式遗传算法编码、适应度与参数表4.1 精确求解的黑匣子变量数、求解时间与规模阈值上一章的例子只有几千个变量CBC 求解器几秒钟就出结果。但当候选站上升到 500 个、需求点上升到 2000 个分配变量就是 100 万个二元变量带容量约束之后CBC 经常跑一两个小时还不见收敛输出一个还凑合的可行解就停在那里。这不是代码写错了而是混合整数规划本身的组合爆炸。我的经验阈值大致是无容量约束、变量数 5 万以下开源求解器可以当作常规武器变量数超过 20 万就该考虑换商业求解器或者上启发式算法。这里提醒一点商业求解器不是万能后悔药哪怕是 Gurobi遇到带容量约束的大规模选址仍然会把大量时间花在证明最优性上。实际项目中我们往往只需要一个“比人工方案好 15% 且能被业务接受”的解所以启发式算法不是退而求其次而是更聪明的做法。4.2 遗传算法编码与约束修补染色体怎么设计才算合理用遗传算法求解选址问题最自然的编码方式是二进制染色体染色体长度等于候选站数量某位为 1 表示建设该站0 表示不建。但直接随机生成初始种群会制造大量不满足“最多建 P 个站”的个体如果靠适应度函数惩罚这些个体搜索效率会很差。常见做法是在生成个体时做约束修补先随机选若干个位点置 1数量控制在 P 以内其余全部置 0这样每个个体天然满足数量上限。适应度函数里要同时处理覆盖率和容量约束。覆盖率低于下限时加大惩罚项容量超限时按超出比例线性惩罚。下面是一段适应度函数的核心逻辑可以套进任意 Python 遗传算法框架中。def fitness(individual, sites, demand, dist, R, P, min_cover, alpha0.7): # individual: 0/1 列表长度等于候选站数量 build_cost sum(sites[i].build_cost for i in range(len(individual)) if individual[i] 1) if sum(individual) P: return 1e9 # 超过建站数量上限直接给极大惩罚 total_w demand[demand_w].sum() covered_w 0.0 dist_cost 0.0 for _, r in demand.iterrows(): in_range [ i for i in range(len(individual)) if individual[i] 1 and dist[(i, r[gid])] R ] if not in_range: continue # 如果有多个站都在半径内按最近的那个计距离成本 nearest min(dist[(i, r[gid])] for i in in_range) covered_w r[demand_w] dist_cost r[demand_w] * nearest coverage covered_w / total_w penalty 0.0 if coverage min_cover: penalty (min_cover - coverage) * 1e6 # 覆盖率惩罚项 return alpha * build_cost (1 - alpha) * dist_cost penalty这段代码里alpha是成本与距离的权重0.7 意味着更看重建设成本控制适合预算紧张的项目反过来 0.4 则更看重用户体验布局会更分散。penalty的1e6是经验值必须比正常目标值大一个数量级才能把不满足覆盖率的个体淘汰掉。距离矩阵dist用的是上一章算好的关系表遗传算法里需要频繁查dist[(i, r[gid])]建议预存成字典而不是在循环里反复读 CSV否则一次适应度评估就会慢到无法接受。4.3 适应度函数与关键参数种群、交叉、变异怎么定遗传算法不是黑匣子参数设对了效果立竿见影。下面的参数表是我在充电站选址场景里常用的初值可以直接抄。参数经验取值调整方向种群规模40 到 100候选站多时取大否则 40 够用最大迭代代数100 到 300看适应度曲线是否到平台期交叉概率0.7 到 0.9偏低会让种群很快同质化变异概率1/L 到 3/LL 为染色体长度1/L 是底线小于它等于没变异选择方式二元锦标赛简单稳定不容易被超强个体主导精英保留数2 到 5保留前几名防止最好解丢失终止条件连续 30 代最优适应度无改进比死等 300 代更省时间需要特别说明的是变异概率。很多初学者把变异概率设成 0.01 以下对于长度 500 的染色体这意味着平均每代只有 5 个基因位会翻转搜索基本停滞。我一般按染色体长度的倒数取比如 500 个候选站就把变异率设到 0.002 到 0.006。种群规模不需要太大因为容量约束会把大量随机个体判死有效的多样性更多来自变异而不是种群大小。5. 选址老翻车的 5 个坑距离、权重与容量约束都踩过5.1 直接用经纬度算直线距离选出来的站全偏到中心现象用同一份数据跑模型无论怎么调参数选中站点总是往城市几何中心聚集且和实际车流热力对不上。原因经纬度坐标在不同纬度下代表的实际距离完全不同直接用经纬度差值当距离相当于把高纬度的 1 度经度当成赤道的 1 度距离被系统性拉近导致模型误以为外围需求点离中心很近。更隐蔽的是国内地图数据普遍是 GCJ-02 坐标系而部分开源数据是 WGS-84两套坐标系混用会让站点偏移几百米到几公里。解决所有坐标统一到同一坐标系先按章节 3.2 的 Web Mercator 公式转成米制再算距离。市区内如果有路网数据优先用最短路距离拿不到路网时米制欧氏距离乘 1.3 的绕路系数比直接经纬度算直线更接近真实。5.2 用出行需求顶替充电需求覆盖率虚高一倍现象模型算出的覆盖率高达 90%但建完站后发现部分场站日订单量只有预期的三分之一。原因充电需求和出行需求是两回事。一个片区白天人流量大只能说明有人经过不代表他们会停下来充电。充电行为大多发生在目的地停留期间住宅过夜、商场购物、办公楼上班。如果直接把人口热力或 POI 总量当需求权重会把地铁站这种人流大但停车时间短的地方定义成高需求点。解决给不同类型 POI 设置不同的充电折算系数而不是统一当作一个权重。我常用的折算系数表如下POI 类型充电折算系数理由居住区0.2 到 0.3过夜慢充为主快充需求集中在早高峰前办公区0.15 到 0.25白天停车时间长快充需求中等商超综合体0.3 到 0.4停留 1 到 2 小时是高价值快充场景交通枢纽0.1 到 0.2车流大但停留短快充需求反而低高速服务区0.5 到 0.6停留目的性强快充需求占比高5.3 容量约束只写上限、不写排队桩数变成纸面数据现象模型输出某个站配置 6 个桩理论产能充足但实际高峰时段排队半小时用户差评如潮。原因容量约束写的是“一天最多服务多少车次”这是个总量指标没考虑充电需求的时间分布。充电高峰集中在傍晚和周末峰值小时需求可能是平均值的 3 到 4 倍。一个站在高峰期可能出现排队而总容量数据看起来还是够的。解决在容量约束里打一个高峰集中系数。比如日均服务能力 96 车次峰值系数取 1.5则模型里该站的容量上限设为 64 车次相当于强制预留 50% 的裕量。如果项目要求排队等待时间不超过 10 分钟就要引入 M/M/C 排队模型的近似公式把等待时间折算成容量折减。第一阶段用峰值系数足够第二阶段再上排队仿真。5.4 成本权重拍脑袋算法结果运营不接受现象算法跑出一版“全局最优”方案业务部门看到之后说成本高、位置偏直接推翻重做。原因目标函数里建设成本、距离成本、覆盖率三者的权重是主观定的不同权重组合能产出完全不同的站址组合。如果建设成本权重给得过高模型会优先选郊区便宜地块如果距离成本权重过高模型又把站往租金最贵的市中心挤。运营部门关注的其实是单站盈亏而不是全局最优。解决不要一次性给死权重。先把纯成本模型和纯覆盖模型各跑一遍得到两个极端解看总成本和覆盖率的差值范围。再结合实际投资预算圈定权重区间。我习惯把建设成本权重固定在 0.6 到 0.7 之间然后用距离成本权重做敏感性分析表给业务方展示“多花 200 万能把平均距离从 5.2 公里压到 4.1 公里”让他们拍板而不是算法替他们拍板。5.5 只做静态快照不做增长预留第二年就得返工现象按当前需求分布算出的最优方案第二年就出现部分站点排队严重而另一些新建小区周边又没有站。原因充电需求不是静态的。新能源车保有量在增长单车充电功率从 60kW 往 120kW、240kW 走需求重心也在跟着城市扩张移动。静态快照模型只优化当前时刻没有考虑站点的可扩展性和电网预留容量。解决做至少三套需求情景保守、基准、激进分别对应未来 3 到 5 年的保有量增速和平均功率提升幅度。在候选站数据里增加一个“可扩展性”字段比如可扩容桩数、电网接入余量然后在约束里对高增长区域设置最低覆盖率要求。这样算出的站址不会在第二年就被迫废弃至少保留了扩容空间。6. 选址结果的闭环验证敏感性分析、模型校准与决策输出模型跑完不是终点还要验证它是否值得信任。我最常用的验证方式是把已有运营站点的历史订单数据拿出来让模型只在这些站点里做一次重算对比模型输出的覆盖权重和真实订单量的相关性。如果相关系数低于 0.6说明需求权重定义有问题回去先调 POI 折算系数而不是继续调求解器参数。敏感性分析是给决策层看的第二张牌。固定其他参数单独把建站数量上限从 5 逐步增加到 15记录每个方案的总成本和覆盖率画成一张曲线表。你会看到覆盖率从 0.6 涨到 0.8 时成本增长平缓再从 0.8 涨到 0.9 时成本陡增。曲线拐点附近就是预算和体验平衡的位置。同样方法可以用于覆盖半径和距离成本权重。最后一步把算法输出整理成三档候选站清单必选站、备选站、淘汰站。必选站是多个情景下都稳定出现的站这类站点不存在争议直接进建设计划备选站是在基准情景下成立、但激进情景下需要观察的站放进储备库等需求数据更新后再评估淘汰站则可以明确告诉业务方不建议投资的理由。这样输出的结果不是一堆坐标而是一份决策依据。我自己的习惯是每份选址报告附一页“模型未考虑因素”例如峰谷电价政策变化、周边竞争场站的价格战、土地租期风险。这页内容常常比模型本身还重要。选址优化研究的价值不只是算出站址而是帮你把模糊的直觉变成可以比较、可以推翻、可以迭代的决策过程希望帮到你。本文还有配套的精品资源点击获取