202.Vue3 + OpenLayers 实战:加载 GPX 数据并导出 GeoJSON 文件

张开发
2026/4/15 5:46:26 15 分钟阅读

分享文章

202.Vue3 + OpenLayers 实战:加载 GPX 数据并导出 GeoJSON 文件
一、效果预览功能包括✅ 加载本地 GPX 文件示例为 public 目录✅ 自动绘制轨迹点 线✅ 自动缩放至轨迹范围✅ 一键导出 GeoJSON 文件二、前言在 GIS 可视化开发中我们经常会遇到轨迹数据处理的需求比如读取 GPXGPS 轨迹文件在地图上展示轨迹将轨迹转换为通用格式GeoJSON进行存储或分析本文将基于Vue3 OpenLayers带你实现一个完整的小功能加载 GPX 文件 → 地图展示 → 导出 GeoJSON 文件同时这篇文章是偏实战的代码可以直接落地使用。三、核心依赖npm install ol file-saver四、核心实现思路整体流程非常清晰GPX 文件 → OpenLayers 解析 → Feature → GeoJSON → 导出文件拆解一下关键步骤1️⃣ 加载 GPX 文件通过fetch获取 GPX 文件内容fetch(GPX_PUBLIC_URL).then(res res.text())2️⃣ GPX 转 Feature地图可识别const features new GPX().readFeatures(gpxtext, { dataProjection: EPSG:4326, featureProjection: EPSG:3857, })说明EPSG:4326GPS 原始坐标经纬度EPSG:3857Web 地图坐标OpenLayers 使用3️⃣ Feature 加入地图source.addFeatures(features)4️⃣ 转 GeoJSONgeoData.value new GeoJSON().writeFeaturesObject(features, { dataProjection: EPSG:4326, featureProjection: EPSG:3857, })5️⃣ 导出文件const blob new Blob([res], { type: application/geojson;charsetutf-8, }) FileSaver.saveAs(blob, my.geojson)6️⃣ 自动缩放到轨迹map.value.getView().fit(extent, { padding: [40, 40, 40, 40], maxZoom: 16, })五、完整代码实现!-- * Author: 彭麒 * Date: 2026/4/8 * Email: 1062470959qq.com * Description: 此源码版权归吉檀迦俐所有可供学习和借鉴或商用。 -- template div classcontainer div classw-full flex justify-center flex-wrap div classfont-bold text-[24px] Vue3 Openlayers加载GPX数据导出geojson文件 /div /div h4 el-button typeprimary sizesmall clickaddGPX加载gpx文件/el-button el-button typedanger sizesmall clickexportJson导出为geoJson文件/el-button /h4 div idvue-openlayers/div /div /template script setup import { onMounted, ref } from vue import ol/ol.css import { Map, View } from ol import Tile from ol/layer/Tile import XYZ from ol/source/XYZ import VectorLayer from ol/layer/Vector import VectorSource from ol/source/Vector import Style from ol/style/Style import Circle from ol/style/Circle import Fill from ol/style/Fill import Stroke from ol/style/Stroke import GPX from ol/format/GPX import GeoJSON from ol/format/GeoJSON import FileSaver from file-saver const GPX_PUBLIC_URL ${import.meta.env.BASE_URL}map/fells_loop.gpx.replace(/\/{2,}/g, /) // state const map ref(null) const source new VectorSource() const geoData ref({}) // 方法 const exportJson () { const res JSON.stringify(geoData.value, null, ) const blob new Blob([res], { type: application/geojson;charsetutf-8, }) FileSaver.saveAs(blob, my.geojson) } const addGPX () { fetch(GPX_PUBLIC_URL) .then((response) { if (!response.ok) { throw new Error(加载 GPX 失败${response.status} ${response.url}) } return response.text() }) .then((gpxtext) { // 加载到地图GPX 坐标通常为 WGS84 const features new GPX().readFeatures(gpxtext, { dataProjection: EPSG:4326, featureProjection: EPSG:3857, }) source.clear() source.addFeatures(features) geoData.value new GeoJSON().writeFeaturesObject(features, { dataProjection: EPSG:4326, featureProjection: EPSG:3857, }) const extent source.getExtent() if ( map.value features.length 0 extent.every((v) Number.isFinite(v)) ) { map.value.getView().fit(extent, { padding: [40, 40, 40, 40], maxZoom: 16 }) } }) .catch((err) { console.error(err) }) } const initMap () { const googleLayer new Tile({ source: new XYZ({ url: https://www.google.com/maps/vt?lyrsmglenx{x}y{y}z{z}, crossOrigin: anonymous, }), }) const styleMap { Point: new Style({ image: new Circle({ fill: new Fill({ color: #ff0000, }), radius: 5, stroke: new Stroke({ color: blue, width: 1, }), }), }), LineString: new Style({ stroke: new Stroke({ color: #FFFF00, width: 3, }), }), MultiLineString: new Style({ stroke: new Stroke({ color: blue, width: 3, }), }), // GPX 里若出现面少见或其它类型避免 style 返回 undefined Polygon: new Style({ stroke: new Stroke({ color: #00FFFF, width: 2 }), fill: new Fill({ color: rgba(0,255,255,0.15) }), }), MultiPolygon: new Style({ stroke: new Stroke({ color: #00FFFF, width: 2 }), fill: new Fill({ color: rgba(0,255,255,0.15) }), }), GeometryCollection: new Style({ stroke: new Stroke({ color: #888888, width: 2 }), }), } const vectorLayer new VectorLayer({ visible: true, zIndex: 3, source: source, style: (feature) { const geom feature.getGeometry() if (!geom) return styleMap.Point return styleMap[geom.getType()] || styleMap.LineString }, }) map.value new Map({ target: vue-openlayers, layers: [googleLayer, vectorLayer], view: new View({ center: [-7916041.528716288, 5228379.045749711], zoom: 12, }), }) } // 生命周期 onMounted(() { initMap() }) /script style scoped .container { width: 840px; height: 620px; margin: 50px auto; border: 1px solid #42b983; } #vue-openlayers { width: 800px; height: 450px; margin: 0 auto; border: 1px solid #42b983; position: relative; } /style六、常见问题❓ 1. 为什么要做坐标转换因为GPX 是WGS84经纬度OpenLayers 默认是Web Mercator 不转换会导致轨迹位置错乱❓ 2. 为什么用 GeoJSONGeoJSON 优点通用格式前后端通用支持地图、分析、存储可直接用于 Mapbox / OpenLayers / Leaflet❓ 3. GPX 可以上传吗可以扩展为input typefile changehandleUpload /再用FileReader读取即可。七、可扩展方向进阶如果你要做项目这里是升级方向 功能增强GPX 文件上传本地多轨迹叠加轨迹动画播放轨迹点信息展示时间、速度 工程优化封装useOpenlayershook图层管理模块化样式配置抽离八、总结这篇文章带你实现了一个完整的 GIS 小功能✔ GPX 加载✔ 地图展示✔ GeoJSON 导出✔ 自动视野定位核心其实就一句话OpenLayers 负责展示GeoJSON 负责数据流通如果你后面想做 轨迹回放 / 实时定位 / WebGIS 系统这套思路可以直接复用

更多文章