mirror of
https://github.com/eatfishfish/openpilot-server.git
synced 2026-08-22 04:42:32 +00:00
631 lines
27 KiB
HTML
631 lines
27 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head><meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
|
<title>Route replay</title>
|
|
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css"/>
|
|
<style>
|
|
html,
|
|
body,
|
|
#container {
|
|
height: 100%;width: 100%;font-family: "微软雅黑";
|
|
}
|
|
.input-card {width: auto;min-width: 0;padding: .45rem .55rem;display: grid;grid-template-columns: 9.2rem 14.2rem 3.8rem 5rem;align-items: center;gap: .35rem;white-space: nowrap;}
|
|
.input-card .input-item {height: auto;margin: 0;display: flex;align-items: center;gap: .25rem;min-width: 0;}
|
|
.input-card .btn {margin-right: 0;width: auto;min-width: 3.2rem;padding: 0 .35rem;height: 2.25rem;line-height: 2.25rem;}
|
|
.input-card .btn:last-child {margin-right: 0;}
|
|
#myTime {width: 9rem;height: 2.25rem;box-sizing: border-box;padding: 0 .25rem;line-height: normal;}
|
|
#tripSelect {width: 14rem;height: 2.25rem;box-sizing: border-box;min-width: 0;padding: .25rem 1.75rem .25rem .35rem;line-height: normal;}
|
|
.amap-marker-label{border: 0;background-color: transparent;}
|
|
.amap-marker-content img {width: 25px;height: 34px;}
|
|
.info {position: relative;top: 0;right: 0;min-width: 0;font-size: 16px;border: 1px solid rgb(204, 204, 204);white-space: nowrap;padding: 0 4px;}
|
|
#carGpsTip {position: fixed;left: calc(50% + 18px);top: calc(50% - 34px);z-index: 10000;display: none;white-space: nowrap;font-size: 16px;border: 1px solid rgb(204, 204, 204);background: #fff;padding: 0 4px;pointer-events: none;}
|
|
#loadStatus {position: fixed;left: 50%;top: 1rem;transform: translateX(-50%);z-index: 10001;padding: .35rem .65rem;background: rgba(255,255,255,.94);border: 1px solid #ccc;font-size: 13px;white-space: nowrap;}
|
|
@media (max-width: 760px) {
|
|
.input-card {max-width: calc(100% - 1rem);grid-template-columns: 8rem 10rem 3.2rem 4.5rem;gap: .25rem;overflow-x: auto;}
|
|
.input-card .btn {min-width: 2.8rem;padding: 0 .25rem;font-size: 12px;}
|
|
#myTime {width: 7.8rem;font-size: 12px;}
|
|
#tripSelect {width: 9.8rem;font-size: 12px;}
|
|
}
|
|
</style></head><body>
|
|
|
|
|
|
<div id="container"></div>
|
|
<div id="carGpsTip"></div>
|
|
<div id="loadStatus">GPS数据加载中...</div>
|
|
<div class="input-card">
|
|
<div class="input-item">
|
|
<input type="date" style="direction: rtl" onchange="onDataChange()" id="myTime"/>
|
|
</div>
|
|
<div class="input-item">
|
|
<select id="tripSelect" onchange="selectTrip(this.value)"></select>
|
|
</div>
|
|
<div class="input-item">
|
|
<input type="button" class="btn" value="播放" id="playToggle" onclick="toggleAnimation()"/>
|
|
</div>
|
|
<div class="input-item">
|
|
<input type="button" class="btn" value="隐藏GPS" id="gpsToggle" onclick="toggleGpsPoints()"/>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=6a5cbc4029bcee01691fab072982280f&plugin=AMap.Driving"></script>
|
|
|
|
<script>
|
|
document.getElementById("myTime").value= '_DATE_';
|
|
var replayDongleId = '_DONGLE_ID_';
|
|
|
|
function onDataChange() {
|
|
var oTimer = document.getElementById("myTime");
|
|
var value = oTimer.value;
|
|
var dongleid = replayDongleId || localStorage.getItem("dongleId");
|
|
window.location= "/replaygps/"+dongleid+"/"+value;
|
|
}
|
|
</script>
|
|
<script>
|
|
var lineArr = [];
|
|
var passedPolyline;
|
|
var tripPolylines = [];
|
|
var marker;
|
|
var startMarker;
|
|
var endMarker;
|
|
var selectedTripIndex = 0;
|
|
var showPointStatus = true;
|
|
var animationPlaying = false;
|
|
var currentGpsPointIndex = -1;
|
|
var currentGpsLabelMarker;
|
|
var currentTimeLabel;
|
|
var wayMarkerArray = new Array();
|
|
var tripPaths = [];
|
|
var rows = [];
|
|
var gpsRenderVersion = 0;
|
|
|
|
var map = new AMap.Map("container", {
|
|
resizeEnable: true,
|
|
center: [114.03, 22.54],
|
|
zoom: 17
|
|
});
|
|
|
|
loadGpsData();
|
|
|
|
async function loadGpsData() {
|
|
setControlsDisabled(true);
|
|
setLoadStatus("GPS数据加载中...");
|
|
try {
|
|
var date = document.getElementById("myTime").value;
|
|
var url = "/replaygpsdata/" + encodeURIComponent(replayDongleId) + "/" + encodeURIComponent(date);
|
|
var response = await fetch(url, {cache: "no-store"});
|
|
if (!response.ok) {
|
|
throw new Error("GPS数据加载失败");
|
|
}
|
|
|
|
var gpsData = await response.json();
|
|
rows = gpsData.data && gpsData.data.rows ? gpsData.data.rows : [];
|
|
if (gpsData.date) {
|
|
document.getElementById("myTime").value = gpsData.date;
|
|
}
|
|
if (rows.length === 0) {
|
|
setLoadStatus("当天没有GPS数据");
|
|
return;
|
|
}
|
|
|
|
var firstPoint = rows[0].gpsInfos && rows[0].gpsInfos[0];
|
|
if (firstPoint) {
|
|
map.setCenter([firstPoint.longitude, firstPoint.latitude]);
|
|
}
|
|
|
|
setLoadStatus("正在生成行程...");
|
|
await buildTripPaths();
|
|
initTripSelect();
|
|
drawPassedPolyLine();
|
|
await drawAllTrips();
|
|
selectTrip(0);
|
|
setLoadStatus("");
|
|
} catch (error) {
|
|
console.error(error);
|
|
setLoadStatus(error.message || "GPS数据加载失败");
|
|
} finally {
|
|
setControlsDisabled(false);
|
|
}
|
|
}
|
|
|
|
function setControlsDisabled(disabled) {
|
|
document.getElementById("tripSelect").disabled = disabled;
|
|
document.getElementById("playToggle").disabled = disabled;
|
|
document.getElementById("gpsToggle").disabled = disabled;
|
|
}
|
|
|
|
function setLoadStatus(text) {
|
|
var status = document.getElementById("loadStatus");
|
|
status.innerText = text;
|
|
status.style.display = text ? "block" : "none";
|
|
}
|
|
|
|
function nextFrame() {
|
|
return new Promise(function (resolve) {
|
|
requestAnimationFrame(resolve);
|
|
});
|
|
}
|
|
|
|
async function buildTripPaths() {
|
|
var processed = 0;
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var points = rows[i].gpsInfos || [];
|
|
var path = [];
|
|
for (var j = 0; j < points.length; j++) {
|
|
path.push(new AMap.LngLat(points[j].longitude, points[j].latitude));
|
|
processed++;
|
|
if (processed % 1000 === 0) {
|
|
await nextFrame();
|
|
}
|
|
}
|
|
tripPaths.push(path);
|
|
}
|
|
}
|
|
|
|
function initTripSelect() {
|
|
var select = document.getElementById("tripSelect");
|
|
select.innerHTML = "";
|
|
for (var i = 0; i < rows.length; i++) {
|
|
var option = document.createElement("option");
|
|
option.value = i;
|
|
option.text = rows[i].pointDay;
|
|
select.appendChild(option);
|
|
}
|
|
}
|
|
|
|
async function drawAllTrips() {
|
|
for (var i = 0; i < tripPaths.length; i++) {
|
|
if (tripPaths[i].length < 2) {
|
|
tripPolylines[i] = null;
|
|
continue;
|
|
}
|
|
setLoadStatus("正在绘制行程 " + (i + 1) + "/" + tripPaths.length);
|
|
await drawTripSegments(i, tripPaths[i], rows[i].gpsInfos || []);
|
|
}
|
|
}
|
|
|
|
async function drawTripSegments(tripIndex, path, points) {
|
|
var base = new AMap.Polyline({
|
|
map: map,
|
|
path: path,
|
|
strokeColor: getTripColor(tripIndex),
|
|
strokeOpacity: tripIndex === selectedTripIndex ? 1 : 0.55,
|
|
strokeWeight: tripIndex === selectedTripIndex ? 16 : 11,
|
|
zIndex: 40
|
|
});
|
|
var speeds = [];
|
|
for (var i = 1; i < path.length; i++) {
|
|
var segPoints = [path[i - 1], path[i]];
|
|
var speed = getSegmentSpeed(points[i - 1], points[i]);
|
|
var speedSegment = new AMap.Polyline({
|
|
map: map,
|
|
path: segPoints,
|
|
showDir: true,
|
|
strokeColor: getSpeedColor(speed),
|
|
strokeOpacity: tripIndex === selectedTripIndex ? 1 : 0.45,
|
|
strokeWeight: tripIndex === selectedTripIndex ? 6 : 3,
|
|
zIndex: 50
|
|
});
|
|
speeds.push(speedSegment);
|
|
if (i % 200 === 0) {
|
|
await nextFrame();
|
|
}
|
|
}
|
|
tripPolylines[tripIndex] = {base: base, speeds: speeds};
|
|
}
|
|
|
|
function selectTrip(index) {
|
|
selectedTripIndex = parseInt(index, 10) || 0;
|
|
if (!rows[selectedTripIndex] || tripPaths[selectedTripIndex].length === 0) {
|
|
return;
|
|
}
|
|
|
|
stopAnimation();
|
|
setAnimationPlaying(false);
|
|
lineArr = tripPaths[selectedTripIndex];
|
|
setTripHighlight();
|
|
resetCar();
|
|
resetStartEndMarkers();
|
|
renderGpsPoints();
|
|
currentGpsPointIndex = -1;
|
|
clearCurrentTimeLabel();
|
|
hideCarGpsTip();
|
|
passedPolyline.setPath([]);
|
|
var fitItems = [];
|
|
var tripLines = tripPolylines[selectedTripIndex];
|
|
if (tripLines) {
|
|
fitItems.push(tripLines.base);
|
|
for (var i = 0; i < tripLines.speeds.length; i++) {
|
|
fitItems.push(tripLines.speeds[i]);
|
|
}
|
|
}
|
|
fitItems.push(startMarker);
|
|
fitItems.push(endMarker);
|
|
map.setFitView(fitItems);
|
|
}
|
|
|
|
function setTripHighlight() {
|
|
for (var i = 0; i < tripPolylines.length; i++) {
|
|
var tripLines = tripPolylines[i];
|
|
if (!tripLines) {
|
|
continue;
|
|
}
|
|
tripLines.base.setOptions({
|
|
strokeOpacity: i === selectedTripIndex ? 1 : 0.55,
|
|
strokeWeight: i === selectedTripIndex ? 16 : 11
|
|
});
|
|
for (var j = 0; j < tripLines.speeds.length; j++) {
|
|
tripLines.speeds[j].setOptions({
|
|
strokeOpacity: i === selectedTripIndex ? 1 : 0.45,
|
|
strokeWeight: i === selectedTripIndex ? 6 : 3
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function resetCar() {
|
|
if (marker) {
|
|
marker.setMap(null);
|
|
}
|
|
marker = new AMap.Marker({
|
|
map: map,
|
|
zIndex: 9999,
|
|
position: lineArr[0],
|
|
icon: "https://lbsyun.baidu.com/jsdemo/img/car.png",
|
|
offset: new AMap.Pixel(-26, -13),
|
|
autoRotation: true
|
|
});
|
|
marker.on('moving', function (e) {
|
|
passedPolyline.setPath(e.passedPath);
|
|
focusMapOnCar();
|
|
showCurrentGpsTime();
|
|
});
|
|
}
|
|
|
|
function resetStartEndMarkers() {
|
|
if (startMarker) {
|
|
startMarker.setMap(null);
|
|
}
|
|
if (endMarker) {
|
|
endMarker.setMap(null);
|
|
}
|
|
|
|
startMarker = new AMap.Marker({
|
|
position: lineArr[0],
|
|
icon: 'https://webapi.amap.com/theme/v1.3/markers/n/start.png',
|
|
map: map
|
|
});
|
|
endMarker = new AMap.Marker({
|
|
position: lineArr[lineArr.length - 1],
|
|
icon: 'https://webapi.amap.com/theme/v1.3/markers/n/end.png',
|
|
map: map
|
|
});
|
|
}
|
|
|
|
function drawPassedPolyLine() {
|
|
passedPolyline = new AMap.Polyline({
|
|
map: map,
|
|
strokeColor: "#2b7fff",
|
|
strokeWeight: 6
|
|
});
|
|
}
|
|
|
|
function showGpsPoints() {
|
|
showPointStatus = true;
|
|
updateGpsToggleText();
|
|
hideCarGpsTip();
|
|
renderGpsPoints();
|
|
if (animationPlaying) {
|
|
currentGpsPointIndex = -1;
|
|
showCurrentGpsTime();
|
|
}
|
|
}
|
|
|
|
function hideGpsPoints() {
|
|
showPointStatus = false;
|
|
currentGpsPointIndex = -1;
|
|
updateGpsToggleText();
|
|
renderGpsPoints();
|
|
if (animationPlaying) {
|
|
currentGpsPointIndex = -1;
|
|
showCurrentGpsTime();
|
|
}
|
|
}
|
|
|
|
function toggleGpsPoints() {
|
|
if (showPointStatus) {
|
|
hideGpsPoints();
|
|
} else {
|
|
showGpsPoints();
|
|
}
|
|
}
|
|
|
|
function updateGpsToggleText() {
|
|
var button = document.getElementById("gpsToggle");
|
|
if (button) {
|
|
button.value = showPointStatus ? "隐藏GPS" : "显示GPS";
|
|
}
|
|
}
|
|
|
|
function clearGpsPoints() {
|
|
gpsRenderVersion++;
|
|
currentGpsLabelMarker = null;
|
|
wayMarkerArray.forEach(function (wayMarkerPoint) {
|
|
wayMarkerPoint.setMap(null);
|
|
});
|
|
wayMarkerArray = new Array();
|
|
}
|
|
|
|
async function renderGpsPoints() {
|
|
clearGpsPoints();
|
|
currentGpsPointIndex = -1;
|
|
if (!showPointStatus || !rows[selectedTripIndex]) {
|
|
return;
|
|
}
|
|
|
|
var renderVersion = gpsRenderVersion;
|
|
var data = rows[selectedTripIndex];
|
|
for (var j = 0; j < data.gpsInfos.length; j++) {
|
|
if (renderVersion !== gpsRenderVersion || !showPointStatus) {
|
|
return;
|
|
}
|
|
var gpsInfo = data.gpsInfos[j];
|
|
var wayMarker = new AMap.Marker({
|
|
map: map,
|
|
position: [gpsInfo.longitude, gpsInfo.latitude],
|
|
offset: new AMap.Pixel(-13, -30)
|
|
});
|
|
wayMarkerArray.push(wayMarker);
|
|
if (animationPlaying && showPointStatus && j === currentGpsPointIndex) {
|
|
currentGpsPointIndex = -1;
|
|
showCurrentGpsTime(j);
|
|
}
|
|
if ((j + 1) % 100 === 0) {
|
|
await nextFrame();
|
|
}
|
|
}
|
|
}
|
|
|
|
function startAnimation() {
|
|
if (marker && lineArr.length > 1) {
|
|
setAnimationPlaying(true);
|
|
if (showPointStatus && wayMarkerArray.length === 0) {
|
|
renderGpsPoints();
|
|
}
|
|
focusMapOnCar();
|
|
showCurrentGpsTime(0);
|
|
marker.moveAlong(lineArr, 99200);
|
|
}
|
|
}
|
|
|
|
function pauseAnimation() {
|
|
if (marker) {
|
|
marker.pauseMove();
|
|
setAnimationPlaying(false);
|
|
hideCarGpsTip();
|
|
}
|
|
}
|
|
|
|
function toggleAnimation() {
|
|
if (animationPlaying) {
|
|
pauseAnimation();
|
|
} else {
|
|
startAnimation();
|
|
}
|
|
}
|
|
|
|
function setAnimationPlaying(isPlaying) {
|
|
animationPlaying = isPlaying;
|
|
var button = document.getElementById("playToggle");
|
|
if (button) {
|
|
button.value = animationPlaying ? "暂停" : "播放";
|
|
}
|
|
}
|
|
|
|
function resumeAnimation() {
|
|
if (marker) {
|
|
marker.resumeMove();
|
|
}
|
|
}
|
|
|
|
function stopAnimation() {
|
|
if (marker) {
|
|
marker.stopMove();
|
|
}
|
|
setAnimationPlaying(false);
|
|
hideCarGpsTip();
|
|
}
|
|
|
|
function focusMapOnCar() {
|
|
if (!marker) {
|
|
return;
|
|
}
|
|
|
|
var position = marker.getPosition();
|
|
if (position) {
|
|
map.setCenter(position);
|
|
}
|
|
}
|
|
|
|
function showCurrentGpsTime(forceIndex) {
|
|
if (!rows[selectedTripIndex]) {
|
|
return;
|
|
}
|
|
|
|
var index = typeof forceIndex === "number" ? forceIndex : getNearestGpsPointIndex(marker.getPosition());
|
|
if (index < 0) {
|
|
return;
|
|
}
|
|
if (showPointStatus && index === currentGpsPointIndex) {
|
|
return;
|
|
}
|
|
|
|
clearGpsPointLabels();
|
|
clearCurrentTimeLabel();
|
|
currentGpsPointIndex = index;
|
|
var gpsInfo = rows[selectedTripIndex].gpsInfos[index];
|
|
var clock = formatGpsClock(gpsInfo.create_time);
|
|
var content = "<div class='info'>" + clock + "</div>";
|
|
if (showPointStatus && wayMarkerArray[index]) {
|
|
hideCarGpsTip();
|
|
currentGpsLabelMarker = wayMarkerArray[index];
|
|
currentGpsLabelMarker.setLabel({
|
|
offset: new AMap.Pixel(20, 20),
|
|
content: content,
|
|
direction: 'right'
|
|
});
|
|
} else {
|
|
showCarGpsTip(clock);
|
|
}
|
|
}
|
|
|
|
function formatGpsClock(value) {
|
|
var parts = value.split(" ");
|
|
return parts.length > 1 ? parts[1] : value;
|
|
}
|
|
|
|
function clearGpsPointLabels() {
|
|
if (currentGpsLabelMarker) {
|
|
currentGpsLabelMarker.setLabel({
|
|
offset: new AMap.Pixel(20, 20),
|
|
content: "",
|
|
direction: 'right'
|
|
});
|
|
currentGpsLabelMarker = null;
|
|
}
|
|
}
|
|
|
|
function showCurrentTimeLabel(position, content) {
|
|
currentTimeLabel = new AMap.Marker({
|
|
map: map,
|
|
position: position,
|
|
content: "<div></div>",
|
|
offset: new AMap.Pixel(0, 0),
|
|
zIndex: 10000
|
|
});
|
|
currentTimeLabel.setLabel({
|
|
offset: new AMap.Pixel(20, -20),
|
|
content: content,
|
|
direction: 'right'
|
|
});
|
|
}
|
|
|
|
function clearCurrentTimeLabel() {
|
|
if (currentTimeLabel) {
|
|
currentTimeLabel.setMap(null);
|
|
currentTimeLabel = null;
|
|
}
|
|
}
|
|
|
|
function showCarGpsTip(clock) {
|
|
var tip = document.getElementById("carGpsTip");
|
|
if (tip) {
|
|
tip.innerText = clock;
|
|
tip.style.display = "block";
|
|
}
|
|
}
|
|
|
|
function hideCarGpsTip() {
|
|
var tip = document.getElementById("carGpsTip");
|
|
if (tip) {
|
|
tip.style.display = "none";
|
|
tip.innerText = "";
|
|
}
|
|
}
|
|
|
|
function getNearestGpsPointIndex(position) {
|
|
if (!position || lineArr.length === 0) {
|
|
return -1;
|
|
}
|
|
|
|
var nearestIndex = 0;
|
|
var nearestDistance = Number.MAX_VALUE;
|
|
for (var i = 0; i < lineArr.length; i++) {
|
|
var distance = position.distance(lineArr[i]);
|
|
if (distance < nearestDistance) {
|
|
nearestDistance = distance;
|
|
nearestIndex = i;
|
|
}
|
|
}
|
|
return nearestIndex;
|
|
}
|
|
|
|
function getSegmentSpeed(a, b) {
|
|
var start = parseGpsTime(a.create_time);
|
|
var end = parseGpsTime(b.create_time);
|
|
var seconds = (end - start) / 1000;
|
|
if (seconds <= 0) {
|
|
return 0;
|
|
}
|
|
return getDistance(a, b) / seconds;
|
|
}
|
|
|
|
function parseGpsTime(value) {
|
|
return new Date(value.replace(/-/g, "/")).getTime();
|
|
}
|
|
|
|
function getDistance(a, b) {
|
|
var earthRadius = 6371000;
|
|
var lat1 = toRad(parseFloat(a.latitude));
|
|
var lat2 = toRad(parseFloat(b.latitude));
|
|
var deltaLat = toRad(parseFloat(b.latitude) - parseFloat(a.latitude));
|
|
var deltaLng = toRad(parseFloat(b.longitude) - parseFloat(a.longitude));
|
|
var h = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
|
|
Math.cos(lat1) * Math.cos(lat2) *
|
|
Math.sin(deltaLng / 2) * Math.sin(deltaLng / 2);
|
|
return earthRadius * 2 * Math.atan2(Math.sqrt(h), Math.sqrt(1 - h));
|
|
}
|
|
|
|
function toRad(value) {
|
|
return value * Math.PI / 180;
|
|
}
|
|
|
|
function getSpeedColor(speed) {
|
|
var maxSpeed = 20;
|
|
var ratio = Math.max(0, Math.min(speed / maxSpeed, 1));
|
|
var red = Math.round(214 * (1 - ratio) + 25 * ratio);
|
|
var green = Math.round(69 * (1 - ratio) + 165 * ratio);
|
|
var blue = Math.round(49 * (1 - ratio) + 90 * ratio);
|
|
return rgbToHex(red, green, blue);
|
|
}
|
|
|
|
function getTripColor(index) {
|
|
var colors = [
|
|
"#2563eb", "#9333ea", "#f97316", "#0891b2",
|
|
"#db2777", "#7c3aed", "#0f766e", "#ca8a04"
|
|
];
|
|
return colors[index % colors.length];
|
|
}
|
|
|
|
function rgbToHex(red, green, blue) {
|
|
return "#" + [red, green, blue].map(function (value) {
|
|
var hex = value.toString(16);
|
|
return hex.length === 1 ? "0" + hex : hex;
|
|
}).join("");
|
|
}
|
|
|
|
// 保留旧按钮或缓存页面调用,避免旧事件找不到函数。
|
|
function showDate() {
|
|
showGpsPoints();
|
|
}
|
|
|
|
function hideDate() {
|
|
hideGpsPoints();
|
|
}
|
|
|
|
function parseRouteToPath(route, type) {
|
|
var path = [];
|
|
for (var i = 0, l = route.steps.length; i < l; i++) {
|
|
var step = route.steps[i];
|
|
for (var j = 0, n = step.path.length; j < n; j++) {
|
|
path.push(step.path[j]);
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|