|
|
@ -362,18 +362,21 @@ |
|
|
<script setup> |
|
|
<script setup> |
|
|
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue' |
|
|
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue' |
|
|
import { useRoute, useRouter } from 'vue-router' |
|
|
import { useRoute, useRouter } from 'vue-router' |
|
|
import * as maptalks from 'maptalks' |
|
|
|
|
|
|
|
|
import mapboxgl from 'mapbox-gl' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
import { useUiStore } from '@/stores/modules/uiStore' |
|
|
import { useUiStore } from '@/stores/modules/uiStore' |
|
|
import { useUserStore } from '@/stores/modules/userStore' |
|
|
import { useUserStore } from '@/stores/modules/userStore' |
|
|
import commonRefs from '@/utils/commonRefs' |
|
|
import commonRefs from '@/utils/commonRefs' |
|
|
import mapHelper from '@/core/mapHelper' |
|
|
import mapHelper from '@/core/mapHelper' |
|
|
import mapConfig, { createTiandituBaseLayer } from '@/config/map' |
|
|
|
|
|
|
|
|
import mapConfig, { MAP_STYLES } from '@/config/map' |
|
|
import LivePlayer from '@/components/LivePlayer.vue' |
|
|
import LivePlayer from '@/components/LivePlayer.vue' |
|
|
import { getLivePlayURL, heartbeatLive, joinLive, leaveLive } from '@/api/live' |
|
|
import { getLivePlayURL, heartbeatLive, joinLive, leaveLive } from '@/api/live' |
|
|
|
|
|
|
|
|
const LAYER_ID = 'monitor-devices' |
|
|
|
|
|
const BINDING_LAYER_ID = 'monitor-binding' |
|
|
|
|
|
|
|
|
const DEVICE_SOURCE = 'monitor-devices' |
|
|
|
|
|
const DEVICE_CIRCLE = 'monitor-devices-circle' |
|
|
|
|
|
const DEVICE_LABEL = 'monitor-devices-label' |
|
|
|
|
|
const BINDING_SOURCE = 'monitor-binding' |
|
|
|
|
|
const BINDING_LINE = 'monitor-binding-line' |
|
|
|
|
|
|
|
|
const STATUS_COLORS = { |
|
|
const STATUS_COLORS = { |
|
|
online: '#21a06a', |
|
|
online: '#21a06a', |
|
|
@ -401,13 +404,10 @@ const dockStatusOpen = ref(false) |
|
|
const selectedDockIds = reactive(new Set()) |
|
|
const selectedDockIds = reactive(new Set()) |
|
|
|
|
|
|
|
|
let mapInstance = null |
|
|
let mapInstance = null |
|
|
let deviceLayer = null |
|
|
|
|
|
let bindingLayer = null |
|
|
|
|
|
let bindingLine = null |
|
|
|
|
|
|
|
|
let layersReady = false |
|
|
const mapMode = ref('satellite') |
|
|
const mapMode = ref('satellite') |
|
|
const mapScaleText = ref('2 km') |
|
|
const mapScaleText = ref('2 km') |
|
|
let suppressMapClick = false |
|
|
let suppressMapClick = false |
|
|
const markerById = new Map() |
|
|
|
|
|
|
|
|
|
|
|
const dockExtraCommands = [ |
|
|
const dockExtraCommands = [ |
|
|
{ title: '一键起飞', desc: '无人机将从机巢起飞并进入待命状态。', icon: '#i-plane', hint: '下发指令' }, |
|
|
{ title: '一键起飞', desc: '无人机将从机巢起飞并进入待命状态。', icon: '#i-plane', hint: '下发指令' }, |
|
|
@ -440,75 +440,132 @@ const mapMarkers = computed(() => |
|
|
Object.values(devices.assets).filter((a) => a.hasCoordinates && !a.inDock) |
|
|
Object.values(devices.assets).filter((a) => a.hasCoordinates && !a.inDock) |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
function markerSymbol(asset, selected = false) { |
|
|
|
|
|
const fill = STATUS_COLORS[asset.statusClass] || STATUS_COLORS.offline |
|
|
|
|
|
const radius = asset.type === 'drone' ? 8 : 10 |
|
|
|
|
|
return [ |
|
|
|
|
|
{ |
|
|
|
|
|
markerType: 'ellipse', |
|
|
|
|
|
markerFill: fill, |
|
|
|
|
|
markerLineColor: '#fff', |
|
|
|
|
|
markerLineWidth: selected ? 3 : 2, |
|
|
|
|
|
markerWidth: selected ? radius * 2.4 : radius * 2, |
|
|
|
|
|
markerHeight: selected ? radius * 2.4 : radius * 2, |
|
|
|
|
|
markerDx: 0, |
|
|
|
|
|
markerDy: 0, |
|
|
|
|
|
markerOpacity: 1, |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
textName: asset.name || '', |
|
|
|
|
|
textFill: '#fff', |
|
|
|
|
|
textHaloFill: 'rgba(24,35,45,.86)', |
|
|
|
|
|
textHaloRadius: 3, |
|
|
|
|
|
textSize: 11, |
|
|
|
|
|
textDy: selected ? 18 : 16, |
|
|
|
|
|
textWeight: 500, |
|
|
|
|
|
}, |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
function statusColor(statusClass) { |
|
|
|
|
|
return STATUS_COLORS[statusClass] || STATUS_COLORS.offline |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function syncMarkers() { |
|
|
|
|
|
if (!deviceLayer) return |
|
|
|
|
|
|
|
|
|
|
|
const nextIds = new Set() |
|
|
|
|
|
for (const asset of mapMarkers.value) { |
|
|
|
|
|
|
|
|
function devicesFeatureCollection() { |
|
|
|
|
|
return { |
|
|
|
|
|
type: 'FeatureCollection', |
|
|
|
|
|
features: mapMarkers.value |
|
|
|
|
|
.map((asset) => { |
|
|
const lon = Number(asset.longitude) |
|
|
const lon = Number(asset.longitude) |
|
|
const lat = Number(asset.latitude) |
|
|
const lat = Number(asset.latitude) |
|
|
if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue |
|
|
|
|
|
|
|
|
|
|
|
nextIds.add(asset.id) |
|
|
|
|
|
|
|
|
if (!Number.isFinite(lon) || !Number.isFinite(lat)) return null |
|
|
const selected = selectedId.value === asset.id |
|
|
const selected = selectedId.value === asset.id |
|
|
let marker = markerById.get(asset.id) |
|
|
|
|
|
if (!marker) { |
|
|
|
|
|
marker = new maptalks.Marker([lon, lat], { |
|
|
|
|
|
id: asset.id, |
|
|
|
|
|
cursor: 'pointer', |
|
|
|
|
|
properties: { assetId: asset.id }, |
|
|
|
|
|
symbol: markerSymbol(asset, selected), |
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
type: 'Feature', |
|
|
|
|
|
// asset.id 多为字符串,省略 numeric Feature.id,靠 setData 全量刷新 |
|
|
|
|
|
properties: { |
|
|
|
|
|
assetId: String(asset.id), |
|
|
|
|
|
name: asset.name || '', |
|
|
|
|
|
kind: asset.type, |
|
|
|
|
|
selected, |
|
|
|
|
|
color: statusColor(asset.statusClass), |
|
|
|
|
|
radius: asset.type === 'drone' ? (selected ? 10 : 8) : (selected ? 12 : 10), |
|
|
|
|
|
}, |
|
|
|
|
|
geometry: { type: 'Point', coordinates: [lon, lat] }, |
|
|
|
|
|
} |
|
|
}) |
|
|
}) |
|
|
marker.on('click', (e) => { |
|
|
|
|
|
e?.domEvent?.stopPropagation?.() |
|
|
|
|
|
suppressMapClick = true |
|
|
|
|
|
select(asset.id) |
|
|
|
|
|
setTimeout(() => { suppressMapClick = false }, 0) |
|
|
|
|
|
|
|
|
.filter(Boolean), |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function bindingFeatureCollection() { |
|
|
|
|
|
const ends = bindingEndpoints() |
|
|
|
|
|
if (!ends) { |
|
|
|
|
|
return { type: 'FeatureCollection', features: [] } |
|
|
|
|
|
} |
|
|
|
|
|
return { |
|
|
|
|
|
type: 'FeatureCollection', |
|
|
|
|
|
features: [ |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'Feature', |
|
|
|
|
|
properties: { label: '绑定设备' }, |
|
|
|
|
|
geometry: { type: 'LineString', coordinates: ends }, |
|
|
|
|
|
}, |
|
|
|
|
|
], |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function ensureMonitorLayers() { |
|
|
|
|
|
if (!mapInstance || !mapInstance.isStyleLoaded()) return |
|
|
|
|
|
if (!mapInstance.getSource(DEVICE_SOURCE)) { |
|
|
|
|
|
mapInstance.addSource(DEVICE_SOURCE, { type: 'geojson', data: devicesFeatureCollection() }) |
|
|
|
|
|
} |
|
|
|
|
|
if (!mapInstance.getSource(BINDING_SOURCE)) { |
|
|
|
|
|
mapInstance.addSource(BINDING_SOURCE, { type: 'geojson', data: bindingFeatureCollection() }) |
|
|
|
|
|
} |
|
|
|
|
|
if (!mapInstance.getLayer(BINDING_LINE)) { |
|
|
|
|
|
mapInstance.addLayer({ |
|
|
|
|
|
id: BINDING_LINE, |
|
|
|
|
|
type: 'line', |
|
|
|
|
|
source: BINDING_SOURCE, |
|
|
|
|
|
paint: { |
|
|
|
|
|
'line-color': 'rgba(117,191,246,0.95)', |
|
|
|
|
|
'line-width': 2, |
|
|
|
|
|
'line-dasharray': [2, 1.5], |
|
|
|
|
|
'line-opacity': 0.95, |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!mapInstance.getLayer(DEVICE_CIRCLE)) { |
|
|
|
|
|
mapInstance.addLayer({ |
|
|
|
|
|
id: DEVICE_CIRCLE, |
|
|
|
|
|
type: 'circle', |
|
|
|
|
|
source: DEVICE_SOURCE, |
|
|
|
|
|
paint: { |
|
|
|
|
|
'circle-radius': ['get', 'radius'], |
|
|
|
|
|
'circle-color': ['get', 'color'], |
|
|
|
|
|
'circle-stroke-color': '#fff', |
|
|
|
|
|
'circle-stroke-width': [ |
|
|
|
|
|
'case', |
|
|
|
|
|
['to-boolean', ['get', 'selected']], |
|
|
|
|
|
3, |
|
|
|
|
|
2, |
|
|
|
|
|
], |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!mapInstance.getLayer(DEVICE_LABEL)) { |
|
|
|
|
|
mapInstance.addLayer({ |
|
|
|
|
|
id: DEVICE_LABEL, |
|
|
|
|
|
type: 'symbol', |
|
|
|
|
|
source: DEVICE_SOURCE, |
|
|
|
|
|
layout: { |
|
|
|
|
|
'text-field': ['get', 'name'], |
|
|
|
|
|
'text-size': 11, |
|
|
|
|
|
'text-offset': [0, 1.4], |
|
|
|
|
|
'text-anchor': 'top', |
|
|
|
|
|
'text-allow-overlap': false, |
|
|
|
|
|
}, |
|
|
|
|
|
paint: { |
|
|
|
|
|
'text-color': '#fff', |
|
|
|
|
|
'text-halo-color': 'rgba(24,35,45,.86)', |
|
|
|
|
|
'text-halo-width': 1.2, |
|
|
|
|
|
}, |
|
|
}) |
|
|
}) |
|
|
marker.addTo(deviceLayer) |
|
|
|
|
|
markerById.set(asset.id, marker) |
|
|
|
|
|
} else { |
|
|
|
|
|
marker.setCoordinates([lon, lat]) |
|
|
|
|
|
marker.setSymbol(markerSymbol(asset, selected)) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
layersReady = true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (const [id, marker] of markerById) { |
|
|
|
|
|
if (!nextIds.has(id)) { |
|
|
|
|
|
marker.remove() |
|
|
|
|
|
markerById.delete(id) |
|
|
|
|
|
|
|
|
function clearMonitorLayers() { |
|
|
|
|
|
if (!mapInstance) return |
|
|
|
|
|
for (const id of [DEVICE_LABEL, DEVICE_CIRCLE, BINDING_LINE]) { |
|
|
|
|
|
if (mapInstance.getLayer(id)) mapInstance.removeLayer(id) |
|
|
} |
|
|
} |
|
|
|
|
|
for (const id of [DEVICE_SOURCE, BINDING_SOURCE]) { |
|
|
|
|
|
if (mapInstance.getSource(id)) mapInstance.removeSource(id) |
|
|
} |
|
|
} |
|
|
syncBindingLine() |
|
|
|
|
|
|
|
|
layersReady = false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function syncMarkers() { |
|
|
|
|
|
if (!mapInstance || !layersReady) return |
|
|
|
|
|
const src = mapInstance.getSource(DEVICE_SOURCE) |
|
|
|
|
|
if (src) src.setData(devicesFeatureCollection()) |
|
|
|
|
|
syncBindingLine() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
function updateMapScale() { |
|
|
function updateMapScale() { |
|
|
if (!mapInstance) return |
|
|
if (!mapInstance) return |
|
|
@ -543,29 +600,16 @@ function bindingEndpoints() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function syncBindingLine() { |
|
|
function syncBindingLine() { |
|
|
if (!bindingLayer) return |
|
|
|
|
|
const ends = bindingEndpoints() |
|
|
|
|
|
if (!ends) { |
|
|
|
|
|
if (bindingLine) { |
|
|
|
|
|
bindingLine.remove() |
|
|
|
|
|
bindingLine = null |
|
|
|
|
|
} |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
if (!bindingLine) { |
|
|
|
|
|
bindingLine = new maptalks.LineString(ends, { |
|
|
|
|
|
symbol: { |
|
|
|
|
|
lineColor: 'rgba(117,191,246,0.95)', |
|
|
|
|
|
lineWidth: 2, |
|
|
|
|
|
lineDasharray: [8, 6], |
|
|
|
|
|
lineOpacity: 0.95, |
|
|
|
|
|
}, |
|
|
|
|
|
properties: { label: '绑定设备' }, |
|
|
|
|
|
}) |
|
|
|
|
|
bindingLine.addTo(bindingLayer) |
|
|
|
|
|
} else { |
|
|
|
|
|
bindingLine.setCoordinates(ends) |
|
|
|
|
|
|
|
|
if (!mapInstance || !layersReady) return |
|
|
|
|
|
const src = mapInstance.getSource(BINDING_SOURCE) |
|
|
|
|
|
if (src) src.setData(bindingFeatureCollection()) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function rebuildAfterStyle() { |
|
|
|
|
|
layersReady = false |
|
|
|
|
|
ensureMonitorLayers() |
|
|
|
|
|
syncMarkers() |
|
|
|
|
|
updateMapScale() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setMapMode(mode) { |
|
|
function setMapMode(mode) { |
|
|
@ -573,13 +617,13 @@ function setMapMode(mode) { |
|
|
mapMode.value = mode |
|
|
mapMode.value = mode |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
const tk = mapConfig.token |
|
|
|
|
|
if (!tk) { |
|
|
|
|
|
ui.toast('缺少天地图 token,无法切换底图') |
|
|
|
|
|
|
|
|
if (!mapConfig.token) { |
|
|
|
|
|
ui.toast('缺少 Mapbox token,无法切换底图') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
mapMode.value = mode |
|
|
mapMode.value = mode |
|
|
mapInstance.setBaseLayer(createTiandituBaseLayer(maptalks, tk, mode)) |
|
|
|
|
|
|
|
|
mapInstance.setStyle(MAP_STYLES[mode] || MAP_STYLES.satellite) |
|
|
|
|
|
mapInstance.once('style.load', rebuildAfterStyle) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function fitAll() { |
|
|
function fitAll() { |
|
|
@ -597,23 +641,18 @@ function fitAllMarkers() { |
|
|
.filter(([lon, lat]) => Number.isFinite(lon) && Number.isFinite(lat)) |
|
|
.filter(([lon, lat]) => Number.isFinite(lon) && Number.isFinite(lat)) |
|
|
|
|
|
|
|
|
if (!coords.length) { |
|
|
if (!coords.length) { |
|
|
mapInstance.setCenter(mapConfig.center) |
|
|
|
|
|
mapInstance.setZoom(mapConfig.zoom) |
|
|
|
|
|
|
|
|
mapInstance.jumpTo({ center: mapConfig.center, zoom: mapConfig.zoom }) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (coords.length === 1) { |
|
|
if (coords.length === 1) { |
|
|
mapInstance.animateTo({ center: coords[0], zoom: 16 }, { duration: 450 }) |
|
|
|
|
|
|
|
|
mapInstance.easeTo({ center: coords[0], zoom: 16, duration: 450 }) |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const extent = new maptalks.Extent( |
|
|
|
|
|
Math.min(...coords.map((c) => c[0])), |
|
|
|
|
|
Math.min(...coords.map((c) => c[1])), |
|
|
|
|
|
Math.max(...coords.map((c) => c[0])), |
|
|
|
|
|
Math.max(...coords.map((c) => c[1])), |
|
|
|
|
|
|
|
|
const bounds = coords.reduce( |
|
|
|
|
|
(b, c) => b.extend(c), |
|
|
|
|
|
new mapboxgl.LngLatBounds(coords[0], coords[0]), |
|
|
) |
|
|
) |
|
|
mapInstance.fitExtent(extent, 0, { animation: true, duration: 450, padding: [60, 60, 60, 60] }) |
|
|
|
|
|
|
|
|
mapInstance.fitBounds(bounds, { padding: 60, duration: 450 }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function flyToSelected() { |
|
|
function flyToSelected() { |
|
|
@ -623,57 +662,60 @@ function flyToSelected() { |
|
|
const lon = Number(asset.longitude) |
|
|
const lon = Number(asset.longitude) |
|
|
const lat = Number(asset.latitude) |
|
|
const lat = Number(asset.latitude) |
|
|
if (!Number.isFinite(lon) || !Number.isFinite(lat)) return |
|
|
if (!Number.isFinite(lon) || !Number.isFinite(lat)) return |
|
|
mapInstance.animateTo({ center: [lon, lat], zoom: Math.max(mapInstance.getZoom(), 16) }, { duration: 450 }) |
|
|
|
|
|
|
|
|
mapInstance.easeTo({ |
|
|
|
|
|
center: [lon, lat], |
|
|
|
|
|
zoom: Math.max(mapInstance.getZoom(), 16), |
|
|
|
|
|
duration: 450, |
|
|
|
|
|
}) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function onMapBlankClick() { |
|
|
|
|
|
|
|
|
function onMapClick(e) { |
|
|
if (suppressMapClick) return |
|
|
if (suppressMapClick) return |
|
|
|
|
|
const feats = mapInstance.queryRenderedFeatures(e.point, { |
|
|
|
|
|
layers: [DEVICE_CIRCLE, DEVICE_LABEL].filter((id) => mapInstance.getLayer(id)), |
|
|
|
|
|
}) |
|
|
|
|
|
const id = feats[0]?.properties?.assetId |
|
|
|
|
|
if (id) { |
|
|
|
|
|
suppressMapClick = true |
|
|
|
|
|
select(String(id)) |
|
|
|
|
|
setTimeout(() => { suppressMapClick = false }, 0) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
clearSelection() |
|
|
clearSelection() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function setupMap() { |
|
|
async function setupMap() { |
|
|
const map = await commonRefs.getRef('map') |
|
|
const map = await commonRefs.getRef('map') |
|
|
if (!map) { |
|
|
if (!map) { |
|
|
ui.toast('地图未初始化(缺少天地图 token)') |
|
|
|
|
|
|
|
|
ui.toast('地图未初始化(缺少 Mapbox token)') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
mapInstance = map |
|
|
mapInstance = map |
|
|
mapHelper.toggleMapMode({ is2D: true }) |
|
|
mapHelper.toggleMapMode({ is2D: true }) |
|
|
|
|
|
|
|
|
const existing = map.getLayer(LAYER_ID) |
|
|
|
|
|
if (existing) existing.remove() |
|
|
|
|
|
const existingBinding = map.getLayer(BINDING_LAYER_ID) |
|
|
|
|
|
if (existingBinding) existingBinding.remove() |
|
|
|
|
|
|
|
|
|
|
|
deviceLayer = new maptalks.VectorLayer(LAYER_ID, [], { zIndex: 120, forceRenderOnMoving: true }) |
|
|
|
|
|
deviceLayer.addTo(map) |
|
|
|
|
|
bindingLayer = new maptalks.VectorLayer(BINDING_LAYER_ID, [], { zIndex: 110, forceRenderOnMoving: true }) |
|
|
|
|
|
bindingLayer.addTo(map) |
|
|
|
|
|
map.on('click', onMapBlankClick) |
|
|
|
|
|
map.on('zoomend', updateMapScale) |
|
|
|
|
|
map.on('moveend', updateMapScale) |
|
|
|
|
|
updateMapScale() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const onReady = () => { |
|
|
|
|
|
ensureMonitorLayers() |
|
|
syncMarkers() |
|
|
syncMarkers() |
|
|
if (selectedId.value) flyToSelected() |
|
|
if (selectedId.value) flyToSelected() |
|
|
else fitAllMarkers() |
|
|
else fitAllMarkers() |
|
|
|
|
|
updateMapScale() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (map.isStyleLoaded()) onReady() |
|
|
|
|
|
else map.once('load', onReady) |
|
|
|
|
|
|
|
|
|
|
|
map.on('click', onMapClick) |
|
|
|
|
|
map.on('zoom', updateMapScale) |
|
|
|
|
|
map.on('move', updateMapScale) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function teardownMap() { |
|
|
function teardownMap() { |
|
|
if (mapInstance) { |
|
|
if (mapInstance) { |
|
|
mapInstance.off('click', onMapBlankClick) |
|
|
|
|
|
mapInstance.off('zoomend', updateMapScale) |
|
|
|
|
|
mapInstance.off('moveend', updateMapScale) |
|
|
|
|
|
const layer = mapInstance.getLayer(LAYER_ID) |
|
|
|
|
|
if (layer) layer.remove() |
|
|
|
|
|
const binding = mapInstance.getLayer(BINDING_LAYER_ID) |
|
|
|
|
|
if (binding) binding.remove() |
|
|
|
|
|
} |
|
|
|
|
|
markerById.clear() |
|
|
|
|
|
bindingLine = null |
|
|
|
|
|
bindingLayer = null |
|
|
|
|
|
deviceLayer = null |
|
|
|
|
|
|
|
|
mapInstance.off('click', onMapClick) |
|
|
|
|
|
mapInstance.off('zoom', updateMapScale) |
|
|
|
|
|
mapInstance.off('move', updateMapScale) |
|
|
|
|
|
clearMonitorLayers() |
|
|
|
|
|
} |
|
|
mapInstance = null |
|
|
mapInstance = null |
|
|
commonRefs.clearPendingList('map') |
|
|
commonRefs.clearPendingList('map') |
|
|
} |
|
|
} |
|
|
@ -1104,12 +1146,11 @@ function goDetail() { |
|
|
|
|
|
|
|
|
function zoomIn() { |
|
|
function zoomIn() { |
|
|
if (!mapInstance) return |
|
|
if (!mapInstance) return |
|
|
mapInstance.setZoom(Math.min(mapInstance.getMaxZoom(), mapInstance.getZoom() + 1)) |
|
|
|
|
|
|
|
|
mapInstance.zoomTo(Math.min(mapInstance.getMaxZoom(), mapInstance.getZoom() + 1), { duration: 200 }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function zoomOut() { |
|
|
function zoomOut() { |
|
|
if (!mapInstance) return |
|
|
if (!mapInstance) return |
|
|
mapInstance.setZoom(Math.max(mapInstance.getMinZoom(), mapInstance.getZoom() - 1)) |
|
|
|
|
|
|
|
|
mapInstance.zoomTo(Math.max(mapInstance.getMinZoom(), mapInstance.getZoom() - 1), { duration: 200 }) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function runCommand(title, desc) { |
|
|
async function runCommand(title, desc) { |
|
|
|