如何使用geoarrow裁剪内存无法容纳的超大型shapefile
直接调用st_crop()报错的逻辑很直接:st_crop()是sf包提供的泛型函数,目前仅针对加载到内存的sf/sfc类空间对象实现了对应处理方法。open_dataset()返回的是Arrow的惰性计算数据集对象,本质上没有把真实数据加载到R内存中,也不属于sf识别的空间对象类型,自然匹配不到可执行的方法,直接抛出错误。
你测试时用的2022年年中发布的早期版本geoarrow,还没有实现Arrow数据集层面的惰性空间计算、空间谓词下推功能,所以没法直接对未加载到内存的Arrow Dataset直接做裁剪操作。
不需要把全量数据加载到内存,两种方案都可以实现大体积空间数据裁剪,根据自己的场景选就行:
方案1:基于Arrow流式读取+分块裁剪
如果已经把所有shapefile转成了geoarrow格式的分区数据集,可以用Arrow的流式读取能力,分块加载数据做裁剪,每处理完一块就释放内存,全程内存占用只和单块数据大小有关,不会爆内存:
library(sf) library(arrow) library(geoarrow) library(dplyr) # 定义裁剪范围,注意要和数据坐标系一致 crop_ext <- st_bbox( c(xmin = -80, xmax = -78, ymin = 34.5, ymax = 36), crs = st_crs(4267) # NAD27坐标系EPSG编码为4267 ) # 打开数据集,设置分块大小,可根据自己的内存调整,比如每次读5000行 ds <- open_dataset(tmpdir) output <- list() batch_reader <- ds$ScanBatches$create(batch_size = 5000) while (!is.null(current_batch <- batch_reader$Next())) { # 当前块转成sf对象 batch_sf <- as.data.frame(current_batch) |> st_as_sf() # 对当前块做裁剪 batch_cropped <- st_crop(batch_sf, crop_ext) output[[length(output) + 1]] <- batch_cropped # 清理临时对象释放内存 rm(current_batch, batch_sf, batch_cropped) gc() } # 合并所有分块的裁剪结果 final_result <- bind_rows(output)
如果写入geoarrow数据集的时候,提前把每个要素的bbox拆成xmin/xmax/ymin/ymax四个单独的数值列,还可以在分块读取前先做一层粗过滤:直接用Arrow的数值过滤能力,把bbox和裁剪范围完全不相交的行直接跳过,连几何列都不用读,处理速度会更快。
方案2:单shapefile循环裁剪(最稳妥)
如果你还没把原始shapefile转成geoarrow格式,你原本考虑的逐个读取单份shapefile裁剪的方案是稳定性最高、改造成本最低的选择。shapefile本身是单文件存储格式,本身不支持部分行随机读取,循环处理的内存开销等于单个shapefile的大小,只要单个shapefile体积不超过内存上限就完全没问题:
library(sf) library(dplyr) # 替换成你存储所有shapefile的文件夹路径 shp_dir <- "your_shapefile_folder_path" shp_list <- list.files(shp_dir, pattern = "\\.shp$", full.names = TRUE, recursive = TRUE) crop_ext <- st_bbox( c(xmin = -80, xmax = -78, ymin = 34.5, ymax = 36), crs = st_crs(4267) ) output <- lapply(shp_list, \(fpath) { shp <- read_sf(fpath) cropped <- st_crop(shp, crop_ext) rm(shp) gc() return(cropped) }) final_result <- bind_rows(output)
后续geoarrow版本迭代完善Arrow空间计算引擎支持后,确实可以实现不加载全量数据的惰性空间裁剪、谓词下推,但你当前使用的早期版本暂不支持该能力,不用在这个方向上浪费时间调试。
内容的提问来源于stack exchange,提问作者Philippe Massicotte

