Java+GDAL GeoJSON数据读取入库精选指南
在面向空间的矢量数据处理中,Shapefile确实是常年霸榜的“老大哥”。但如果你只盯着它,可能会错过不少好东西——比如在WebGIS世界里非常活跃的GeoJSON。今天就来聊聊这个基于JSON的地理信息数据交换格式,看看它到底怎么用,以及如何通过Ja va和GDAL把它高效地塞进空间数据库里。
先简单介绍一下GeoJSON:它本质上就是JSON,只不过加了一套专门描述地理对象的规矩。可以表示点、线、面,还有它们的集合,比如多点、多线、多面,甚至几何集合。每个地理对象层层嵌套,结构清晰,浏览器原生支持,所以在前端GIS应用里特别吃香。
GeoJSON格式总是由一个顶层对象组成,这个对象必须有 type 成员,取值可以是 Point、LineString、Polygon、Feature、FeatureCollection 等。还可以带可选的 crs(坐标参考系统)和 bbox(边界框)。看个例子就明白了:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": {
"prop0": "value0"
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [
[102.0, 0.0],
[103.0, 1.0],
[104.0, 0.0],
[105.0, 1.0]
]
},
"properties": {
"prop0": "value0",
"prop1": 0.0
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[100.0, 0.0],
[101.0, 0.0],
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0]
]
]
},
"properties": {
"prop0": "value0",
"prop1": {
"this": "that"
}
}
}
]
}
接下来,我们就从实际数据出发,走一遍完整的流程:用QGIS了解数据长什么样,在PostGIS里设计空间表,再用Ja va+GDAL把GeoJSON批量写进数据库。
一、基础数据介绍
示例数据是2015年左右的全国分级地名数据,包含省会城市、地级市、县级市、区县四类,全部以GeoJSON格式存储。原始数据来自GISer last,大家有兴趣的话可以自行搜索获取。
1. 数据格式
地名数据主要是点数据,除了乡镇一级数据量稍大,其他级别的文件都不大,远没到MB级别。先用QGIS把它们打开看看效果。
加载进QGIS后,可以查看空间属性,比如投影信息、属性字段等。为了展示得更清晰,我叠加了省级行政区划矢量数据,并打开了标注,效果如下:
2. 数据参数
在QGIS里查看数据的属性信息,得到以下参数:
| 序号 | 参数 | 说明 |
|---|---|---|
| 1 | 存储类型 | GeoJSON |
| 2 | 编码 | UTF-8 |
| 3 | 几何图形 | Point |
| 4 | 坐标参考系 | EPSG:4326 - WGS 84 - 地理的 |
| 5 | 范围 | 87.6149638000000550,20.0500348290000261 : 126.5286520170000131,45.8019539320000604 |
| 6 | 单位 | 度 |
属性字段如下:
| 序号 | 属性名 | 数据类型 | 说明 |
|---|---|---|---|
| 1 | NAME | String | 地名 |
| 2 | PIINYIN | String | 地名拼音 |
| 3 | CLASS | String | 类型 |
| 4 | BZ | String | 备注 |
| 5 | SLX | String | 标记(暂无特殊含义) |
各层级数据条数:
| 序号 | 数据名称 | 数据条数 |
|---|---|---|
| 1 | 省会城市地名 | 29 |
| 2 | 地级市地名 | 283 |
| 3 | 县级市地名 | 339 |
| 4 | 区县地名 | 2567 |
了解了数据结构和属性后,接下来就可以设计空间数据库表了。
二、空间数据库设计
我们选用PostGIS作为空间数据库。设计思路很简单:建立一张空间表,字段直接映射GeoJSON中的属性,同时增加主键和空间几何字段 geom,并在 geom 上建立GIST空间索引——这一步至关重要,没有索引的空间查询慢得让人抓狂。
1. 主体表模型
实体映射关系如下:
2. DDL语句与空间索引
先建表,再建索引。注意空间索引必须用GIST,普通BTREE对几何列无效。
CREATE TABLE "public"."biz_geographic_name" (
"pk_id" int8 NOT NULL,
"name" varchar(255) COLLATE "pg_catalog"."default" NOT NULL,
"pinyin" varchar(255) COLLATE "pg_catalog"."default",
"classz" varchar(4) COLLATE "pg_catalog"."default",
"bz" varchar(100) COLLATE "pg_catalog"."default",
"slx" varchar(20) COLLATE "pg_catalog"."default",
"geom" "public"."geometry" NOT NULL,
CONSTRAINT "pk_biz_geographic_name" PRIMARY KEY ("pk_id")
);
ALTER TABLE "public"."biz_geographic_name" OWNER TO "ghy01";
CREATE INDEX "idex_biz_geographic_name_classz" ON "public"."biz_geographic_name" USING btree ("classz" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_geographic_name_geom" ON "public"."biz_geographic_name" USING gist ("geom" "public"."gist_geometry_ops_2d");
COMMENT ON COLUMN "public"."biz_geographic_name"."pk_id" IS '主键id';
COMMENT ON COLUMN "public"."biz_geographic_name"."name" IS '地名';
COMMENT ON COLUMN "public"."biz_geographic_name"."pinyin" IS '汉语拼音';
COMMENT ON COLUMN "public"."biz_geographic_name"."classz" IS 'classz';
COMMENT ON COLUMN "public"."biz_geographic_name"."bz" IS '备注';
COMMENT ON COLUMN "public"."biz_geographic_name"."slx" IS 'slx';
COMMENT ON COLUMN "public"."biz_geographic_name"."geom" IS '空间对象';
COMMENT ON TABLE "public"."biz_geographic_name" IS '地名基础信息表,用于存储中国范围内的地名信息';
至此空间表设计完成,索引也都建好了。
三、GDAL解析及入库
核心环节来了:怎么用Ja va+GDAL把GeoJSON数据读出来,再通过MyBatis-Plus写入PostGIS?
1. GDAL中GeoJSON驱动
GDAL对不同数据格式使用不同的驱动。解析GeoJSON就用 GeoJSON 驱动。它支持读写GeoJSON格式,并且提供了一些配置选项,比如是否展平嵌套属性(FLATTEN_NESTED_ATTRIBUTES)、是否把数组当字符串处理(ARRAY_AS_STRING)等。实际开发中按需设置就行。
2. 解析GeoJSON
关键代码如下:
@Test
public void readDjsGeoJSON() {
String strVectorFile = "path/2015省市区县乡镇地名数据/地名点_地级市.geojson";
ogr.RegisterAll();
gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "YES");
gdal.SetConfigOption("SHAPE_ENCODING", "UTF-8");
String strDriverName = "GeoJSON";
org.gdal.ogr.Driver oDriver = ogr.GetDriverByName(strDriverName);
if (oDriver == null) {
System.out.println(strDriverName + " 驱动不可用!");
return;
}
DataSource dataSource = oDriver.Open(strVectorFile);
Layer layer = dataSource.GetLayer(0);
SpatialReference spatialReference = layer.GetSpatialRef();
String srid = spatialReference.GetAttrValue("AUTHORITY", 1);
long featureCount = layer.GetFeatureCount();
List list = new ArrayList();
for (int i = 0; i < featureCount; i++) {
Feature feature = layer.GetFeature(i);
String name = feature.GetFieldAsString("NAME");
String pinyin = feature.GetFieldAsString("PINYIN");
String classz = feature.GetFieldAsString("CLASS");
String bz = feature.GetFieldAsString("BZ");
String slx = feature.GetFieldAsString("SLX");
Geometry geom = feature.GetGeometryRef();
String wkt = geom.ExportToWkt();
wkt = "SRID=" + srid + ";" + wkt;
list.add(new GeographicName(name, pinyin, classz, bz, slx, wkt));
System.out.println("name=" + name + " classz=" + classz + " wkt=" + wkt);
}
geographicNameService.sa veBatch(list, 300);
dataSource.delete();
gdal.GDALDestroyDriverManager();
}
步骤清晰:打开文件 → 获取图层 → 逐条读取属性字段和几何对象 → 构造WKT(带上SRID) → 批量插入。
3. Ja va模型
实体类(省略Mapper和Service):
package com.yelang.project.extend.earthquake.domain;
import ja va.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yelang.framework.handler.PgGeometryTypeHandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "biz_geographic_name", autoResultMap = true)
public class GeographicName implements Serializable {
private static final long serialVersionUID = -3694849578429480952L;
@TableId(value = "pk_id")
private Long pkId;
private String name;
private String pinyin;
private String classz;
private String bz;
private String slx;
public GeographicName(String name, String pinyin, String classz, String bz, String slx, String geom) {
super();
this.name = name;
this.pinyin = pinyin;
this.classz = classz;
this.bz = bz;
this.slx = slx;
this.geom = geom;
}
@TableField(typeHandler = PgGeometryTypeHandler.class)
private String geom;
@TableField(exist = false)
private String geomJson;
}
这里用到了自定义类型处理器 PgGeometryTypeHandler,负责把WKT字符串转换成PostGIS的geometry对象。
4. 数据入库
用JUnit跑一下测试,数据批量插入到数据库中。
控制台输出显示正在执行插入操作:
进入数据库验证一下:
select * from biz_geographic_name;
用Na vicat连接数据库,可以看到地级市的地名数据已经成功写入空间表中。
总结一下,整个流程并不复杂,关键是理解GeoJSON的结构、GDAL驱动的工作原理,以及PostGIS空间索引的重要性。把这几步串联起来,以后遇到类似的数据入库需求,就可以照方抓药了。








