基于WebGIS的淮河水量水质监测系统:从WebGIS架构到GeoServer与PostGIS实战
发布时间:2026/9/26 14:00:01来源:尧图网络
简介基于WebGIS的淮河水量水质监测系统完整源码面向高校计算机相关专业学生及企业初级开发者适用于课程设计、毕业设计或初期项目立项代码经测试运行正常可直接部署使用。压缩包共545个文件以Java源码、class编译文件、JSP页面为主要开发产物搭配JavaScript脚本、CSS样式、jar依赖库及地图资源等整体大小19.94MB目录结构清晰便于按模块查找。已有123人学习下载。项目覆盖水质站点、水质等级、水位监控等典型业务完整呈现从DAO数据持久化、Servlet业务控制到JSP前端展示的分层实现并结合WebGIS地图展示与交互帮助理解监测系统从后端到前端的完整流程。此外项目中附有说明文档还能借鉴项目结构划分、数据库访问方式及前端渲染思路适合作为Java Web学习、GIS集成实践和毕业设计参考。1. 淮河水量水质监测为什么绕不开 WebGIS一张图与三类硬需求做过水利信息化的人都知道淮河流域的监测数据从来不缺缺的是把数据的位置、时间和指标串起来的那张图。这个标题里的“基于WebGIS的淮河水量水质监测系统”核心就是把分散在河道上的遥测站变成地图上可点击的点点开能看到水位、流量和pH、COD、氨氮这些水质数据。所以拿到这类源码我从来不会先急着跑起来而是先检查三件事坐标系对不对时间字段齐不齐图层样式有没有生效。这三个地方是整套系统翻车率最高的位置。这套方案适合三类人给水文部门做监测展示系统的开发者、GIS相关课程的课设和毕设学生以及想把已有监测数据快速落到地图上的运维人员。2. 开工前先定架构GeoServer、PostGIS 与三张核心表2.1 这套 WebGIS 选型为什么在源码里最常见一个基于 WebGIS 的监测系统骨架通常由三部分拼起来空间数据存储、地图服务发布、前端渲染。这类源码里最常见的组合是 PostgreSQL PostGIS 存空间数据GeoServer 负责把数据库里的表发布成 WMS/WFS 服务前端用 Leaflet 加载瓦片和矢量。选这套组合不是因为它最时髦而是因为它部署链路短GeoServer 跑在 JDK Tomcat 上公司里有台老服务器就能带起来PostGIS 提供了足够用的空间索引和坐标转换函数Leaflet 的 API 对新人也友好。还有一个现实原因WMS 服务是标准协议后面不管换 Vue 还是 React或者干脆用 iframe 嵌入一张地图图层服务都不用重写。反观 ArcGIS JS API 这类方案虽然渲染细腻但授权和部署成本高源码包很难让普通用户开箱即跑。所以你在网上看到的“完整源码”类项目十有八九是 GeoServer Leaflet 这套组合。2.2 三张核心表站点、水量、水质怎么建我建议先把数据结构理清楚再去碰前端。站点表存空间位置水量表存水位和流量时间序列水质表存采样指标。下面是可以直接导入 PostgreSQL 的建表语句CREATE TABLE station ( id SERIAL PRIMARY KEY, station_code VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, lon NUMERIC(10, 6) NOT NULL, lat NUMERIC(10, 6) NOT NULL ); CREATE TABLE quantity ( id BIGSERIAL PRIMARY KEY, station_id INTEGER REFERENCES station(id), ts TIMESTAMPTZ NOT NULL, water_level NUMERIC(8, 2), flow NUMERIC(10, 2) ); CREATE TABLE quality ( id BIGSERIAL PRIMARY KEY, station_id INTEGER REFERENCES station(id), sample_date DATE NOT NULL, ph NUMERIC(4, 2), do_mg_l NUMERIC(5, 2), cod_mg_l NUMERIC(6, 2), nh3n_mg_l NUMERIC(6, 3), tp_mg_l NUMERIC(6, 3) ); CREATE INDEX idx_quantity_station_ts ON quantity (station_id, ts); CREATE INDEX idx_quality_station_date ON quality (station_id, sample_date);建表时有两个点值得说明。ts我用TIMESTAMPTZ而不是TIMESTAMP因为遥测数据经常要跨时区对比如果存成不带时区的时间后面做日聚合和比对时会差 8 小时这种错位在图上很难肉眼发现。水质指标字段用NUMERIC而不是VARCHAR是因为很多原始报文会把“未检出”写成0.01这样的字符串如果字段类型是字符型后续 SLD 分级设色会直接失效。2.3 时序对齐水量是分钟级水质是日报怎么让它们不打架水量遥测站一般每 15 分钟或每小时上报一条数据水质采样通常是每天一次甚至四小时一次。如果直接把两张表 JOIN 进地图图层一个站点可能匹配出几十条记录地图上同一个点会叠出密密麻麻的重复标注。常见做法是先做日聚合把水量归到天再和水质按日期关联。CREATE VIEW quantity_daily AS SELECT station_id, DATE_TRUNC(day, ts)::DATE AS day, AVG(water_level) AS mean_level, MAX(water_level) AS max_level, AVG(flow) AS mean_flow FROM quantity GROUP BY station_id, DATE_TRUNC(day, ts)::DATE;这个视图把分钟级数据压缩成每天一行用日平均水位代表当天状态。它在后面 GeoServer 发布图层时是核心数据源。如果你不想建视图也可以把这段 SQL 直接写进 GeoServer 的 SQL 视图里但那样调试时很难单独验证我习惯先在 pgAdmin 里跑通视图确认没报错再进 GeoServer。提示聚合时单独留一个max_level字段很实用。淮河汛期水位变化快日平均水位容易掩盖短时间内涨水的风险地图上展示均值、点开看最大值比只放一个平均值可靠得多。3. 数据入库到图层发布坐标系、SQL 视图与 Leaflet 加载3.1 入库前的坐标处理统一到 EPSG:4326 再进地图这个项目能踩的第一个大坑就是站点坐标的投影不统一。常见情况有三种遥测报表给的是 CGCS2000 经纬度坐标值看起来和 WGS84 差不多老系统导出的是度分秒格式比如 1122334.44 表示 112°23′34.44″还有一部分是从高斯投影平面坐标反算出来的。这几类数据如果直接当普通经纬度丢进 Leaflet点位会成片偏移甚至飞到淮河流域外面。我的一般做法是先把站点表里所有坐标统一转成十进制的 WGS84 经纬度再入 PostGIS。如果原始表里存的是度分秒格式先做一次清洗UPDATE station SET lon FLOOR(lon / 10000) FLOOR(MOD(lon, 10000) / 100) / 60 MOD(lon, 100) / 3600, lat FLOOR(lat / 10000) FLOOR(MOD(lat, 10000) / 100) / 60 MOD(lat, 100) / 3600 WHERE lon 1000 OR lat 90;这段 SQL 的逻辑是度分秒 1122334.44 的前两位是度中间两位是分最后两位是秒所以先除以 10000 取度再取余数除以 100 拿分最后取余拿秒。执行完可以抽查几个站点转出来的经纬度在淮河流域坐标范围内就说明清洗对了。如果手头还有投影坐标文件可以用 GDAL 一行命令转成 WGS84gdaltransform -s_srs EPSG:4513 -t_srs EPSG:4326 -output_xy-s_srs要填源数据实际的投影代号比如 CGCS2000 3 度带高斯投影在不同带号下对应的 EPSG 码不一样不确定时可以打开 QGIS 看原始数据的属性确认。坐标这一步没有捷径做错了后面所有图层的对位都是错的。3.2 GeoServer 发布 SQL 视图把日期过滤做进地图服务里GeoServer 里发布 PostGIS 数据的常规做法是添加新数据源然后勾选“在图层预览之前配置 SQL 视图”。这样写的好处是可以把 2.3 节里的聚合逻辑和日期过滤直接下沉到数据库前端请求某个时间范围时地图服务只返回这个范围内的站点数据而不是把所有年份的点一次性拉出来。SQL 视图的内容可以这么写SELECT s.id, s.name, s.lon, s.lat, q.day, q.mean_level, q.max_level, q.mean_flow, q.ph, q.cod_mg_l, q.do_mg_l, q.nh3n_mg_l FROM station s JOIN quantity_daily q ON s.id q.station_id WHERE q.day :fromDate AND q.day :toDate这里的:fromDate和:toDate是 GeoServer 的 SQL 视图参数发布界面里需要手动定义这两个参数类型选java.util.Date格式是yyyy-MM-dd。发布完成之后调用 WMS 时通过viewparams传参curl http://localhost:8080/geoserver/po/wms?serviceWMSversion1.1.0requestGetMaplayerspo:v_quality_dailybbox110,20,122,36width800height600formatimage/pngviewparamsfromDate:2024-08-01;toDate:2024-08-31注意viewparams里多个参数用分号分隔参数名要和 SQL 里的冒号变量对应。我见过有人把fromDate写成from_date结果 GeoServer 一直报参数缺失卡了大半天。发布后先用 Layer Preview 里的 OpenLayers 预览确认能看到点再进前端。3.3 前端用 Leaflet 加载 WMS 并做分级设色前端加载 WMS 的 Leaflet 写法很固定const qualityLayer L.tileLayer.wms(http://localhost:8080/geoserver/po/wms, { layers: po:v_quality_daily, format: image/png, transparent: true, version: 1.1.0, viewparams: fromDate:2024-08-01;toDate:2024-08-31 }); qualityLayer.addTo(map);transparent: true一定要设否则 WMS 返回的是一张带白底的图片会把底图遮住。viewparams写在请求参数里每次切换时间范围时用setUrl重设地址再刷新图层就能实现日期切换。这里不建议用 WMS 内置的 time 维度因为 SQL 视图的路子在调试时更直观参数出错也能在 GeoServer 的日志里看到。要让点按水质好坏显示不同颜色需要在 GeoServer 样式里配 SLD。比如 COD 浓度低于 20 显示蓝色20 到 40 显示橙色高于 40 显示红色核心规则片段如下Rule ogc:Filter ogc:PropertyIsLessThan ogc:PropertyNamecod_mg_l/ogc:PropertyName ogc:Literal20/ogc:Literal /ogc:PropertyIsLessThan /ogc:Filter PointSymbolizer Graphic Mark WellKnownNamecircle/WellKnownName Fill CssParameter namefill#1E90FF/CssParameter /Fill /Mark Size10/Size /Graphic /PointSymbolizer /Rule一个完整的 SLD 文件里需要写多个 Rule按浓度区间从低到高排列GeoServer 会从上到下匹配第一个满足条件的 Rule 并画点。调试时留意一个现象如果某个站点的cod_mg_l字段是空值或字符串这条 Rule 不会命中点在图上会消失。所以入库清洗时一定要把水质字段统一成数值型或者用PropertyIsNull单独给空值配一个灰色符号。4. 避坑坐标系、时间对齐与样式不生效的 6 个现场问题4.1 坐标偏移点位跑到安徽去了现象站点在行政表格里登记的坐标看起来正常但叠加到底图上后整体往某个方向偏移了几十公里有的站点直接落到省外。原因多数情况是把 CGCS2000 的经纬度当成 WGS84 直接加载到 Leaflet或者反过来把 WGS84 坐标叠加到了 CGCS2000 的底图上。两者差异在小比例尺下不明显放大到乡镇级别就会看出系统性偏移。解决先确认底图瓦片是什么坐标系再决定站点表存哪套坐标。如果底图是常见的 WGS84 Web 墨卡托瓦片站点入库就用 EPSG:4326如果底图是天地图的 CGCS2000 瓦片站点坐标就统一转成 EPSG:4490。最忌讳的是站点表混着两套坐标前 100 个站是旧的后 50 个站是新的出问题根本排查不到。4.2 度分秒被当成十进制小数处理现象部分站点的经纬度在数据库里是 112.233444 这样的小数但实际上是度分秒的压缩写法。直接用的时候这些站点会整体偏移几百公里而且误差方向不一致像是一把芝麻撒错了地方。原因原始遥测报文或 Excel 导入时没做单位转换把“度分秒”按“度”的小数位读进去了。解决用 3.1 节里的 UPDATE 语句把度分秒字段转成十进制度。转换后可以顺手做一个校验查询把转换结果超出正常流域经纬度范围的记录列出来逐条核对原始报文。这一步虽然枯燥但能省掉后面所有图层对不上的麻烦。4.3 水质指标带着“未检出”字符入库地图上站点消失现象COD、氨氮等字段在报表里显示“0.01”或“未检出”入库后地图渲染时这些点不出现或者整张图只有零星几个点。原因字段值不是数值型SLD 的数值比较规则匹配不到GeoServer 把这条点记录过滤掉了。解决入库时用 CASE WHEN 统一处理把“未检出”转成检出限的一半比如 0.005同时单独保留一个标记字段用于展示。如果项目要求严格不想要半检出限这种估算值也可以转成 NULL然后在 SLD 里配一条PropertyIsNull规则用灰色符号标注“无数据”比让点直接消失友好得多。4.4 水量和水质 JOIN 出很多重复点现象地图上一个站点叠了十几个点点击时弹窗分不清是哪条记录。原因水量表是分钟级数据直接和水质表 JOIN 后一天里每一条水量记录都会带一份水质数据记录数爆炸。解决回到 2.3 节先建quantity_daily视图把水量压到日粒度再参与 JOIN。在 GeoServer SQL 视图里也尽量基于这个视图查询不要在视图外再做一次细粒度 JOIN。4.5 改了样式但地图颜色没变化现象SLD 文件改了好几遍刷新浏览器点的颜色还是老样子像是改了个假文件。原因有三层GeoServer 端样式缓存、请求 URL 没带新的 styles 参数、浏览器端图片缓存。最常见的是 GeoServer 保存样式后没有触发图层重新加载。解决在 GeoServer 的 Layer 页面里点一下“重新加载”或者调用 REST 接口清缓存。前端调试时可以给 WMS 的 URL 加一个冗余参数比如_t1700000000每次更新时间戳绕开浏览器图片缓存确认到底是后端没生效还是前端没刷新。4.6 前端页面和后端地图服务跨域现象前端跑在 8080 端口GeoServer 跑在 9090 端口浏览器里地图空白F12 看到 WMS 请求被 CORS 策略拦截。原因Leaflet 请求跨域资源服务端没有返回允许跨域的响应头。解决开发阶段在 GeoServer 的 web.xml 里启用了 CORS 过滤器属于最快的后悔药。生产环境我更推荐用 Nginx 把前端页面、后端 API、GeoServer 统一反代到同一个域名下路径分别用/、/api/、/gis/区分既解决跨域也方便统一加访问权限。5. 源码跑通后的第一件事点击站点把过程线拉出来这套源码跑通之后别急着去调样式先做一次链路验证。第一步把数据库脚本导入 PostgreSQL确认三张核心表都在第二步启动 GeoServer在 Layer Preview 里能找到发布的站点图层第三步打开前端页面确认地图上能看到站点。三步全过说明后端链路基本健康。接下来我会做一件更有价值的验证点击地图上的站点拉出它最近 30 天的水位和 COD 过程线。这一步能同时验证站点 ID 传递、数据库查询、前端图表渲染三段逻辑而且也是甲方验收时必看的功能。做法不复杂后端提供一个查询接口前端用 ECharts 画双轴折线fetch(/api/station/ stationId /trend?days30) .then(res res.json()) .then(data { trendChart.setOption({ tooltip: { trigger: axis }, xAxis: { data: data.dates }, yAxis: [ { type: value, name: 水位(m) }, { type: value, name: COD(mg/L) } ], series: [ { name: 水位, type: line, data: data.levels, yAxisIndex: 0 }, { name: COD, type: line, data: data.cods, yAxisIndex: 1 } ] }); });代码里的stationId从地图点击事件里取Leaflet 的 WMS 图层可以通过getFeatureInfoUrl拿到站点属性再解析出 ID。ECharts 这里用双 Y 轴是因为水位和 COD 的量纲完全不在一个数量级硬塞进同一根轴总有一条线被压成直线。如果源码里已经集成了图表库优先复用没有的话再引入 ECharts 也不迟。做完这一步这套系统才算是从“静态点图”变成了真正可用的监测工具。我每次验收这类项目都坚持让开发人员当场点击三五个站点核对过程线数据这一关过了再谈界面美化。这个习惯帮我避掉了不少“图上好看、点开没数据”的返工。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网