当前位置: 首页 > news >正文

OpenLayers 综合案例-轨迹回放

看过的知识不等于学会。唯有用心总结、系统记录,并通过温故知新反复实践,才能真正掌握一二
作为一名摸爬滚打三年的前端开发,开源社区给了我饭碗,我也将所学的知识体系回馈给大家,助你少走弯路!
OpenLayers、Leaflet 快速入门 ,每周保持更新 2 个案例
Cesium 快速入门,每周保持更新 4 个案例

OpenLayers 综合案例-轨迹回放

Vue 3 + OpenLayers 实现的 WebGIS 应用提供了完整的轨迹回放功能

主要功能

  1. 地图上显示一条预设轨迹线。
  2. 小车🚗沿轨迹逐点移动,拐弯时方向自动调整。
  3. 支持“开始”“暂停”“继续”“重置”操作。
  4. 显示当前进度和状态。

在这里插入图片描述

MP4效果动画

<template><div class="car-track-container"><div ref="mapContainer" class="map"></div><div class="map-controls"><h3>小车轨迹回放 🚗</h3><div class="control-group"><button class="control-btn" @click="startTrack" :disabled="playing">开始</button><button class="control-btn" @click="pauseTrack" :disabled="!playing">暂停</button><buttonclass="control-btn"@click="resumeTrack":disabled="playing || finished">继续</button><button class="control-btn" @click="resetTrack">重置</button></div><div class="stats-section"><div>进度:<span class="progress">{{ progress + 1 }} / {{ trackPoints.length }}</span></div><div>状态: <span class="status">{{ statusText }}</span></div></div></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted, computed } from "vue";
import Map from "ol/Map";
import View from "ol/View";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { XYZ, Vector as VectorSource } from "ol/source";
import { LineString, Point } from "ol/geom";
import Feature from "ol/Feature";
import { Style, Stroke, Icon } from "ol/style";
import { defaults as defaultControls, FullScreen, ScaleLine } from "ol/control";
import { fromLonLat } from "ol/proj";
import "ol/ol.css";const map = ref(null);
const mapContainer = ref(null);
const vectorSource = ref(null);
const carFeature = ref(null);
const trackFeature = ref(null);
const playing = ref(false);
const finished = ref(false);
const progress = ref(0);
const timer = ref(null);// 预设轨迹点(经纬度)
const trackPoints = ref([[116.39, 39.9],[116.392, 39.901],[116.395, 39.902],[116.398, 39.903],[116.4, 39.905],[116.402, 39.908],[116.404, 39.91],[116.406, 39.912],[116.408, 39.914],[116.41, 39.916],
]);const statusText = computed(() => {if (finished.value) return "已完成";if (playing.value) return "播放中";if (progress.value > 0) return "已暂停";return "未开始";
});// 计算两点间的角度(弧度,正北为0,顺时针)
function getAngle(p1, p2) {const [x1, y1] = p1;const [x2, y2] = p2;const dx = x2 - x1;const dy = y2 - y1;return Math.atan2(dy, dx);
}function carStyle(angle = 0) {return new Style({image: new Icon({src: "/src/assets/car.png",anchor: [0.5, 0.5],rotateWithView: true,rotation: -angle, // 加上这一行实现方向旋转}),});
}function trackLineStyle() {return new Style({stroke: new Stroke({ color: "#1976d2", width: 4, lineDash: [8, 8] }),});
}// 绘制轨迹线
function drawTrack() {if (trackFeature.value) vectorSource.value.removeFeature(trackFeature.value);const coords = trackPoints.value.map((pt) => fromLonLat(pt));trackFeature.value = new Feature({ geometry: new LineString(coords) });trackFeature.value.setStyle(trackLineStyle());vectorSource.value.addFeature(trackFeature.value);
}// 绘制小车
function drawCar(idx = 0, angle = 0) {if (carFeature.value) vectorSource.value.removeFeature(carFeature.value);const coord = fromLonLat(trackPoints.value[idx]);carFeature.value = new Feature({ geometry: new Point(coord) });carFeature.value.setStyle(carStyle(angle));vectorSource.value.addFeature(carFeature.value);
}// 开始
function startTrack() {if (playing.value || finished.value) return;playing.value = true;finished.value = false;if (progress.value === 0) {drawCar(0, getAngle(trackPoints.value[0], trackPoints.value[1]));}runTrack();
}// 开始
function runTrack() {if (timer.value) clearInterval(timer.value);timer.value = setInterval(() => {if (!playing.value) return;if (progress.value >= trackPoints.value.length - 1) {playing.value = false;finished.value = true;clearInterval(timer.value);return;}const idx = progress.value;const nextIdx = idx + 1;const angle = getAngle(trackPoints.value[idx], trackPoints.value[nextIdx]);drawCar(nextIdx, angle);progress.value = nextIdx;}, 600);
}// 暂停
function pauseTrack() {playing.value = false;if (timer.value) clearInterval(timer.value);
}// 继续
function resumeTrack() {if (playing.value || finished.value) return;playing.value = true;runTrack();
}// 重置
function resetTrack() {playing.value = false;finished.value = false;progress.value = 0;if (timer.value) clearInterval(timer.value);drawCar(0, getAngle(trackPoints.value[0], trackPoints.value[1]));
}onMounted(() => {vectorSource.value = new VectorSource();const vectorLayer = new VectorLayer({source: vectorSource.value,zIndex: 10,});const baseLayer = new TileLayer({source: new XYZ({url: "https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}",}),});map.value = new Map({target: mapContainer.value,layers: [baseLayer, vectorLayer],view: new View({center: fromLonLat(trackPoints.value[0]),zoom: 14,}),controls: defaultControls().extend([new FullScreen(), new ScaleLine()]),});drawTrack();drawCar(0, getAngle(trackPoints.value[0], trackPoints.value[1]));
});onUnmounted(() => {if (map.value) map.value.dispose();if (timer.value) clearInterval(timer.value);
});
</script><style scoped>
.car-track-container {position: relative;width: 100vw;height: 100vh;overflow: hidden;background: linear-gradient(135deg, #1a237e, #4a148c);font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
.map {width: 100%;height: 100%;background: #0d47a1;
}
.map-controls {position: absolute;top: 20px;right: 20px;background: rgba(255, 255, 255, 0.95);border-radius: 12px;padding: 20px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);z-index: 2;width: 320px;backdrop-filter: blur(5px);border: 1px solid rgba(255, 255, 255, 0.3);
}
.map-controls h3 {margin-top: 0;margin-bottom: 15px;color: #1a237e;font-size: 1.4rem;
}
.control-group {display: flex;flex-wrap: wrap;gap: 10px;margin-bottom: 15px;
}
.control-btn {flex: 1 1 45%;padding: 12px 15px;border: none;border-radius: 8px;background: #3f51b5;color: white;font-weight: 600;cursor: pointer;transition: all 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 8px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.control-btn:disabled {background: #b0bec5;cursor: not-allowed;
}
.control-btn:hover:not(:disabled) {background: #303f9f;transform: translateY(-2px);box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
}
.stats-section {margin-top: 10px;font-size: 1.1rem;color: #1976d2;
}
.progress {font-weight: bold;color: #ffc107;
}
.status {font-weight: bold;color: #388e3c;
}
</style>
http://www.lryc.cn/news/601897.html

相关文章:

  • java的break能加标签,return可以加标签吗
  • 83.删除排序链表中的重复元素
  • DeepCompare文件深度对比软件“.dpcp“工程项目文件功能深度解析
  • vector使用和模拟
  • 网络虚拟化:veth,bridge,network namespace与docker网络
  • ESP32学习-FreeRTOS队列使用指南与实战
  • 使用低级上位画图法理解在对磁盘空间进行容量分配时【低级单位上位至高级单位的换算】
  • Mybatis Plus 多数据源
  • AI时代,我们更需要自己的开发方式与平台
  • Coze Studio概览(一)
  • 链表算法常用技巧与操作
  • 通过硬编码函数地址并转换为函数指针来调用函数
  • 【Agentic】通过LangGrah实现RAG评分和重写
  • 清华大学具身智能多传感器融合感知综述:背景、方法、挑战与展望
  • Flutter开发实战之CI/CD与发布流程
  • 网易大模型算法岗面经80道
  • JSON格式化与结构对比
  • 移植pbrt中的并行化到ray trace in weeks中
  • LangGraph底层API入门总结
  • OpenLayers 综合案例-地图绘制
  • 十字链表以及实现
  • SpringAI入门及浅实践,实战 Spring‎ AI 调用大模型、提示词工程、对话记忆、Adv‎isor 的使用
  • 第五章 中央处理器(CPU)知识体系与考法总结
  • 【第六节】方法与事件处理器
  • Gradle#Plugin
  • Windows---动态链接库Dynamic Link Library(.dll)
  • 2025.7.27总结—新励成
  • Kubernetes 核心组件解析
  • HCIE学习之路:MSTP实现负载均衡实验
  • 【INT范围提取字符串数字为正数】2022-8-29