跳到主要内容Java+Leaflet 构建湖南省道路长度 WebGIS 系统 | 极客日志Java大前端java
Java+Leaflet 构建湖南省道路长度 WebGIS 系统
综述由AI生成Java 后端结合 Leaflet 前端实现湖南省道路长度数据的可视化展示。通过 PostGIS 空间查询获取路网数据,利用 MVC 架构处理业务逻辑,前端根据里程阈值动态渲染地图颜色与标注。最终实现了按地市统计的道路分布分析及交互式地图展示。
疯疯癫癫2 浏览 引言
地理信息系统(GIS)在交通管理和城市规划中扮演着关键角色。针对湖南省复杂的交通网络,如何高效展示和管理道路长度信息是一个实际痛点。传统的静态管理方式存在更新滞后、查询不便等问题。本实践基于 Java 后端与 Leaflet 前端,构建了一个可视化的 WebGIS 系统,旨在直观呈现各地市道路里程数据。
基础空间数据
系统依赖的空间数据主要涉及三张核心表:城市道路里程业务表、行政驻地点位表以及市级行政区划面状表。数据来源参考 OSM 路网信息,虽非实时权威数据,但足以支撑技术验证与逻辑演示。
数据表结构
| 序号 | 表名 | 说明 |
|---|
| 1 | biz_urban_road_mileage_info | 城市道路里程信息表(业务表) |
| 2 | biz_geographic_name | 城市名称信息表(点状空间数据) |
| 3 | biz_city | 市级行政区划信息表(面状空间数据) |
空间检索 SQL
通过关联这三张表,我们可以获取指定省份下各地市的道路总长及行政中心坐标。以下 SQL 以湖南省代码 430000 为例,实际应用中可动态替换省份参数:
SELECT t1.*, T3.province_code, t3.province_name, st_asgeojson ( t3.geom ) geomJson, st_x ( t2.geom ) lon, st_y ( t2.geom ) lat
FROM biz_urban_road_mileage_info t1, biz_geographic_name t2, biz_city t3
WHERE t1.parent_code = '430000'
AND t1.city_code = t3.city_code
AND T1.city_name = t2.NAME
AND st_contains ( t3.geom, t2.geom );
执行后返回的 GeoJSON 格式数据可直接用于前端地图渲染。
Java 后台实现
后端采用经典的 MVC 三层架构,利用 MyBatis-Plus 简化数据库操作。
视图对象设计
为了将后端查询结果适配前端需求,我们扩展了原有的实体类,增加省份代码、名称及 GeoJSON 字段。这样 Controller 层可以直接返回包含地图所需数据的 VO 对象。
package com.yelang.project.extend.earthquake.domain;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import lombok.EqualsAndHashCode;
lombok.ToString;
{
;
String provinceCode;
String provinceName;
String geomJson;
String lat;
String lon;
}
import
@Data
@ToString(callSuper=true)
@EqualsAndHashCode(callSuper=false)
public
class
UrbanRoadMileageInfoVO
extends
UrbanRoadMileageInfo
implements
Serializable
private
static
final
long
serialVersionUID
=
1101541707654186490L
@TableField(exist = false, value = "province_code")
private
@TableField(exist = false, value = "province_name")
private
@TableField(exist = false)
private
private
private
Mapper 层空间查询
Mapper 接口直接封装了空间 SQL 语句。这里定义了两个方法:一个是按省份统计地市路网长度,另一个是获取带行政中心坐标的详细列表。
package com.yelang.project.extend.earthquake.mapper;
import java.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.UrbanRoadMileageInfo;
import com.yelang.project.extend.earthquake.domain.UrbanRoadMileageInfoVO;
public interface UrbanRoadMileageInfoMapper extends BaseMapper<UrbanRoadMileageInfo> {
static final String LIST_BYPROVINCE_SQL = "SELECT t1.city_code, MAX(t1.city_name) AS city_name," +
" SUM(CASE WHEN r.fclass IN ('motorway', 'motorway_link') THEN ST_Length(r.geom::geography) ELSE 0 END) AS highway_length, " +
" SUM(CASE WHEN r.fclass IN ('trunk', 'trunk_link') THEN ST_Length(r.geom::geography) ELSE 0 END) AS trunk_length, " +
" SUM(CASE WHEN r.fclass IN ('primary', 'primary_link') THEN ST_Length(r.geom::geography) ELSE 0 END) AS primary_length, " +
" SUM(CASE WHEN r.fclass IN ('secondary', 'secondary_link') THEN ST_Length(r.geom::geography) ELSE 0 END) AS secondary_length," +
" SUM(CASE WHEN r.fclass IN ('tertiary', 'tertiary_link') THEN ST_Length(r.geom::geography) ELSE 0 END) AS tertiary_length, " +
" SUM(CASE WHEN r.fclass IN ('residential', 'living_street') THEN ST_Length(r.geom::geography) ELSE 0 END) AS residential_length, " +
" SUM(CASE WHEN r.fclass IN ('service', 'unclassified') THEN ST_Length(r.geom::geography) ELSE 0 END) AS service_length, " +
" SUM(CASE WHEN r.fclass IN ('footway', 'pedestrian', 'path') THEN ST_Length(r.geom::geography) ELSE 0 END) AS pedestrian_length, " +
" SUM(CASE WHEN r.fclass = 'cycleway' THEN ST_Length(r.geom::geography) ELSE 0 END) AS cycleway_length, " +
" SUM(CASE WHEN r.fclass = 'track' THEN ST_Length(r.geom::geography) ELSE 0 END) AS track_length," +
" SUM(CASE WHEN r.fclass in ('steps', 'footway') THEN ST_Length(r.geom::geography) ELSE 0 END) AS steps_length," +
" SUM(ST_Length(r.geom::geography)) AS total_length " +
" FROM biz_road_network r JOIN biz_city t1 ON ST_Contains(t1.geom, r.geom) WHERE t1.province_code = #{province_code} " +
" GROUP BY t1.city_code ORDER BY total_length DESC ";
@Select(LIST_BYPROVINCE_SQL)
List<UrbanRoadMileageInfo> getListByProvinceCode(@Param("province_code") String provinceCode);
final static String GET_ROADMILEAGELIST_BY_PROVINCECODE = "<script>" +
" SELECT t1.*, T3.province_code, t3.province_name, st_asgeojson ( t3.geom ) geomJson, " +
" st_x ( t2.geom ) lon, st_y ( t2.geom ) lat " +
" FROM biz_urban_road_mileage_info t1, biz_geographic_name t2, biz_city t3 " +
" WHERE t1.parent_code = #{provinceCode} AND t1.city_code = t3.city_code " +
" AND T1.city_name = t2.NAME AND st_contains ( t3.geom, t2.geom ) " +
"</script>";
@Select(GET_ROADMILEAGELIST_BY_PROVINCECODE)
List<UrbanRoadMileageInfoVO> getRoadMileageList(@Param("provinceCode") String provinceCode);
}
控制层 API
Controller 主要负责路由分发和权限控制。这里定义了页面跳转入口和数据接口,支持动态传入省份代码获取对应列表。
package com.yelang.project.extend.earthquake.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yelang.framework.web.controller.BaseController;
import com.yelang.framework.web.domain.AjaxResult;
import com.yelang.project.extend.earthquake.domain.UrbanRoadMileageInfoVO;
import com.yelang.project.extend.earthquake.service.IUrbanRoadMileageInfoService;
@Controller
@RequestMapping("/eq/urbanroadmileageinfo")
public class UrbanRoadMileageInfoController extends BaseController {
private String prefix = "earthquake/urbanroadmileageinfo";
@Autowired
private IUrbanRoadMileageInfoService urbanRoadMileageInfoService;
@RequiresPermissions("eq:urbanroadmileageinfo:map")
@GetMapping("/")
public String main(ModelMap mmap) {
return prefix + "/main";
}
@RequiresPermissions("eq:urbanroadmileageinfo:list")
@GetMapping("/data/{pcode}")
@ResponseBody
public AjaxResult ewsnProvinceList(@PathVariable("pcode") String pcode) {
List<UrbanRoadMileageInfoVO> dataList = urbanRoadMileageInfoService.getRoadMileageList(pcode);
return AjaxResult.success().put("data", dataList);
}
}
WebGIS 界面实现
前端使用 Leaflet 库进行地图渲染,重点在于根据后端返回的数据动态配置图层样式和图例。
颜色映射逻辑
为了直观区分不同里程规模的地市,我们定义了颜色阈值数组,并通过函数将数值映射为 Hex 颜色码。
var colorList = [
{name:"5千公里以下",color:"#00FF00",rgb:new Color(0, 255, 0),colorDesc:"绿色"},
{name:"5千 -8千公里",color:"#FFFF00",rgb:new Color(255, 255, 0),colorDesc:"黄色"},
{name:"8千 -1.1万公里",color:"#FFA500",rgb:new Color(255, 165, 0),colorDesc:"蓝色"},
{name:"1.1万 -1.4万公里",color:"#113fc1",rgb:new Color(255, 0, 0),colorDesc:"橙色"},
{name:"1.4万 -1.6万公里",color:"#800080",rgb:new Color(128, 0, 128),colorDesc:"紫色"},
{name:"1.6万以上",color:"#FF0000",rgb:new Color(153, 51, 102),colorDesc:"红色"}
];
function getColorByLength(length){
if(length >= 0 && length <= 5000) return "#00FF00";
if(length >= 5001 && length <= 8000) return "#FFFF00";
if(length >= 8001 && length <= 11000) return "#FFA500";
if(length >= 11001 && length <= 14000) return "#113fc1";
if(length >= 14001 && length <= 16000) return "#800080";
if(length >= 16001) return "#FF0000";
}
地图渲染与交互
数据库中的长度单位通常为米,前端展示时需转换为万公里。加载数据时,遍历返回的 GeoJSON 数据,为每个地市生成对应的多边形图层和标记点。
function buildShowInfo(index, color, data){
var length = parseFloat(data.totalLength) / (1000.0 * 10000);
var result = "<div style='background-color:" + color + ";' class='animation-spaceInDown onclick='showDetails('"+data.cityCode+"')>'><div>" + data.cityName;
result += "<span>:" + length.toFixed(2) + "万公里</span></div></div>";
return result;
}
function previewRoadMap(pid, provinceCode, name){
previewProvince(pid, name);
$.ajax({
type: "get",
url: ctx + "eq/urbanroadmileageinfo/data/" + provinceCode,
dataType: "json",
cache: false,
success: function(result){
if(result.code == web_status.SUCCESS){
collisionLayer.clearLayers();
var dataArray = result.data;
if(dataArray != null && dataArray.length > 1){
for(var i = 0; i < dataArray.length; i++){
var areaData = dataArray[i];
var tempTotalLength = parseFloat(areaData.totalLength) / 1000.0;
var color = getColorByLength(tempTotalLength);
var areaLayer = L.geoJSON(JSON.parse(areaData.geomJson),{
style: {color:color, fillColor:color, weight:3, opacity:0.65, fillOpacity: 0.65}
}).addTo(mymap);
var myIcon = L.divIcon({
iconSize: null,
className: '',
popupAnchor:[5,5],
shadowAnchor:[5,5],
html: buildShowInfo(i, color, areaData)
});
L.marker([areaData.lat, areaData.lon], { icon: myIcon }).addTo(collisionLayer);
}
collisionLayer.addTo(showLayerGroup);
}
}
},
error:function(){
$.modal.alertWarning("获取空间信息失败");
}
});
}
点击标记点可触发详情查看功能,目前预留了 showDetails 接口,可根据实际需求扩展至二级页面或弹窗展示更细粒度的道路分级统计。
成果展示
系统运行后,可清晰看到湖南省各地市道路分布情况。按总里程降序排列,长沙市、衡阳市、岳阳市位列前三,且数据在空间上呈现出东部和东南部聚集的特征。
从区域细分来看,东北部岳阳、益阳、常德等地市道路里程较长,而湘西北地区受地质环境影响,路网密度相对较低。南部地区如邵阳、永州等仍有较大发展空间,未来需结合人口流动趋势优化规划。
总结
本次实践完成了从空间数据准备、Java 后端处理到 Leaflet 前端可视化的全流程开发。通过 PostGIS 强大的空间计算能力配合 MVC 架构,实现了高效的道路数据检索与展示。后续可进一步集成实时路况监测或引入大数据分析模块,提升系统的决策支持能力。
相关免费在线工具
- 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