|
|
|
|
@ -21,6 +21,8 @@ class DroneMap:
|
|
|
|
|
self.font_scale = 1.0
|
|
|
|
|
self.socket_colors = {}
|
|
|
|
|
self.drone_group_colors = {}
|
|
|
|
|
self.mission_following_enabled = False
|
|
|
|
|
self.mission_following_drone_id = None
|
|
|
|
|
|
|
|
|
|
# 創建橋接對象
|
|
|
|
|
self.bridge = MapBridge()
|
|
|
|
|
@ -199,16 +201,18 @@ class DroneMap:
|
|
|
|
|
bridge = channel.objects.bridge;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var map = L.map('map').setView([0, 0], 19);
|
|
|
|
|
var map = L.map('map', {maxZoom: 20}).setView([0, 0], 19);
|
|
|
|
|
|
|
|
|
|
// 創建不同的地圖圖層
|
|
|
|
|
var streetLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
|
|
|
maxZoom: 19,
|
|
|
|
|
maxNativeZoom: 19,
|
|
|
|
|
maxZoom: 20,
|
|
|
|
|
attribution: '© OpenStreetMap contributors'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
|
|
|
|
maxZoom: 19,
|
|
|
|
|
maxNativeZoom: 19,
|
|
|
|
|
maxZoom: 20,
|
|
|
|
|
attribution: 'Tiles © Esri'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@ -217,7 +221,6 @@ class DroneMap:
|
|
|
|
|
currentLayer.addTo(map);
|
|
|
|
|
var isSatellite = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 地圖點擊事件
|
|
|
|
|
map.on('click', function(e) {
|
|
|
|
|
if (selectionMode === 'polygon') {
|
|
|
|
|
@ -349,6 +352,7 @@ class DroneMap:
|
|
|
|
|
var socketColors = {};
|
|
|
|
|
var droneGroupColors = {};
|
|
|
|
|
var focusedId = null;
|
|
|
|
|
var missionFollowingEnabled = false;
|
|
|
|
|
var initialized = false;
|
|
|
|
|
var trajectoryGroup = L.layerGroup().addTo(map);
|
|
|
|
|
|
|
|
|
|
@ -767,15 +771,34 @@ class DroneMap:
|
|
|
|
|
|
|
|
|
|
function focusOn(id) {
|
|
|
|
|
if (!markers[id]) return;
|
|
|
|
|
focusedId = id;
|
|
|
|
|
// 非任務狀態只移動一次;任務執行中才記住焦點持續跟隨。
|
|
|
|
|
focusedId = missionFollowingEnabled ? id : null;
|
|
|
|
|
var latlng = markers[id].getLatLng();
|
|
|
|
|
autoCenteringMap = true;
|
|
|
|
|
map.flyTo(latlng, map.getZoom());
|
|
|
|
|
setTimeout(() => { autoCenteringMap = false; }, 500);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
function setMissionFollowingEnabled(enabled, preferredId) {
|
|
|
|
|
missionFollowingEnabled = Boolean(enabled);
|
|
|
|
|
if (!missionFollowingEnabled) {
|
|
|
|
|
// 非任務狀態不跟隨 UAV 或目標點,保留使用者手動地圖視角。
|
|
|
|
|
focusedId = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 任務已在執行時保留原有焦點;尚無焦點時跟隨指定 UAV。
|
|
|
|
|
if (focusedId && markers[focusedId]) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (preferredId && markers[preferredId]) {
|
|
|
|
|
focusOn(preferredId);
|
|
|
|
|
initialized = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
if (missionFollowingEnabled && focusedId && markers[focusedId]) {
|
|
|
|
|
var latlng = markers[focusedId].getLatLng();
|
|
|
|
|
autoFollowPanTo(latlng);
|
|
|
|
|
}
|
|
|
|
|
@ -793,7 +816,7 @@ class DroneMap:
|
|
|
|
|
.setLatLng([lat, lon])
|
|
|
|
|
.setRotationAngle(heading);
|
|
|
|
|
idLabels[id].setLatLng([lat, lon]);
|
|
|
|
|
if (id === focusedId) {
|
|
|
|
|
if (missionFollowingEnabled && id === focusedId) {
|
|
|
|
|
autoFollowPanTo(markers[id].getLatLng());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@ -813,7 +836,6 @@ class DroneMap:
|
|
|
|
|
if (bridge) {
|
|
|
|
|
bridge.emitDroneClicked(id);
|
|
|
|
|
}
|
|
|
|
|
focusOn(id);
|
|
|
|
|
})
|
|
|
|
|
.addTo(map);
|
|
|
|
|
|
|
|
|
|
@ -828,14 +850,23 @@ class DroneMap:
|
|
|
|
|
if (bridge) {
|
|
|
|
|
bridge.emitDroneClicked(id);
|
|
|
|
|
}
|
|
|
|
|
focusOn(id);
|
|
|
|
|
})
|
|
|
|
|
.addTo(map);
|
|
|
|
|
|
|
|
|
|
if (!initialized || id < focusedId) {
|
|
|
|
|
focusedId = id;
|
|
|
|
|
if (!initialized) {
|
|
|
|
|
// 第一次收到 UAV 位置時只做一次初始置中,
|
|
|
|
|
// 非任務狀態不設 focusedId,所以後續不會跟隨。
|
|
|
|
|
if (missionFollowingEnabled) {
|
|
|
|
|
focusedId = id;
|
|
|
|
|
}
|
|
|
|
|
autoCenteringMap = true;
|
|
|
|
|
map.setView([lat, lon], 19);
|
|
|
|
|
setTimeout(() => { autoCenteringMap = false; }, 300);
|
|
|
|
|
initialized = true;
|
|
|
|
|
} else if (missionFollowingEnabled &&
|
|
|
|
|
(!focusedId || id < focusedId)) {
|
|
|
|
|
focusedId = id;
|
|
|
|
|
map.setView([lat, lon], 19);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -975,6 +1006,9 @@ class DroneMap:
|
|
|
|
|
self.set_font_scale(self.font_scale)
|
|
|
|
|
self.set_socket_colors(self.socket_colors)
|
|
|
|
|
self.set_drone_group_colors(self.drone_group_colors)
|
|
|
|
|
self.set_mission_following(
|
|
|
|
|
self.mission_following_enabled,
|
|
|
|
|
self.mission_following_drone_id)
|
|
|
|
|
else:
|
|
|
|
|
_log("ERROR", "地圖載入失敗")
|
|
|
|
|
|
|
|
|
|
@ -1021,6 +1055,17 @@ class DroneMap:
|
|
|
|
|
if self.map_loaded:
|
|
|
|
|
self.map_view.page().runJavaScript(f"focusOn('{drone_id}');")
|
|
|
|
|
|
|
|
|
|
def set_mission_following(self, enabled, preferred_drone_id=None):
|
|
|
|
|
"""只在任務執行中啟用 UAV 自動跟隨。"""
|
|
|
|
|
self.mission_following_enabled = bool(enabled)
|
|
|
|
|
self.mission_following_drone_id = preferred_drone_id
|
|
|
|
|
if self.map_loaded:
|
|
|
|
|
preferred_json = json.dumps(preferred_drone_id)
|
|
|
|
|
self.map_view.page().runJavaScript(
|
|
|
|
|
"setMissionFollowingEnabled("
|
|
|
|
|
f"{str(self.mission_following_enabled).lower()}, "
|
|
|
|
|
f"{preferred_json});")
|
|
|
|
|
|
|
|
|
|
def set_font_scale(self, scale):
|
|
|
|
|
"""設定地圖 HTML 控制項的字體倍率。"""
|
|
|
|
|
self.font_scale = scale
|
|
|
|
|
|