无法按空间范围筛选GTFS数据(gtfstools)
我在尝试通过合并省内多家运营商的GTFS数据,构建米兰地区的统一GTFS。
在合并之前,我需要对每个GTFS进行空间筛选,只保留在米兰区域运营的服务,因为不少运营商也在省外提供服务。
然而,在对市政运营商(ATM)的GTFS应用空间筛选时,我遇到了问题。具体来说,在筛选阶段出现了sfheaders的错误。
这个问题仅出现在ATM数据集中: i) 其他所有GTFS数据源在筛选时都没有错误地完成过滤; ii) 其中还包括甚至不包含shapes.txt文件的数据源
我已经检查过ATM GTFS中的shapes数据:i) 结构看起来有效;ii) 我在筛选前明确按shape_id和 shape_pt_sequence对其进行了排序。
尽管做了这些检查,错误仍然存在,我也无法确定根本原因。
stibm_comuni = st_read(path)
stibm = stibm_comuni %>%
summarise(geometry = st_union(geometry))
# UPLOAD individual GTFS =======================================================
gtfs_dir = 'GTFS - Milano' # Set working directory
# Dictionary: name -> zip filename
gtfs_files <- list(
atm = "gtfs.zip",
trenord = "trenord_gtfs.zip",
movibus = "2026-03-06_Movibus.zip",
stav = "2026-01-30_STAV.zip",
airpullman = "2025-09-25_Airpullman.zip",
star = "2025-07-29_STAR.zip"
)
gtfs_list <- lapply(gtfs_files, function(filename) {
path <- file.path(gtfs_dir, filename)
message("Reading: ", filename)
read_gtfs(path)
})
#list2env(gtfs_list, envir = .GlobalEnv)
# Order shapes files in the list
gtfs_list <- lapply(gtfs_list, function(gtfs) {
if (!is.null(gtfs$shapes) && nrow(gtfs$shapes) > 0) {
shapes_dt <- as.data.table(gtfs$shapes)
setorder(shapes_dt, shape_id, shape_pt_sequence)
gtfs$shapes <- shapes_dt
}
return(gtfs)
})
# Filter each feed by STIBM boundaries
gtfs_stibm <- lapply(names(gtfs_list), function(name) {
message("Filtering: ", name)
tryCatch({
result <- filter_by_spatial_extent(gtfs_list[[name]], stibm)
message("OK: ", name)
result
}, error = function(e) {
message("FAILED: ", name, " -> keeping original. Error: ", e$message)
gtfs_list[[name]]
})
})
where I get an error for ATM GTFS:
Filtering: atm FAILED: atm -> keeping original. Error: sfheaders - error indexing lines, perhaps caused by un-ordered data? Filtering: trenord s2/s2polyline.cc:633 WARNING S2Polyline::Shape with one vertex has no edges OK: trenord Filtering: movibus OK: movibus Filtering: stav OK: stav Filtering: airpullman OK: airpullman Filtering: star OK: star
Anyone can help on this?
解决方案
你需要在对 filter_by_spatial_extent(gtfs_list[["atm"]], stibm) 进行操作之前,先按 trip_id 与 stop_sequence 对 gtfs_list[["atm"]]$stop_times 进行排序。修复方法是
# Order shapes & stop_times
gtfs_list <- lapply(gtfs_list, \(gtfs) {
if (!is.null(gtfs$shapes) && nrow(gtfs$shapes) > 0 && !is.null(gtfs$stop_times) && nrow(gtfs$stop_times) > 0) {
data.table::setorder(gtfs$shapes, shape_id, shape_pt_sequence)
# Stop times need to be ordered by trip_id & stop_sequence
data.table::setorder(gtfs$stop_times, trip_id, stop_sequence)
}
return(gtfs)
})
Root cause
错误来自 这一行 于 gtfstools::filter_by_spatial_extent()
trips_sf <- get_trip_geometry(gtfs_list[["atm"]], file = "stop_times")
这会因为 sort_sequence = FALSE 而抛出该错误。
如果未排序,会触发 此问题(testthat的 sfheaders:::rcpp_sf_linestring) 或 此相关的GitHub问题:
sfheaders:::rcpp_sf_linestring( data.frame(id1 = c(1,2,3), id2 = c(1,2,1), x = 1:3, y = 1:3, z = 1:3, m = 1:3), c(2L:3L), 1L, "", keep = TRUE )
最小可复现错误
因此,产生此问题所需的最小代码量,是像下面一样,在列 trip_id 与 stop_sequence 中打乱连贯性,就像下方所示:
trip_id = c(1,2,3)
stop_sequence = c(1,2,1)
library(gtfstools)
library(data.table)
set.seed(42)
data_path <- system.file("extdata/spo_gtfs.zip", package = "gtfstools")
gtfs <- read_gtfs(data_path)
# messing up contiguity intentionally
gtfs$stop_times <- gtfs$stop_times[sample(.N)]
filter_by_spatial_extent(gtfs, bbox)
# or
get_trip_geometry(gtfs, file = "stop_times")
错误:sfheaders - error indexing lines, perhaps caused by un-ordered data?