feat(map): 实现封闭区域人员定位功能
parent
c903aa9a99
commit
85885a7d8f
|
|
@ -60,3 +60,7 @@ export const getLargeScreenVideoPage = declareRequest(
|
|||
"biFullLoading",
|
||||
"Post > @/keyProject/keyProject/largeScreenVideoPage",
|
||||
);
|
||||
export const getClosedAreaListByCorpinfoId = declareRequest(
|
||||
"biFullLoading",
|
||||
"Post > @/primeport/closedArea/listByCorpinfoId",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue