You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
11 KiB
361 lines
11 KiB
|
5 days ago
|
<!doctype html>
|
||
|
|
<html lang="zh">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8" />
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
|
|
<title>轨迹点实现</title>
|
||
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol/ol.css" />
|
||
|
|
<style>
|
||
|
|
#map {
|
||
|
|
width: 100%;
|
||
|
|
height: 500px;
|
||
|
|
}
|
||
|
|
#controls {
|
||
|
|
padding: 10px;
|
||
|
|
background: #f5f5f5;
|
||
|
|
}
|
||
|
|
#info-panel {
|
||
|
|
padding: 10px;
|
||
|
|
background: white;
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
.timeline {
|
||
|
|
display: flex;
|
||
|
|
overflow-x: auto;
|
||
|
|
padding: 10px 0;
|
||
|
|
}
|
||
|
|
.timeline-marker {
|
||
|
|
padding: 5px 10px;
|
||
|
|
margin: 0 5px;
|
||
|
|
background: #1890ff;
|
||
|
|
color: white;
|
||
|
|
border-radius: 3px;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="controls">
|
||
|
|
<button id="btn-play">播放轨迹</button>
|
||
|
|
<button id="btn-pause">暂停</button>
|
||
|
|
<div id="info-panel">轨迹信息将显示在这里</div>
|
||
|
|
</div>
|
||
|
|
<div id="map"></div>
|
||
|
|
<div class="timeline" id="timeline"></div>
|
||
|
|
<script src="https://unpkg.com/ol@9.2.4/dist/ol.js"></script>
|
||
|
|
<script>
|
||
|
|
// 轨迹数据(示例数据 - 北京到天津的轨迹)
|
||
|
|
const trackData = [
|
||
|
|
{ time: '09:00', lng: 116.404, lat: 39.915, speed: 60, name: '起点' },
|
||
|
|
{ time: '09:15', lng: 116.704, lat: 39.915, speed: 65, name: '大兴区' },
|
||
|
|
{ time: '09:30', lng: 116.904, lat: 39.815, speed: 70, name: '廊坊' },
|
||
|
|
{ time: '09:45', lng: 117.204, lat: 39.715, speed: 75, name: '武清区' },
|
||
|
|
{ time: '10:00', lng: 117.404, lat: 39.615, speed: 80, name: '天津市区' }
|
||
|
|
]
|
||
|
|
|
||
|
|
// 全局变量
|
||
|
|
let map, trackSource, trackPlayer
|
||
|
|
|
||
|
|
// 初始化地图
|
||
|
|
function initMap() {
|
||
|
|
const source = new ol.source.XYZ({
|
||
|
|
url: 'http://qtbj.icpcdev.site/roadmap/{z}/{x}/{y}.png',
|
||
|
|
maxZoom: 16,
|
||
|
|
minZoom: 10
|
||
|
|
})
|
||
|
|
|
||
|
|
map = new ol.Map({
|
||
|
|
target: 'map',
|
||
|
|
layers: [
|
||
|
|
new ol.layer.Tile({
|
||
|
|
source: source
|
||
|
|
})
|
||
|
|
],
|
||
|
|
view: new ol.View({
|
||
|
|
center: ol.proj.fromLonLat([116.404, 39.915]),
|
||
|
|
zoom: 10
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// 创建轨迹图层
|
||
|
|
trackSource = new ol.source.Vector()
|
||
|
|
const trackLayer = new ol.layer.Vector({
|
||
|
|
source: trackSource,
|
||
|
|
style: function (feature) {
|
||
|
|
const type = feature.get('type')
|
||
|
|
if (type === 'track-line') {
|
||
|
|
return new ol.style.Style({
|
||
|
|
stroke: new ol.style.Stroke({
|
||
|
|
color: '#1890FF',
|
||
|
|
width: 4,
|
||
|
|
lineDash: [5, 5]
|
||
|
|
})
|
||
|
|
})
|
||
|
|
} else if (type === 'track-point') {
|
||
|
|
const speed = feature.get('speed')
|
||
|
|
return new ol.style.Style({
|
||
|
|
image: new ol.style.Circle({
|
||
|
|
radius: 6,
|
||
|
|
fill: new ol.style.Fill({
|
||
|
|
color: speed > 70 ? '#FF4D4F' : '#52C41A'
|
||
|
|
}),
|
||
|
|
stroke: new ol.style.Stroke({
|
||
|
|
color: 'white',
|
||
|
|
width: 2
|
||
|
|
})
|
||
|
|
}),
|
||
|
|
text: new ol.style.Text({
|
||
|
|
text: feature.get('name') || feature.get('time'),
|
||
|
|
offsetY: -15,
|
||
|
|
fill: new ol.style.Fill({ color: '#000' }),
|
||
|
|
stroke: new ol.style.Stroke({ color: '#fff', width: 3 })
|
||
|
|
})
|
||
|
|
})
|
||
|
|
} else if (type === 'moving-point') {
|
||
|
|
return new ol.style.Style({
|
||
|
|
image: new ol.style.Circle({
|
||
|
|
radius: 10,
|
||
|
|
fill: new ol.style.Fill({ color: '#FAAD14' }),
|
||
|
|
stroke: new ol.style.Stroke({
|
||
|
|
color: 'white',
|
||
|
|
width: 3
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
map.addLayer(trackLayer)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 轨迹播放器类
|
||
|
|
class TrackPlayer {
|
||
|
|
constructor(trackData, source, map) {
|
||
|
|
this.trackData = trackData
|
||
|
|
this.source = source
|
||
|
|
this.map = map
|
||
|
|
this.currentIndex = 0
|
||
|
|
this.isPlaying = false
|
||
|
|
this.animationId = null
|
||
|
|
this.movingPoint = null
|
||
|
|
|
||
|
|
this.initMovingPoint()
|
||
|
|
this.createTimeline()
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化移动点
|
||
|
|
initMovingPoint() {
|
||
|
|
this.movingPoint = new ol.Feature({
|
||
|
|
geometry: new ol.geom.Point(
|
||
|
|
ol.proj.fromLonLat([this.trackData[0].lng, this.trackData[0].lat])
|
||
|
|
),
|
||
|
|
type: 'moving-point'
|
||
|
|
})
|
||
|
|
this.source.addFeature(this.movingPoint)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建时间轴
|
||
|
|
createTimeline() {
|
||
|
|
const timeline = document.getElementById('timeline')
|
||
|
|
timeline.innerHTML = ''
|
||
|
|
|
||
|
|
this.trackData.forEach((point, index) => {
|
||
|
|
const pointElement = document.createElement('div')
|
||
|
|
pointElement.className = 'timeline-point'
|
||
|
|
pointElement.innerHTML = `
|
||
|
|
<div>${point.time}</div>
|
||
|
|
<div>${point.name}</div>
|
||
|
|
`
|
||
|
|
pointElement.addEventListener('click', () => {
|
||
|
|
this.jumpToPoint(index)
|
||
|
|
})
|
||
|
|
timeline.appendChild(pointElement)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 播放轨迹
|
||
|
|
play() {
|
||
|
|
if (this.isPlaying) return
|
||
|
|
this.isPlaying = true
|
||
|
|
this.currentIndex = 0
|
||
|
|
this.updateTimeline()
|
||
|
|
this.animate()
|
||
|
|
}
|
||
|
|
|
||
|
|
// 暂停播放
|
||
|
|
pause() {
|
||
|
|
this.isPlaying = false
|
||
|
|
if (this.animationId) {
|
||
|
|
cancelAnimationFrame(this.animationId)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 跳转到指定点
|
||
|
|
jumpToPoint(index) {
|
||
|
|
this.pause()
|
||
|
|
this.currentIndex = Math.max(0, Math.min(index, this.trackData.length - 1))
|
||
|
|
this.updateMovingPoint()
|
||
|
|
this.updateTimeline()
|
||
|
|
}
|
||
|
|
|
||
|
|
// 动画循环
|
||
|
|
animate() {
|
||
|
|
if (!this.isPlaying || this.currentIndex >= this.trackData.length - 1) {
|
||
|
|
this.isPlaying = false
|
||
|
|
this.updateInfo('轨迹播放完成')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const start = this.trackData[this.currentIndex]
|
||
|
|
const end = this.trackData[this.currentIndex + 1]
|
||
|
|
this.moveToNextPoint(start, end, 0, 1500) // 1.5秒移动到下一点
|
||
|
|
}
|
||
|
|
|
||
|
|
// 移动到下一点
|
||
|
|
moveToNextPoint(start, end, progress, duration) {
|
||
|
|
if (!this.isPlaying) return
|
||
|
|
|
||
|
|
if (progress >= 1) {
|
||
|
|
this.currentIndex++
|
||
|
|
this.updateTimeline()
|
||
|
|
this.animate()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 计算插值坐标
|
||
|
|
const lng = start.lng + (end.lng - start.lng) * progress
|
||
|
|
const lat = start.lat + (end.lat - start.lat) * progress
|
||
|
|
|
||
|
|
// 更新移动点位置
|
||
|
|
this.movingPoint.getGeometry().setCoordinates(ol.proj.fromLonLat([lng, lat]))
|
||
|
|
|
||
|
|
// 更新信息面板
|
||
|
|
this.updateInfo(end, progress)
|
||
|
|
|
||
|
|
// 平滑跟随移动点
|
||
|
|
if (progress > 0.3) {
|
||
|
|
// 延迟一点开始跟随
|
||
|
|
this.map.getView().animate({
|
||
|
|
center: ol.proj.fromLonLat([lng, lat]),
|
||
|
|
duration: 200,
|
||
|
|
easing: ol.easing.linear
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 继续动画
|
||
|
|
progress += 16 / duration // 基于60fps
|
||
|
|
this.animationId = requestAnimationFrame(() => {
|
||
|
|
this.moveToNextPoint(start, end, progress, duration)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新移动点位置(直接跳转)
|
||
|
|
updateMovingPoint() {
|
||
|
|
const point = this.trackData[this.currentIndex]
|
||
|
|
this.movingPoint.getGeometry().setCoordinates(ol.proj.fromLonLat([point.lng, point.lat]))
|
||
|
|
|
||
|
|
this.map.getView().animate({
|
||
|
|
center: ol.proj.fromLonLat([point.lng, point.lat]),
|
||
|
|
zoom: 14,
|
||
|
|
duration: 500
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新信息面板
|
||
|
|
updateInfo(point, progress) {
|
||
|
|
const progressText = progress ? `进度: ${Math.round(progress * 100)}%` : ''
|
||
|
|
document.getElementById('info-panel').innerHTML = `
|
||
|
|
<div><strong>当前位置:</strong> ${point.name}</div>
|
||
|
|
<div><strong>时间:</strong> ${point.time}</div>
|
||
|
|
<div><strong>速度:</strong> ${point.speed} km/h</div>
|
||
|
|
<div><strong>${progressText}</strong></div>
|
||
|
|
`
|
||
|
|
}
|
||
|
|
|
||
|
|
// 更新时间轴高亮
|
||
|
|
updateTimeline() {
|
||
|
|
const points = document.querySelectorAll('.timeline-point')
|
||
|
|
points.forEach((point, index) => {
|
||
|
|
point.classList.toggle('active', index === this.currentIndex)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 重置轨迹
|
||
|
|
reset() {
|
||
|
|
this.pause()
|
||
|
|
this.currentIndex = 0
|
||
|
|
this.updateMovingPoint()
|
||
|
|
this.updateTimeline()
|
||
|
|
this.updateInfo(this.trackData[0], 0)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 添加轨迹到地图
|
||
|
|
function addTrackToMap() {
|
||
|
|
// 清空现有轨迹
|
||
|
|
trackSource.clear()
|
||
|
|
|
||
|
|
// 添加轨迹点
|
||
|
|
trackData.forEach((point, index) => {
|
||
|
|
const feature = new ol.Feature({
|
||
|
|
geometry: new ol.geom.Point(ol.proj.fromLonLat([point.lng, point.lat])),
|
||
|
|
type: 'track-point',
|
||
|
|
time: point.time,
|
||
|
|
speed: point.speed,
|
||
|
|
name: point.name,
|
||
|
|
index: index
|
||
|
|
})
|
||
|
|
trackSource.addFeature(feature)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 添加轨迹线
|
||
|
|
const lineCoords = trackData.map((p) => ol.proj.fromLonLat([p.lng, p.lat]))
|
||
|
|
const lineFeature = new ol.Feature({
|
||
|
|
geometry: new ol.geom.LineString(lineCoords),
|
||
|
|
type: 'track-line'
|
||
|
|
})
|
||
|
|
trackSource.addFeature(lineFeature)
|
||
|
|
|
||
|
|
// 自适应缩放
|
||
|
|
const extent = trackSource.getExtent()
|
||
|
|
map.getView().fit(extent, {
|
||
|
|
padding: [50, 50, 50, 50],
|
||
|
|
duration: 1000
|
||
|
|
})
|
||
|
|
|
||
|
|
// 初始化播放器
|
||
|
|
trackPlayer = new TrackPlayer(trackData, trackSource, map)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 页面加载完成后初始化
|
||
|
|
document.addEventListener('DOMContentLoaded', function () {
|
||
|
|
// 初始化地图
|
||
|
|
initMap()
|
||
|
|
|
||
|
|
// 绑定按钮事件
|
||
|
|
// document.getElementById('btn-add-track').addEventListener('click', addTrackToMap);
|
||
|
|
document.getElementById('btn-play').addEventListener('click', function () {
|
||
|
|
if (trackPlayer) trackPlayer.play()
|
||
|
|
})
|
||
|
|
|
||
|
|
// document.getElementById('btn-play')
|
||
|
|
// document.getElementById('btn-pause').addEventListener('click', function() {
|
||
|
|
// if (trackPlayer) trackPlayer.pause();
|
||
|
|
// });
|
||
|
|
// document.getElementById('btn-reset').addEventListener('click', function() {
|
||
|
|
// if (trackPlayer) trackPlayer.reset();
|
||
|
|
// });
|
||
|
|
// document.getElementById('btn-clear').addEventListener('click', function() {
|
||
|
|
// trackSource.clear();
|
||
|
|
// document.getElementById('timeline').innerHTML = '';
|
||
|
|
// document.getElementById('info-panel').innerHTML = '轨迹已清空';
|
||
|
|
// });
|
||
|
|
// trackPlayer.play();
|
||
|
|
// 页面加载后自动添加示例轨迹
|
||
|
|
setTimeout(addTrackToMap, 1500)
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|