增加远程、本地查看摄像头功能

This commit is contained in:
iRankoo
2026-08-15 19:17:12 +08:00
parent b54ad411c0
commit f3a99f9467
11 changed files with 2346 additions and 289 deletions
+193 -14
View File
@@ -174,15 +174,183 @@
window.location = url;
}
function showSentry() {
did = localStorage.getItem("dongleId");
url = "FRP_SVR/"+String(did)
console.log(url)
window.location = url;
}
function recordOperationTime(){
function showSentry() {
did = localStorage.getItem("dongleId");
url = "http://192.168.1.113:10201/"+String(did)
console.log(url)
window.location = url;
}
let h264Ws = null;
let h264Decoder = null;
let h264Configured = false;
let h264Timestamp = 0;
let h264Frames = 0;
function findH264NalUnits(data) {
const units = [];
let start = -1;
let index = 0;
while (index + 3 < data.length) {
let prefixLength = 0;
if (data[index] === 0 && data[index + 1] === 0 && data[index + 2] === 1) {
prefixLength = 3;
} else if (index + 4 < data.length &&
data[index] === 0 && data[index + 1] === 0 &&
data[index + 2] === 0 && data[index + 3] === 1) {
prefixLength = 4;
}
if (!prefixLength) {
index += 1;
continue;
}
if (start >= 0) {
units.push(data.subarray(start, index));
}
start = index + prefixLength;
index = start;
}
if (start >= 0 && start < data.length) {
units.push(data.subarray(start));
}
return units;
}
function inspectH264(data) {
let key = false;
let codec = null;
for (const nal of findH264NalUnits(data)) {
if (!nal.length) {
continue;
}
const type = nal[0] & 0x1f;
key = key || type === 5;
if (type === 7 && nal.length >= 4) {
codec = "avc1." + [nal[1], nal[2], nal[3]]
.map(function(value) { return value.toString(16).padStart(2, "0"); })
.join("").toUpperCase();
}
}
return {key: key, codec: codec};
}
function closeH264Decoder() {
if (h264Decoder) {
try {
h264Decoder.close();
} catch (_) {
}
}
h264Decoder = null;
h264Configured = false;
h264Timestamp = 0;
}
function createH264Decoder(camera) {
if (!("VideoDecoder" in window)) {
document.getElementById("h264Status").innerText = "WebCodecs H.264 is not supported";
return false;
}
const canvas = document.getElementById("h264Frame");
const context = canvas.getContext("2d");
h264Decoder = new VideoDecoder({
output: function(frame) {
if (canvas.width !== frame.displayWidth || canvas.height !== frame.displayHeight) {
canvas.width = frame.displayWidth;
canvas.height = frame.displayHeight;
}
context.drawImage(frame, 0, 0, canvas.width, canvas.height);
frame.close();
h264Frames += 1;
document.getElementById("h264Status").innerText = "Live " + camera + " / " + h264Frames + " frames";
},
error: function(error) {
console.error("H.264 decoder error", error);
document.getElementById("h264Status").innerText = "H.264 decode error";
closeH264Decoder();
}
});
return true;
}
function playH264(camera) {
const dongleId = localStorage.getItem("dongleId");
if (!dongleId) {
alert("Please input dongle ID first.");
return;
}
stopH264();
document.getElementById("h264Panel").style.display = "";
document.getElementById("h264Status").innerText = "Connecting " + camera;
h264Frames = 0;
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
h264Ws = new WebSocket(protocol + "//" + location.host + "/h264/stream?dongle_id=" + encodeURIComponent(dongleId) + "&camera=" + encodeURIComponent(camera));
h264Ws.binaryType = "arraybuffer";
h264Ws.onopen = function() {
document.getElementById("h264Status").innerText = "Waiting for H.264 keyframe " + camera;
};
h264Ws.onmessage = function(event) {
const data = new Uint8Array(event.data);
const info = inspectH264(data);
if (!h264Configured) {
if (!info.key || !info.codec || (!h264Decoder && !createH264Decoder(camera))) {
return;
}
try {
h264Decoder.configure({
codec: info.codec,
optimizeForLatency: true,
hardwareAcceleration: "prefer-hardware"
});
h264Configured = true;
} catch (error) {
console.error("H.264 decoder configure error", error);
document.getElementById("h264Status").innerText = "Unsupported H.264 stream";
closeH264Decoder();
return;
}
}
try {
h264Decoder.decode(new EncodedVideoChunk({
type: info.key ? "key" : "delta",
timestamp: h264Timestamp,
data: data
}));
h264Timestamp += 50000;
} catch (error) {
console.error("H.264 decode submission error", error);
closeH264Decoder();
}
};
h264Ws.onclose = function() {
document.getElementById("h264Status").innerText = "Stopped";
};
h264Ws.onerror = function() {
h264Ws.close();
};
recordOperationTime();
}
function stopH264() {
if (h264Ws) {
h264Ws.onclose = null;
h264Ws.close();
h264Ws = null;
}
closeH264Decoder();
const canvas = document.getElementById("h264Frame");
if (canvas) {
canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
}
const status = document.getElementById("h264Status");
if (status) {
status.innerText = "Stopped";
}
}
function recordOperationTime(){
strSec = Math.floor(Date.now() / 1000)
localStorage.setItem("operationTime", strSec)
}
@@ -237,11 +405,22 @@
&nbsp;&nbsp;
<input type="button" value="AMap" class="button button1" onclick = "showAmap();" />
&nbsp;&nbsp;
<input type="button" value="Nav" class="button button1" onclick = "showNavigation();" />
</div>
</br></br></br></br>
<div style="text-align:center"><label style="display: none;" id="lastActive" > </label ></div>
<input type="button" value="Nav" class="button button1" onclick = "showNavigation();" />
</br></br>
<input type="button" value="Road Cam" class="button button1" onclick = "playH264('roadCameraState');" />
&nbsp;&nbsp;
<input type="button" value="Wide Cam" class="button button1" onclick = "playH264('wideRoadCameraState');" />
&nbsp;&nbsp;
<input type="button" value="Stop Cam" class="button button1" onclick = "stopH264();" />
</div>
</br></br></br></br>
<div id="h264Panel" style="display:none;text-align:center;margin:0 auto;max-width:960px;">
<div id="h264Status" style="margin-bottom:8px;">Stopped</div>
<canvas id="h264Frame" aria-label="H.264 preview" style="width:100%;background:#000;min-height:240px;object-fit:contain;"></canvas>
</div>
<div style="text-align:center"><label style="display: none;" id="lastActive" > </label ></div>
</body>
+630 -273
View File
@@ -1,273 +1,630 @@
<!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 .btn {margin-right: 1.2rem;width: 9rem;}
.input-card .btn:last-child {margin-right: 0;}
.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);}
</style></head><body>
<div id="container"></div>
<div class="input-card">
<div class="input-item">
<input type="date" style="direction: rtl" onchange="onDataChange()" id="myTime"/>
</div>
<div class="input-item">
<input type="button" class="btn" value="开始动画" id="start" onclick="startAnimation()"/>
<input type="button" class="btn" value="暂停动画" id="pause" onclick="pauseAnimation()"/>
</div>
<!--<div class="input-item">
<input type="button" class="btn" value="继续动画" id="resume" onclick="resumeAnimation()"/>
<input type="button" class="btn" value="停止动画" id="stop" onclick="stopAnimation()"/>
</div>-->
<div class="input-item">
<input type="button" class="btn" value="显示时间" id="showDate" onclick="showDate()"/>
<input type="button" class="btn" value="隐藏时间" id="hideDate" onclick="hideDate()"/>
</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_';
function onDataChange() {
var oTimer = document.getElementById("myTime");
var value = oTimer.value;
dongleid = localStorage.getItem("dongleId");
window.location= "/replaygps/"+dongleid+"/"+value;
}
</script>
<script>
//只要起点 和 终点的 经纬度信息
var allData = '_GPS_DATA_';
var lineArr;
var polyline;
var passedPolyline;
//全部的点
var path = [];
//是否显示时间
var showStatus = true;
//途经点
var wayMarker;
//初始化中心坐标
var centerGPS = _GPS_CENTER_;
//途经点数组。方便清空数据
var wayMarkerArray = new Array();
var carStart, carEnd;
//创建地图。中心点、缩放等级
var map = new AMap.Map("container", {
resizeEnable: true,
center: [centerGPS[0], centerGPS[1]],
zoom: 17
});
//驾车策略基础信息
var drivingOption = {
policy: AMap.DrivingPolicy.LEAST_DISTANCE,
ferry: 1,
}
var gpsData = JSON.parse(allData)
var rowLength = gpsData.data.rows.length;
var gpsLength = gpsData.data.rows[rowLength - 1].gpsInfos.length;
for(var i=0;i<rowLength;i++){
wayMarkerArray[i] = new Array();
}
//添加小汽车
carStart = [gpsData.data.rows[0].gpsInfos[0].longitude, gpsData.data.rows[0].gpsInfos[0].latitude]
carEnd = [gpsData.data.rows[rowLength - 1].gpsInfos[gpsLength - 1].longitude, gpsData.data.rows[rowLength - 1].gpsInfos[gpsLength - 1].latitude]
addCar('');
for (var i = 0; i < rowLength; i++) {
getRoute(i);
}
function getRoute(i) {
var data = gpsData.data.rows[i];
addMarker(i);
pts = getWayPoints(data.gpsInfos);
drawTrack(pts);
drawPassedPolyLine();
lineArr = pts;
return
}
function getWayPoints(coords) {
var GPS = new Array();
if (coords.length > 1) {
//不要第一个和最后一个,会导致起点终点图标被覆盖
for (var i = 1; i < coords.length - 1; i++) {
var wayPoint = coords[i];
var wayPointGPS = new AMap.LngLat(wayPoint.longitude, wayPoint.latitude)
GPS.push(wayPointGPS);
}
}
return GPS;
}
// 绘制轨迹
function drawTrack(path) {
color = "#"+parseInt(Math.random()*65536).toString(16)+ "5b"
polyline = new AMap.Polyline({
map: map,
path: path,
showDir: true,
strokeColor: color,//"#18a45b", //线颜色
strokeWeight: 6, //线宽
});
}
// 绘制运动轨迹样式
function drawPassedPolyLine() {
passedPolyline = new AMap.Polyline({
map: map,
strokeColor: "#AF5", //线颜色
strokeWeight: 6, //线宽
});
}
/*
增加轨迹回放的小汽车和车牌
@param plate - 车牌
*/
function addCar(plate) {
if (plate == "")
{
marker = new AMap.Marker({
map: map,
zIndex: 9999,
position: [carStart[0], carStart[1]],
icon: "https://lbsyun.baidu.com/jsdemo/img/car.png",
offset: new AMap.Pixel(-26, -13),
autoRotation: true,
});
}
else
{
marker = new AMap.Marker({
map: map,
zIndex: 9999,
position: [carStart[0], carStart[1]],
icon: "https://lbsyun.baidu.com/jsdemo/img/car.png",
offset: new AMap.Pixel(-26, -13),
autoRotation: true,
label: {
content: "<div class='info'>" + plate + "</div>",
offset: new AMap.Pixel(-26, -35),
autoRotation: true
}
});
}
}
// 显示时间
function showDate() {
showStatus = true;
for (var i = 0; i < rowLength; i++) {
addMarker(i);
}
}
// 隐藏时间
function hideDate() {
showStatus = false;
for (var i = 0; i < rowLength; i++) {
addMarker(i);
}
}
// 实例化点标记
function addMarker(i) {
if (null != wayMarker) {
wayMarkerArray[i].forEach(function (wayMarkerPoint) {
wayMarkerPoint.setMap(null);
})
wayMarkerArray[i] = new Array();
}
var data = gpsData.data.rows[i];
for (var j = 0; j < data.gpsInfos.length; j++) {
var gpsInfo = data.gpsInfos[j];
wayMarker = new AMap.Marker({
map: map,
position: [gpsInfo.longitude, gpsInfo.latitude],
offset: new AMap.Pixel(-13, -30)
});
if (showStatus) {
wayMarker.setLabel({
offset: new AMap.Pixel(20, 20), //设置文本标注偏移量
content: "<div class='info'>" + gpsInfo.create_time + "</div>", //设置文本标注内容
direction: 'right' //设置文本标注方位
});
}
wayMarkerArray[i].push(wayMarker);
}
}
/**********起点ICON**********/
var startMarker = new AMap.Marker({
position: carStart,
icon: 'https://webapi.amap.com/theme/v1.3/markers/n/start.png',
map: map
})
/**********终点ICON**********/
var endMarker = new AMap.Marker({
position: carEnd,
icon: 'https://webapi.amap.com/theme/v1.3/markers/n/end.png',
map: map
})
// 调整视野达到最佳显示区域
map.setFitView([startMarker, endMarker])
marker.on('moving', function (e) {
passedPolyline.setPath(e.passedPath);
});
/**********动画 START**********/
function startAnimation() {
marker.moveAlong(lineArr, 992);
}
function pauseAnimation() {
marker.pauseMove();
}
function resumeAnimation() {
marker.resumeMove();
}
function stopAnimation() {
marker.stopMove();
}
/**********动画 END**********/
// 解析DrivingRoute对象,构造成AMap.Polyline的path参数需要的格式
function parseRouteToPath(route, type) {
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>
<!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>