Update GUI 2.6.0: trajectory, color, window, serial JSON
This commit is contained in:
+71
-22
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt6.QtCore import QTimer, pyqtSignal, QObject, pyqtSlot
|
||||
from PyQt6.QtWebChannel import QWebChannel
|
||||
@@ -17,6 +19,8 @@ class DroneMap:
|
||||
self.map_loaded = False
|
||||
self.pending_map_updates = {}
|
||||
self.font_scale = 1.0
|
||||
self.socket_colors = {}
|
||||
self.drone_group_colors = {}
|
||||
|
||||
# 創建橋接對象
|
||||
self.bridge = MapBridge()
|
||||
@@ -281,38 +285,37 @@ class DroneMap:
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function createArrowIcon(color) {
|
||||
function createArrowIcon(color, groupColor) {
|
||||
const outlineColor = groupColor || 'transparent';
|
||||
return L.divIcon({
|
||||
className: 'drone-arrow',
|
||||
html: `
|
||||
<div style="
|
||||
width: 30px; height: 30px;
|
||||
width: 34px; height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30">
|
||||
<polygon points="15,0 30,30 15,25 0,30" fill="${color || '#FF0000'}" />
|
||||
<svg width="34" height="34" viewBox="0 0 34 34">
|
||||
<polygon
|
||||
points="17,3 31,31 17,26 3,31"
|
||||
fill="${color || '#FF0000'}"
|
||||
stroke="${outlineColor}"
|
||||
stroke-width="3"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15]
|
||||
iconSize: [34, 34],
|
||||
iconAnchor: [17, 17]
|
||||
});
|
||||
}
|
||||
|
||||
function getColorBySocketId(id) {
|
||||
if (id.startsWith("s0_")) return "#00BFFF";
|
||||
if (id.startsWith("s1_")) return "#FFD700";
|
||||
if (id.startsWith("s2_")) return "#FF6969";
|
||||
if (id.startsWith("s3_")) return "#FF69B4";
|
||||
if (id.startsWith("s4_")) return "#00FA9A";
|
||||
if (id.startsWith("s5_")) return "#9370DB";
|
||||
if (id.startsWith("s6_")) return "#FFA500";
|
||||
if (id.startsWith("s7_")) return "#20B2AA";
|
||||
if (id.startsWith("s8_")) return "#7CFC00";
|
||||
if (id.startsWith("s9_")) return "#FF8C00";
|
||||
return "#AAAAAA";
|
||||
const match = /^s([^_]+)_/.exec(id || '');
|
||||
const socketId = match ? match[1] : 'default';
|
||||
return socketColors[socketId] || socketColors.default || "#E0E0E0";
|
||||
}
|
||||
|
||||
function createIdIcon(id) {
|
||||
@@ -340,8 +343,11 @@ class DroneMap:
|
||||
var markers = {};
|
||||
var idLabels = {};
|
||||
var trajectories = {};
|
||||
const maxTrajectoryPoints = 300;
|
||||
const maxTrajectoryPoints = 2000;
|
||||
const trajectoryMinDistanceMeters = 0.05;
|
||||
var trajectoryLines = {};
|
||||
var socketColors = {};
|
||||
var droneGroupColors = {};
|
||||
var focusedId = null;
|
||||
var initialized = false;
|
||||
var trajectoryGroup = L.layerGroup().addTo(map);
|
||||
@@ -703,7 +709,7 @@ class DroneMap:
|
||||
color: color,
|
||||
weight: 3,
|
||||
opacity: 0.7,
|
||||
smoothFactor: 1
|
||||
smoothFactor: 0.1
|
||||
}).addTo(trajectoryGroup);
|
||||
}
|
||||
}
|
||||
@@ -746,7 +752,7 @@ class DroneMap:
|
||||
if (markers[id]) {
|
||||
const lastPos = markers[id].getLatLng();
|
||||
const distance = lastPos.distanceTo([lat, lon]);
|
||||
if (distance > 1) {
|
||||
if (distance >= trajectoryMinDistanceMeters) {
|
||||
addTrajectoryPoint(id, lat, lon);
|
||||
}
|
||||
|
||||
@@ -760,7 +766,7 @@ class DroneMap:
|
||||
|
||||
const color = getColorBySocketId(id);
|
||||
markers[id] = L.marker([lat, lon], {
|
||||
icon: createArrowIcon(color),
|
||||
icon: createArrowIcon(color, droneGroupColors[id]),
|
||||
rotationAngle: heading,
|
||||
rotationOrigin: 'center'
|
||||
})
|
||||
@@ -798,6 +804,33 @@ class DroneMap:
|
||||
}
|
||||
}
|
||||
|
||||
function setDroneGroupColors(groupColors) {
|
||||
droneGroupColors = groupColors || {};
|
||||
Object.keys(markers).forEach(id => {
|
||||
markers[id].setIcon(
|
||||
createArrowIcon(getColorBySocketId(id), droneGroupColors[id])
|
||||
);
|
||||
if (typeof markers[id].setRotationAngle === 'function') {
|
||||
markers[id].setRotationAngle(markers[id].options.rotationAngle || 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setSocketColors(colors) {
|
||||
socketColors = colors || {};
|
||||
Object.keys(markers).forEach(id => {
|
||||
markers[id].setIcon(
|
||||
createArrowIcon(getColorBySocketId(id), droneGroupColors[id])
|
||||
);
|
||||
if (typeof markers[id].setRotationAngle === 'function') {
|
||||
markers[id].setRotationAngle(markers[id].options.rotationAngle || 0);
|
||||
}
|
||||
});
|
||||
Object.keys(trajectoryLines).forEach(id => {
|
||||
trajectoryLines[id].setStyle({ color: getColorBySocketId(id) });
|
||||
});
|
||||
}
|
||||
|
||||
function clearAllTrajectories() {
|
||||
trajectories = {};
|
||||
Object.values(trajectoryLines).forEach(line => {
|
||||
@@ -904,6 +937,8 @@ class DroneMap:
|
||||
if ok:
|
||||
self.map_loaded = True
|
||||
self.set_font_scale(self.font_scale)
|
||||
self.set_socket_colors(self.socket_colors)
|
||||
self.set_drone_group_colors(self.drone_group_colors)
|
||||
else:
|
||||
_log("ERROR", "地圖載入失敗")
|
||||
|
||||
@@ -918,7 +953,7 @@ class DroneMap:
|
||||
|
||||
js_commands = []
|
||||
for drone_id, (lat, lon, heading) in self.pending_map_updates.items():
|
||||
js_commands.append(f"updateDrone({lat:.6f}, {lon:.6f}, '{drone_id}', {heading:.1f});")
|
||||
js_commands.append(f"updateDrone({lat:.8f}, {lon:.8f}, '{drone_id}', {heading:.1f});")
|
||||
|
||||
if js_commands:
|
||||
combined_js = "\n".join(js_commands)
|
||||
@@ -930,6 +965,20 @@ class DroneMap:
|
||||
"""清除所有軌跡"""
|
||||
if self.map_loaded:
|
||||
self.map_view.page().runJavaScript("clearAllTrajectories();")
|
||||
|
||||
def set_drone_group_colors(self, drone_group_colors):
|
||||
"""設定每台無人機的 group 外框顏色;未分組者不傳入即為透明。"""
|
||||
self.drone_group_colors = dict(drone_group_colors or {})
|
||||
if self.map_loaded:
|
||||
payload = json.dumps(self.drone_group_colors)
|
||||
self.map_view.page().runJavaScript(f"setDroneGroupColors({payload});")
|
||||
|
||||
def set_socket_colors(self, socket_colors):
|
||||
"""設定 socket 顏色,供地圖箭頭本體與軌跡線使用。"""
|
||||
self.socket_colors = dict(socket_colors or {})
|
||||
if self.map_loaded:
|
||||
payload = json.dumps(self.socket_colors)
|
||||
self.map_view.page().runJavaScript(f"setSocketColors({payload});")
|
||||
|
||||
def focus_on_drone(self, drone_id):
|
||||
"""聚焦到指定無人機"""
|
||||
|
||||
Reference in New Issue
Block a user