进阶教程
SpringBoot+PostGIS震中影响范围可视化实战评测
摘要
基于SpringBoot与PostGIS,利用全国行政村点位数据,按震中距离划分同心圆影响圈,实现震中
地震这种自然灾害,说实话,到现在还是没法准确预测。一旦强震真来了,破坏力摆在那里,损失往往非常惨重。通常,震后救援部门会迅速响应,但具体怎么救,很大程度上取决于震中在哪儿、震级有多大——这些都是救援方案的核心变量。关于具体如何实施救援,这里就不展开了。今天我们聊点更实际的:作为GIS开发人员,能不能利用我们的技术,给相关部门提供一些信息基础,帮他们做决策?
思路是这样的:我们有全国行政村级点位数据,然后根据地震的震中位置,按距离划几个圈——比如1公里以内、1到3.5公里、3.5到5公里(这几个区间只是参考,实际用的时候肯定要结合更多因素)。本文就沿着这个思路,结合地震信息,基于SpringBoot + PostGIS + Leaflet这套技术栈,重点演示如何做地震影响范围分析。如果你对WebGIS开发感兴趣,这应该有点参考价值。
## 一、基础数据
既然是WebGIS项目,影像底图得先安排上。我们用的是本地离线化的XYZ瓦片。地震基础信息和全国行政村点位数据则存在PostGIS里,程序后台已经提前灌进去了。下面简单看看这两张表的逻辑。
### 1、地震基础信息
地震信息表的逻辑结构:
对应的建表SQL:
```sql
-- ------------------------------
-- Table structure for biz_earthquake_info
-- ----------------------------
DROP TABLE IF EXISTS "public"."biz_earthquake_info";
CREATE TABLE "public"."biz_earthquake_info" (
"id" int8 NOT NULL,
"eq_time" timestamp(6) NOT NULL,
"eq_lng" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"eq_lat" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"eq_depth" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
"eq_level" varchar(8) COLLATE "pg_catalog"."default",
"eq_location" varchar(255) COLLATE "pg_catalog"."default",
"create_by" varchar(64) COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"update_by" varchar(64) COLLATE "pg_catalog"."default",
"update_time" timestamp(6)
);
COMMENT ON COLUMN "public"."biz_earthquake_info"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_time" IS '发震时间';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_lng" IS '发震经度';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_lat" IS '发震纬度';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_depth" IS '震源深度,单位千米';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_level" IS '震级';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_location" IS '震中位置';
COMMENT ON COLUMN "public"."biz_earthquake_info"."create_by" IS '创建人';
COMMENT ON COLUMN "public"."biz_earthquake_info"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."biz_earthquake_info"."update_by" IS '修改人';
COMMENT ON COLUMN "public"."biz_earthquake_info"."update_time" IS '修改时间';
-- Indexes
CREATE INDEX "idx_biz_earthquake_info_depth" ON "public"."biz_earthquake_info" USING btree ("eq_depth" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_earthquake_info_etime" ON "public"."biz_earthquake_info" USING btree ("eq_time" "pg_catalog"."timestamp_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_earthquake_info_qlevel" ON "public"."biz_earthquake_info" USING btree ("eq_level" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
-- Primary Key
ALTER TABLE "public"."biz_earthquake_info" ADD CONSTRAINT "pk_biz_earthquake_info" PRIMARY KEY ("id");
```
### 2、全国行政村
行政村点位表的逻辑结构:
建表SQL:
```sql
-- ------------------------------
-- Table structure for biz_village
-- ----------------------------
DROP TABLE IF EXISTS "public"."biz_village";
CREATE TABLE "public"."biz_village" (
"id" int8 NOT NULL,
"province_name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
"city_code" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
"city_name" varchar(512) COLLATE "pg_catalog"."default",
"area_code" varchar(64) COLLATE "pg_catalog"."default",
"area_name" varchar(512) COLLATE "pg_catalog"."default",
"township_code" varchar(64) COLLATE "pg_catalog"."default",
"township_name" varchar(512) COLLATE "pg_catalog"."default",
"village_code" varchar(64) COLLATE "pg_catalog"."default",
"village_name" varchar(512) COLLATE "pg_catalog"."default",
"address" varchar(512) COLLATE "pg_catalog"."default",
"type" varchar(32) COLLATE "pg_catalog"."default",
"lng" varchar(24) COLLATE "pg_catalog"."default",
"lat" varchar(24) COLLATE "pg_catalog"."default",
"geom" "public"."geometry"
);
COMMENT ON COLUMN "public"."biz_village"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_village"."province_name" IS '省份名称';
COMMENT ON COLUMN "public"."biz_village"."city_code" IS '市级编码';
COMMENT ON COLUMN "public"."biz_village"."city_name" IS '市级名称';
COMMENT ON COLUMN "public"."biz_village"."area_code" IS '区县编码';
COMMENT ON COLUMN "public"."biz_village"."area_name" IS '区县名称';
COMMENT ON COLUMN "public"."biz_village"."township_code" IS '乡镇编码';
COMMENT ON COLUMN "public"."biz_village"."township_name" IS '乡镇名称';
COMMENT ON COLUMN "public"."biz_village"."village_code" IS '乡村编码';
COMMENT ON COLUMN "public"."biz_village"."village_name" IS '乡村名称';
COMMENT ON COLUMN "public"."biz_village"."address" IS '地址';
COMMENT ON COLUMN "public"."biz_village"."type" IS '类型';
COMMENT ON COLUMN "public"."biz_village"."lng" IS '经度';
COMMENT ON COLUMN "public"."biz_village"."lat" IS '纬度';
COMMENT ON COLUMN "public"."biz_village"."geom" IS 'geom';
-- Indexes
CREATE INDEX "idx_biz_village_areacode" ON "public"."biz_village" USING btree ("area_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_village_city_code" ON "public"."biz_village" USING btree ("city_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_village_geom" ON "public"."biz_village" USING gist ("geom" "public"."gist_geometry_ops_2d");
-- Primary Key
ALTER TABLE "public"."biz_village" ADD CONSTRAINT "pk_biz_village" PRIMARY KEY ("id");
```
## 二、Ja va后台服务设计
后台用Ja va开发,框架选了SpringBoot,ORM用的MyBatis-Plus。整体是标准的MVC三层架构。这套系统访问压力不大,所以直接上单体架构,够用。
### 1、实体类设计
这次只需要做地震覆盖范围查询,因此定义个VO视图对象就够了。关键代码:
```ja va
package com.yelang.project.extend.earthquake.domain;
import ja va.io.Serializable;
import ja va.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class EarthquakeVillageVo implements Serializable {
private static final long serialVersionUID = -4857307169183564693L;
private BigDecimal dist; //距离
private String address; //位置
private String villageName; //村庄名称
private String lng; //经度
private String lat; //纬度
}
```
### 2、Mapper类设计
```ja va
package com.yelang.project.extend.earthquake.mapper;
import ja va.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yelang.project.extend.earthquake.domain.EarthquakeVillageVo;
import com.yelang.project.extend.earthquake.domain.Village;
public interface VillageMapper extends BaseMapper {
static final String FIND_LIST_BY_LNG_LAT = "<script>"
+ "with bp as ( select st_geomfromtext(${pointinfo},4326) :: geography tp ) "
+ "select st_distance(t.geom :: geography, bp.tp) dist, t.address, t.village_name, t.lng, t.lat "
+ "from biz_village t, bp where st_dwithin(t.geom :: geography, bp.tp, 5000 ) order by dist "
+ "</script>";
@Select(FIND_LIST_BY_LNG_LAT)
List findListByLngLat(@Param("pointinfo") String pointinfo);
}
```
这里有个关键点需要说明:我们的表中`geom`字段用的是`geometry`类型,坐标系是4326,但4326坐标系默认的单位是度。而在实际业务中,距离当然是用“米”更直观。解决方法是把数据类型转成`geography`,这样计算出来的距离单位就是米了。上面SQL里的5000表示5000米(也就是5公里),实际项目里可以做成动态参数,这里先硬编码演示功能。
### 3、控制器设计
Service层比较简单,就是把控制器的参数传给Mapper调用,代码就不贴了。直接看控制器:
```ja va
/**
* 震中位置5公里分析
* @param lng 经度
* @param lat 纬度
* @return
*/
@PostMapping("/villageinfo")
@ResponseBody
public AjaxResult earthinfo(String lng, String lat) {
List list = earthquakeInfoService.findListByLngLat(lng, lat);
AjaxResult ar = AjaxResult.success();
ar.put("data", list);
return ar;
}
```
## 三、前端展示
前端选的是Leaflet,配合Bootstrap和jQuery。想用Vue或React的朋友可以自己改造,这里就不提供对应代码了。
页面主要功能包括:地震信息查询、地图浏览(缩放、漫游)、影响范围分析、地震信息弹窗提示、三级范围展示、图例展示等。这些具体实现在之前的文章里有过介绍,不重复了,直接上关键代码。
### 1、初始化图例
图例帮助理解地图标记的含义。这里根据距离震中的远近,用不同颜色标记行政村点位:
```ja vascript
function initLegend() {
const legend = L.control.Legend({
position: "bottomleft",
collapsed: false,
symbolWidth: 24,
opacity: 1,
title: "图例",
column: 2,
legends: [
{ label: ">3.5公里", type: "circle", radius: 6, color: "green", fillColor: "green", fillOpacity: 0.6, weight: 2 },
{ label: "1-3.5公里", type: "circle", radius: 6, color: "yellow", fillColor: "yellow", fillOpacity: 0.6, weight: 2 },
{ label: "小于1公里", type: "circle", radius: 6, color: "red", fillColor: "red", fillOpacity: 0.6, weight: 2 }
]
}).addTo(mymap);
}
```
### 2、震中位置及影响范围标记
震中用Marker标记,影响范围用圆形表示。
### 3、行政村点查询及标记
前端用Ajax把地震经纬度传给后台,后台算出结果后返回每个点的经纬度、距离和行政区名称。前端再把这些点动态绘到地图上:
```ja vascript
$.ajax({
type: "post",
url: prefix + "/villageinfo",
data: {"lng": lng, "lat": lat},
success: function(rsData) {
var villageData = rsData.data;
for (var i = 0; i < villageData.length; i++) {
var info = villageData[i];
var dist = info.dist;
var strokeStyleSet = "green";
if (parseFloat(dist) > 1000 && parseFloat(dist) <= 3500) {
strokeStyleSet = "yellow";
}
if (parseFloat(dist) <= 1000) {
strokeStyleSet = "red";
}
var marker = L.circleMarker(new L.LatLng(info.lat, info.lng), {
radius: 8,
labelStyle: {
text: info.villageName,
rotation: 0,
zIndex: i,
strokeStyle: strokeStyleSet
}
});
var content = "地址:" + info.address + "
震中位置:" + name; content += "
距离震中(千米):" + info.dist; marker.bindPopup(content); marker.addTo(showLayerGroup); } mymap.fitBounds(showLayerGroup.getBounds()); } }); ``` 最终效果是这样的:
## 总结
整个思路其实不复杂:地震数据有了,行政村点位数据也有了,关键是利用PostGIS的空间查询能力,把“距离震中多远”这个问题跑通。前后端打通之后,地图上就能直观看到哪些村庄在哪个影响范围里。对于救援部门来说,这个信息能帮他们更快地判断重点区域在哪,往哪个方向调派资源。当然,这里只是一个技术验证,真正投入实战需要考虑的因素还有很多,但至少,这个路子是走得通的。
对应的建表SQL:
```sql
-- ------------------------------
-- Table structure for biz_earthquake_info
-- ----------------------------
DROP TABLE IF EXISTS "public"."biz_earthquake_info";
CREATE TABLE "public"."biz_earthquake_info" (
"id" int8 NOT NULL,
"eq_time" timestamp(6) NOT NULL,
"eq_lng" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"eq_lat" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
"eq_depth" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
"eq_level" varchar(8) COLLATE "pg_catalog"."default",
"eq_location" varchar(255) COLLATE "pg_catalog"."default",
"create_by" varchar(64) COLLATE "pg_catalog"."default",
"create_time" timestamp(6),
"update_by" varchar(64) COLLATE "pg_catalog"."default",
"update_time" timestamp(6)
);
COMMENT ON COLUMN "public"."biz_earthquake_info"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_time" IS '发震时间';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_lng" IS '发震经度';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_lat" IS '发震纬度';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_depth" IS '震源深度,单位千米';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_level" IS '震级';
COMMENT ON COLUMN "public"."biz_earthquake_info"."eq_location" IS '震中位置';
COMMENT ON COLUMN "public"."biz_earthquake_info"."create_by" IS '创建人';
COMMENT ON COLUMN "public"."biz_earthquake_info"."create_time" IS '创建时间';
COMMENT ON COLUMN "public"."biz_earthquake_info"."update_by" IS '修改人';
COMMENT ON COLUMN "public"."biz_earthquake_info"."update_time" IS '修改时间';
-- Indexes
CREATE INDEX "idx_biz_earthquake_info_depth" ON "public"."biz_earthquake_info" USING btree ("eq_depth" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_earthquake_info_etime" ON "public"."biz_earthquake_info" USING btree ("eq_time" "pg_catalog"."timestamp_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_earthquake_info_qlevel" ON "public"."biz_earthquake_info" USING btree ("eq_level" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
-- Primary Key
ALTER TABLE "public"."biz_earthquake_info" ADD CONSTRAINT "pk_biz_earthquake_info" PRIMARY KEY ("id");
```
### 2、全国行政村
行政村点位表的逻辑结构:
建表SQL:
```sql
-- ------------------------------
-- Table structure for biz_village
-- ----------------------------
DROP TABLE IF EXISTS "public"."biz_village";
CREATE TABLE "public"."biz_village" (
"id" int8 NOT NULL,
"province_name" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
"city_code" varchar(16) COLLATE "pg_catalog"."default" NOT NULL,
"city_name" varchar(512) COLLATE "pg_catalog"."default",
"area_code" varchar(64) COLLATE "pg_catalog"."default",
"area_name" varchar(512) COLLATE "pg_catalog"."default",
"township_code" varchar(64) COLLATE "pg_catalog"."default",
"township_name" varchar(512) COLLATE "pg_catalog"."default",
"village_code" varchar(64) COLLATE "pg_catalog"."default",
"village_name" varchar(512) COLLATE "pg_catalog"."default",
"address" varchar(512) COLLATE "pg_catalog"."default",
"type" varchar(32) COLLATE "pg_catalog"."default",
"lng" varchar(24) COLLATE "pg_catalog"."default",
"lat" varchar(24) COLLATE "pg_catalog"."default",
"geom" "public"."geometry"
);
COMMENT ON COLUMN "public"."biz_village"."id" IS '主键';
COMMENT ON COLUMN "public"."biz_village"."province_name" IS '省份名称';
COMMENT ON COLUMN "public"."biz_village"."city_code" IS '市级编码';
COMMENT ON COLUMN "public"."biz_village"."city_name" IS '市级名称';
COMMENT ON COLUMN "public"."biz_village"."area_code" IS '区县编码';
COMMENT ON COLUMN "public"."biz_village"."area_name" IS '区县名称';
COMMENT ON COLUMN "public"."biz_village"."township_code" IS '乡镇编码';
COMMENT ON COLUMN "public"."biz_village"."township_name" IS '乡镇名称';
COMMENT ON COLUMN "public"."biz_village"."village_code" IS '乡村编码';
COMMENT ON COLUMN "public"."biz_village"."village_name" IS '乡村名称';
COMMENT ON COLUMN "public"."biz_village"."address" IS '地址';
COMMENT ON COLUMN "public"."biz_village"."type" IS '类型';
COMMENT ON COLUMN "public"."biz_village"."lng" IS '经度';
COMMENT ON COLUMN "public"."biz_village"."lat" IS '纬度';
COMMENT ON COLUMN "public"."biz_village"."geom" IS 'geom';
-- Indexes
CREATE INDEX "idx_biz_village_areacode" ON "public"."biz_village" USING btree ("area_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_village_city_code" ON "public"."biz_village" USING btree ("city_code" COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST);
CREATE INDEX "idx_biz_village_geom" ON "public"."biz_village" USING gist ("geom" "public"."gist_geometry_ops_2d");
-- Primary Key
ALTER TABLE "public"."biz_village" ADD CONSTRAINT "pk_biz_village" PRIMARY KEY ("id");
```
## 二、Ja va后台服务设计
后台用Ja va开发,框架选了SpringBoot,ORM用的MyBatis-Plus。整体是标准的MVC三层架构。这套系统访问压力不大,所以直接上单体架构,够用。
### 1、实体类设计
这次只需要做地震覆盖范围查询,因此定义个VO视图对象就够了。关键代码:
```ja va
package com.yelang.project.extend.earthquake.domain;
import ja va.io.Serializable;
import ja va.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class EarthquakeVillageVo implements Serializable {
private static final long serialVersionUID = -4857307169183564693L;
private BigDecimal dist; //距离
private String address; //位置
private String villageName; //村庄名称
private String lng; //经度
private String lat; //纬度
}
```
### 2、Mapper类设计
```ja va
package com.yelang.project.extend.earthquake.mapper;
import ja va.util.List;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yelang.project.extend.earthquake.domain.EarthquakeVillageVo;
import com.yelang.project.extend.earthquake.domain.Village;
public interface VillageMapper extends BaseMapper
### 3、行政村点查询及标记
前端用Ajax把地震经纬度传给后台,后台算出结果后返回每个点的经纬度、距离和行政区名称。前端再把这些点动态绘到地图上:
```ja vascript
$.ajax({
type: "post",
url: prefix + "/villageinfo",
data: {"lng": lng, "lat": lat},
success: function(rsData) {
var villageData = rsData.data;
for (var i = 0; i < villageData.length; i++) {
var info = villageData[i];
var dist = info.dist;
var strokeStyleSet = "green";
if (parseFloat(dist) > 1000 && parseFloat(dist) <= 3500) {
strokeStyleSet = "yellow";
}
if (parseFloat(dist) <= 1000) {
strokeStyleSet = "red";
}
var marker = L.circleMarker(new L.LatLng(info.lat, info.lng), {
radius: 8,
labelStyle: {
text: info.villageName,
rotation: 0,
zIndex: i,
strokeStyle: strokeStyleSet
}
});
var content = "地址:" + info.address + "震中位置:" + name; content += "
距离震中(千米):" + info.dist; marker.bindPopup(content); marker.addTo(showLayerGroup); } mymap.fitBounds(showLayerGroup.getBounds()); } }); ``` 最终效果是这样的:
## 总结
整个思路其实不复杂:地震数据有了,行政村点位数据也有了,关键是利用PostGIS的空间查询能力,把“距离震中多远”这个问题跑通。前后端打通之后,地图上就能直观看到哪些村庄在哪个影响范围里。对于救援部门来说,这个信息能帮他们更快地判断重点区域在哪,往哪个方向调派资源。当然,这里只是一个技术验证,真正投入实战需要考虑的因素还有很多,但至少,这个路子是走得通的。 来源:互联网
免责声明
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。