一、开篇痛点
前面我们解决了:
- Geometry 怎么存(第一篇)
- 怎么展示(1.1 篇 + 第 4 篇)
- 怎么查(第 2 篇)
- 怎么翻页(第 3 篇)
但有一个问题一直没解决:数据从哪来?
二、三种方案总览
| 方案 | 速度 | 适用场景 | 难度 |
|---|---|---|---|
| PostgreSQL COPY | ⭐⭐⭐⭐⭐ | 大数据量首选 | ★★☆☆☆ |
| MP saveBatch | ⭐⭐⭐ | 中小数据 / API 导入 | ★☆☆☆☆ |
| ogr2ogr | ⭐⭐⭐⭐ | Shapefile 导入 | ★★★☆☆ |
三、方案一:PostgreSQL COPY(最快)
3.1 CSV 文件
以下面这个csv为例
name,category,lon,lat 天安门,景点,116.397,39.903 故宫,景点,116.397,39.916 王府井,商圈,116.417,39.914 西单,商圈,116.375,39.913 颐和园,景点,116.275,39.999保存时编码选 UTF-8,避免中文乱码
3.2 建临时表
CREATETABLEpoi_temp(nameTEXT,categoryTEXT,lonDOUBLEPRECISION,latDOUBLEPRECISION);3.3 COPY 导入数据
Windows 本地开发推荐使用\copy(客户端复制),避免 PostgreSQL 服务权限问题:
\copy poi_temp(name,category,lon,lat)FROM'D:\testw\tests.csv'WITH(FORMAT csv,HEADER,DELIMITER',');3.4 转换为 Geometry
INSERTINTOpoi(name,category,geom)SELECTname,category,ST_SetSRID(ST_MakePoint(lon,lat),4326)::geometry(Point,4326)FROMpoi_temp;3.5 清理与建索引
DROPTABLEpoi_temp;CREATEINDEXidx_poi_geomONpoiUSINGGIST(geom);四、方案二:MyBatis-Plus saveBatch(灵活)
4.1 Import DTO
publicclassPoiImportDto{privateStringname;privateStringcategory;privateDoublelon;privateDoublelat;// getter / setter}4.2 Service 实现
@Override@TransactionalpublicintbatchImport(List<PoiImportDto>list){List<PoiEntity>entities=list.stream().map(dto->{PoiEntitye=newPoiEntity();e.setName(dto.getName());e.setCategory(dto.getCategory());Stringwkt="POINT("+dto.getLon()+" "+dto.getLat()+")";try{Geometrygeom=newWKTReader().read(wkt);geom.setSRID(4326);e.setGeom(geom);}catch(ParseExceptionex){thrownewRuntimeException("Invalid WKT: "+wkt,ex);}returne;}).collect(Collectors.toList());saveBatch(entities,1000);// 每批 1000 条returnentities.size();}4.3 Controller
@PostMapping("/import")publicR<Integer>importPoi(@RequestBodyList<PoiImportDto>list){returnR.ok(poiService.batchImport(list));}4.4 curl 测试
curl-XPOST http://localhost:8080/poi/import\-H"Content-Type: application/json"\-d'[ {"name":"天安门","category":"景点","lon":116.397,"lat":39.903}, {"name":"故宫","category":"景点","lon":116.397,"lat":39.916} ]'⚠️ saveBatch 的 batchSize 设太大(如 10000)会内存溢出,设太小(如 100)会频繁提交变慢。1000~2000 是甜点区。
五、方案三:ogr2ogr(Shapefile 导入)
5.1 安装 GDAL
# Ubuntuapt-getinstallgdal-bin# Macbrewinstallgdal5.2 查看 Shapefile 信息
ogrinfo-sopoi.shp poi5.3 导入命令
ogr2ogr-f"PostgreSQL"\PG:"dbname=postgis_demo user=postgres password=123456 host=localhost"\poi.shp\-nlnpoi\-overwrite\-lcoGEOMETRY_NAME=geom\-lcoFID=id\-nltPOINT\-s_srsEPSG:4326\-t_srsEPSG:4326参数说明:
| 参数 | 含义 |
|---|---|
| -f “PostgreSQL” | 目标格式为 PostgreSQL |
| -nln poi | 目标表名 |
| -overwrite | 覆盖已有表 |
| -lco GEOMETRY_NAME=geom | Geometry 列名 |
| -lco FID=id | 主键列名 |
| -nlt POINT | 几何类型 |
| -s_srs / -t_srs | 源/目标坐标系 |
⚠️ Shapefile 中文乱码?加参数:--config SHAPE_ENCODING UTF-8
这个方案也可以用程序来实现,一般生产是采用这种方式,写一个使用gdal导入shp至postgis数据库的方法,前端通过传一个含shp的压缩文件,调用接口将数据发布到数据库,这个后面会单独出一期文章
六、导入后必做的 4 步验证
数据导入后,建议做以下四项验证:
-- 1. 总数SELECTCOUNT(*)FROMpoi;-- 2. SRID 一致性(期望结果:4326)SELECTDISTINCTST_SRID(geom)FROMpoi;-- 3. 几何有效性(期望结果:0)SELECTCOUNT(*)FROMpoiWHERENOTST_IsValid(geom);-- 4. 空间索引是否存在SELECTindexnameFROMpg_indexesWHEREtablename='poi';| 验证项 | 期望结果 | 不通过怎么办 |
|---|---|---|
| 总数 | 与实际数据量一致 | 检查 CSV 行数 / 编码 |
| SRID | 全部为 4326 | UPDATE 设置 SRID |
| 几何有效性 | 0 条无效 | 排查坐标顺序 / 范围 |
| 空间索引 | idx_poi_geom 存在 | CREATE INDEX |
七、踩坑复盘
⚠️ 坑 1:CSV 的经纬度顺序
很多人写成 lat, lon,但 PostGIS 要的是 lon, lat(即 X, Y)。写反了,数据会跑到非洲去。
-- 正确:经度在前,纬度在后ST_MakePoint(116.397,39.903)--⚠️ 坑 2:SRID 忘记设置
ST_MakePoint(lon, lat) 不设置 SRID,默认是 0。后续空间查询报错:Mixed SRID。
-- 解决:显式设置 SRIDST_SetSRID(ST_MakePoint(lon,lat),4326)::geometry(Point,4326)⚠️ 坑 3:saveBatch 的 batchSize
设太大(如 10000)→ 内存溢出。设太小(如 100)→ 频繁提交,慢。1000~2000 是甜点区。
⚠️ 坑 4:Shapefile 中文乱码
中文字段名 / 属性值乱码。解决:ogr2ogr 加--config SHAPE_ENCODING UTF-8
⚠️ 坑 5:事务太大导致超时
一次性 saveBatch(50 万条) → 事务超时。解决:分批 + @Transactional + 合理 batchSize。
⚠️ 坑 6:Windows 下 COPY 权限问题
在 Windows 上,COPY 要求 PostgreSQL 服务账户对目标路径有读取权限。如果报 Permission denied,改用\copy(客户端复制),只需要当前用户权限。
-- 服务端复制(需要数据库进程权限)COPY poi_tempFROM'/data/poi.csv'...-- 客户端复制(只需要当前用户权限,推荐本地开发使用)\copy poi_tempFROM'D:\testw\tests.csv'...⚠️ 坑 7:Excel 打开 CSV 只占一个单元格
直接双击 CSV 用 Excel 打开,可能因系统默认分隔符不是英文逗号,导致整行被识别为一个字段。解决方案:用记事本 / VS Code 创建 CSV,或在 Excel 中通过“数据 → 获取数据 → 从文本/CSV”导入并显式指定逗号分隔符。