Node.js API导入Shapefile至PostGIS:仅点数据可用,线面报错
问题:Shapefile导入PostGIS时线/面类型触发RangeError错误
我用Node.js编写了/upload API接口,用于将Shapefile数据导入PostGIS数据库。流程是通过shapefile库读取Shapefile,先判断目标表是否存在,不存在则创建包含geom字段的表,再调用ogr2ogr命令完成数据导入。但目前仅能成功导入点类型Shapefile,导入线或面类型时会触发RangeError错误,报错源自shapefile库的Dbf模块,试过多个Shapefile都无法解决,希望得到解决方法。
核心代码
app.get('/upload', async (req, res) => { try { const basePath = req.headers.path; const fileName = basePath.match(/[^\\/]+$/); const shpData = await shapefile.read(basePath); const columnNames = Object.keys(shpData.features[0].properties); const tableName = fileName[0].split('.')[0]; const tableExistsQuery = `SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = '${tableName}')`; const { rows } = await client.query(tableExistsQuery); const tableExists = rows[0].exists; if (!tableExists) { const createTableQuery = ` CREATE TABLE ${tableName} ( gid SERIAL PRIMARY KEY, geom GEOMETRY, ${columnNames.map(columnName => `${columnName} VARCHAR(255)`).join(', ')} ) `; await client.query(createTableQuery); const uploadDataQuery = `ogr2ogr -f "PostgreSQL" PG:"dbname=${process.env.DATABASE} user=${process.env.USER} password=${process.env.PASSWORD} host=${process.env.HOST} port=${process.env.PORT}" ${basePath}.shp -nln ${tableName} -nlt PROMOTE_TO_MULTI`; exec(uploadDataQuery, (error, stdout, stderr) => { if (error) { console.error(`Error: ${error.message}`); return; } if (stderr) { console.error(`stderr: ${stderr}`); return; } console.log(`stdout: ${stdout}`); }); } res.status(200).json({ message: 'Data uploaded in PostGIS database.' }); } catch (error) { console.error('Error uploading data:', error); res.status(500).json({ error: 'Error uploading data.' }); } });
报错信息
2024-03-12 10:10:19 Error uploading data: RangeError: Offset is outside the bounds of the DataView 2024-03-12 10:10:19 at DataView.getUint8 () 2024-03-12 10:10:19 at new Dbf (/usr/src/app/node_modules/shapefile/dist/shapefile.node.js:80:20) 2024-03-12 10:10:19 at /usr/src/app/node_modules/shapefile/dist/shapefile.node.js:65:14 2024-03-12 10:10:19 at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
解决方法
1. 移除shapefile库依赖(最优方案)
既然最终用ogr2ogr完成数据导入,完全不需要先用shapefile库读取数据。直接通过文件名提取表名,让ogr2ogr自动处理表创建和字段映射:
- 去掉
shapefile.read相关代码,直接从basePath提取表名 - 删除手动建表逻辑,ogr2ogr会根据Shapefile的字段自动创建对应表结构,包括正确的几何字段类型
- 调整ogr2ogr命令,确保参数正确,同时处理异步执行问题(用
util.promisify包装exec,等待导入完成再返回响应)
修改后的示例代码:
const { exec } = require('child_process'); const util = require('util'); const execPromise = util.promisify(exec); app.get('/upload', async (req, res) => { try { const basePath = req.headers.path; const fileName = basePath.match(/[^\\/]+$/); const tableName = fileName[0].split('.')[0]; const tableExistsQuery = `SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = '${tableName}')`; const { rows } = await client.query(tableExistsQuery); const tableExists = rows[0].exists; if (!tableExists) { const uploadDataQuery = `ogr2ogr -f "PostgreSQL" PG:"dbname=${process.env.DATABASE} user=${process.env.USER} password=${process.env.PASSWORD} host=${process.env.HOST} port=${process.env.PORT}" ${basePath}.shp -nln ${tableName} -nlt PROMOTE_TO_MULTI -lco GEOMETRY_NAME=geom -lco FID=gid`; await execPromise(uploadDataQuery); console.log('Data imported successfully'); } res.status(200).json({ message: 'Data uploaded in PostGIS database.' }); } catch (error) { console.error('Error uploading data:', error); res.status(500).json({ error: 'Error uploading data.' }); } });
2. 若需手动建表,改用ogrinfo获取字段信息
如果必须手动创建表结构,不要用shapefile库,改用ogrinfo命令解析Shapefile的元数据:
- 执行
ogrinfo -so ${basePath}.shp ${tableName}获取字段名和类型 - 解析命令输出,生成对应的建表SQL语句
- 这样可以避免shapefile库的解析bug,同时获取更准确的字段类型(比如数值型、日期型,而不是统一用VARCHAR(255))
3. 升级或替换shapefile库
如果一定要保留shapefile库的使用:
- 升级到该库的最新版本,查看是否有修复Dbf模块的相关bug
- 替换为更稳定的Shapefile解析库,比如
shpjs或@turf/turf中的相关模块
内容的提问来源于stack exchange,提问作者Daniel Riaño
相关产品推荐
相关产品推荐

