diff --git a/src/api/biMark/index.js b/src/api/biMark/index.js index cc5c2af..d9f91f1 100644 --- a/src/api/biMark/index.js +++ b/src/api/biMark/index.js @@ -60,3 +60,7 @@ export const getLargeScreenVideoPage = declareRequest( "biFullLoading", "Post > @/keyProject/keyProject/largeScreenVideoPage", ); +export const getClosedAreaListByCorpinfoId = declareRequest( + "biFullLoading", + "Post > @/primeport/closedArea/listByCorpinfoId", +); diff --git a/src/pages/Container/Map/components/BottomUtils/index.js b/src/pages/Container/Map/components/BottomUtils/index.js index 07842c0..ac9259b 100644 --- a/src/pages/Container/Map/components/BottomUtils/index.js +++ b/src/pages/Container/Map/components/BottomUtils/index.js @@ -56,6 +56,15 @@ const branchOfficeItemAnimation = { visible: { y: 0, opacity: 1, transition: { duration: 0.3, ease: "easeOut" } }, }; +// 接口使用分号分隔坐标点,仅保留可以组成多边形的有效封闭区域。 +const formatClosedAreas = (data = []) => data.map(item => ({ + ...item, + position: (item.location || "").split(";").map((coordinate) => { + const [x, y] = coordinate.split(",").map(Number); + return { x, y }; + }).filter(point => Number.isFinite(point.x) && Number.isFinite(point.y)), +})).filter(item => item.position.length >= 3); + function BottomUtils(props) { const { mapMethods, @@ -104,6 +113,7 @@ function BottomUtils(props) { // 底部工具栏被外部操作重置时,人员定位的 WebSocket 和 Cesium 实体不会随勾选状态自动销毁,需要统一清理。 peoplePositionDisconnect(); mapMethods.current.removePeoplePointAll(); + mapMethods.current.removePeoplePositionArea(); setPeopleList([]); setList(produce((draft) => { draft.forEach((item) => { @@ -189,20 +199,59 @@ function BottomUtils(props) { const optionsItemsClick = async (index, index1, item) => { const check = !list[index].list[index1].check; - updateOptionCheck(index, index1, check); + + // 两种定位共用同一套人员实体和 WebSocket,冲突时保留当前状态并让用户主动取消已开启功能。 + if (check && item.type === "person" && list.some(group => group.list?.some(option => option.type === "peoplePosition" && option.check))) { + message.warning("请先取消【人员定位】中的【人员定位】"); + return; + } + if (check && item.type === "peoplePosition" && list.some(group => group.list?.some(option => option.type === "person" && option.check))) { + message.warning("请先取消【封闭区域】中的【人员】"); + return; + } if (!check) { if (item.type === "peoplePosition") { + updateOptionCheck(index, index1, false); resetPeopleTrajectory(index); mapMethods.current.removePeopleTrajectory(); peoplePositionDisconnect(); + mapMethods.current.removePeoplePointAll(); + mapMethods.current.removePeoplePositionArea(); + setPeopleList([]); return; } + if (item.type === "person" && currentPort === "00003") { + updateOptionCheck(index, index1, false); + peoplePositionDisconnect(); + mapMethods.current.removePeoplePointAll(); + mapMethods.current.removePeoplePositionArea(); + setPeopleList([]); + return; + } + updateOptionCheck(index, index1, false); mapMethods.current.removeMarkPoint(item.type); return; } + if (item.type === "person" && currentPort === "00003") { + const { data } = await props.getClosedAreaListByCorpinfoId({ corpinfoId: currentBranchOffice, pageIndex: 1, pageSize: 9999 }); + const areaList = formatClosedAreas(data || []); + if (areaList.length === 0) { + message.warning("暂无有效封闭区域"); + return; + } + updateOptionCheck(index, index1, true); + mapMethods.current.removePeoplePointAll(); + mapMethods.current.addPeoplePositionArea(areaList); + mapMethods.current.initPeoplePositionClock(); + peoplePositionConnect(areaList); + return; + } + if (item.type === "peoplePosition" && currentPort === "00003") { + updateOptionCheck(index, index1, true); + mapMethods.current.removePeoplePositionArea(); // 连接 WebSocket 前初始化本次定位会话的统一时钟,首批人员共享同一采样基准。 mapMethods.current.initPeoplePositionClock(); peoplePositionConnect(); @@ -215,11 +264,11 @@ function BottomUtils(props) { ); if (!isPeoplePositionChecked) { message.warning("请先选择人员定位!!!"); - updateOptionCheck(index, index1, false); return; } } + updateOptionCheck(index, index1, true); await addMarkPoint(item); }; diff --git a/src/pages/Container/Map/js/useMapMethods.js b/src/pages/Container/Map/js/useMapMethods.js index 8124b2f..3d94036 100644 --- a/src/pages/Container/Map/js/useMapMethods.js +++ b/src/pages/Container/Map/js/useMapMethods.js @@ -255,6 +255,35 @@ export default function useMapMethods(viewerRef, request) { removeEntityCollection(viewer, "fourColorDiagramEntityCollection"); }; + // 人员点位使用贴地显示,区域也通过地形和 3D Tiles 分类贴地,避免零高度多边形被倾斜摄影遮挡。 + const addPeoplePositionArea = (areaList = []) => { + const viewer = getViewer(); + removeEntityCollection(viewer, "peoplePositionAreaEntityCollection"); + const entityCollection = createEntityCollection("peoplePositionAreaEntityCollection"); + viewer.dataSources.add(entityCollection); + areaList.forEach((area) => { + entityCollection.entities.add( + new Cesium.Entity({ + id: createId(), + name: area.closedAreaName, + polygon: { + hierarchy: Cesium.Cartesian3.fromDegreesArray(formatPolygon(area)), + material: Cesium.Color.fromCssColorString("rgba(221,0,0,0.4)"), + outline: true, + outlineColor: Cesium.Color.fromCssColorString("rgba(51,35,108,0.63)"), + classificationType: Cesium.ClassificationType.BOTH, + zIndex: 1, + }, + }), + ); + }); + }; + + const removePeoplePositionArea = () => { + const viewer = getViewer(); + removeEntityCollection(viewer, "peoplePositionAreaEntityCollection"); + }; + // 添加墙 const addWall = (id, corpionId) => { const edgeMap = { @@ -552,6 +581,8 @@ export default function useMapMethods(viewerRef, request) { removeBranchOfficePoint, addFourColorDiagram, removeFourColorDiagram, + addPeoplePositionArea, + removePeoplePositionArea, addWall, removeWall, addMarkPoint, diff --git a/src/pages/Container/Map/js/usePeoplePosition.js b/src/pages/Container/Map/js/usePeoplePosition.js index 06c67c1..5a991b1 100644 --- a/src/pages/Container/Map/js/usePeoplePosition.js +++ b/src/pages/Container/Map/js/usePeoplePosition.js @@ -1,11 +1,12 @@ import { useWebSocket } from "ahooks"; import { useRef } from "react"; import edgeQHD from "./edge/qhd.js"; -import { isPointWithinTheArea } from "./utils"; +import { isPointInAreas, isPointWithinTheArea } from "./utils"; export default function usePeoplePosition(mapMethods, currentBranchOffice, portArea, onPeopleChange) { const markType = "peoplePosition"; const points = useRef([]); + const filterAreas = useRef([]); const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:"; // 生产环境的接口地址由 index.html 在运行时注入 window.process,不能使用会在打包阶段被替换的 process.env。 @@ -19,14 +20,27 @@ export default function usePeoplePosition(mapMethods, currentBranchOffice, portA const peopleList = Array.isArray(data) ? data : [data]; for (let i = 0; i < peopleList.length; i++) { const item = peopleList[i]; + if (!item?.locationRawJson) + continue; const location = JSON.parse(item.locationRawJson).position; - if (!item || !location || !location.lon || !location.lat) + if (!location || !location.lon || !location.lat) + continue; + const id = item.terminalNo || item.staffNo; + if (!id) continue; const index = points.current.findIndex( - item1 => item1.id.toString() === (item.terminalNo || item.staffNo).toString(), + item1 => item1.id.toString() === id.toString(), ); const x = location.lon; const y = location.lat; + // 封闭区域模式只保留围栏内人员;人员离开围栏后立即移除已有点位。 + if (filterAreas.current.length > 0 && !isPointInAreas(filterAreas.current, x, y)) { + if (index !== -1) { + mapMethods.current?.removePeoplePoint(points.current[index].id); + points.current.splice(index, 1); + } + continue; + } const pointColor = isPointWithinTheArea(edgeQHD(), x, y); if (index !== -1) { points.current[index].x = x; @@ -37,7 +51,7 @@ export default function usePeoplePosition(mapMethods, currentBranchOffice, portA else { const perLoc = { ...item, - id: item.terminalNo || item.staffNo, + id, name: item.staffName, x, y, @@ -66,5 +80,15 @@ export default function usePeoplePosition(mapMethods, currentBranchOffice, portA }, ); - return { connect, disconnect }; + const connectPeoplePosition = (areas = []) => { + filterAreas.current = areas; + connect(); + }; + + const disconnectPeoplePosition = () => { + filterAreas.current = []; + disconnect(); + }; + + return { connect: connectPeoplePosition, disconnect: disconnectPeoplePosition }; } diff --git a/src/pages/Container/Map/js/utils.js b/src/pages/Container/Map/js/utils.js index 968798b..2c9ae1d 100644 --- a/src/pages/Container/Map/js/utils.js +++ b/src/pages/Container/Map/js/utils.js @@ -246,7 +246,7 @@ export function textFormatter(value, maxLength = 4) { } } -const isPointInPolygon = (polygon, lng, lat) => { +export const isPointInPolygon = (polygon, lng, lat) => { const numberOfPoints = polygon.length; const polygonLats = []; const polygonLngs = []; @@ -273,19 +273,19 @@ const isPointInPolygon = (polygon, lng, lat) => { } return polygonContainsPoint; }; -const forEachIsPointInPolygon = (polygonList = [], lng, lat) => { +export const isPointInAreas = (polygonList = [], lng, lat) => { return polygonList.some((item) => { return isPointInPolygon(item.position, lng, lat); }); }; export const isPointWithinTheArea = (pointBox = {}, x, y) => { - if (forEachIsPointInPolygon(pointBox.redList, x, y)) { + if (isPointInAreas(pointBox.redList, x, y)) { return "red"; } - else if (forEachIsPointInPolygon(pointBox.orangeList, x, y)) { + else if (isPointInAreas(pointBox.orangeList, x, y)) { return "orange"; } - else if (forEachIsPointInPolygon(pointBox.yellowList, x, y)) { + else if (isPointInAreas(pointBox.yellowList, x, y)) { return "yellow"; } else {