跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

SpringBoot 联合 PostGIS 实现在线地图行政区划离线存储

基于 SpringBoot 框架结合 PostGIS 地理空间数据库,利用高德地图开放平台 API 获取在线行政区划数据并离线存储的方案。通过设计统一物理表维护层级关系,使用递归方式获取多级行政区信息,并将坐标转换为 WKT 格式存入数据库。最终实现了数据的本地化管理与 WebGIS 可视化展示,解决了网络不稳定或数据安全要求较高场景下的地图数据应用问题。

日志猎手发布于 2026/3/27更新于 2026/10/1697 浏览
SpringBoot 联合 PostGIS 实现在线地图行政区划离线存储

前言

本文将探讨如何结合 SpringBoot 框架和专业的地理空间数据库 PostGIS,以高德地图开放平台为例,实现在线地图行政区划数据的离线存储,以满足多样化的业务需求,提升系统的灵活性和可靠性。

一、行政区划物理表设计

本节将对行政区划的物理表设计和表结构脚本进行详细的介绍。物理表很重要,是我们后续内容的开展和继续的基础。

1、物理表字段设计

为了有效减少物理表的数量,与一些按照不同层级设计不同的表的方法不一样,这里我们增加了一个 parent_id 字段,以此来维护上下级的关系,达到一张表管理不限层级的数据。

2、表结构脚本

为方便大家学习,这里直接分享我的行政区划物理表数据设计,仅供参考。这里的字段信息大家可以根据自己的需要进行灵活调整。如果有字段信息没有包含进来,可以新增。实例表结构如下,大家可以按需使用:

CREATE TABLE "public"."biz_region_info" (
  "pk_id" int8 NOT NULL,
  "parent_id" int8 DEFAULT 0,
  "city_code" varchar(6) COLLATE "pg_catalog"."default" DEFAULT ''::character varying,
  "adcode" varchar(6) COLLATE "pg_catalog"."default" DEFAULT ''::character varying,
  "name" varchar(50) COLLATE "pg_catalog"."default" DEFAULT ''::character varying,
  "polyline_geom" "public"."geometry",
  "center" varchar(30) COLLATE "pg_catalog"."default" DEFAULT ''::character varying,
  "level" varchar(10) COLLATE "pg_catalog"."default" DEFAULT ''::character varying,
  "lon" ,
  "lat" ,
  "center_geom" "public"."geometry",
   "pk_biz_region_info"  ("pk_id")
);
COMMENT   "public"."biz_region_info"."pk_id"  ;
COMMENT   "public"."biz_region_info"."parent_id"  ;
COMMENT   "public"."biz_region_info"."city_code"  ;
COMMENT   "public"."biz_region_info"."adcode"  ;
COMMENT   "public"."biz_region_info"."name"  ;
COMMENT   "public"."biz_region_info"."polyline_geom"  ;
COMMENT   "public"."biz_region_info"."center"  ;
COMMENT   "public"."biz_region_info"."level"  ;
COMMENT   "public"."biz_region_info"."lon"  ;
COMMENT   "public"."biz_region_info"."lat"  ;
COMMENT   "public"."biz_region_info"."center_geom"  ;
COMMENT   "public"."biz_region_info"  ;
numeric
numeric
CONSTRAINT
PRIMARY KEY
ON
COLUMN
IS
'主键'
ON
COLUMN
IS
'上级主键,最上级默认 0'
ON
COLUMN
IS
'城市编码'
ON
COLUMN
IS
'区域编码'
ON
COLUMN
IS
'行政区名称'
ON
COLUMN
IS
'行政区边界坐标点'
ON
COLUMN
IS
'区域中心点'
ON
COLUMN
IS
'行政区划级别'
ON
COLUMN
IS
'中心点经度'
ON
COLUMN
IS
'中心点纬度'
ON
COLUMN
IS
'中心点位置'
ON
TABLE
IS
'行政区划信息表'

需要注意的是,这里没有增加空间索引和其他的物理索引。在实际业务中,我们会经常进行行政区划内的数据检索,因此建议大家在使用时,对行政区边界坐标点字段设计一个空间索引。

二、行政区划在线 API

本节将重点介绍高德地图的行政区划检索在线 API,主要包括请求参数和响应参数。这是后续我们使用在线地图的基础知识。行政区域查询是一类简单的 HTTP 接口,根据用户输入的搜索条件可以帮助用户快速的查找特定的行政区域信息。例如:中国>山东省>济南市>历下区>舜华路街道(国>省>市>区>街道)。

URL请求方式
https://restapi.amap.com/v3/config/district?parametersGET

parameters 代表的参数包括必填参数和可选参数。所有参数均使用和号字符 (&) 进行分隔。下面的列表枚举了这些参数及其使用规则。

1、请求参数简介

参数名含义规则说明是否必须缺省值
key请求服务权限标识用户在高德地图官网申请 Web 服务 API 类型 KEY必填无
keywords查询关键字规则:只支持单个关键词语搜索关键词支持:行政区名称、citycode、adcode
例如,在 subdistrict=2,搜索省份(例如山东),能够显示市(例如济南),区(例如历下区)
adcode 信息可参考城市编码表获取
可选无
subdistrict子级行政区规则:设置显示下级行政区级数(行政区级别包括:国家、省/直辖市、市、区/县、乡镇/街道多级数据)
可选值:0、1、2、3 等数字,并以此类推
0:不返回下级行政区;
1:返回下一级行政区;
2:返回下两级行政区;
3:返回下三级行政区;

需要在此特殊说明,目前部分城市和省直辖县因为没有区县的概念,故在市级下方直接显示街道。
例如:广东 - 东莞、海南 - 文昌市
可选1
page需要第几页数据最外层的 districts 最多会返回 20 个数据,若超过限制,请用 page 请求下一页数据。
例如:page=2;page=3。默认:page=1
可选1
offset最外层返回数据个数可选20
extensions返回结果控制此项控制行政区信息中返回行政区边界坐标点;可选值:base、all;
base:不返回行政区边界坐标点;
all:只返回当前查询 district 的边界值,不返回子节点的边界值;
目前不能返回乡镇/街道级别的边界值
可选base
filter根据区划过滤按照指定行政区划进行过滤,填入后则只返回该省/直辖市信息
需填入 adcode,为了保证数据的正确,强烈建议填入此参数
可选
callback回调函数callback 值是用户定义的函数名称,此参数只在 output=JSON 时有效可选
output返回数据格式类型可选值:JSON,XML可选JSON

2、响应参数简介

行政区域查询的响应结果的格式由请求参数 output 指定。

名称含义规则说明
status返回结果状态值值为 0 或 1,0 表示失败;1 表示成功
info返回状态说明返回状态说明,status 为 0 时,info 返回错误原因,否则返回'OK'。
infocode状态码返回状态说明,10000 代表正确,详情参阅 info 状态表
suggestion建议结果列表
keywords建议关键字列表
cities建议城市列表
districts行政区列表
district行政区信息
citycode城市编码
adcode区域编码街道没有独有的 adcode,均继承父类(区县)的 adcode
name行政区名称
polyline行政区边界坐标点当一个行政区范围,由完全分隔两块或者多块的地块组成,每块地的 polyline 坐标串以
center区域中心点乡镇级别返回的 center 是边界线上的形点,其他行政级别返回的 center 不一定是中心点,若政府机构位于面内,则返回政府坐标,政府不在面内,则返回繁华点坐标。
level行政区划级别country:国家
province:省份(直辖市会在 province 显示)
city:市(直辖市会在 province 显示)
district:区县
street:街道
districts下级行政区列表,包含 district 元素

三、Java 后台数据获取及空间存储

SpringBoot 是一个开源的 Java 基于 Spring 框架的快速开发平台,它简化了基于 Spring 框架的应用开发,通过一系列的默认配置和约定,让开发者能够快速搭建出稳定、高效的系统。PostGIS 是一个开源对象关系型空间数据库扩展,它允许用户在 PostgreSQL 数据库中存储和操作地理空间数据,提供了强大的空间数据类型和几何函数。通过将 SpringBoot 与 PostGIS 结合,我们可以充分利用 SpringBoot 的开发优势和 PostGIS 的地理空间处理能力,构建出一个高效、稳定的在线地图行政区划离线存储系统。在实践过程中,我们将面临数据格式转换、性能优化以及数据更新与同步等挑战,但这些挑战也将为我们提供宝贵的经验和学习机会。

1、API 服务调用

为了简化调用,我们使用 UniHttp 进行在线服务的集成,接口定义如下:

package org.yelang.pcwater.amap.service;
import com.burukeyou.uniapi.http.annotation.HttpApi;
import com.burukeyou.uniapi.http.annotation.param.QueryPar;
import com.burukeyou.uniapi.http.annotation.request.GetHttpInterface;
import com.burukeyou.uniapi.http.core.response.HttpResponse;

/**
 * - 高德地图开放平台 API 接口,负责与高德交互
 */
@HttpApi(url = "https://restapi.amap.com")
public interface AmapService {
    /**
     * - 行政区域查询 API 服务
     *
     * @param key 请求服务权限标识,必填
     * @param keywords 查询关键字
     * @param subdistrict 子级行政区
     * @param extensions 此项控制行政区信息中返回行政区边界坐标点;可选值:base、all;
     * -base:不返回行政区边界坐标点;all:只返回当前查询 district
     * -的边界值,不返回子节点的边界值;目前不能返回乡镇/街道级别的边界值
     * @return
     */
    @GetHttpInterface("/v3/config/district")
    public HttpResponse<String> district(@QueryPar("key") String key, @QueryPar("keywords") String keywords, @QueryPar("subdistrict") int subdistrict, @QueryPar("extensions") String extensions);
}

下面针对返回的数据进行反序列化,将字符串转换为对象,后续可以直接进行数据入库。对应我们定义两个 JavaBean 实体类,一个是行政区划响应类,代码如下:

package org.yelang.pcwater.amap.dto.amap.district;
import java.util.List;
import org.yelang.pcwater.amap.dto.amap.CommonDTO;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString(callSuper=true)//callSuper=true 表示输出父类属性
@EqualsAndHashCode(callSuper=true)
public class DistrictRespDTO extends CommonDTO{
    private static final long serialVersionUID = 3562870512223434443L;
    private List<DistrictDTO> districts;//行政区列表
}

还有一个最重要的行政区划信息类,在这里类中包含了一个 List 的集合,就是用来保存当前行政区划的下级单位,代码如下:

package org.yelang.pcwater.amap.dto.amap.district;
import java.io.Serializable;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

/**
 * - 行政区信息数据传输对象
 */
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class DistrictDTO implements Serializable{
    private static final long serialVersionUID = -6086240995930742268L;
    @SerializedName("citycode")
    private String cityCode;//城市编码
    private String adcode;//区域编码
    private String name;//行政区名称
    private String polyline;//行政区边界坐标点
    private String center;//区域中心点
    private String level;//行政区划级别
    private List<DistrictDTO> districts;//下级行政区列表,包含 district 元素
}

2、递归获取数据

下面以深圳市为例,详细讲解三级行政区划的数据获取及离线存储。在前面的接口说明中,已经了解了一次可以返回多个下层数据的信息。大家可以根据自己的需要来调整业务,但是不可避免的需要对不同层级的数据进行统一处理。这里演示一种简单的使用递归的方式来进行数据的获取。

package org.yelang.pcwater.service.impl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.yelang.pcwater.amap.dto.amap.district.DistrictDTO;
import org.yelang.pcwater.amap.dto.amap.district.DistrictRespDTO;
import org.yelang.pcwater.amap.service.AmapService;
import org.yelang.pcwater.domain.RegionInfo;
import org.yelang.pcwater.mapper.RegionInfoMapper;
import org.yelang.pcwater.service.IRegionInfoService;
import org.yelang.pcwater.utils.PolygonStringToWKTConverter;
import org.yelang.pcwater.utils.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
import com.google.gson.Gson;

@Service
public class RegionInfoServiceImpl extends ServiceImpl<RegionInfoMapper, RegionInfo> implements IRegionInfoService{
    // 高德地图的 key,需要替换成自己申请的 key
    private static final String DEFAULT_AMAP_KEY = "yourkey";
    private static final String EXTENSIONS = "all";
    
    @Autowired
    private AmapService amapService;
    
    @Override
    public void syncDistrict() throws InterruptedException {
        Gson gson = new Gson();
        int subdistrict = 3;// 返回下三级行政区
        HttpResponse<String> result = amapService.district(DEFAULT_AMAP_KEY, "深圳市", subdistrict, EXTENSIONS);
        if (StringUtils.isEmpty(result.getBodyResult())) {
            return;
        }
        DistrictRespDTO districtRespDTO = gson.fromJson(result.getBodyResult(), DistrictRespDTO.class);
        List<RegionInfo> regionList = new ArrayList<RegionInfo>();
        this.extractDistrict(districtRespDTO.getDistricts(), regionList, 0L);
        if(StringUtils.isEmpty(regionList)) {
            return ;
        }
        for (RegionInfo regionInfo : regionList) {
            String polylineGeom = regionInfo.getPolylineGeom();
            String level = regionInfo.getLevel();
            //市、区等行政区划如果第一轮没有采集需要重新获取行政区划边界
            if(StringUtils.isEmpty(polylineGeom) && (level.equalsIgnoreCase("city") || level.equalsIgnoreCase("district"))) {
                //System.out.println("单独处理边界" + regionInfo.getAdcode()+ regionInfo.getName());
                //这里只需要返回当前层级即可
                HttpResponse<String> _tempResp = amapService.district(DEFAULT_AMAP_KEY, regionInfo.getAdcode(), 0, EXTENSIONS);
                String amapPolyline = this.getPolyline(_tempResp.getBodyResult());
                if(StringUtils.isNotEmpty(amapPolyline)) {
                    String polylineGeomWkt = PolygonStringToWKTConverter.convertToWKT(amapPolyline);
                    regionInfo.setPolylineGeom("SRID=" + 4326 + ";" + polylineGeomWkt);//拼接 srid,实现动态写入
                }
                Thread.sleep(100);
            }
        }
        this.saveBatch(regionList, 50);
    }

    /**
     * - 行政区划信息抽取
     * @param dataList
     * @param regionList
     * @param parentId
     */
    public void extractDistrict(List<DistrictDTO> dataList, List<RegionInfo> regionList, Long parentId) {
        if (StringUtils.isNotEmpty(dataList)) {
            for (DistrictDTO dto : dataList) {
                Long pkId = IdWorker.getId();
                String center = dto.getCenter();
                String polyline = dto.getPolyline();
                String level = dto.getLevel();
                double lon = Double.parseDouble(center.split(",")[0]);
                double lat = Double.parseDouble(center.split(",")[1]);
                String centerGeomWkt = PolygonStringToWKTConverter.point(lon, lat);
                RegionInfo region= new RegionInfo(pkId, parentId, dto.getCityCode(), dto.getAdcode(), dto.getName(), center, level, lon, lat, "SRID=" + 4326 + ";" + centerGeomWkt);
                if(StringUtils.isNotEmpty(polyline)) {
                    String polylineGeomWkt = PolygonStringToWKTConverter.convertToWKT(polyline);
                    region.setPolylineGeom("SRID=" + 4326 + ";" + polylineGeomWkt);//拼接 srid,实现动态写入
                }
                regionList.add(region);
                if (StringUtils.isNotEmpty(dto.getDistricts())) {
                    extractDistrict(dto.getDistricts(), regionList, pkId);
                }
            }
        }
    }

    /**
     * - 从响应文本中获取边界
     * @param responseResult 响应文本
     * @return
     */
    public String getPolyline(String responseResult) {
        String result = "";
        if (StringUtils.isEmpty(responseResult)) {
            return result;
        }
        DistrictRespDTO districtRespDTO = new Gson().fromJson(responseResult, DistrictRespDTO.class);
        if(null != districtRespDTO && StringUtils.isNotEmpty(districtRespDTO.getDistricts())) {
            result = districtRespDTO.getDistricts().get(0).getPolyline();
        }
        return result;
    }
}

使用以上代码就可以实现将行政区划代码离线存储保存到空间数据库中。大家可以使用 Junit 的方式进行集成,也可以做成 API 的方式进行调用。需要说明的是,在使用高德地图时,返回的数据坐标是高德的坐标系,在设置 SRID 时,大家可以使用 4326,但是在程序中需要进行标注,尤其是在别的底图上展示时,对坐标进行处理,处理偏移的问题。具体的处理方法,这里不进行赘述。

四、成果展示

本节将对深圳的三级行政区划信息进行展示,包括直接在 PostGIS 中进行查询和 WebGIS 可视化。以此验证我们的数据采集是否成功。

1、PostGIS 数据查询

在我们的程序中调用数据库的方法后,可以实现将深圳市的三级行政区划数据进行 PostGIS 存储,下面来验证一下是否成功,使用以下 SQL 语句查询,

select * from biz_region_info;

在 Navicat 客户端中执行以上 sql 后,在返回结果中可以看到以下内容:

如果能看到输出,说明数据已经成功进行了采集。

2、WebGIS 可视化

根据实际的需要,我们可以将上述的行政区划数据进行空间可视化。这里我们使用 Leaflet 对深圳市的三级区划进行展示,这里仅演示实际的效果,具体 Leaflet 的集成内容,之前有系列博客讲解。

深圳市行政区划

坪山行政区划

五、总结

以上就是本文的主要内容,本文将深入探讨如何结合强大的 SpringBoot 框架和专业的地理空间数据库 PostGIS,以高德地图开放平台为例,实现在线地图行政区划数据的离线存储,以满足多样化的业务需求,提升系统的灵活性和可靠性。通过本次实践探索,我们不仅能够掌握 SpringBoot 和 PostGIS 在地理空间数据处理方面的应用,还能深入了解在线地图数据离线存储的关键技术和实现方法。这将为我们在地理信息系统开发、大数据处理、移动应用开发等领域提供宝贵的经验和参考。

目录

  1. 前言
  2. 一、行政区划物理表设计
  3. 1、物理表字段设计
  4. 2、表结构脚本
  5. 二、行政区划在线 API
  6. 1、请求参数简介
  7. 2、响应参数简介
  8. 三、Java 后台数据获取及空间存储
  9. 1、API 服务调用
  10. 2、递归获取数据
  11. 四、成果展示
  12. 1、PostGIS 数据查询
  13. 2、WebGIS 可视化
  14. 五、总结

更多推荐文章

查看全部
  • Python 深度学习:ResNet、LSTM、ShuffleNet 与 CNN 网络结构对比分析
  • 8款降低AIGC检测率的论文辅助工具推荐
  • Python 官方下载与开发环境搭建完整指南
  • 基于 Leaflet Trackplayer 实现 WebGIS 高速轨迹可视化
  • 基于 Leaflet Trackplayer 的 WebGIS 高速公路轨迹可视化
  • 基于 Leaflet Trackplayer 的高速公路轨迹 WebGIS 可视化
  • 融合手术导板与增强现实的椎弓根螺钉置入技术验证
  • 基于 Leaflet Trackplayer 的 WebGIS 高速公路轨迹可视化
  • Kafka 核心机制与架构深度解析
  • Docker Compose 实践:简单拓扑、数据库代理与 WordPress 部署
  • WordPress 搭建与 Spring Boot 接口设计及前端依赖修复
  • WordPress 性能极致优化指南:从服务器底层到前端缓存
  • Python3 基础语法与核心数据类型详解
  • 踩坑与成长:WordPress、MyBatis-Plus 及前端依赖问题解决记录
  • Flutter mediapipe_core 鸿蒙化适配指南:端侧 AI 推理与视觉任务实战
  • DreamZero:世界动作模型作为零样本策略
  • C++ 搜索引擎通用工具模块:文件读取与中文分词实现
  • MATLAB 智能代码生成工具 Copilot_AI 功能解析
  • C++ 二叉搜索树详解:增删查改与 Key/Value 场景实现
  • 远程配置 VSCode:GitHub Copilot 安装后无法使用的排查与解决

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online