23 changed files with 605 additions and 109 deletions
@ -0,0 +1,360 @@ |
|||
<!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> |
|||
Loading…
Reference in new issue