import React, { useEffect, useRef, useState, useMemo, useImperativeHandle, forwardRef, useCallback } from 'react'; import carImg from '../assets/images/carplane.png'; import inspectImg from "../assets/images/inspectCar.png" import planeImg from '../assets/images/planeicon.png'; import cameraImg from '../assets/images/camera.png'; import droneInspectImg from '../assets/images/xunjiancar.png'; import gecocar from '../assets/images/gecaocar.png'; import arrowImg from '../assets/images/arrow.png'; import iotImg from '../assets/images/iot.png'; import envConfig from '../../env'; import { TracePoint, TPAction, TPMode } from '../lib/TracePoint/tracePoint'; import { useTranslation } from 'react-i18next'; const CYAN_ALARM_COLOR = new window.Cesium.Color( 34 / 255, 211 / 255, 238 / 255, 1 ); const CYAN_GLOW_COLOR = new window.Cesium.Color( 34 / 255, 211 / 255, 238 / 255, 1 ); window.Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmNjk0NjI1MC0wZGYyLTQ1YzItYjJmZi0wY2M5ODEwODllYzIiLCJpZCI6Mzk3MTQxLCJpYXQiOjE3NzI1MjkwMjV9.U6qmakhDgXJVXwLFyWzSxAUOFZRrDkIsRUPP6CCdnKg"; const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine, finishPoint, drawRealTime, realtimePosition, realtimeType, pathPoints = [], devices = [], onCameraClick, focusLocation, points = [], markedPoints = [], onPathChange, from, onClearDraw, onMarkPoint, routeMode = '', polyPoints, mapLayer }, ref) => { const containerRef = useRef(null); const viewerRef = useRef(null); const tooltipRef = useRef(null); const hasFlownRef = useRef(false); const eventHandlerRef = useRef(null); const entityMapRef = useRef(new Map()); const alarmMapRef = useRef(new Map()); const iotMapRef = useRef(new Map()); const planLineRef = useRef(null); // points 规划航线 const polygonRef = useRef(null); // coverage 区域多边形 const navLineRef = useRef(null); // 完成点实时轨迹 const markedPointsRef = useRef([]); // 标记点实体 const { t } = useTranslation(); const realtimeEntityRef = useRef(null); const realtimeLineRef = useRef(null); const realtimeTrackRef = useRef([]); const tracePointRef = useRef(new TracePoint()); const lastFinishPointRef = useRef(null); const isNavigationModeRef = useRef(false); const followCarEntityRef = useRef(null); // 跟随小车的实例 const isCameraFlyingRef = useRef(false); const lastFlightPromiseRef = useRef(null); const [drawPoints, setDrawPoints] = useState([]); const drawLineRef = useRef(null); const pointEntitiesRef = useRef([]); const movingPathRef = useRef([]); const movingIndexRef = useRef(0); const isMovingRef = useRef(false); const moveSpeedRef = useRef(0.02); const movingEntityRef = useRef(null); const realtimeCartesianRef = useRef([]); // 直接存 Cartesian3(性能最高) const realtimeLineEntityRef = useRef(null); const realtimeCarEntityRef = useRef(null); const realtimeLastPointRef = useRef(null); const realtimeHiddenRef = useRef(false); const focusEntityRef = useRef(null); const yellowCircleCanvas = useMemo( () => createYellowCircleCanvas(30), [] ); const glowCircleCanvas = useMemo( () => createGlowCircleCanvas(45), [] ); const glowBlinkColor = useMemo( () => createGlowBlinkColor({ minAlpha: 0.25, maxAlpha: 0.75, speed: 1.1 }), [] ); const clearDrawingPath = () => { const viewer = viewerRef.current; if (!viewer) return; pointEntitiesRef.current.forEach(p => { viewer.entities.removeById(p); }); pointEntitiesRef.current = []; if (drawLineRef.current) { viewer.entities.remove(drawLineRef.current); drawLineRef.current = null; } setDrawPoints([]); onPathChange && onPathChange([]); }; const updateDrawPath = (newPoints) => { const viewer = viewerRef.current; if (!viewer) return; pointEntitiesRef.current.forEach(id => { viewer.entities.removeById(id); }); pointEntitiesRef.current = []; newPoints.forEach((p, i) => { const id = `draw-point-${i}`; viewer.entities.add({ id, position: window.Cesium.Cartesian3.fromDegrees(p.lon, p.lat), point: { color: window.Cesium?.Color.SKYBLUE, pixelSize: 10, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, label: { text: `${i + 1}`, font: '12px sans-serif', pixelOffset: new window.Cesium.Cartesian2(0, -15), fillColor: window.Cesium?.Color.WHITE } }); pointEntitiesRef.current.push(id); }); if (drawLineRef.current) { viewer.entities.remove(drawLineRef.current); } if (newPoints.length >= 2) { const positions = newPoints.map(p => { return window.Cesium.Cartesian3.fromDegrees(p.lon, p.lat); }); drawLineRef.current = viewer.entities.add({ polyline: { positions: positions, width: 4, material: window.Cesium?.Color.SKYBLUE, clampToGround: true, } }); } console.log(newPoints, "newPoints") onPathChange && onPathChange(newPoints); }; const calculateDirection = (point1, point2) => { const lon1 = point1.lon; const lat1 = point1.lat; const lon2 = point2.lon; const lat2 = point2.lat; const dLon = lon2 - lon1; const dLat = lat2 - lat1; const angle = Math.atan2(dLat, dLon); return angle; }; const drawTrackLine = (points) => { const viewer = viewerRef.current; if (!viewer) return; const Cesium = window.Cesium; // ====================== // 是否实时模式 // ====================== const isRealtimeMode = !!( realtimePosition?.lon && realtimePosition?.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)) ); if (planLineRef.current) { viewer.entities.remove(planLineRef.current); } // 2. 传入的 points 本身为空 → 清空轨迹退出 if (!points || points.length === 0) { const viewer = viewerRef.current; if (viewer) { try { if (planLineRef.current) { viewer.entities.remove(planLineRef.current); planLineRef.current = null; } } catch (e) { } viewer.scene.requestRender(); // 强制刷新! } return; } const validPoints = points?.filter(p => { const lon = Number(p.lng); const lat = Number(p.lat); return ( !isNaN(lon) && !isNaN(lat) && Math.abs(lon) < 180 && Math.abs(lat) < 90 && lon !== 0 && lat !== 0 ); }); if (validPoints.length === 0) return; const positions = validPoints.map(point => Cesium.Cartesian3.fromDegrees( Number(point.lng), Number(point.lat) ) ); const trackLine = viewer.entities.add({ polyline: { positions: positions, width: 2, // 规划航线用橙色,和实时轨迹区分 material: Cesium.Color.ORANGE.withAlpha(0.8), clampToGround: true, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, }); planLineRef.current = trackLine; // ====================== // 🚀 非实时模式 -> 自动飞到轨迹 // ====================== if (!isRealtimeMode) { let minLon = 180, maxLon = -180; let minLat = 90, maxLat = -90; validPoints.forEach(p => { minLon = Math.min(minLon, p.lng); maxLon = Math.max(maxLon, p.lng); minLat = Math.min(minLat, p.lat); maxLat = Math.max(maxLat, p.lat); }); if (getCameraMode() !== "track") return; // 单个点 if (validPoints.length === 1) { const p = validPoints[0]; viewer.scene.requestRender(); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees( p.lng, p.lat, 800 ), duration: 1.2 }); viewer.scene.requestRender(); } else { const rectangle = Cesium.Rectangle.fromDegrees( minLon, minLat, maxLon, maxLat ); viewer.scene.requestRender(); viewer.camera.flyTo({ destination: rectangle, duration: 1.2 }); viewer.scene.requestRender(); } } }; const drawPolygon = (points) => { const viewer = viewerRef.current; if (!viewer) return; const isRealtimeMode = !!( realtimePosition?.lon && realtimePosition?.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)) ); const Cesium = window.Cesium; // 每次绘制区域前,先移除上一次绘制的地块 if (polygonRef.current) { try { viewer.entities.remove(polygonRef.current); polygonRef.current = null; } catch (e) { } } if (planLineRef.current) { try { viewer.entities.remove(planLineRef.current); planLineRef.current = null; } catch (e) { } } const validPoints = points?.filter(p => { const lon = Number(p.lng); const lat = Number(p.lat); return ( !isNaN(lon) && !isNaN(lat) && Math.abs(lon) < 180 && Math.abs(lat) < 90 && lon !== 0 && lat !== 0 ); }); if (validPoints.length === 0) return; const positions = validPoints.map(point => Cesium.Cartesian3.fromDegrees( Number(point.lng), Number(point.lat) ) ); // coverage(区域覆盖)模式下以多边形 polygon 框绘制,否则为折线 const isCoverage = routeMode === 'coverage'; let trackLine; if (isCoverage) { trackLine = viewer.entities.add({ polygon: { hierarchy: positions, material: Cesium.Color.ORANGE.withAlpha(0.2), outline: true, outlineColor: Cesium.Color.ORANGE.withAlpha(0.8), height: 0, clampToGround: true, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, }); } else { trackLine = viewer.entities.add({ polyline: { positions: positions, width: 2, material: Cesium.Color.ORANGE.withAlpha(0.8), clampToGround: true, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, }); } polygonRef.current = trackLine; // ====================== // 🚀 非实时模式 -> 自动飞到轨迹 // ====================== if (!isRealtimeMode) { let minLon = 180, maxLon = -180; let minLat = 90, maxLat = -90; validPoints.forEach(p => { minLon = Math.min(minLon, p.lng); maxLon = Math.max(maxLon, p.lng); minLat = Math.min(minLat, p.lat); maxLat = Math.max(maxLat, p.lat); }); if (getCameraMode() !== "track") return; // 单个点 if (validPoints.length === 1) { const p = validPoints[0]; viewer.scene.requestRender(); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees( p.lng, p.lat, 800 ), duration: 1.2 }); viewer.scene.requestRender(); } else { const rectangle = Cesium.Rectangle.fromDegrees( minLon, minLat, maxLon, maxLat ); viewer.scene.requestRender(); viewer.camera.flyTo({ destination: rectangle, duration: 1.2 }); viewer.scene.requestRender(); } } }; useEffect(() => { if (polyPoints && polyPoints.length > 0) { drawPolygon(polyPoints) } else { // polyPoints 被清空时,移除已绘制的区域 const viewer = viewerRef.current; if (viewer && polygonRef.current) { try { viewer.entities.remove(polygonRef.current); polygonRef.current = null; } catch (e) { } } } }, [polyPoints]) useEffect(() => { if (viewerRef.current) { drawTrackLine(points); } }, [points]); // 渲染绿色标记点 const drawMarkedPoints = (points) => { const viewer = viewerRef.current; if (!viewer) return; // 先清空之前的标记点 markedPointsRef.current.forEach(id => { viewer.entities.removeById(id); }); markedPointsRef.current = []; if (!points || points.length === 0) return; points.forEach((p, i) => { const id = `marked-point-${i}`; viewer.entities.add({ id, position: window.Cesium.Cartesian3.fromDegrees(p.lon, p.lat), point: { color: window.Cesium.Color.GREEN, pixelSize: 15, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, label: { text: `${i + 1}`, font: '14px sans-serif', pixelOffset: new window.Cesium.Cartesian2(0, -20), fillColor: window.Cesium.Color.WHITE, outlineColor: window.Cesium.Color.BLACK, outlineWidth: 2, style: window.Cesium.LabelStyle.FILL_AND_OUTLINE, } }); markedPointsRef.current.push(id); }); }; useEffect(() => { if (viewerRef.current) { drawMarkedPoints(markedPoints); } }, [markedPoints]); function showRobotTooltip(tooltip, robot, position, t) { const statusMap = { 1: { text: t('common.working'), color: "#10b981" }, 2: { text: t('devices.idle'), color: "#f59e0b" }, 3: { text: t('devices.offline'), color: "#f87171" } }; let status = statusMap[robot.feStatus] || statusMap[1]; tooltip.style.display = "block"; tooltip.style.left = position.x + 12 + "px"; tooltip.style.top = position.y + 12 + "px"; let deviceValuesHTML = ''; if (robot.devicevalue && robot.devicevalue.length > 0) { deviceValuesHTML = robot.devicevalue.map((device) => `
${device.name}: ${device.value}${device.unit}
`).join(''); } tooltip.innerHTML = `
🤖 ${robot.deviceAlias || robot.serialNumber || robot.name}
${deviceValuesHTML ? `
${t('devices.deviceData')}:${deviceValuesHTML}
` : ''}
${t('systemSetting.longitude')}:${Number(robot.lastRunningStatus?.longitude).toFixed(6)}
${t('systemSetting.latitude')}:${Number(robot.lastRunningStatus?.latitude).toFixed(6)}
`; } function fetchPublishedLayers() { const baseUrl = envConfig.geoserver_url; //const baseUrl = 'https://gis.caasrobot.com/geoserver'; const getCapabilitiesUrl = `${baseUrl}/wms?service=WMS&version=1.1.1&request=GetCapabilities`; fetch(getCapabilitiesUrl) .then(response => response.text()) .then(xmlText => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); const layerElements = xmlDoc.getElementsByTagName('Layer'); const uniqueLayerNames = new Set(); const publishedLayers = []; for (let i = 0; i < layerElements.length; i++) { const layer = layerElements[i]; const nameElement = layer.getElementsByTagName('Name')[0]; const titleElement = layer.getElementsByTagName('Title')[0]; if (nameElement && nameElement.textContent) { const layerName = nameElement.textContent; if (layerName.includes(':') && !uniqueLayerNames.has(layerName)) { uniqueLayerNames.add(layerName); publishedLayers.push({ name: layerName, title: titleElement ? titleElement.textContent : layerName }); } } } displayPublishedLayers(publishedLayers, baseUrl); }) .catch(error => console.error('获取图层失败:', error)); } function displayPublishedLayers(publishedLayers, baseUrl) { //console.log('已发布的图层:', publishedLayers) publishedLayers.forEach((layer, index) => { const [workspace, layerName] = layer.name.split(':'); if (workspace && layerName) { addLayerFromList(layer.name, baseUrl, index); } }); } function addLayerFromList(layerFullName, baseUrl, index) { const [workspace, layerName] = layerFullName.split(':'); if (!workspace || !layerName) return; const wmsUrl = `${baseUrl}/wms`; addWmsLayerToCesium(wmsUrl, workspace, layerName, index); } function getLayerBoundsAndFit(wmsUrl, layerName) { console.log('=== 获取图层范围开始 ==='); const getCapabilitiesUrl = `${wmsUrl}?service=WMS&version=1.1.1&request=GetCapabilities`; fetch(getCapabilitiesUrl) .then(response => response.text()) .then(xmlText => { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); const layerElements = xmlDoc.getElementsByTagName('Layer'); let layerBbox = null; for (let i = 0; i < layerElements.length; i++) { const layer = layerElements[i]; const nameElement = layer.getElementsByTagName('Name')[0]; if (nameElement && nameElement.textContent === layerName) { const bboxElement = layer.getElementsByTagName('BoundingBox')[0]; if (bboxElement) { const minx = parseFloat(bboxElement.getAttribute('minx')); const miny = parseFloat(bboxElement.getAttribute('miny')); const maxx = parseFloat(bboxElement.getAttribute('maxx')); const maxy = parseFloat(bboxElement.getAttribute('maxy')); layerBbox = [minx, miny, maxx, maxy]; console.log('找到图层范围:', layerBbox); break; } } } if (layerBbox) { try { const bounds = new AMap.Bounds( new AMap.LngLat(layerBbox[0], layerBbox[1]), new AMap.LngLat(layerBbox[2], layerBbox[3]) ); map.setFitView(bounds, 15, [50, 50, 50, 50]); console.log('地图已定位到图层范围'); } catch (error) { console.error('地图定位失败:', error); } } else { console.warn('未找到图层范围,使用默认位置'); map.setCenter([120.565, 31.453]); map.setZoom(10); } }) .catch(error => { console.error('获取图层范围失败:', error); map.setCenter([120.565, 31.453]); map.setZoom(10); }); } const getCameraMode = () => { const hasRealtime = realtimePosition?.lon && realtimePosition?.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)); const hasTrack = (points && points.length > 0) || polyPoints?.length > 0; if (hasRealtime) return "realtime"; if (hasTrack) return "track"; return "devices"; }; function addWmsLayerToCesium(wmsUrl, workspace, layerName, index) { const layerIdentifier = `${workspace}:${layerName}`; let baseUrl = wmsUrl; const cesiumLayers = []; //if (index == 2 && from == "other") { // //flyToLayer(layerIdentifier, baseUrl, 100); //} //if (index == 0 && from == "other") { // flyToLayer(layerIdentifier, baseUrl, 100); //} //getLayerBoundsAndFit(wmsUrl, layerName); if (baseUrl.includes('/gwc/service/wms')) { baseUrl = baseUrl.replace('/gwc/service/wms', ''); } else if (baseUrl.includes('/gwc/service/wmts')) { baseUrl = baseUrl.replace('/gwc/service/wmts', ''); } else if (baseUrl.includes('/wms?')) { baseUrl = baseUrl.split('/wms?')[0]; } else if (baseUrl.includes('/wms')) { baseUrl = baseUrl.split('/wms')[0]; } try { const imageryProvider = new window.Cesium.WebMapServiceImageryProvider({ url: `${baseUrl}/gwc/service/wms`, layers: layerIdentifier, parameters: { service: 'WMS', version: '1.1.1', request: 'GetMap', transparent: true, format: 'image/png', srs: 'EPSG:4326', width: 256, height: 256, styles: '' }, tilingScheme: new window.Cesium.GeographicTilingScheme(), tileWidth: 256, tileHeight: 256, maximumLevel: 21, minimumLevel: 1, credit: new window.Cesium.Credit('GeoServer GWC WMS'), usePreCachedTilesIfAvailable: true, enablePickFeatures: false, maximumRequests: 15, retryAttempts: 2 }); const layer = viewerRef.current.imageryLayers.addImageryProvider(imageryProvider); cesiumLayers.push(layer); } catch (gwcWmsError) { try { const imageryProvider = new window.Cesium.WebMapServiceImageryProvider({ url: `${baseUrl}/${workspace}/wms`, layers: layerIdentifier, parameters: { service: 'WMS', version: '1.1.1', request: 'GetMap', transparent: true, format: 'image/png', srs: 'EPSG:3857', width: 256, height: 256, styles: '' }, tilingScheme: new window.Cesium.WebMercatorTilingScheme(), tileWidth: 256, tileHeight: 256, maximumLevel: 18, minimumLevel: 5, credit: new window.Cesium.Credit('GeoServer WMS'), usePreCachedTilesIfAvailable: true, enablePickFeatures: false, maximumRequests: 10, retryAttempts: 2 }); const layer = viewerRef.current.imageryLayers.addImageryProvider(imageryProvider); cesiumLayers.push(layer); } catch (wmsError) { const imageryProvider = new window.Cesium.WebMapServiceImageryProvider({ url: `${baseUrl}/gwc/service/wms`, layers: layerIdentifier, parameters: { service: 'WMS', version: '1.1.1', request: 'GetMap', transparent: true, format: 'image/png', srs: 'EPSG:4326', width: 256, height: 256, styles: '' }, tilingScheme: new window.Cesium.GeographicTilingScheme(), tileWidth: 256, tileHeight: 256, maximumLevel: 21, minimumLevel: 1, credit: new window.Cesium.Credit('GeoServer GWC WMS'), usePreCachedTilesIfAvailable: true, enablePickFeatures: false, maximumRequests: 15, retryAttempts: 2 }); const layer = viewerRef.current.imageryLayers.addImageryProvider(imageryProvider); cesiumLayers.push(layer); } } } function flyToLayer(layerIdentifier, baseUrl, flyHeight = 100) { //console.log('开始获取图层范围', layerIdentifier); const url = `${baseUrl}/wms?service=WMS&version=1.1.1&request=GetCapabilities`; fetch(url) .then(res => res.text()) .then(xmlText => { const xml = new DOMParser().parseFromString(xmlText, 'text/xml'); const layerElements = xml.getElementsByTagName('Layer'); let targetBbox = null; for (let i = 0; i < layerElements.length; i++) { const layer = layerElements[i]; const nameElement = layer.getElementsByTagName('Name')[0]; if (nameElement && nameElement.textContent === layerIdentifier) { const bboxElement = layer.getElementsByTagName('BoundingBox')[0]; if (bboxElement) { const minx = parseFloat(bboxElement.getAttribute('minx')); const miny = parseFloat(bboxElement.getAttribute('miny')); const maxx = parseFloat(bboxElement.getAttribute('maxx')); const maxy = parseFloat(bboxElement.getAttribute('maxy')); targetBbox = [minx, miny, maxx, maxy]; console.log('读取到图层bbox', targetBbox); } break; } } if (!targetBbox) { console.warn('该图层未找到BoundingBox,无法飞行', layerIdentifier); return; } const [minx, miny, maxx, maxy] = targetBbox; // 取图层中心点 const centerLon = (minx + maxx) / 2; const centerLat = (miny + maxy) / 2; viewerRef.current.camera.flyTo({ // 固定高度,第三个参数就是高度(单位米) destination: window.Cesium.Cartesian3.fromDegrees(centerLon, centerLat, flyHeight), orientation: { heading: 0, pitch: window.Cesium.Math.toRadians(-90), // 垂直俯视 roll: 0 }, duration: 2 }); }) .catch(err => { console.error('获取WMS Capabilities失败', err); }); } function getRobotEntityId(robot, index) { if (robot.id != null) return `robot-${robot.id}`; if (robot.name) return `robot-${robot.name}`; return `robot-index-${index}`; } function createGlowCircleCanvas(size = 40, innerAlpha = 0.8) { const canvas = document.createElement("canvas"); canvas.width = Math.max(1, Number(size) || 40); canvas.height = Math.max(1, Number(size) || 40); const ctx = canvas.getContext("2d"); const r = Math.max(1, Number(size) || 40) / 2; const gradient = ctx.createRadialGradient(r, r, r * 0.2, r, r, r); gradient.addColorStop(0, `rgba(255,255,255,${innerAlpha})`); gradient.addColorStop(0.4, `rgba(255,255,255,0.6)`); gradient.addColorStop(0.7, `rgba(255,255,255,0.3)`); gradient.addColorStop(1, `rgba(255,255,25,0)`); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(r, r, r, 0, Math.PI * 2); ctx.fill(); return canvas; } function createGlowBlinkColor({ minAlpha = 0.2, maxAlpha = 0.9, speed = 1.2 } = {}) { const start = Date.now(); return new window.Cesium.CallbackProperty(() => { const t = (Date.now() - start) / 1000; const alpha = minAlpha + (maxAlpha - minAlpha) * (0.5 + 0.5 * Math.sin(t * speed * Math.PI * 2)); return CYAN_GLOW_COLOR.withAlpha(alpha); }, false); } function createRobotEntity(viewer, robot, index) { const raw = robot?.lastRunningStatus || {}; const parsed = parseLngLat(raw.longitude, raw.latitude); if (!parsed) return null; const { longitude, latitude } = parsed; const baseId = getRobotEntityId(robot, index); viewer.entities.removeById(baseId); viewer.entities.removeById(baseId + "_alarm"); const position = window.Cesium.Cartesian3.fromDegrees(longitude, latitude, 0); viewer.entities.add({ id: baseId + "_alarm", position, billboard: { image: createGlowCircleCanvas(45), width: 45, height: 45, verticalOrigin: window.Cesium.VerticalOrigin.BOTTOM, pixelOffset: new window.Cesium.Cartesian2(0, -6), color: createGlowBlinkColor({ minAlpha: 0.25, maxAlpha: 0.75, speed: 1.1 }), disableDepthTestDistance: Number.POSITIVE_INFINITY } }); const entity = viewer.entities.add({ id: baseId, position, billboard: { image: carImg, width: 30, height: 30, verticalOrigin: window.Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY }, label: { text: robot.name, font: "14px sans-serif", pixelOffset: new window.Cesium.Cartesian2(0, -40), fillColor: window.Cesium?.Color.WHITE, showBackground: true, backgroundColor: window.Cesium?.Color.BLACK.withAlpha(0.6) } }); entity.robotData = robot; return entity; } useEffect(() => { if (!containerRef.current) return; if (viewerRef.current) return; const viewer = new window.Cesium.Viewer(containerRef.current, { animation: false, timeline: false, baseLayerPicker: true, geocoder: false, homeButton: false, sceneModePicker: false, navigationHelpButton: false, fullscreenButton: false, infoBox: false, imageryProvider: new window.Cesium.UrlTemplateImageryProvider({ url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", fileExtension: "jpg" }) }); viewerRef.current = viewer; viewer.scene.globe.baseColor = window.Cesium?.Color.BLACK; viewer.scene.globe.depthTestAgainstTerrain = false; viewer.scene.requestRenderMode = true; // 👈 加这个(只在需要时渲染) viewer.scene.globe.enableLighting = false; //viewer.camera.flyTo({ // // 中国中心大致坐标 + 合适高度(200万米) // destination: window.Cesium.Cartesian3.fromDegrees(103.84, 31.15, 2000000), // orientation: { // heading: window.Cesium.Math.toRadians(0), // pitch: window.Cesium.Math.toRadians(-90), // roll: 0 // }, // duration: 0 // 初始化直接到位,无需动画 //}); try { fetchPublishedLayers() } catch (e) { } return () => { if (lastFlightPromiseRef.current) viewer.camera.cancelFlight(); }; }, []); // 根据场站映射图层定位地图 useEffect(() => { if (!mapLayer || !viewerRef.current) return; flyToLayer(mapLayer, envConfig.geoserver_url, 100); }, [mapLayer]); useEffect(() => { // 如果有 realtimePosition,不显示 focusEntity const hasRealtime = realtimePosition?.lon && realtimePosition?.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)); if (!viewerRef.current || !focusLocation || hasRealtime) { if (focusEntityRef.current) { viewerRef.current?.entities.remove(focusEntityRef.current); focusEntityRef.current = null; } return; } const viewer = viewerRef.current; const Cesium = window.Cesium; // 选择图片 let image = carImg; if (focusLocation.type === "plane" || focusLocation.type === "drone") { image = planeImg; } else if (focusLocation.type === "robot") { image = droneInspectImg; } else if (focusLocation.type === "mower") { image = carImg; } // 如果已经有 focusEntity,先移除 if (focusEntityRef.current) { viewer.entities.remove(focusEntityRef.current); } // 添加新的 focusEntity focusEntityRef.current = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(Number(focusLocation.lon), Number(focusLocation.lat), 0), billboard: { image: image, width: 30, height: 30, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, label: { text: focusLocation.name, font: "12px sans-serif", pixelOffset: new Cesium.Cartesian2(0, -80), fillColor: Cesium.Color.YELLOW, showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.6) } }); viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(Number(focusLocation.lon), Number(focusLocation.lat), focusLocation.height || 800), orientation: { heading: Cesium.Math.toRadians(0.0), pitch: Cesium.Math.toRadians(-90.0), roll: 0.0, }, duration: 1.5 }); }, [focusLocation]); function flyToRobots(viewer, robots) { if (!robots || robots.length === 0) return; let west = 180, south = 90, east = -180, north = -90; robots.forEach(r => { const lon = Number(r?.lastRunningStatus?.longitude); const lat = Number(r?.lastRunningStatus?.latitude); west = Math.min(west, lon); south = Math.min(south, lat); east = Math.max(east, lon); north = Math.max(north, lat); }); const rectangle = window.Cesium.Rectangle.fromDegrees(west, south, east, north); viewer.camera.flyTo({ destination: rectangle, duration: 1.5 }); } function parseLngLat(lon, lat) { const longitude = Number(lon); const latitude = Number(lat); if (Number.isFinite(longitude) && Number.isFinite(latitude)) { return { longitude, latitude }; } return null; } function createYellowCircleDataUrl(size = 30) { const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, size, size); ctx.beginPath(); ctx.arc(size / 2, size / 2, size / 2 - 1, 0, Math.PI * 2); ctx.fillStyle = 'rgba(255, 215, 0, 1)'; ctx.fill(); return canvas.toDataURL(); } const yellowCircleImg = useMemo(() => createYellowCircleDataUrl(30), []); // 监听 drawRealTime 变化,每次开始执行任务时重置为定位模式 useEffect(() => { if (drawRealTime) { // 开始执行任务时:重置为定位模式,清空之前的轨迹数据 tracePointRef.current.setMode(TPMode.LOCATION); isNavigationModeRef.current = false; lastFinishPointRef.current = null; // 清空导航相关的实体 const viewer = viewerRef.current; if (viewer) { if (navLineRef.current) { viewer.entities.remove(navLineRef.current); navLineRef.current = null; } if (followCarEntityRef.current) { viewer.entities.remove(followCarEntityRef.current); followCarEntityRef.current = null; } viewer.scene.requestRender(); } } else { tracePointRef.current.setMode(TPMode.LOCATION); isNavigationModeRef.current = false; lastFinishPointRef.current = null; } }, [drawRealTime]); // 处理完成点 useEffect(() => { if (!finishPoint || !finishPoint.lng || !finishPoint.lat || !drawRealTime) { // 没有 finishPoint 时,移除跟随小车 const viewer = viewerRef.current; if (viewer && followCarEntityRef.current) { viewer.entities.remove(followCarEntityRef.current); followCarEntityRef.current = null; } return; } const point = { lng: finishPoint.lng, lat: finishPoint.lat }; // 如果是定位模式且还没有导航模式,则切换到导航模式 if (!isNavigationModeRef.current && drawRealTime) { tracePointRef.current.setMode(TPMode.NAVIGATION); isNavigationModeRef.current = true; } tracePointRef.current.upsert(point, TPAction.ADD); lastFinishPointRef.current = finishPoint; // 更新轨迹绘制 updateTraceLine(); // 更新或添加跟随小车 }, [finishPoint, drawRealTime]); // 更新跟随小车 const updateFollowCar = (cartesianPos, heading = 0) => { const viewer = viewerRef.current; if (!viewer || !cartesianPos) return; const Cesium = window.Cesium; const hpr = new Cesium.HeadingPitchRoll(heading, 0, 0); const orientation = Cesium.Transforms.headingPitchRollQuaternion(cartesianPos, hpr); if (followCarEntityRef.current) { followCarEntityRef.current.position = cartesianPos; followCarEntityRef.current.orientation = orientation; } else { followCarEntityRef.current = viewer.entities.add({ position: cartesianPos, orientation: orientation, billboard: { image: carImg, width: 30, height: 30, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY, color: Cesium.Color.YELLOW.withAlpha(0.8), }, }); } }; const updateTraceLine = (clear = false) => { const viewer = viewerRef.current; if (!viewer) return; const Cesium = window.Cesium; // 主动清空 或 轨迹数据为空:统一清理 if (clear) { tracePointRef.current.reset?.(); lastFinishPointRef.current = null; isNavigationModeRef.current = false; realtimeCartesianRef.current = []; realtimeLastPointRef.current = null; if (navLineRef.current) { viewer.entities.remove(navLineRef.current); navLineRef.current = null; } if (followCarEntityRef.current) { viewer.entities.remove(followCarEntityRef.current); followCarEntityRef.current = null; } if (realtimeLineEntityRef.current) { viewer.entities.remove(realtimeLineEntityRef.current); realtimeLineEntityRef.current = null; } viewer.scene.requestRender(); return; } const tracePoints = tracePointRef.current.getTracePoint(); if (!tracePoints || tracePoints.length === 0) { if (navLineRef.current) { viewer.entities.remove(navLineRef.current); navLineRef.current = null; } if (followCarEntityRef.current) { viewer.entities.remove(followCarEntityRef.current); followCarEntityRef.current = null; } viewer.scene.requestRender(); return; } const validPoints = tracePoints?.filter(p => { const lng = Number(p.lng); const lat = Number(p.lat); return !isNaN(lng) && !isNaN(lat) && Math.abs(lng) < 180 && Math.abs(lat) < 90 && lng !== 0 && lat !== 0; }); if (validPoints.length === 0) { if (followCarEntityRef.current) { viewer.entities.remove(followCarEntityRef.current); followCarEntityRef.current = null; } return; } const positions = validPoints.map(point => Cesium.Cartesian3.fromDegrees(Number(point.lng), Number(point.lat)) ); // 更新轨迹线 if (navLineRef.current) { navLineRef.current.polyline.positions = positions; } else { navLineRef.current = viewer.entities.add({ polyline: { positions: positions, width: 2, material: Cesium.Color.BLUE.withAlpha(0.9), clampToGround: true, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, }); } // 计算小车朝向 let heading = 0; const lastIdx = positions.length - 1; if (lastIdx > 0) { const prevCart = positions[lastIdx - 1]; const currCart = positions[lastIdx]; const prevCartographic = Cesium.Cartographic.fromCartesian(prevCart); const currCartographic = Cesium.Cartographic.fromCartesian(currCart); const dLon = currCartographic.longitude - prevCartographic.longitude; const dLat = currCartographic.latitude - prevCartographic.latitude; heading = Math.atan2(dLat, dLon); } // 直接传 Cartesian3,无需二次转换 //updateFollowCar(positions[lastIdx], heading); viewer.scene.requestRender(); }; // 清空规划航线(使用上一次的 ifClearPlanLine 作为比较,避免重复触发) const prevIfClearPlanLine = useRef(0); useEffect(() => { if (ifClearPlanLine !== prevIfClearPlanLine.current && ifClearPlanLine) { const viewer = viewerRef.current; if (viewer) { if (planLineRef.current) { viewer.entities.remove(planLineRef.current); planLineRef.current = null; } if (polygonRef.current) { viewer.entities.remove(polygonRef.current); polygonRef.current = null; } viewer.scene.requestRender(); } prevIfClearPlanLine.current = ifClearPlanLine; } }, [ifClearPlanLine]); // 清空实时轨迹 const prevIfClearNavLine = useRef(0); useEffect(() => { if (ifClearNavLine !== prevIfClearNavLine.current && ifClearNavLine) { updateTraceLine(true); prevIfClearNavLine.current = ifClearNavLine; } }, [ifClearNavLine]); // 兼容旧 ifClear:同时清空两类 useEffect(() => { if (ifClear) { const viewer = viewerRef.current; if (viewer) { if (planLineRef.current) { viewer.entities.remove(planLineRef.current); planLineRef.current = null; } updateTraceLine(true); } } }, [ifClear]) useEffect(() => { const viewer = viewerRef.current; if (!viewer || !window.Cesium) return; const Cesium = window.Cesium; const isRealtimeMode = realtimePosition?.lon && realtimePosition?.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)); const nextIds = new Set(); const validPositions = []; devices.forEach((device, index) => { const raw = device?.lastRunningStatus || {}; const parsed = parseLngLat(raw.longitude, raw.latitude); if (!parsed) return; const { longitude, latitude } = parsed; validPositions.push({ lon: longitude, lat: latitude }); const id = getRobotEntityId(device, index); nextIds.add(id); const isIOT = device.from == "IOT"; const position = Cesium.Cartesian3.fromDegrees( longitude, latitude, 1 ); // ============================== // 图标选择 // ============================== let image = carImg; let width = 30; let height = 30; const carsize = device?.productId == 138 ? 30 : 30; if (isIOT) { image = device?.type === "camera" ? cameraImg : iotImg; width = device?.type === "camera" ? 40 : 20; height = device?.type === "camera" ? 40 : 20; } else { image = device?.type === "plane" ? planeImg : // 飞机 device?.type === "drone" ? planeImg : // 无人机 device?.type === "mower" ? carImg : // 割草 device?.type === "robot" ? droneInspectImg : // 巡检 carImg; // 默认车 width = device?.type === "plane" || device?.type === "drone" || device?.type === "drone-inspect" ? 30 : carsize; height = device?.type === "plane" || device?.type === "drone" || device?.type === "drone-inspect" ? 30 : carsize; } // ============================== // entity 获取 / 创建 // ============================== let entity = entityMapRef.current.get(id); if (!entity) { entity = viewer.entities.add({ id, position, billboard: { image, width, height, verticalOrigin: window.Cesium.VerticalOrigin.BOTTOM, disableDepthTestDistance: Number.POSITIVE_INFINITY, show: !isIOT }, label: { text: device.name || "", font: "12px sans-serif", pixelOffset: new Cesium.Cartesian2(0, -40), fillColor: Cesium?.Color.WHITE, showBackground: true, backgroundColor: Cesium?.Color.BLACK.withAlpha(0.6) }, properties: { robot: device } }); entityMapRef.current.set(id, entity); } else { // ============================== // 更新 entity // ============================== entity.position = position; entity.billboard.image = image; entity.billboard.width = width; entity.billboard.height = height; entity.label.text = device.name || ""; entity.properties.robot = device; } }); // ============================== // 删除不存在设备 // ============================== entityMapRef.current.forEach((entity, id) => { if (!nextIds.has(id)) { viewer.entities.remove(entity); entityMapRef.current.delete(id); } }); // ============================== // 请求渲染 // ============================== viewer.scene.requestRender(); // ============================== // 自动 flyTo // ============================== if (!isRealtimeMode && validPositions.length > 0) { let minLon = 180, maxLon = -180, minLat = 90, maxLat = -90; validPositions.forEach(p => { minLon = Math.min(minLon, p.lon); maxLon = Math.max(maxLon, p.lon); minLat = Math.min(minLat, p.lat); maxLat = Math.max(maxLat, p.lat); }); if (getCameraMode() !== "devices") return; if (validPositions.length === 1) { const p = validPositions[0]; viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees( p.lon, p.lat, 800 ), duration: 1.2 }); } else { const rectangle = Cesium.Rectangle.fromDegrees( minLon, minLat, maxLon, maxLat ); viewer.camera.flyTo({ destination: rectangle, duration: 1.2 }); } } }, [devices, realtimePosition]); useEffect(() => { if (!viewerRef.current || pathPoints.length < 2) return; const viewer = viewerRef.current; movingPathRef.current = [...points]; movingIndexRef.current = 0; isMovingRef.current = true; let targetEntity = null; for (let [id, ent] of entityMapRef.current) { targetEntity = ent; break; } if (!targetEntity) return; movingEntityRef.current = targetEntity; const animateMove = () => { if (!isMovingRef.current) return; const path = movingPathRef.current; const idx = movingIndexRef.current; if (idx >= path.length - 1) { isMovingRef.current = false; return; } const p1 = path[idx]; const p2 = path[idx + 1]; const step = moveSpeedRef.current; const lon = p1.lon + (p2.lon - p1.lon) * step; const lat = p1.lat + (p2.lat - p1.lat) * step; const newPos = window.Cesium.Cartesian3.fromDegrees(lon, lat, 1); targetEntity.position = newPos; const heading = Math.atan2(p2.lat - p1.lat, p2.lon - p1.lon); targetEntity.orientation = window.Cesium.Transforms.headingPitchRollQuaternion( newPos, new window.Cesium.HeadingPitchRoll(heading, 0, 0) ); const distance = Math.hypot(lon - p2.lon, lat - p2.lat); if (distance < 0.00005) { movingIndexRef.current += 1; } viewer.scene.requestRender(); requestAnimationFrame(animateMove); }; animateMove(); return () => { isMovingRef.current = false; }; }, [points]); useEffect(() => { const viewer = viewerRef.current; if (!viewer) return; const handler = new window.Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); eventHandlerRef.current = handler; handler.setInputAction((movement) => { const picked = viewer.scene.pick(movement.endPosition); if (window.Cesium.defined(picked) && picked.id?.properties?.robot) { const robot = picked.id.properties.robot.getValue(); showRobotTooltip(tooltipRef.current, robot, movement.endPosition, t); } else { tooltipRef.current.style.display = "none"; } }, window.Cesium.ScreenSpaceEventType.MOUSE_MOVE); handler.setInputAction((movement) => { const picked = viewer.scene.pick(movement.position); if (window.Cesium.defined(picked) && picked.id?.properties?.robot) { const robot = picked.id.properties.robot.getValue(); robot.feStatus == 1 && onCameraClick?.(robot); } }, window.Cesium.ScreenSpaceEventType.LEFT_CLICK); handler.setInputAction((movement) => { if (from !== "deviceStatus") return; const cartesian = viewer.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid); if (!cartesian) return; const cartographic = window.Cesium.Cartographic.fromCartesian(cartesian); const lon = window.Cesium.Math.toDegrees(cartographic.longitude); const lat = window.Cesium.Math.toDegrees(cartographic.latitude); const newPoint = { lon, lat }; const newPoints = [...drawPoints, newPoint]; setDrawPoints(newPoints); updateDrawPath(newPoints); onMarkPoint?.({ lon, lat }); }, window.Cesium.ScreenSpaceEventType.LEFT_CLICK); if (onClearDraw) { onClearDraw(clearDrawingPath); } return () => { handler.destroy(); }; }, [drawPoints, from]); //useEffect(() => { // const viewer = viewerRef.current; // if (!viewer) return; // const tick = viewer.clock.onTick.addEventListener(() => { // viewer.scene.requestRender(); // }); // return () => { // viewer.clock.onTick.removeEventListener(tick); // }; //}, []); function createYellowCircleCanvas(size = 40) { const canvas = document.createElement("canvas"); canvas.width = size; canvas.height = size; const ctx = canvas.getContext("2d"); const r = size / 2; ctx.beginPath(); ctx.arc(r, r, r, 0, Math.PI * 2); ctx.fillStyle = "#facc15"; ctx.fill(); return canvas; } // ====================== // ✅ 永久不闪烁 · 实时轨迹终极版 // ====================== useEffect(() => { const viewer = viewerRef.current; if (!viewer || !window.Cesium) return; const Cesium = window.Cesium; viewer.scene.requestRender(); // ============================= // 判断是否是无人机模式 // ============================= const isDroneMode = realtimeType === 'plane' || realtimeType === 'drone' || devices.some(d => d.type === 'plane' || d.type === 'drone'); // ============================= // 判断实时数据是否合法 // ============================= const hasRealtime = realtimePosition && realtimePosition.lon && realtimePosition.lat && !isNaN(Number(realtimePosition.lon)) && !isNaN(Number(realtimePosition.lat)); // ==================================== // 情况1:没有实时数据 -> 清理所有 realtime // ==================================== if (!hasRealtime) { if (realtimeCarEntityRef.current) { viewer.entities.remove(realtimeCarEntityRef.current); realtimeCarEntityRef.current = null; } // 移除实时轨迹线 if (realtimeLineEntityRef.current) { viewer.entities.remove(realtimeLineEntityRef.current); realtimeLineEntityRef.current = null; } realtimeCartesianRef.current = []; realtimeLastPointRef.current = null; realtimeHiddenRef.current = false; hasFlownRef.current = false; // 恢复所有设备显示 entityMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = true; }); alarmMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = true; }); iotMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = true; }); viewer.scene.requestRender(); return; } // ==================================== // 情况2:有实时数据 -> 隐藏 focusEntity // ==================================== if (focusEntityRef.current) { viewer.entities.remove(focusEntityRef.current); focusEntityRef.current = null; } // ==================================== // 解析实时坐标 // ==================================== entityMapRef.current.forEach((entity) => { if (entity.billboard) entity.billboard.show = false; }); alarmMapRef.current.forEach((entity) => { if (entity.billboard) entity.billboard.show = false; }); iotMapRef.current.forEach((entity) => { if (entity.billboard) entity.billboard.show = false; }); const lon = Number(realtimePosition.lon || finishPoint.lng); const lat = Number(realtimePosition.lat || finishPoint.lat); const position = Cesium.Cartesian3.fromDegrees(lon, lat, 1); // ==================================== // 无人机模式:保持原来的逻辑 // ==================================== if (isDroneMode || from == "deviceStatus") { // 轨迹点去抖动(防止重复点) const last = realtimeLastPointRef.current; let shouldAdd = true; if (last) { const dx = last.lon - lon; const dy = last.lat - lat; const dist = Math.sqrt(dx * dx + dy * dy); //if (dist < 0.0000001) { // shouldAdd = false; //} } if (shouldAdd && drawRealTime) { realtimeLastPointRef.current = { lon, lat }; realtimeCartesianRef.current.push( Cesium.Cartesian3.fromDegrees(lon, lat) ); } // 第一次进入 realtime -> 隐藏所有设备 if (!realtimeHiddenRef.current) { realtimeHiddenRef.current = true; entityMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); alarmMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); iotMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); } // 无人机图标 const img = realtimeType === 'plane' || realtimeType === 'drone' ? planeImg : carImg; const imgWidth = 30; const imgHeight = 30; // 如果有航向角,设置旋转 let rotation = 0; if (realtimePosition?.heading !== undefined && !isNaN(Number(realtimePosition.heading))) { let headingDeg = Number(realtimePosition.heading); rotation = Cesium.Math.toRadians(headingDeg); } if (!realtimeCarEntityRef.current) { realtimeCarEntityRef.current = viewer.entities.add({ position: position, billboard: { image: img, width: imgWidth, height: imgHeight, verticalOrigin: Cesium.VerticalOrigin.CENTER, rotation: rotation, disableDepthTestDistance: Number.POSITIVE_INFINITY, } }); } else { realtimeCarEntityRef.current.position = position; realtimeCarEntityRef.current.billboard.image = img; realtimeCarEntityRef.current.billboard.width = imgWidth; realtimeCarEntityRef.current.billboard.height = imgHeight; realtimeCarEntityRef.current.billboard.rotation = rotation; } // 无人机模式的实时轨迹线 if (drawRealTime) { if (realtimeLineEntityRef.current) { // 已存在,CallbackProperty 会自动更新 } else { const callbackPositions = new Cesium.CallbackProperty(() => { return realtimeCartesianRef.current; }, false); realtimeLineEntityRef.current = viewer.entities.add({ polyline: { positions: callbackPositions, width: 5, material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.25, color: Cesium?.Color.LIME }), clampToGround: true, disableDepthTestDistance: Number.POSITIVE_INFINITY } }); } } else { // 不绘制实时轨迹,移除线 if (realtimeLineEntityRef.current) { viewer.entities.remove(realtimeLineEntityRef.current); realtimeLineEntityRef.current = null; } realtimeCartesianRef.current = []; } // 无人机模式不需要完成点逻辑,直接返回 if (!hasFlownRef.current) { hasFlownRef.current = true; if (getCameraMode() !== "realtime") return; viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(lon, lat, 30), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-90), roll: 0 }, duration: 1.2 }); } viewer.scene.requestRender(); return; } // ==================================== // 机器人模式:使用 TracePoint 逻辑 // ==================================== const point = { lng: lon, lat: lat }; // 如果是定位模式且 drawRealTime 为 false,保持定位模式 if (!drawRealTime) { tracePointRef.current.setMode(TPMode.LOCATION); isNavigationModeRef.current = false; } // 更新当前点 tracePointRef.current.upsert(point, TPAction.UPDATE); // 更新轨迹绘制 updateTraceLine(); // 第一次进入 realtime -> 隐藏所有设备 if (!realtimeHiddenRef.current) { realtimeHiddenRef.current = true; entityMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); alarmMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); iotMapRef.current.forEach(e => { if (e.billboard) e.billboard.show = false; }); } // 机器人图标 const img = carImg; const imgWidth = 30; const imgHeight = 30; // 如果有航向角,设置旋转 let rotation = 0; if (realtimePosition?.heading !== undefined && !isNaN(Number(realtimePosition.heading))) { let headingDeg = Number(realtimePosition.heading); rotation = Cesium.Math.toRadians(headingDeg); } if (!realtimeCarEntityRef.current) { realtimeCarEntityRef.current = viewer.entities.add({ position: position, billboard: { image: img, width: imgWidth, height: imgHeight, verticalOrigin: Cesium.VerticalOrigin.CENTER, rotation: rotation, disableDepthTestDistance: Number.POSITIVE_INFINITY, } }); } else { realtimeCarEntityRef.current.position = position; realtimeCarEntityRef.current.billboard.image = img; realtimeCarEntityRef.current.billboard.width = imgWidth; realtimeCarEntityRef.current.billboard.height = imgHeight; realtimeCarEntityRef.current.billboard.rotation = rotation; } // 机器人模式只在导航模式下显示额外的实时轨迹线 if (drawRealTime && isNavigationModeRef.current) { // 轨迹点去抖动(防止重复点) const last = realtimeLastPointRef.current; let shouldAdd = true; if (last) { const dx = last.lon - lon; const dy = last.lat - lat; const dist = Math.sqrt(dx * dx + dy * dy); //if (dist < 0.0000001) { // shouldAdd = false; //} } //if (shouldAdd) { // realtimeLastPointRef.current = { lon, lat }; // realtimeCartesianRef.current.push( // Cesium.Cartesian3.fromDegrees(lon, lat) // ); //} //if (realtimeLineEntityRef.current) { // // 已存在,CallbackProperty 会自动更新 //} else { // const callbackPositions = new Cesium.CallbackProperty(() => { // return realtimeCartesianRef.current; // }, false); // realtimeLineEntityRef.current = viewer.entities.add({ // polyline: { // positions: callbackPositions, // width: 5, // material: new Cesium.PolylineGlowMaterialProperty({ // glowPower: 0.25, // color: Cesium?.Color.LIME // }), // clampToGround: true, // disableDepthTestDistance: Number.POSITIVE_INFINITY // } // }); //} } else { // 移除实时轨迹线 if (realtimeLineEntityRef.current) { viewer.entities.remove(realtimeLineEntityRef.current); realtimeLineEntityRef.current = null; } realtimeCartesianRef.current = []; } if (!hasFlownRef.current) { hasFlownRef.current = true; if (getCameraMode() !== "realtime") return; viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees( lon, lat, 30 ), orientation: { heading: 0, pitch: Cesium.Math.toRadians(-90), roll: 0 }, duration: 1.2 }); } viewer.scene.requestRender(); }, [realtimePosition]); // 截图功能 const captureScreenshot = useCallback(() => { return new Promise((resolve) => { const viewer = viewerRef.current; if (!viewer) { resolve(null); return; } // 使用 Cesium 的截图功能 viewer.scene.render(); const canvas = viewer.scene.canvas; const dataUrl = canvas.toDataURL('image/jpeg', 0.8); resolve(dataUrl); }); }, []); // 暴露 ref 方法 useImperativeHandle(ref, () => ({ captureScreenshot })); return (
); }); export default CesiumMap;