游乐游手机版
首页/AI教程/文章详情

SpringBoot全国风景区WebGIS按省展示实践

时间:2026-06-22 15:29
利用SpringBoot与PostGIS构建后台服务,结合Leaflet前端实现全国风景区按省份的WebGIS可视化展示。数据库含14847条景区记录,通过空间关联查询完成省份内景点标注,直观呈现浙江、山东、四川等省份的旅游资源空间分布。

前言

旅行这件事,说到底是心灵的一次放空。每一次出发,都像翻开一本新书,你不知道下一页会遇见怎样的风景,但总能在某个拐角处,被一缕阳光、一阵山风击中,然后突然想明白一些事。路上看过的山川湖海,遇见的有趣灵魂,最后都悄悄沉淀下来,变成心里最踏实的底气。身体和灵魂,总有一个要在路上——这话听多了,但真到了路上,才知道那种“在路上”的感觉有多上瘾。

不知道你是否还记得自己上一次旅行是什么时候?是和谁一起?最难忘的风景又是哪里?除了风景,还有什么让你念念不忘?既然选择了出发,那就去那些风景好的地方吧,去认真的景区里走走,把心放空,也把能量蓄满。然后带着这些能量,继续扎进生活里——这才是旅行的意义。

不管你是旅游爱好者,还是文旅行业的分析达人,用WebGIS来一场时空之旅,在地图上把旅行的足迹标出来,都是一件挺酷的事。这篇文章就以全国的旅游资源为样本,按省份展示,把不同省份的风景区信息标注在地图上。顺便也能看看,全国范围里,这些风景区的空间分布到底长什么样。

文章会先介绍一下数据库里风景区的基本情况,然后说说怎么用SpringBoot搭一个风景区查询的后台,接着讲前端怎么基于Leaflet做可视化,最后拿几个实际省份的案例来看看效果。喜欢旅游的朋友,别急着关页面,咱们一起看看图,也算云旅行了。

一、全国风景区信息介绍

这一节先说说核心数据——全国风景区信息。其实之前在另一篇文章里已经详细讲过全国A级风景区数据入库的过程,用的是Ja va和PostGIS那一套。不过为了让大家读起来不觉得突兀,这里重新把表结构拿出来说一下,也方便熟悉空间表的设计思路。

1、全国范围内数据分布

数据入库的细节就不重复了,直接看表结构:

对应的建表脚本如下:

CREATE TABLE "public"."biz_scenic_spot" (
  "id" int8 NOT NULL,
  "name" varchar(255) COLLATE "pg_catalog"."default",
  "level" varchar(4) COLLATE "pg_catalog"."default",
  "province" varchar(255) COLLATE "pg_catalog"."default",
  "city" varchar(255) COLLATE "pg_catalog"."default",
  "area" varchar(255) COLLATE "pg_catalog"."default",
  "address" varchar(255) COLLATE "pg_catalog"."default",
  "evaluation_time" varchar(255) COLLATE "pg_catalog"."default",
  "publish_time" varchar(255) COLLATE "pg_catalog"."default",
  "lng_gcj02" varchar(30) COLLATE "pg_catalog"."default",
  "lat_gcj02" varchar(30) COLLATE "pg_catalog"."default",
  "lng_bd09" varchar(30) COLLATE "pg_catalog"."default",
  "lat_bd09" varchar(30) COLLATE "pg_catalog"."default",
  "lng_wgs84" varchar(30) COLLATE "pg_catalog"."default",
  "lat_wgs84" varchar(30) COLLATE "pg_catalog"."default",
  "geom" "public"."geometry",
  "publish_link" varchar(255) COLLATE "pg_catalog"."default",
  CONSTRAINT "pk_biz_scenic_spot" PRIMARY KEY ("id")
);
CREATE INDEX "idx_biz_scenic_spot_geom" ON "public"."biz_scenic_spot" USING gist ("geom" "public"."gist_geometry_ops_2d");
COMMENT ON COLUMN "public"."biz_scenic_spot"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_scenic_spot"."name" IS '景区名称';
COMMENT ON COLUMN "public"."biz_scenic_spot"."level" IS '景区级别';
COMMENT ON COLUMN "public"."biz_scenic_spot"."province" IS '所属省份';
COMMENT ON COLUMN "public"."biz_scenic_spot"."city" IS '所属城市';
COMMENT ON COLUMN "public"."biz_scenic_spot"."area" IS '所属区县';
COMMENT ON COLUMN "public"."biz_scenic_spot"."address" IS '地址';
COMMENT ON COLUMN "public"."biz_scenic_spot"."evaluation_time" IS '评定时间';
COMMENT ON COLUMN "public"."biz_scenic_spot"."publish_time" IS '发布时间';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lng_gcj02" IS 'lng_GCJ02';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lat_gcj02" IS 'lat_GCJ02';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lng_bd09" IS 'lng_BD09';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lat_bd09" IS 'lat_BD09';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lng_wgs84" IS 'lng_WGS84';
COMMENT ON COLUMN "public"."biz_scenic_spot"."lat_wgs84" IS 'lat_WGS84';
COMMENT ON COLUMN "public"."biz_scenic_spot"."publish_link" IS '发布链接';
COMMENT ON TABLE "public"."biz_scenic_spot" IS '全国风景区信息表';

数据库里一共有14847条风景区记录。按级别分组统计一下,结果如下(其中3条没有具体级别,估计是数据异常):

序号景区级别数量
15A336
24A4486
33A7866
42A2066
5A93

2、全国风景区分布

从数据库层面先看看全国的风景区分布长什么样。直接用省份字段分组(没有关联外部的省份信息表,用的是风景区表里的省份字段):

select count(1) num,province from biz_scenic_spot group by province order by num desc;

结果如下:

序号省份景区数量
1浙江1322
2山东1205
3四川895
4广西662
5安徽605
6江苏600
7广东597
8河南580
9湖北576
10新疆574
11贵州570
12云南561
13辽宁558
14湖南550
16陕西527
17河北490
18福建463
19甘肃442
20内蒙古428
21江西421
22黑龙江409
23吉林275
24重庆272
25山西268
26北京216
27青海163
28西藏148
29宁夏147
30上海139
31天津100
32海南84

结果一目了然:浙江、山东、四川稳居前三,旅游资源相当丰富。后三位则是海南、天津、上海。当然,这只是表格里的数字,要让数据真正有空间感,还得靠地图说话。接下来就用WebGIS把这些分布落到地图上。

3、PostGIS空间关联查询

为了让景区信息具有空间分布特征,我们把它和省级行政区划表(biz_province)做空间关联。用st_contains(geom, geom)来判定某个省份范围内包含了哪些景点。举个例子,查湖南省(code=430000)的风景区:

SELECT T.*, st_asgeojson(T.geom) AS geomJson 
FROM biz_province P, biz_scenic_spot T 
WHERE P.code = '430000' AND st_contains(P.geom, T.geom);

二、后台查询的设计与实现

后台服务这块,还是用熟悉的SpringBoot + Mybatis-Plus组合,空间数据库选PostGIS。整体按MVC模式分层来设计。

1、Model和Mapper层

Model层包括实体类和视图对象,Mapper层就是数据访问层。基于Mybatis-Plus,操作起来很顺手。实体类代码:

package com.yelang.project.extend.scenicspot.domain;

import ja va.io.Serializable;
import org.apache.ibatis.type.BlobByteObjectArrayTypeHandler;
import org.apache.ibatis.type.BlobInputStreamTypeHandler;
import org.apache.ibatis.type.BlobTypeHandler;
import org.apache.ibatis.type.ByteArrayTypeHandler;
import org.apache.ibatis.type.ByteObjectArrayTypeHandler;
import org.apache.ibatis.type.ByteTypeHandler;
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.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@TableName(value = "biz_scenic_spot", autoResultMap = true)
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class ScenicSpot implements Serializable {
    private static final long serialVersionUID = 1830004907219610805L;
    @TableId
    private Long id;
    private String name;
    private String level;
    private String province;
    private String city;
    private String area;
    private String address;
    @TableField(value="evaluation_time")
    private String evaluationTime;
    @TableField(value="publish_time")
    private String publishTime;
    @TableField(value="publish_link")
    private String publishLink;
    @TableField(value="lng_GCJ02")
    private String lngGCJ02;
    @TableField(value="lat_GCJ02")
    private String latGCJ02;
    @TableField(value="lng_BD09")
    private String lngBD09;
    @TableField(value="lat_BD09")
    private String latBD09;
    @TableField(value="lng_WGS84")
    private String lngWGS84;
    @TableField(value="lat_WGS84")
    private String latWGS84;
    @TableField(typeHandler = PgGeometryTypeHandler.class)
    private String geom;
    @TableField(exist=false)
    private String geomJson;
    @TableField(exist=false)
    private byte[] tile;

    public ScenicSpot(String name, String level, String province, String city, String area, String address,
                      String evaluationTime, String publishTime, String publishLink,
                      String lngGCJ02, String latGCJ02, String lngBD09, String latBD09,
                      String lngWGS84, String latWGS84, String geom) {
        super();
        this.name = name;
        this.level = level;
        this.province = province;
        this.city = city;
        this.area = area;
        this.address = address;
        this.evaluationTime = evaluationTime;
        this.publishTime = publishTime;
        this.publishLink = publishLink;
        this.lngGCJ02 = lngGCJ02;
        this.latGCJ02 = latGCJ02;
        this.lngBD09 = lngBD09;
        this.latBD09 = latBD09;
        this.lngWGS84 = lngWGS84;
        this.latWGS84 = latWGS84;
        this.geom = geom;
    }
}

Mapper层主要提供按省级行政区划代码查询风景区列表的方法,关键代码:

package com.yelang.project.extend.scenicspot.mapper;

import ja va.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.type.JdbcType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yelang.project.extend.scenicspot.domain.ScenicSpot;
import com.yelang.project.extend.scenicspot.domain.TileBox;

public interface ScenicSpotMapper extends BaseMapper {
    static final String FIND_GEOJSON_SQL = "";

    @Select(FIND_GEOJSON_SQL)
    ScenicSpot findGeoJsonById(@Param("id") Long id, @Param("name") String name);

    static final String FIND_LISTBY_PCODE = "";

    @Select(FIND_LISTBY_PCODE)
    List findListByPcode(@Param("code") String code);
}

2、业务层和控制层设计

业务层比较简单,就是搭桥。关键代码如下:

package com.yelang.project.extend.scenicspot.service.impl;

@Service
public class ScenicSpotServiceImpl extends ServiceImpl implements IScenicSpotService {
    @Override
    public List findListByPcode(String code) {
        return this.baseMapper.findListByPcode(code);
    }
}
package com.yelang.project.extend.scenicspot.controller;

import ja va.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sspot/province")
public class ScenicSpotProvinceController extends BaseController {
    private String prefix = "scenicspot/province";
    @Autowired
    private IProvinceService provinceService;
    @Autowired
    private IScenicSpotService sspotService;

    @RequiresPermissions("sspot:province:view")
    @GetMapping()
    public String map() {
        return prefix + "/map";
    }

    @RequiresPermissions("sspot:province:list")
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list(Province province) {
        startPage();
        List list = provinceService.selectList(province);
        return getDataTable(list);
    }

    @RequiresPermissions("sspot:province:geom")
    @GetMapping("/geojson/{id}")
    @ResponseBody
    public AjaxResult getGeojson(@PathVariable("id") Long id) {
        Province province = provinceService.findGeoJsonById(id, null);
        return AjaxResult.success().put("data", province.getGeomJson());
    }

    @RequiresPermissions("sspot:province:sspotlist")
    @GetMapping("/datalist/{code}")
    @ResponseBody
    public AjaxResult sspotList(@PathVariable("code") String code) {
        List list = sspotService.findListByPcode(code);
        AjaxResult ar = AjaxResult.success();
        ar.put("data", list);
        return ar;
    }
}

控制层提供了四个接口:页面跳转、省份列表、省份GeoJSON、省份下辖风景区列表。后台就这么搭起来了,服务端的工作基本搞定。

三、WebGIS可视化

前端可视化这块,用Leaflet框架,配合Canvas文本标记方法。地图定义和列表展示不是重点,直接跳过,说核心。

1、省份范围可视化

界面里先展示省份列表,点击某个省份的操作按钮,获取该省份的code,然后拉取对应的省级行政区划GeoJSON,用Leaflet展示到地图上。关键代码:

function showProvince(id) {
    var myStyle = {color:"white", weight:5, "opacity":1, fillOpacity: 0};
    $.ajax({
        type: "get",
        url: prefix + "/geojson/" + id,
        data: {},
        async: false,
        dataType: "json",
        cache: false,
        processData: false,
        success: function(result) {
            if (undefined != provinceAreaLayer) {
                provinceAreaLayer.removeFrom(mymap);
            }
            if (result.code == web_status.SUCCESS) {
                var geojson = JSON.parse(result.data);
                provinceAreaLayer = L.geoJSON(geojson, {style: myStyle}).addTo(mymap);
                mymap.setView(provinceAreaLayer.getBounds().getCenter(), 7);
            }
        },
        error: function() {
            $.modal.alertWarning("获取空间信息失败");
        }
    });
}

2、省级风景区可视化展示

通过code关联两个表,做空间叠加分析,把省份范围内的风景区全部标出来。核心代码:

function showScenicSpot(code) {
    $.ajax({
        type: "get",
        url: prefix + "/datalist/" + code,
        dataType: "json",
        cache: false,
        processData: false,
        success: function(result) {
            if (result.code == web_status.SUCCESS) {
                var strokeStyleSet = "#23168d";
                var lat, lng, cityInfo;
                for (var i = 0; i < result.data.length; i++) {
                    var dataInfo = result.data[i];
                    var geomObj = JSON.parse(dataInfo.geomJson);
                    if (i == 0) {
                        lat = geomObj.coordinates[1];
                        lng = geomObj.coordinates[0];
                        continue;
                    }
                    var radiusSize = 5;
                    switch(dataInfo.level) {
                        case '5A': strokeStyleSet = "#c50808"; radiusSize += 7; break;
                        case '4A': strokeStyleSet = "#c37322"; radiusSize += 5; break;
                        case '3A': strokeStyleSet = "#6f8d16"; radiusSize += 3; break;
                        case '2A': strokeStyleSet = "#168d40"; radiusSize += 1; break;
                        default: strokeStyleSet = "#23168d";
                    }
                    var content = "名称:" + dataInfo.name 
                        + "
级别:" + dataInfo.level; content += "
所属行政区划:" + dataInfo.province + "/" + dataInfo.city + "/" + dataInfo.area; content += "
评定时间:" + dataInfo.evaluationTime; var latlng = new L.latLng(geomObj.coordinates[1], geomObj.coordinates[0]); let marker = L.circleMarker(latlng, { radius: radiusSize, color: strokeStyleSet, labelStyle: { offsetX: 0, offsetY: 30, text: dataInfo.name, rotation: 0, zIndex: radiusSize, minZoom: 5, strokeStyle: strokeStyleSet } }).addTo(showLayerGroup); marker.bindPopup(content); } mymap.addLayer(showLayerGroup); } }, error: function() { $.modal.alertWarning("获取信息失败"); } }); }

3、成果展示

代码跑通之后,来看看几个省份的实际效果。选了山西、辽宁、江苏、安徽、新疆五个省份的风景区分布图,直观感受一下WebGIS带来的空间视角。

山西省风景区分布图山西省风景区分布图

辽宁省风景区分布图辽宁省风景区分布图

江苏省风景区分布图江苏省风景区分布图

安徽省风景区分布图安徽省风景区分布图

新疆维吾尔自治区风景区分布示意图新疆维吾尔自治区风景区分布示意图

总结

从数据入库到后台服务,再到前端可视化,整个流程走下来,算是一次比较完整的WebGIS实践。通过PostGIS的空间查询能力,把全国风景区数据按省份切分,再用Leaflet落到地图上,确实能很直观地看到旅游资源在空间上的分布规律。希望这个思路能给想做类似项目的朋友一些参考。

来源:https://cloud.tencent.com.cn/developer/article/2693745
上一篇机械运动中运动和静止的相对性怎么判断 下一篇WorkBuddy保姆级教程二:第一次对话超简单操作指南
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
CapCut AI Docker 一键部署:镜像拉取、端口映射与数据目录配置教程
AI教程 · 2026-06-30

CapCut AI Docker 一键部署:镜像拉取、端口映射与数据目录配置教程

CapCutAI容器化部署需先确认镜像来源与授权范围,再完成环境准备、镜像拉取、端口映射、数据目录挂载和启动验证,适合本地试用、团队内网演示与轻量化AI剪辑服务管理。

CapCut AI Windows本地安装配置2026最新版含下载与环境要求
AI教程 · 2026-06-30

CapCut AI Windows本地安装配置2026最新版含下载与环境要求

CapCutAI与剪映AI在Windows端适合短视频、口播、课程和营销素材剪辑,安装前需确认系统、显卡、存储与网络条件,优先选择官方渠道下载,并完成账号、素材目录、硬件加速和导出参数配置。

Veo新手保姆级安装教程:从下载到首次运行
AI教程 · 2026-06-30

Veo新手保姆级安装教程:从下载到首次运行

Veo适合用文字生成短视频,新手应先确认官方入口、准备账号与设备环境,再按网页或应用方式完成启用。首次运行重点在提示词、参数、素材合规与结果保存,避免使用非官方安装包。

Veo本地模型运行下载路径设置与性能优化指南
AI教程 · 2026-06-30

Veo本地模型运行下载路径设置与性能优化指南

Veo本地模型部署需先确认模型来源与硬件条件,再完成下载校验、目录规划、路径配置和推理参数优化。重点关注显存占用、依赖版本、缓存位置、授权范围与常见报错处理。

Veo安装失败解决指南:常见报错与日志排查及升级回滚方案
AI教程 · 2026-06-30

Veo安装失败解决指南:常见报错与日志排查及升级回滚方案

Veo安装失败通常与系统环境、依赖版本、网络源、权限和缓存有关。排查时应先确认版本要求,再查看安装日志,按报错类型处理,并提前备份项目,确保升级与回滚可控。