基于Ol+geoserver的OGC协议验证平台开发日志——2、点线面数据加载的代码与逻辑、跨域问题的解决
1代码方面https://github.com/ZoeyLee0723/ogc-forge-Atticus项目可以在github找到2026年5月做完首先为了这个实例平台我准备了点线面三种数据分别命名为point/string/polygon其都是shp格式的数据我们通过以前的方法将这些数据录入到postgresql具体的方法可以见https://mp.csdn.net/mp_blog/creation/editor/155060418其中包括了shp数据从放入postgresql再到geoserver的全过程。由于是wfs因此我们要加载geojson数据在以前我是直接拿了geoserver的一整条wfs数据里面包裹着参数http://localhost:8080/geoserver/tiger/ows?serviceWFSversion1.0.0requestGetFeaturetypeNametiger%3Agiant_polygonoutputFormatapplication%2FjsonmaxFeatures50但现在我换了架构在src/api/wfs.js下这个只负责请求不负责vue和olimport axios from axios const WFS_BASE /geoserver/ogcforge/ows export const wfsApi { /** * 通过 WFS 协议获取要素返回 GeoJSON */ async getFeatures(typeName) { const params { service: WFS, version: 1.0.0, request: GetFeature, typeName, outputFormat: application/json, srsName: EPSG:4326, } const res await axios.get(WFS_BASE, { params }) return res.data },这里面会有一个跨域的问题我们使用vite.config.js来解决import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /geoserver: { target: http://localhost:8080, changeOrigin: true } } } })在这里还有相关的样式什么的在src/utils/featureStyles.jsimport { Style, Fill, Stroke, Circle } from ol/style /** 点样式红色圆点 白色描边 */ export function getPointStyle() { return new Style({ image: new Circle({ radius: 7, fill: new Fill({ color: #e74c3c }), stroke: new Stroke({ color: #fff, width: 2 }) }) }) } /** 线样式蓝色 3px */ export function getLineStyle() { return new Style({ stroke: new Stroke({ color: #2980b9, width: 3 }) }) } /** 面样式半透明黄色填充 橙色描边 */ export function getPolygonStyle() { return new Style({ fill: new Fill({ color: rgba(241, 196, 15, 0.3) }), stroke: new Stroke({ color: #e67e22, width: 2 }) }) }使用pinia对地图状态进行管理src/stores/layerStore.jsimport { defineStore } from pinia export const useLayerStore defineStore(layer, { state: () ({ pointGeoJson: null, lineGeoJson: null, polygonGeoJson: null }), actions: { setPoint(data) { this.pointGeoJson data }, setLine(data) { this.lineGeoJson data }, setPolygon(data) { this.polygonGeoJson data } } })这里核心的来了我们不打算直接在ogclab.vue里面直接加载图层而是通过引入逻辑的形式这里是src/composables/useOlMap.js这里在加载的时候记得转坐标pointLayer.getSource().addFeatures(format.readFeatures(geojson, projectionOpts))import { watch } from vue import VectorLayer from ol/layer/Vector import VectorSource from ol/source/Vector import { GeoJSON } from ol/format import { useLayerStore } from /stores/layerStore import { getPointStyle, getLineStyle, getPolygonStyle } from /utils/featureStyles export function useOlMap() { const layerStore useLayerStore() const format new GeoJSON() // 1. 创建三个业务矢量图层各自绑定样式 const pointLayer new VectorLayer({ source: new VectorSource(), style: getPointStyle(), }) const lineLayer new VectorLayer({ source: new VectorSource(), style: getLineStyle(), }) const polygonLayer new VectorLayer({ source: new VectorSource(), style: getPolygonStyle(), }) // 2. 挂载到地图上 function addBusinessLayers(map) { // 注意叠加顺序面在下线在中点在上 map.addLayer(polygonLayer) map.addLayer(lineLayer) map.addLayer(pointLayer) } // 3. 监听 Store → 自动渲染到 OL // 核心架构Vue 管数据OL 管渲染watch 是桥梁 function setupWatchers() { // 投影转换配置对象提炼出来避免写重复代码 const projectionOpts { dataProjection: EPSG:4326, // 告诉 OL从 GeoServer 拿到的数据是经纬度 featureProjection: EPSG:3857 // 告诉 OL要画在天地图(3857)上 } watch( () layerStore.pointGeoJson, (geojson) { if (geojson) { pointLayer.getSource().clear() pointLayer.getSource().addFeatures(format.readFeatures(geojson, projectionOpts)) } } ) watch( () layerStore.lineGeoJson, (geojson) { if (geojson) { lineLayer.getSource().clear() lineLayer.getSource().addFeatures(format.readFeatures(geojson, projectionOpts)) } } ) watch( () layerStore.polygonGeoJson, (geojson) { if (geojson) { polygonLayer.getSource().clear() polygonLayer.getSource().addFeatures(format.readFeatures(geojson, projectionOpts)) } } ) } return { addBusinessLayers, setupWatchers, pointLayer, lineLayer, polygonLayer } }最后给主视图src/views/OgcLab.vue组装好template div idol-map-container classmap-container/div /template script setup import { onMounted } from vue import Map from ol/Map import View from ol/View import { fromLonLat } from ol/proj import { createTdtVecLayer, createTdtVecAnnoLayer } from /utils/baseLayerSources import { wfsApi } from /api/ogc/wfs import { useLayerStore } from /stores/layerStore import { useOlMap } from /composables/useOlMap let mapInstance null const layerStore useLayerStore() const { addBusinessLayers, setupWatchers } useOlMap() onMounted(async () { initMap() setupWatchers() await loadAllLayers() }) function initMap() { mapInstance new Map({ target: ol-map-container, layers: [createTdtVecLayer(), createTdtVecAnnoLayer()], view: new View({ center: fromLonLat([116.4, 39.9]), zoom: 10 }) }) addBusinessLayers(mapInstance) } async function loadAllLayers() { try { const [pointData, lineData, polygonData] await Promise.all([ wfsApi.getFeatures(ogcforge:point), wfsApi.getFeatures(ogcforge:string), wfsApi.getFeatures(ogcforge:polygon) ]) layerStore.setPoint(pointData) layerStore.setLine(lineData) layerStore.setPolygon(polygonData) console.log([OgcLab] 图层加载完成) } catch (err) { console.error([OgcLab] WFS 加载失败, err) } } /script style scoped .map-container { width: 100%; height: 100vh; } /style2逻辑方面整个项目的具体逻辑以下是我的理解 首先ogclab.vue引入了wfs.js的wfsapi方法当挂载好Onmounted以后就立刻执行loadAllLayers。loadAllLayers调用了wfsapi里面的getfeatures函数传递了typename拿到了之后就调用layerStore里面的action的setxxx方法存入数据数据在layerstore里面被传入states进行状态管理。与此同时因为states出现了变化useolmap.js里面的watch就发力了将格式化之后的geojson数据连着坐标数据projectionOpts一起通过getSource().addFeatures(format.readFeatures())方法添加到了点线面图层中ogclab.vue→wfs.js→layerStore→useOlMap.js→ 地图渲染以及AI的回复1. 你的流程描述完全正确 ogclab.vue → wfs.js → layerStore → useOlMap.js → 地图渲染 这完美体现了 Vue OpenLayers 的“单向数据流”架构就像水流只能从上游往下游流数据只能从请求到渲染不会倒流。这种架构的好处是逻辑清晰调试方便。2. 核心机制watch 是“数据变化的开关” 你提到“states 出现了变化 useOlMap.js 里面的 watch 就发力了”这是最关键的一步。我们可以把 watch 想象成一个 “数据变化探测器” 它在 useOlMap.js 初始化时就“埋”在 layerStore.pointGeoJson 旁边。 当 layerStore.setPoint() 被调用pointGeoJson 从 null 变成有数据时这个探测器立刻“叮”一声触发后面的渲染逻辑。 这就是 Vue 的 “响应式” 核心数据变化 → 自动触发 UI 更新。3. 坐标转换projectionOpts 是“翻译官” 你流程里提到了“连着坐标数据 projectionOpts 一起”这说明你抓住了之前卡住的“坐标系转换”这个关键点projectionOpts 就像在 GeoJSON 和 OpenLayers 之间加了一个 “翻译官” GeoJSON 说“我讲的是经纬度4326。” 翻译官projectionOpts告诉 OpenLayers“请把它翻译成米制坐标3857再画在地图上。” 没有 projectionOpts数据就会“水土不服”跑到非洲去。