Shiny应用中实现用户上传Shapefile并通过st_read正确读取的方法
问题根因说明
临时路径指向的原因
Shiny的文件上传受浏览器安全沙箱规则限制,网页端无法直接获取用户本地磁盘的文件真实存储路径,所有用户选中并上传的文件,都会由服务端临时拷贝到系统临时目录(即报错信息中展示的C:\Users\Ed\AppData\Local\Temp\Rtmpxxx路径),input$shp$datapath指向的就是这个临时拷贝的位置,属于Shiny文件上传机制的正常行为,并非路径配置错误,开发者也无法绕过沙箱限制直接读取用户本地的原始文件。
Shapefile读取失败的核心原因
Shapefile并非单一文件,而是一组同文件名、不同扩展名的配套文件集合:单个.shp文件仅存储几何矢量要素,必须同目录下配套的.shx(空间索引)、.dbf(属性表)、.prj(坐标系定义)等文件共同存在,才能被GDAL/sf正常读取。原代码仅配置了上传单个.shp文件的逻辑,用户上传时仅将.shp单个文件拷贝到临时目录,对应的配套依赖文件没有同步上传;且Shiny会将单个上传的文件自动重命名为0.shp,GDAL在同目录下找不到必须的0.shx索引文件,就会抛出读取错误。
稳定可复用的Shapefile上传实现方案
由于浏览器端文件选择器无法直接递归读取文件夹内的所有配套文件,最稳妥的交互逻辑是引导用户将Shapefile的所有配套文件打包为ZIP压缩包后上传,服务端解压后再完成空间数据读取,具体调整方向如下:
- 调整前端文件上传控件的配置,将上传接受的文件类型改为ZIP压缩包,补充操作提示降低用户操作错误率
- 服务端增加ZIP解压逻辑,将上传的压缩包内容释放到独立临时目录,保证同前缀的配套文件处于同一检索路径下
- 在解压目录内递归检索有效的
.shp文件,通过sf读取,增加缺失SHX文件的容错配置 - 修正原代码中的拼写、逻辑错误(原代码存在包名拼写错误、sf参数拼写错误、响应式对象未正确调用、渲染输出类型不匹配等问题)
修正后的完整核心代码
library(shiny) library(shinydashboard) library(shinythemes) library(sf) library(leaflet) # UI部分 ui = navbarPage("Project Eddy", theme = shinytheme("sandstone"), tabPanel("Location", sidebarLayout( sidebarPanel( fileInput("shp_zip", "请上传打包为ZIP的Shapefile数据", multiple = FALSE, accept = ".zip"), helpText("上传前请确认Shapefile的所有配套文件(.shp/.shx/.dbf/.prj)已全部打包到ZIP压缩包内"), radioButtons("disp", "数据展示范围", choices = c(head = "仅展示数据预览", all = "展示全量数据"), selected = "head") ), mainPanel(leafletOutput(outputId = "Area")) ) ) ) # Server部分 server = function(input, output, session) { myshp.df = reactive({ req(input$shp_zip) # 创建独立临时目录用于解压ZIP,退出时自动清理 tmp_dir <- tempfile(pattern = "shp_upload") dir.create(tmp_dir) on.exit(unlink(tmp_dir, recursive = TRUE), add = TRUE) # 解压上传的ZIP文件到临时目录 unzip(zipfile = input$shp_zip$datapath, exdir = tmp_dir) # 递归查找解压目录下的所有shp文件,兼容ZIP内嵌套一层文件夹的情况 shp_files <- list.files( path = tmp_dir, pattern = "\\.shp$", recursive = TRUE, full.names = TRUE, ignore.case = TRUE ) # 增加异常提示,避免无意义的程序报错 if (length(shp_files) == 0) { showNotification("未在上传的压缩包中找到有效Shapefile文件", type = "error") req(FALSE) } if (length(shp_files) > 1) { showNotification("压缩包内包含多个Shapefile,默认读取第一个匹配的文件", type = "warning") } # 读取shp,增加SHP索引自动重建的容错配置,应对用户漏传shx文件的场景 df <- st_read( dsn = shp_files[1], quiet = TRUE, options = "SHAPE_RESTORE_SHX=YES" ) # 根据用户选择返回对应范围的数据 if(input$disp == "head") { return(head(df)) } else { return(df) } }) output$Area = renderLeaflet({ req(myshp.df()) a <- myshp.df() leaflet(a) %>% addPolygons(stroke = FALSE, fillOpacity = 0.5, smoothFactor = 0.5) %>% addProviderTiles('Esri.WorldImagery') }) } shinyApp(ui, server)
方案优势说明
- 不建议采用引导用户分别上传
.shp/.shx/.dbf/.prj四个独立文件的方案:这类方案用户漏传文件的概率极高,且不同文件上传后会被Shiny分配到不同的临时子目录,GDAL无法识别同前缀的配套文件,实际读取成功率很低 - 读取时加入的
SHAPE_RESTORE_SHX=YES配置,可以在用户漏传.shx索引文件时,由GDAL自动重建空间索引,大幅提升上传容错性 - 临时目录自动清理的逻辑,可以避免长期运行的应用占用过多系统临时存储空间
内容的提问来源于stack exchange,提问作者Ed_Gravy
相关产品推荐
相关产品推荐

