WebGIS + OpenLayers + GeoServer + PostGIS #量算 #点聚合

发布时间:2026/10/30 8:26:22
WebGIS + OpenLayers + GeoServer + PostGIS #量算 #点聚合 前言前两篇我们搞定了地图初始化和基础交互但真实的业务地图数据都在后端几千条管线、地块、监测点位存在数据库里前端怎么高效拿到、怎么渲染、怎么做空间查询本篇讲透一套最经典的开源全栈方案前端 Vue OpenLayers → HTTP → GeoServer发布服务→ PostgreSQL PostGIS空间数据库外加两个高频组件完整代码ol-ext 量算测距、测面积、面积单位点聚合 Cluster上万 POI 不卡顿工程化能直接抄适合水利、管线、自然资源、应急项目。一、全栈架构图┌─────────────────────────────┐ │ 前端 Vue3 OpenLayers │ │ ┌─ WMS 底图/影像 ────────┐ │ │ ├─ WFS 矢量(点击/查询) ──┤ │ HTTP/JSON │ ├─ ol-ext 量算 │ │ ────────────▶ GeoServer(8080) │ └─ Cluster 点聚合 │ │ │ JDBC └─────────────────────────────┘ │ PostgreSQL PostGIS (管线/地块/监测点 空间表)分工一句话PostGIS存空间数据做复杂空间计算ST_Intersects、ST_BufferGeoServer把空间表发布成标准 OGC 服务WMS 瓦片图、WFS 矢量OpenLayers前端渲染 交互 轻量计算大数据空间分析必须放 PostGIS前端只做小数据量的 Turf。二、后端准备1. 安装 PostgreSQL PostGIS# Ubuntu/Debian sudo apt install postgresql postgis # 创建空间数据库 sudo -u postgres psql -c CREATE DATABASE gisdb; sudo -u postgres psql -d gisdb -c CREATE EXTENSION postgis;2. 建空间表管线示例-- 开启空间扩展 CREATE EXTENSION IF NOT EXISTS postgis; -- 管线段表 CREATE TABLE pipe_line ( id SERIAL PRIMARY KEY, name VARCHAR(50), code VARCHAR(20), diameter NUMERIC(10,2), -- 管径mm geom geometry(LineString, 4326) -- 空间字段WGS84 ); -- 监测点位表 CREATE TABLE monitor_point ( id SERIAL PRIMARY KEY, name VARCHAR(50), status INT DEFAULT 1, geom geometry(Point, 4326) ); -- 插入测试数据 INSERT INTO pipe_line (name, code, diameter, geom) VALUES (主管道A, P-001, 800, ST_GeomFromText(LINESTRING(108.95 34.27, 108.97 34.28, 108.99 34.26), 4326)), (支管B, P-002, 400, ST_GeomFromText(LINESTRING(108.96 34.25, 108.98 34.24), 4326)); INSERT INTO monitor_point (name, status, geom) VALUES (站点1, 1, ST_SetSRID(ST_MakePoint(108.95, 34.27), 4326)), (站点2, 0, ST_SetSRID(ST_MakePoint(108.99, 34.26), 4326)), (站点3, 1, ST_SetSRID(ST_MakePoint(108.97, 34.28), 4326));空间字段建议都用 4326 存原始经纬度发布时再投影避免坐标系混乱。3. 空间查询PostGIS 示例后端接口用-- 找某点周围500米内的监测点注意投影换算 SELECT name FROM monitor_point WHERE ST_DWithin( geom::geography, ST_SetSRID(ST_MakePoint(108.96, 34.26), 4326)::geography, 500 ); -- 找与某管线相交的所有管线 SELECT a.name, b.name FROM pipe_line a, pipe_line b WHERE a.id 1 AND ST_Intersects(a.geom, b.geom); -- 算面积、算长度 SELECT ST_Area(geom::geography) FROM some_polygon; SELECT ST_Length(geom::geography) FROM pipe_line WHERE id1;4. GeoServer 发布下载 GeoServer默认 8080admin/geoserver新建工作区 workspace → 新建数据源 → PostgreSQL/PostGIS填库名 / 账号图层里对每个表新建图层设置SRS 为 EPSG:4326配置样式预览时即可拿到 WMS/WFS 服务地址重点GeoServer 默认可能跨域用CORS Filter配置放行否则前端报跨域。三、前端全栈对接Vue3 完整代码template div classmap-wrap div idmap/div div classbar button clickloadWms加载WMS底图/button button clickloadWfs加载WFS管线/button button clickqueryByClick点击查询/button button clickclearVector清除矢量/button /div /div /template script setup import { ref, onMounted, onUnmounted } from vue import Map from ol/Map import View from ol/View import { Tile as TileLayer, Vector as VectorLayer } from ol/layer import { TileWMS, Vector as VectorSource, Cluster } from ol/source import { Style, Fill, Stroke, Circle as CircleStyle, Text } from ol/style import { fromLonLat } from ol/proj import GeoJSON from ol/format/GeoJSON import { bbox as bboxStrategy } from ol/loadingstrategy import ol/ol.css let map, vectorSource // 1. 初始化地图 const initMap () { map new Map({ target: map, layers: [], view: new View({ center: fromLonLat([108.96, 34.26]), zoom: 11, projection: EPSG:3857 }) }) } // 2. 加载 WMSGeoServer发布的底图/影像 const loadWms () { const wmsLayer new TileLayer({ source: new TileWMS({ url: http://localhost:8080/geoserver/gisdb/wms, params: { LAYERS: gisdb:pipe_line, // workspace:layer TILED: true }, serverType: geoserver }) }) map.addLayer(wmsLayer) } // 3. 加载 WFS 矢量管线视口内按需加载 const loadWfs () { vectorSource new VectorSource({ url: (extent) http://localhost:8080/geoserver/gisdb/ows?serviceWFSversion1.1.0requestGetFeature typeNamegisdb:pipe_lineoutputFormatapplication/jsonsrsnameEPSG:3857 bbox${extent.join(,)},EPSG:3857, format: new GeoJSON(), strategy: bboxStrategy // 只加载当前视口平移再按需请求 }) const vectorLayer new VectorLayer({ source: vectorSource, style: new Style({ stroke: new Stroke({ color: #0a6cff, width: 3 }) }) }) map.addLayer(vectorLayer) } // 4. 点击查询 WFS 要素属性 const queryByClick () { map.on(singleclick, (evt) { const feature map.forEachFeatureAtPixel(evt.pixel, f f) if (feature) { const p feature.getProperties() console.log(管线属性, { name: p.name, code: p.code, diameter: p.diameter }) alert(管段${p.name}\n编码${p.code}\n管径${p.diameter}mm) } }) } const clearVector () { vectorSource?.clear() } onMounted(() { initMap() loadWms() loadWfs() }) onUnmounted(() { map?.dispose() }) /script关键点WFS 用bbox 策略srsnameEPSG:3857前端只拿当前屏幕的数据平移再自动请求几万条也不会卡。四、ol-ext 量算测距 测面积完整代码原生 OpenLayers 没有量算控件业界标准是用ol-ext法国团队开源插件生态好。npm install ol-exttemplate div classmap-wrap div idmap/div div classbar button clickstartMeasure(1)测距/button button clickstartMeasure(2)测面积/button button clickclearMeasure清除/button /div /div /template script setup import { onMounted, onUnmounted } from vue import Map from ol/Map import View from ol/View import VectorLayer from ol/layer/Vector import VectorSource from ol/source/Vector import { fromLonLat } from ol/proj import ol/ol.css // ol-ext 量算控件按需引入 import Measure from ol-ext/control/Measure import ol-ext/dist/ol-ext.min.css let map, measureControl, measureSource const initMap () { measureSource new VectorSource() map new Map({ target: map, layers: [ // 这里放你的底图天地图/GeoServer WMS new VectorLayer({ source: measureSource }) // 量算图层 ], view: new View({ center: fromLonLat([108.96, 34.26]), zoom: 11 }) }) // 创建量算控件 measureControl new Measure({ source: measureSource, // 测量结果画到哪个图层 maxLength: 200000, // 最大测距(米) maxArea: 100000000, // 最大测面积(㎡) geodesic: true, // 球面测距(精确,推荐) continueMeasure: true, // 连续测量 units: { length: { m: 1, km: 1000 }, area: { m²: 1, km²: 1000000, 亩: 666.667 } } }) map.addControl(measureControl) } // type: 1测距离, 2测面积 const startMeasure (type) { const isLength type 1 measureControl.getInteraction(true) // 重置当前测量 measureControl.set(type, isLength ? length : area) measureControl.activate() } const clearMeasure () { measureControl.deactivate() measureSource.clear() measureControl.reset() } onMounted(initMap) onUnmounted(() { map?.dispose() }) /script说明ol-ext 的 Measure 支持距离 / 面积自带标签实时显示数值和单位geodesic:true用球面距离WGS84比平面更准量算单位里可以加 “亩”国内项目常用。五、点聚合 Cluster 完整代码上万 POI 不卡顿点位几千上万时直接渲染会掉帧用 OpenLayers 自带的Cluster 聚合近的点合成一个带数字的圆放大自动散开。script setup import { ref, onMounted } from vue import Map from ol/Map import View from ol/View import VectorLayer from ol/layer/Vector import VectorSource from ol/source/Vector import Cluster from ol/source/Cluster import { Style, Fill, Stroke, Circle as CircleStyle, Text } from ol/style import { Point } from ol/geom import Feature from ol/Feature import { fromLonLat } from ol/proj let map, clusterSource, vectorLayer const initCluster () { // 原始点位数据源可以来自WFS/接口/GeoJSON const pointSource new VectorSource() // 生成5000个随机测试点模拟真实POI for (let i 0; i 5000; i) { const lng 108.9 Math.random() * 0.12 const lat 34.2 Math.random() * 0.12 const f new Feature({ geometry: new Point(fromLonLat([lng, lat])), name: 点位${i} }) pointSource.addFeature(f) } // Cluster 聚合数据源distance像素距离在此范围内合并 clusterSource new Cluster({ source: pointSource, distance: 40 // 40像素内的点聚合 }) vectorLayer new VectorLayer({ source: clusterSource, style: (feature) { const size feature.get(features).length // 聚合点数 let color #1e88e5 if (size 100) color #e53935 else if (size 30) color #fb8c00 return new Style({ image: new CircleStyle({ radius: size 100 ? 26 : size 30 ? 20 : 14, fill: new Fill({ color }), stroke: new Stroke({ color: #fff, width: 2 }) }), text: new Text({ text: size.toString(), // 显示聚合数量 fill: new Fill({ color: #fff }), font: bold 12px sans-serif }) }) } }) map.addLayer(vectorLayer) // 点击聚合点 → 弹层显示内部点位可选 map.on(singleclick, (evt) { const feature map.forEachFeatureAtPixel(evt.pixel, f f) if (feature) { const feats feature.get(features) if (feats feats.length 1) { console.log(该聚合含, feats.length, 个点位, feats.map(f f.get(name))) } } }) } /script说明Cluster返回的每个 feature 里带features数组就是聚合进去的子点位。样式根据数量分色分级一眼看出密度。真实项目点位从 WFS / 接口拿替换掉随机生成那段即可。六、空间计算怎么选前端 Turf vs 后端 PostGIS表格场景推荐原因两个点 / 线算距离Turf.js前端零请求秒回鼠标实时绘制算面积Turf/ol-ext交互期后端来不及点是否在面内单个Turf轻量几千条要素做缓冲区 / 相交PostGIS前端会卡死SQL 秒出全库范围检索、空间索引PostGIS数据量大必须后端铁律前端只算用户能一眼看到、量级小的批量、全量、需要索引的交给 PostGIS前端通过接口拿结果。七、全栈踩坑清单血泪坐标系三层不一致库 4326、GeoServer 发布的 SRS、前端 3857任何一层没对上就偏移。建议库和 GeoServer 统一 4326前端投影到 3857GeoServer 跨域默认 CORS 可能没开前端请求直接失败。配好 CORS FilterWFS 一次性加载全库几万条addFeatures直接白屏 → 用bbox策略视口加载WMS 和 WFS 搞混WMS 返回图片适合底图 / 影像WFS 返回矢量适合查询 / 点击。图层多了别都用 WMSol-ext 样式未引入ol-ext.min.css不引量算控件样式全乱Cluster 聚合distance调太小聚合不明显调太大缩放时点位分布失真一般 30-50量算geodesic:false平面量算在大范围时误差大跨省测距误差可能到几公里务必开geodesic:true内存泄漏路由切换 / 页面销毁必须map.dispose()、移除交互和监听否则切几次页面就卡八、小结全栈 WebGIS 的真正难点不在前端画地图而在空间数据的存取、发布和计算分层存储 / 大数据计算 → PostGIS服务发布 → GeoServer渲染交互 → OpenLayers轻量量算 → ol-ext / Turf.js

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询