|
|
@ -229,7 +229,6 @@ |
|
|
<script setup> |
|
|
<script setup> |
|
|
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue' |
|
|
import { computed, nextTick, 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 { useUiStore } from '@/stores/modules/uiStore' |
|
|
import { useUiStore } from '@/stores/modules/uiStore' |
|
|
import request from '@/utils/http' |
|
|
import request from '@/utils/http' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
import { useDeviceStore } from '@/stores/modules/devicesStore' |
|
|
@ -237,8 +236,11 @@ import * as urls from '@/config/urls' |
|
|
import commonRefs from '@/utils/commonRefs' |
|
|
import commonRefs from '@/utils/commonRefs' |
|
|
import mapHelper from '@/core/mapHelper' |
|
|
import mapHelper from '@/core/mapHelper' |
|
|
|
|
|
|
|
|
const ROUTE_LAYER_ID = 'tasks-route-editor' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const ROUTE_SOURCE = 'task-route' |
|
|
|
|
|
const ROUTE_LINE = 'task-route-line' |
|
|
|
|
|
const WP_SOURCE = 'task-waypoints' |
|
|
|
|
|
const WP_CIRCLE = 'task-waypoints-circle' |
|
|
|
|
|
const WP_LABEL = 'task-waypoints-label' |
|
|
|
|
|
|
|
|
const route = useRoute() |
|
|
const route = useRoute() |
|
|
const router = useRouter() |
|
|
const router = useRouter() |
|
|
@ -259,7 +261,6 @@ const routeCursor = ref('--') |
|
|
const routeMapEl = ref(null) |
|
|
const routeMapEl = ref(null) |
|
|
const routeMapReady = ref(false) |
|
|
const routeMapReady = ref(false) |
|
|
let routeMapInstance = null |
|
|
let routeMapInstance = null |
|
|
let routeLayer = null |
|
|
|
|
|
let routeMapAttached = false |
|
|
let routeMapAttached = false |
|
|
|
|
|
|
|
|
const routeForm = reactive({ name: '', altitude: 50, speed: 10, turnMode: 'auto' }) |
|
|
const routeForm = reactive({ name: '', altitude: 50, speed: 10, turnMode: 'auto' }) |
|
|
@ -466,89 +467,128 @@ function resetRouteEditor() { |
|
|
routeCursor.value = '--' |
|
|
routeCursor.value = '--' |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function syncRouteLayer() { |
|
|
|
|
|
if (!routeLayer) return |
|
|
|
|
|
routeLayer.clear() |
|
|
|
|
|
|
|
|
function clearTaskLayers(map) { |
|
|
|
|
|
if (!map) return |
|
|
|
|
|
for (const id of [WP_LABEL, WP_CIRCLE, ROUTE_LINE]) { |
|
|
|
|
|
if (map.getLayer(id)) map.removeLayer(id) |
|
|
|
|
|
} |
|
|
|
|
|
for (const id of [WP_SOURCE, ROUTE_SOURCE]) { |
|
|
|
|
|
if (map.getSource(id)) map.removeSource(id) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function ensureTaskLayers(map) { |
|
|
|
|
|
if (!map.getSource(ROUTE_SOURCE)) { |
|
|
|
|
|
map.addSource(ROUTE_SOURCE, { |
|
|
|
|
|
type: 'geojson', |
|
|
|
|
|
data: { type: 'FeatureCollection', features: [] }, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!map.getSource(WP_SOURCE)) { |
|
|
|
|
|
map.addSource(WP_SOURCE, { |
|
|
|
|
|
type: 'geojson', |
|
|
|
|
|
data: { type: 'FeatureCollection', features: [] }, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!map.getLayer(ROUTE_LINE)) { |
|
|
|
|
|
map.addLayer({ |
|
|
|
|
|
id: ROUTE_LINE, |
|
|
|
|
|
type: 'line', |
|
|
|
|
|
source: ROUTE_SOURCE, |
|
|
|
|
|
paint: { |
|
|
|
|
|
'line-color': '#2d82da', |
|
|
|
|
|
'line-width': 3, |
|
|
|
|
|
'line-opacity': 0.95, |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!map.getLayer(WP_CIRCLE)) { |
|
|
|
|
|
map.addLayer({ |
|
|
|
|
|
id: WP_CIRCLE, |
|
|
|
|
|
type: 'circle', |
|
|
|
|
|
source: WP_SOURCE, |
|
|
|
|
|
paint: { |
|
|
|
|
|
'circle-radius': 9, |
|
|
|
|
|
'circle-color': ['get', 'color'], |
|
|
|
|
|
'circle-stroke-color': '#fff', |
|
|
|
|
|
'circle-stroke-width': 2, |
|
|
|
|
|
}, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
if (!map.getLayer(WP_LABEL)) { |
|
|
|
|
|
map.addLayer({ |
|
|
|
|
|
id: WP_LABEL, |
|
|
|
|
|
type: 'symbol', |
|
|
|
|
|
source: WP_SOURCE, |
|
|
|
|
|
layout: { |
|
|
|
|
|
'text-field': ['get', 'indexLabel'], |
|
|
|
|
|
'text-size': 11, |
|
|
|
|
|
'text-allow-overlap': true, |
|
|
|
|
|
}, |
|
|
|
|
|
paint: { 'text-color': '#fff' }, |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function syncRouteLayer() { |
|
|
|
|
|
if (!routeMapInstance) return |
|
|
|
|
|
ensureTaskLayers(routeMapInstance) |
|
|
const coords = routePoints.value |
|
|
const coords = routePoints.value |
|
|
.map((p) => [Number(p.longitude), Number(p.latitude)]) |
|
|
.map((p) => [Number(p.longitude), Number(p.latitude)]) |
|
|
.filter(([lon, lat]) => Number.isFinite(lon) && Number.isFinite(lat)) |
|
|
.filter(([lon, lat]) => Number.isFinite(lon) && Number.isFinite(lat)) |
|
|
|
|
|
|
|
|
if (coords.length >= 2) { |
|
|
|
|
|
new maptalks.LineString(coords, { |
|
|
|
|
|
symbol: { |
|
|
|
|
|
lineColor: '#2d82da', |
|
|
|
|
|
lineWidth: 3, |
|
|
|
|
|
lineOpacity: 0.95, |
|
|
|
|
|
}, |
|
|
|
|
|
}).addTo(routeLayer) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
coords.forEach((c, i) => { |
|
|
|
|
|
const isStart = i === 0 |
|
|
|
|
|
const isEnd = i === coords.length - 1 && coords.length > 1 |
|
|
|
|
|
const color = isStart ? '#35a66a' : isEnd ? '#c56c28' : '#2d82da' |
|
|
|
|
|
const marker = new maptalks.Marker(c, { |
|
|
|
|
|
cursor: 'pointer', |
|
|
|
|
|
properties: { index: i }, |
|
|
|
|
|
symbol: [ |
|
|
|
|
|
{ |
|
|
|
|
|
markerType: 'ellipse', |
|
|
|
|
|
markerFill: color, |
|
|
|
|
|
markerLineColor: '#fff', |
|
|
|
|
|
markerLineWidth: 2, |
|
|
|
|
|
markerWidth: 18, |
|
|
|
|
|
markerHeight: 18, |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
textName: String(i + 1), |
|
|
|
|
|
textFill: '#fff', |
|
|
|
|
|
textSize: 11, |
|
|
|
|
|
textWeight: 'bold', |
|
|
|
|
|
textDy: 1, |
|
|
|
|
|
|
|
|
const lineData = { |
|
|
|
|
|
type: 'FeatureCollection', |
|
|
|
|
|
features: coords.length >= 2 |
|
|
|
|
|
? [{ type: 'Feature', geometry: { type: 'LineString', coordinates: coords }, properties: {} }] |
|
|
|
|
|
: [], |
|
|
|
|
|
} |
|
|
|
|
|
const wpData = { |
|
|
|
|
|
type: 'FeatureCollection', |
|
|
|
|
|
features: coords.map((c, i) => { |
|
|
|
|
|
const isStart = i === 0 |
|
|
|
|
|
const isEnd = i === coords.length - 1 && coords.length > 1 |
|
|
|
|
|
return { |
|
|
|
|
|
type: 'Feature', |
|
|
|
|
|
properties: { |
|
|
|
|
|
index: i, |
|
|
|
|
|
indexLabel: String(i + 1), |
|
|
|
|
|
color: isStart ? '#35a66a' : isEnd ? '#c56c28' : '#2d82da', |
|
|
}, |
|
|
}, |
|
|
], |
|
|
|
|
|
}) |
|
|
|
|
|
marker.on('click', (e) => { |
|
|
|
|
|
if (e?.domEvent) { |
|
|
|
|
|
e.domEvent.stopPropagation?.() |
|
|
|
|
|
e.domEvent.preventDefault?.() |
|
|
|
|
|
|
|
|
geometry: { type: 'Point', coordinates: c }, |
|
|
} |
|
|
} |
|
|
openWaypointEditor(i) |
|
|
|
|
|
}) |
|
|
|
|
|
marker.addTo(routeLayer) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
}), |
|
|
|
|
|
} |
|
|
|
|
|
routeMapInstance.getSource(ROUTE_SOURCE)?.setData(lineData) |
|
|
|
|
|
routeMapInstance.getSource(WP_SOURCE)?.setData(wpData) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function onRouteMapClick(e) { |
|
|
function onRouteMapClick(e) { |
|
|
if (!routeMapAttached) return |
|
|
|
|
|
// 点击已有航点时由 marker 处理 |
|
|
|
|
|
if (e?.target && e.target !== routeMapInstance) return |
|
|
|
|
|
|
|
|
if (!routeMapAttached || !routeMapInstance) return |
|
|
|
|
|
const hit = routeMapInstance.queryRenderedFeatures(e.point, { |
|
|
|
|
|
layers: [WP_CIRCLE, WP_LABEL].filter((id) => routeMapInstance.getLayer(id)), |
|
|
|
|
|
}) |
|
|
|
|
|
if (hit[0]?.properties?.index != null) { |
|
|
|
|
|
openWaypointEditor(Number(hit[0].properties.index)) |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
if (!validRouteDefaults()) return |
|
|
if (!validRouteDefaults()) return |
|
|
|
|
|
|
|
|
const coord = e?.coordinate |
|
|
|
|
|
if (!coord) return |
|
|
|
|
|
const longitude = Number(coord.x) |
|
|
|
|
|
const latitude = Number(coord.y) |
|
|
|
|
|
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { lng, lat } = e.lngLat |
|
|
routePoints.value.push({ |
|
|
routePoints.value.push({ |
|
|
longitude, |
|
|
|
|
|
latitude, |
|
|
|
|
|
|
|
|
longitude: lng, |
|
|
|
|
|
latitude: lat, |
|
|
altitude: Number(routeForm.altitude), |
|
|
altitude: Number(routeForm.altitude), |
|
|
speed: Number(routeForm.speed), |
|
|
speed: Number(routeForm.speed), |
|
|
turnMode: routeForm.turnMode, |
|
|
turnMode: routeForm.turnMode, |
|
|
}) |
|
|
}) |
|
|
routeCursor.value = formatCoord(longitude, latitude) |
|
|
|
|
|
|
|
|
routeCursor.value = formatCoord(lng, lat) |
|
|
routeFormError.value = '' |
|
|
routeFormError.value = '' |
|
|
syncRouteLayer() |
|
|
syncRouteLayer() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function onRouteMapMove(e) { |
|
|
function onRouteMapMove(e) { |
|
|
if (!routeMapAttached) return |
|
|
if (!routeMapAttached) return |
|
|
const coord = e?.coordinate |
|
|
|
|
|
if (!coord) return |
|
|
|
|
|
routeCursor.value = formatCoord(Number(coord.x), Number(coord.y)) |
|
|
|
|
|
|
|
|
routeCursor.value = formatCoord(e.lngLat.lng, e.lngLat.lat) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function attachRouteMap() { |
|
|
async function attachRouteMap() { |
|
|
@ -559,7 +599,7 @@ async function attachRouteMap() { |
|
|
const map = await commonRefs.getRef('map') |
|
|
const map = await commonRefs.getRef('map') |
|
|
if (!map) { |
|
|
if (!map) { |
|
|
routeMapReady.value = false |
|
|
routeMapReady.value = false |
|
|
ui.toast('地图未初始化(缺少天地图 token)') |
|
|
|
|
|
|
|
|
ui.toast('地图未初始化(缺少 Mapbox token)') |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -569,20 +609,17 @@ async function attachRouteMap() { |
|
|
routeMapAttached = true |
|
|
routeMapAttached = true |
|
|
routeMapReady.value = true |
|
|
routeMapReady.value = true |
|
|
|
|
|
|
|
|
const existing = map.getLayer(ROUTE_LAYER_ID) |
|
|
|
|
|
if (existing) existing.remove() |
|
|
|
|
|
routeLayer = new maptalks.VectorLayer(ROUTE_LAYER_ID, [], { zIndex: 140, forceRenderOnMoving: true }) |
|
|
|
|
|
routeLayer.addTo(map) |
|
|
|
|
|
|
|
|
clearTaskLayers(map) |
|
|
|
|
|
ensureTaskLayers(map) |
|
|
|
|
|
|
|
|
map.on('click', onRouteMapClick) |
|
|
map.on('click', onRouteMapClick) |
|
|
map.on('mousemove', onRouteMapMove) |
|
|
map.on('mousemove', onRouteMapMove) |
|
|
|
|
|
|
|
|
const center = map.getCenter() |
|
|
|
|
|
if (center) routeCursor.value = formatCoord(center.x, center.y) |
|
|
|
|
|
|
|
|
const c = map.getCenter() |
|
|
|
|
|
routeCursor.value = formatCoord(c.lng, c.lat) |
|
|
|
|
|
|
|
|
// 容器迁移后尺寸刷新 |
|
|
|
|
|
requestAnimationFrame(() => { |
|
|
requestAnimationFrame(() => { |
|
|
map.checkSize?.() |
|
|
|
|
|
|
|
|
map.resize() |
|
|
syncRouteLayer() |
|
|
syncRouteLayer() |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
@ -591,16 +628,12 @@ function teardownRouteMap() { |
|
|
if (routeMapInstance) { |
|
|
if (routeMapInstance) { |
|
|
routeMapInstance.off('click', onRouteMapClick) |
|
|
routeMapInstance.off('click', onRouteMapClick) |
|
|
routeMapInstance.off('mousemove', onRouteMapMove) |
|
|
routeMapInstance.off('mousemove', onRouteMapMove) |
|
|
const layer = routeMapInstance.getLayer(ROUTE_LAYER_ID) |
|
|
|
|
|
if (layer) layer.remove() |
|
|
|
|
|
|
|
|
clearTaskLayers(routeMapInstance) |
|
|
} |
|
|
} |
|
|
routeLayer = null |
|
|
|
|
|
|
|
|
|
|
|
if (routeMapAttached) { |
|
|
if (routeMapAttached) { |
|
|
mapHelper.restoreMapContainer() |
|
|
mapHelper.restoreMapContainer() |
|
|
routeMapAttached = false |
|
|
routeMapAttached = false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
routeMapInstance = null |
|
|
routeMapInstance = null |
|
|
routeMapReady.value = false |
|
|
routeMapReady.value = false |
|
|
} |
|
|
} |
|
|
|