智慧环卫项目中,清运车辆通常每隔几秒上报一次位置,一天下来单辆车就会积累数万条轨迹记录。调度中心如果只盯着原始坐标流,很难快速判断哪些区域已经完成收运、哪些垃圾点被遗漏、哪条线路存在空驶。R语言在数据读取、空间分析和组合优化上都有成熟扩展包,可以把这些杂乱的位置报文整理成调度决策需要的信息。下面从数据接入、轨迹清洗、调度优化和部署落地四个角度说明具体做法。

一、GPS定位数据接入与基础解析
环卫车辆上的定位终端一般通过两种方式向服务器发送数据:一种是基于TCP的实时长连接,另一种是定时HTTP批量上报。对于实时接入场景,R可以用socketConnection函数建立连接,再配合jsonlite解析JSON报文。下面这段代码演示了从TCP端口读取一条定位消息并提取经纬度与速度字段的基本过程。
library(jsonlite) con <- socketConnection(host = "192.168.1.50", port = 9001, open = "r", blocking = TRUE) packet <- readLines(con, n = 1, warn = FALSE) msg <- fromJSON(packet) device_id <- msg$device_id lat <- msg$latitude lon <- msg$longitude speed <- msg$speed direction <- msg$heading print(paste(device_id, lat, lon, speed, direction)) close(con)
实际项目中终端上报的字段往往更多,常见的有设备编号、经纬度、速度、方向角、里程、点火状态、报警标志等。R中建议用data.table包进行批量读取,因为该包在数十万行级别的CSV导入和数据筛选上性能明显优于基础数据框。对于已经落盘的历史数据,可以使用fread直接加载,并通过keyby按车辆和时间排序。
library(data.table)
gps_data <- fread("gps_clean.csv", encoding = "UTF-8")
setorder(gps_data, device_id, timestamp)
gps_data[, speed_kmh := speed * 3.6]
gps_data[, status := fifelse(speed_kmh < 5, "slow", "moving")]
print(gps_data[1:5, .(device_id, timestamp, lat, lon, status)])
如果终端上报的是NMEA格式,比如GPGGA或GPRMC语句,则需要单独写解析函数。NMEA语句以逗号分隔字段,纬度格式为度分组合值,需要转换为十进制度。R中的字符串处理函数如strsplit和substr可以完成这项任务。不过目前多数环卫终端已经直接输出JSON,只有在对接旧设备时才需要保留NMEA解析逻辑。
二、轨迹清洗与空间特征提取
原始GPS数据最明显的质量问题是漂移和重复上报。车辆在楼宇密集区或高架桥下会短暂丢失卫星信号,恢复后可能出现数百米的坐标跳跃;有些终端因为网络重传机制,会把同一位置连续上报多次。如果直接把这些数据用于调度分析,垃圾收运点的停留时间会被严重高估,进而影响下一班次的车辆分配。
清洗阶段可以先从速度字段入手,将瞬时速度异常高或坐标落在作业区域之外的点标记为无效。R中可以使用data.table的分组计算快速识别异常点。下面代码删除速度超过120公里每小时且经纬度明显超出城市范围的记录,然后利用前一有效点计算距离增量,剔除漂移过大的点。
library(data.table) library(geosphere) gps_data <- gps_data[lat > 30.0 & lat < 32.0 & lon > 119.0 & lon < 122.0] gps_data <- gps_data[speed_kmh <= 120] gps_data[, prev_lat := shift(lat, 1, type = "lag"), by = device_id] gps_data[, prev_lon := shift(lon, 1, type = "lag"), by = device_id] gps_data[, dist_m := distHaversine(cbind(prev_lon, prev_lat), cbind(lon, lat))] gps_data[is.na(dist_m), dist_m := 0] gps_data <- gps_data[dist_m < 2000]
停留点识别是环卫数据中的关键步骤。垃圾清运车在某一个收集点装卸时,位置会在小范围内持续一段时间。通过计算连续轨迹点之间的时间差和距离差,可以将距离小于30米、持续时间超过2分钟的轨迹段识别为一次有效停留。停留点的质心坐标可以作为该垃圾点的实际位置,用于后续的路线优化和收运台账核对。
library(sf)
gps_sf <- st_as_sf(gps_data, coords = c("lon", "lat"), crs = 4326)
gps_sf$time <- as.POSIXct(gps_sf$timestamp, origin = "1970-01-01", tz = "Asia/Shanghai")
gps_sf <- gps_sf[order(gps_sf$device_id, gps_sf$time), ]
gps_sf$time_diff <- c(0, difftime(gps_sf$time[-1], gps_sf$time[-nrow(gps_sf)], units = "secs"))
gps_sf$dist_to_prev <- st_distance(gps_sf, gps_sf[1, ])
坐标纠偏同样不能忽视。国内地图服务大量使用GCJ-02坐标系,而GPS终端输出的是WGS-84坐标。如果直接把WGS-84坐标叠加到地图底图上,车辆位置会偏离到相邻道路甚至建筑物内部。R中没有统一的纠偏标准包,但可以封装转换函数,或者调用空间数据转换库。调度系统内部应统一使用一种坐标系,入库前完成转换,避免后续分析反复处理。
三、垃圾清运调度优化与可视化看板
在完成轨迹清洗和停留点识别后,就可以把每个垃圾收集点的位置、预计垃圾量、收运时间窗口等字段汇总成调度任务表。清运调度本质上是一个带容量约束的车辆路径问题,目标是在最短总里程或最少车辆数下完成所有收集点的清运。对于中小规模路网,可以先构造距离矩阵,再用启发式算法搜索较优路线。
R中可以用遗传算法扩展包genalg或GA求解旅行商问题的近似解。假设有12个收集点,把访问顺序编码为一条染色体,适应度函数返回该顺序对应的总距离。经过若干代迭代后,可以得到比人工排班更短的清运路线。下面的代码演示了基本流程。
library(genalg)
points_df <- data.frame(
id = 1:12,
lon = runif(12, 119.5, 120.0),
lat = runif(12, 30.5, 31.0)
)
distance_matrix <- as.matrix(dist(points_df[, c("lon", "lat")]))
eval_func <- function(chromosome) {
route <- c(1, chromosome, 1)
total_distance <- 0
for (i in 2:length(route)) {
total_distance <- total_distance + distance_matrix[route[i - 1], route[i]]
}
return(total_distance)
}
ga_result <- rbga.bin(size = 4, popSize = 100, iters = 200, mutationChance = 0.05, evalFunc = eval_func)
best_route <- c(1, order(ga_result$population[1, ]), 1)
print(best_route)
可视化看板是调度系统给调度员最直观的反馈。利用leaflet包可以在交互式地图上同时展示车辆实时位置、历史轨迹、收集点分布和规划路线。调度员通过点击车辆标记查看当前速度、负责片区和剩余收运点数量,也可以手动调整路线后重新计算总里程。
library(leaflet)
leaflet(points_df) %>%
addTiles() %>%
addCircleMarkers(lng = ~lon, lat = ~lat, radius = 6, color = "red", popup = ~paste("收集点", id)) %>%
addPolylines(lng = points_df$lon[best_route], lat = points_df$lat[best_route], color = "blue", weight = 3)
调度优化算法上线前需要做敏感性分析。不同垃圾量分布、交通拥堵时段、车辆载重限制都会影响最优路线。R中可以用历史数据回放不同时间段的收运效率,对比遗传算法、最近邻插入和人工排班的总里程与车辆利用率。分析结果可以用Shiny应用集成成可交互的复盘工具,帮助管理人员判断哪些片区需要增加临时运力。
四、部署与性能优化建议
R脚本处理离线分析任务足够灵活,但一旦要求接近实时更新调度看板,就需要考虑数据存储和计算架构。定位数据建议先写入SQLite或PostgreSQL,R端通过定时任务增量读取最近几分钟的数据,而不是每次全量扫描。RSQLite包可以方便地执行参数化SQL,而dbAppendTable能够快速追加新数据。
library(RSQLite) con_db <- dbConnect(SQLite(), "sanitation.db") dbExecute(con_db, "CREATE TABLE IF NOT EXISTS gps_raw ( device_id TEXT, timestamp INTEGER, lat REAL, lon REAL, speed REAL )") dbAppendTable(con_db, "gps_raw", gps_data) latest <- dbGetQuery(con_db, "SELECT * FROM gps_raw WHERE timestamp > ?", params = list(as.numeric(Sys.time()) - 300)) dbDisconnect(con_db)
空间计算的性能瓶颈主要在距离矩阵生成和空间连接操作。sf包底层依赖GEOS和GDAL,处理几万条轨迹时尚可,但超过百万条时建议先用data.table做粗略过滤,再对候选子集做精确空间计算。对于实时轨迹去噪,可以利用滑动窗口缓存每条车辆最近50个点,只对窗口内数据做距离判断,避免全量重算。
断点续传也是环卫GPS数据链路中必须考虑的环节。车辆终端网络不稳定会导致数据包乱序或重复,R脚本在增量入库时应以设备编号和时间戳作为唯一键,遇到冲突时执行覆盖或跳过策略。这样可以保证即使定时任务暂时中断,恢复后也不会造成数据缺口。另外,调度看板与数据库交互时,建议将地图渲染放在浏览器端,R只负责输出GeoJSON格式的轨迹和任务数据,减少服务器端的不必要计算。
综合来看,R在智慧环卫网络中并不是替代专业GIS平台或大屏系统,而是充当数据处理与分析的中坚层。它能够快速验证调度模型、清洗轨迹数据、生成优化结果,并通过leaflet或Shiny输出可视化界面。对于中小型环卫项目,直接用R构建一套从数据接入到调度看板的原型系统成本很低,后期若数据规模继续扩大,再逐步把关键计算模块迁移到更高效的后端服务也不迟。