feat(map): 集成实时人员定位功能
parent
1f8f47f946
commit
cff14a1652
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -1,4 +1,5 @@
|
|||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
import { message } from "antd";
|
||||
import { produce } from "immer";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
|
@ -9,6 +10,7 @@ import titleOnImg from "~/assets/images/map_bi/bottom_utils/title_on.png";
|
|||
import { NS_BI_MARK } from "~/enumerate/namespace";
|
||||
import { branchOfficeUtilsList } from "~/pages/Container/Map/components/BottomUtils/branchOfficeUtilsList";
|
||||
import { Context } from "~/pages/Container/Map/js/context";
|
||||
import usePeoplePosition from "~/pages/Container/Map/js/usePeoplePosition";
|
||||
import { portUtilsList } from "./portUtilsList";
|
||||
import { usePortUtilsAnimation } from "./usePortUtilsAnimation";
|
||||
import "./index.less";
|
||||
|
|
@ -65,6 +67,8 @@ function BottomUtils(props) {
|
|||
portArea,
|
||||
} = useContext(Context);
|
||||
|
||||
const { connect: peoplePositionConnect, disconnect: peoplePositionDisconnect } = usePeoplePosition(mapMethods, currentBranchOffice);
|
||||
|
||||
const bottomUtilsMode = !pureMap && currentPort && !currentBranchOffice
|
||||
? "port"
|
||||
: !pureMap && currentPort && currentBranchOffice ? "branchOffice" : null;
|
||||
|
|
@ -118,43 +122,91 @@ function BottomUtils(props) {
|
|||
actions.setBottomUtilsIndex(bottomUtilsCurrentIndex === index ? -1 : index);
|
||||
};
|
||||
|
||||
const optionsItemsClick = async (index, index1, item, item1) => {
|
||||
const check = !list[index].list[index1].check;
|
||||
if (check) {
|
||||
if (item1.request) {
|
||||
let pointData = [];
|
||||
const requestParams = { portArea, corpinfoId: currentBranchOffice, pageIndex: 1, pageSize: 9999 };
|
||||
if (dangerWorkTypes.includes(item1.type)) {
|
||||
requestParams.workType = item1.type;
|
||||
}
|
||||
if (gateTypes.includes(item1.type)) {
|
||||
requestParams.gateType = item1.type;
|
||||
}
|
||||
const { data } = await props[item1.request](requestParams);
|
||||
pointData = [...(data || [])];
|
||||
if (dangerWorkTypes.includes(item1.type)) {
|
||||
for (let i = 0; i < pointData.length; i++) {
|
||||
pointData[i].latitude = pointData[i].info.latitude;
|
||||
pointData[i].longitude = pointData[i].info.longitude;
|
||||
}
|
||||
}
|
||||
mapMethods.current.addMarkPoint(pointData, {
|
||||
markType: item1.type,
|
||||
markIcon: item1.markIcon,
|
||||
subLabel: item1.label,
|
||||
titleKey: item1.titleKey,
|
||||
isShowModal: item1.isShowModal,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
mapMethods.current.removeMarkPoint(item1.type);
|
||||
}
|
||||
// 统一维护子菜单勾选状态,避免各业务分支直接修改列表数据。
|
||||
const updateOptionCheck = (index, index1, check) => {
|
||||
setList(produce((draft) => {
|
||||
draft[index].list[index1].check = check;
|
||||
}));
|
||||
};
|
||||
|
||||
// 人员轨迹依赖实时定位数据;取消定位时需要同步取消同组轨迹。
|
||||
const resetPeopleTrajectory = (index) => {
|
||||
setList(produce((draft) => {
|
||||
draft[index].list.forEach((option) => {
|
||||
if (option.type === "peopleTrajectory")
|
||||
option.check = false;
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
// 根据菜单类型补充接口参数转换坐标。
|
||||
const getMarkPointData = async (item) => {
|
||||
const requestParams = { portArea, corpinfoId: currentBranchOffice, pageIndex: 1, pageSize: 9999 };
|
||||
if (dangerWorkTypes.includes(item.type))
|
||||
requestParams.workType = item.type;
|
||||
if (gateTypes.includes(item.type))
|
||||
requestParams.gateType = item.type;
|
||||
|
||||
const { data } = await props[item.request](requestParams);
|
||||
const pointData = data || [];
|
||||
if (dangerWorkTypes.includes(item.type)) {
|
||||
return pointData.map(point => ({
|
||||
...point,
|
||||
latitude: point.info.latitude,
|
||||
longitude: point.info.longitude,
|
||||
}));
|
||||
}
|
||||
return pointData;
|
||||
};
|
||||
|
||||
// 查询并渲染带请求配置的菜单点位;无请求配置的菜单无需执行地图操作。
|
||||
const addMarkPoint = async (item) => {
|
||||
if (!item.request)
|
||||
return;
|
||||
|
||||
const pointData = await getMarkPointData(item);
|
||||
mapMethods.current.addMarkPoint(pointData, {
|
||||
markType: item.type,
|
||||
markIcon: item.markIcon,
|
||||
subLabel: item.label,
|
||||
titleKey: item.titleKey,
|
||||
isShowModal: item.isShowModal,
|
||||
});
|
||||
};
|
||||
|
||||
const optionsItemsClick = async (index, index1, item) => {
|
||||
const check = !list[index].list[index1].check;
|
||||
updateOptionCheck(index, index1, check);
|
||||
|
||||
if (!check) {
|
||||
if (item.type === "peoplePosition") {
|
||||
resetPeopleTrajectory(index);
|
||||
peoplePositionDisconnect();
|
||||
return;
|
||||
}
|
||||
mapMethods.current.removeMarkPoint(item.type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.type === "peoplePosition" && currentPort === "00003") {
|
||||
peoplePositionConnect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.type === "peopleTrajectory") {
|
||||
const isPeoplePositionChecked = list[index].list.some(
|
||||
option => option.type === "peoplePosition" && option.check,
|
||||
);
|
||||
if (!isPeoplePositionChecked) {
|
||||
message.warning("请先选择人员定位!!!");
|
||||
updateOptionCheck(index, index1, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await addMarkPoint(item);
|
||||
};
|
||||
|
||||
const renderPortUtils = () => {
|
||||
if (bottomUtilsMode === "port" && listMode === bottomUtilsMode) {
|
||||
return list.map((item, index) => {
|
||||
|
|
@ -194,7 +246,7 @@ function BottomUtils(props) {
|
|||
transition={{ delay: index1 * portUtilsAnimationConfig.staggerDelay }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
optionsItemsClick(index, index1, item, item1);
|
||||
optionsItemsClick(index, index1, item1);
|
||||
}}
|
||||
>
|
||||
<img src={item1.check ? item1.checkImg : item1.img} alt="" className="child_img" />
|
||||
|
|
@ -239,7 +291,7 @@ function BottomUtils(props) {
|
|||
className={`item ${item1.check ? "active" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
optionsItemsClick(index, index1, item, item1);
|
||||
optionsItemsClick(index, index1, item1);
|
||||
}}
|
||||
variants={branchOfficeItemAnimation}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import CustomModal from "./components/Content/modal";
|
|||
import Header from "./components/Header";
|
||||
import RightUtils from "./components/RightUtils";
|
||||
import { Context, initialMapState, MAP_ACTION, mapReducer } from "./js/context";
|
||||
import useInitMap from "./js/initMap";
|
||||
import mitt from "./js/mitt";
|
||||
import {
|
||||
changeCoverMaskVisibleMittKey,
|
||||
|
|
@ -21,6 +20,7 @@ import {
|
|||
clickPortPointMittKey,
|
||||
resetBottomUtilsMittKey,
|
||||
} from "./js/mittKey";
|
||||
import useInitMap from "./js/useInitMap";
|
||||
import "./index.less";
|
||||
|
||||
function Map(props) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useRef } from "react";
|
||||
import { createObliquePhotography } from "~/pages/Container/Map/js/mapUtils";
|
||||
import useMapMethods from "./mapMethods";
|
||||
import usePointClickEvent from "./pointClickEvent";
|
||||
import useMapMethods from "./useMapMethods";
|
||||
import usePointClickEvent from "./usePointClickEvent";
|
||||
|
||||
const Cesium = window.Cesium;
|
||||
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import { useRef } from "react";
|
||||
import portEntityBillboardImage from "~/assets/images/map_bi/point/dianwei.png";
|
||||
import branchOfficeEntityBillboardImage from "~/assets/images/map_bi/point/gongsidianwei.png";
|
||||
import people_blue from "~/assets/images/map_bi/point/ico_people_blue.png";
|
||||
import people_orange from "~/assets/images/map_bi/point/ico_people_orange.png";
|
||||
import people_red from "~/assets/images/map_bi/point/ico_people_red.png";
|
||||
import people_yellow from "~/assets/images/map_bi/point/ico_people_yellow.png";
|
||||
import edgeCMT from "./edge/cmt.js";
|
||||
import edgeCSY from "./edge/csy.js";
|
||||
import edgeCZKS from "./edge/czks.js";
|
||||
|
|
@ -16,8 +20,8 @@ import {
|
|||
import mitt from "./mitt";
|
||||
import { changeCoverMaskVisibleMittKey } from "./mittKey";
|
||||
import { chunkedLoad, filterNull, formatPolygon } from "./utils";
|
||||
import "./TrajectoryPolylineTrailLinkMaterialProperty.js";
|
||||
import "./WallPolylineTrailLinkMaterialProperty";
|
||||
import "./material/TrajectoryPolylineTrailLinkMaterialProperty.js";
|
||||
import "./material/WallPolylineTrailLinkMaterialProperty";
|
||||
|
||||
const portPoint = [
|
||||
{
|
||||
|
|
@ -53,6 +57,13 @@ const portPoint = [
|
|||
},
|
||||
];
|
||||
|
||||
const peopleImg = {
|
||||
blue: people_blue,
|
||||
orange: people_orange,
|
||||
red: people_red,
|
||||
yellow: people_yellow,
|
||||
}; // 人员定位颜色图片
|
||||
|
||||
const Cesium = window.Cesium;
|
||||
|
||||
export default function useMapMethods(viewerRef, request) {
|
||||
|
|
@ -80,6 +91,8 @@ export default function useMapMethods(viewerRef, request) {
|
|||
height: 900000,
|
||||
});
|
||||
|
||||
const peopleId = useRef([]); // 人员定位 ID 列表
|
||||
|
||||
// 设置中心点
|
||||
const flyTo = ({ longitude, latitude, height } = defineCenterPoint) => {
|
||||
getViewer().camera.flyTo({ destination: getPosition(longitude, latitude, height), duration: 2 });
|
||||
|
|
@ -392,6 +405,76 @@ export default function useMapMethods(viewerRef, request) {
|
|||
removeEntityCollection(getViewer(), `markEntityCollectionMerged${markType ? `_${markType}` : ""}`);
|
||||
};
|
||||
|
||||
const addPeoplePoint = async (point, options) => {
|
||||
const image = peopleImg[point.icon_type];
|
||||
if (!image)
|
||||
return;
|
||||
if (!options.markType)
|
||||
throw new Error("请传入markType(扎点类型)");
|
||||
const clonePoint = { ...point };
|
||||
point.property = new Cesium.SampledPositionProperty();
|
||||
const start = Cesium.JulianDate.now();
|
||||
const position = Cesium.Cartesian3.fromDegrees(point.x, point.y, 0);
|
||||
point.property.addSample(start, position);
|
||||
point.lastTime = start;
|
||||
point.lastIconType = point.icon_type;
|
||||
peopleId.current.push(point.id);
|
||||
getViewer().entities.add({
|
||||
id: point.id,
|
||||
name: point.name,
|
||||
position: point.property,
|
||||
billboard: await getBillboard({
|
||||
image,
|
||||
name: point.name || point.id,
|
||||
}),
|
||||
monitorItems: {
|
||||
data: { ...clonePoint, markType: `标记点_${options.markType}`, isShowModal: options.isShowModal ?? true },
|
||||
},
|
||||
});
|
||||
getViewer().clock.startTime = start.clone();
|
||||
getViewer().clock.currentTime = start.clone();
|
||||
getViewer().clock.clockRange = Cesium.ClockRange.CLAMPED;
|
||||
getViewer().clock.shouldAnimate = false;
|
||||
};
|
||||
|
||||
const updatePeoplePoint = async (point) => {
|
||||
if (getViewer().clock.shouldAnimate === false) {
|
||||
getViewer().clock.shouldAnimate = true;
|
||||
}
|
||||
if (point.icon_type !== point.lastIconType) {
|
||||
const entity = getViewer().entities.getById(point.id);
|
||||
if (!entity)
|
||||
return;
|
||||
entity.billboard.image = (
|
||||
await getBillboard({
|
||||
image: peopleImg[point.icon_type],
|
||||
name: point.name || point.id,
|
||||
})
|
||||
).image;
|
||||
point.lastIconType = point.icon_type;
|
||||
}
|
||||
const position = Cesium.Cartesian3.fromDegrees(point.x, point.y, 0);
|
||||
const nextTime = Cesium.JulianDate.addSeconds(
|
||||
point.lastTime,
|
||||
10,
|
||||
new Cesium.JulianDate(),
|
||||
);
|
||||
point.property.addSample(nextTime, position);
|
||||
point.lastTime = nextTime;
|
||||
};
|
||||
|
||||
const removePeoplePoint = (id) => {
|
||||
getViewer().entities.removeById(id);
|
||||
peopleId.current = peopleId.current.filter(item => item !== id);
|
||||
};
|
||||
|
||||
const removePeoplePointAll = () => {
|
||||
peopleId.current.forEach((id) => {
|
||||
getViewer().entities.removeById(id);
|
||||
});
|
||||
peopleId.current = [];
|
||||
};
|
||||
|
||||
return {
|
||||
flyTo,
|
||||
returnCurrentCenterPoint,
|
||||
|
|
@ -407,5 +490,9 @@ export default function useMapMethods(viewerRef, request) {
|
|||
removeWall,
|
||||
addMarkPoint,
|
||||
removeMarkPoint,
|
||||
addPeoplePoint,
|
||||
updatePeoplePoint,
|
||||
removePeoplePoint,
|
||||
removePeoplePointAll,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { useWebSocket } from "ahooks";
|
||||
import { useRef } from "react";
|
||||
import edgeQHD from "./edge/qhd.js";
|
||||
import { filterNull, isPointWithinTheArea } from "./utils";
|
||||
|
||||
export default function usePeoplePosition(mapMethods, currentBranchOffice) {
|
||||
const markType = "peoplePosition";
|
||||
const points = useRef([]);
|
||||
|
||||
// const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
// const wsUrl = `${wsProtocol}//${process.env.app.API_HOST.replace(/^https?:\/\//, "")}/personnelPosition/ws/location?corpinfoId=${currentBranchOffice}`;
|
||||
const wsUrl = `ws://192.168.10.14/personnelPosition/ws/location?corpinfoId=${currentBranchOffice}`;
|
||||
|
||||
const onMessage = async (message) => {
|
||||
const data = JSON.parse(message.data);
|
||||
const peopleList = filterNull([data]);
|
||||
for (let i = 0; i < peopleList.length; i++) {
|
||||
const item = peopleList[i];
|
||||
if (!item)
|
||||
continue;
|
||||
const index = points.current.findIndex(
|
||||
item1 => item1.id.toString() === (item.terminalNo || item.staffNo).toString(),
|
||||
);
|
||||
const x = item.lon;
|
||||
const y = item.lat;
|
||||
const pointColor = isPointWithinTheArea(edgeQHD(), x, y);
|
||||
if (index !== -1) {
|
||||
points.current[index].x = x;
|
||||
points.current[index].y = y;
|
||||
points.current[index].icon_type = pointColor;
|
||||
mapMethods.current && (await mapMethods.current.updatePeoplePoint(points.current[index]));
|
||||
}
|
||||
else {
|
||||
const perLoc = {
|
||||
...item,
|
||||
id: item.terminalNo || item.staffNo,
|
||||
name: item.staffName,
|
||||
x,
|
||||
y,
|
||||
icon_type: pointColor,
|
||||
};
|
||||
points.current.push(perLoc);
|
||||
mapMethods.current && (await mapMethods.current.addPeoplePoint(perLoc, { markType, isShowModal: false }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const { connect, disconnect } = useWebSocket(
|
||||
wsUrl,
|
||||
{
|
||||
manual: true,
|
||||
onClose: () => {
|
||||
mapMethods.current && mapMethods.current.removePeoplePointAll();
|
||||
points.current = [];
|
||||
},
|
||||
onMessage: (message) => {
|
||||
onMessage(message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return { connect, disconnect };
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// 过滤掉无经纬度的数据
|
||||
export const filterNull = (arr = []) => {
|
||||
return arr.filter(item => (item.longitude || item.lng) && (item.latitude || item.lat));
|
||||
return arr.filter(item => (item.longitude || item.lng || item.lon) && (item.latitude || item.lat));
|
||||
};
|
||||
|
||||
// 格式化多边形
|
||||
|
|
@ -245,3 +245,50 @@ export function textFormatter(value, maxLength = 4) {
|
|||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const isPointInPolygon = (polygon, lng, lat) => {
|
||||
const numberOfPoints = polygon.length;
|
||||
const polygonLats = [];
|
||||
const polygonLngs = [];
|
||||
for (let i = 0; i < numberOfPoints; i++) {
|
||||
polygonLats.push(polygon[i].y);
|
||||
polygonLngs.push(polygon[i].x);
|
||||
}
|
||||
let polygonContainsPoint = false;
|
||||
for (
|
||||
let node = 0, altNode = numberOfPoints - 1;
|
||||
node < numberOfPoints;
|
||||
altNode = node++
|
||||
) {
|
||||
if (
|
||||
(polygonLngs[node] > lng) !== (polygonLngs[altNode] > lng)
|
||||
&& lat
|
||||
< ((polygonLats[altNode] - polygonLats[node])
|
||||
* (lng - polygonLngs[node]))
|
||||
/ (polygonLngs[altNode] - polygonLngs[node])
|
||||
+ polygonLats[node]
|
||||
) {
|
||||
polygonContainsPoint = !polygonContainsPoint;
|
||||
}
|
||||
}
|
||||
return polygonContainsPoint;
|
||||
};
|
||||
const forEachIsPointInPolygon = (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)) {
|
||||
return "red";
|
||||
}
|
||||
else if (forEachIsPointInPolygon(pointBox.orangeList, x, y)) {
|
||||
return "orange";
|
||||
}
|
||||
else if (forEachIsPointInPolygon(pointBox.yellowList, x, y)) {
|
||||
return "yellow";
|
||||
}
|
||||
else {
|
||||
return "blue";
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue