diff --git a/src/assets/images/map_bi/point/ico_people_blue.png b/src/assets/images/map_bi/point/ico_people_blue.png new file mode 100644 index 0000000..3dd97d5 Binary files /dev/null and b/src/assets/images/map_bi/point/ico_people_blue.png differ diff --git a/src/assets/images/map_bi/point/ico_people_orange.png b/src/assets/images/map_bi/point/ico_people_orange.png new file mode 100644 index 0000000..a2fa564 Binary files /dev/null and b/src/assets/images/map_bi/point/ico_people_orange.png differ diff --git a/src/assets/images/map_bi/point/ico_people_red.png b/src/assets/images/map_bi/point/ico_people_red.png new file mode 100644 index 0000000..afbb83c Binary files /dev/null and b/src/assets/images/map_bi/point/ico_people_red.png differ diff --git a/src/assets/images/map_bi/point/ico_people_yellow.png b/src/assets/images/map_bi/point/ico_people_yellow.png new file mode 100644 index 0000000..d0592a3 Binary files /dev/null and b/src/assets/images/map_bi/point/ico_people_yellow.png differ diff --git a/src/pages/Container/Map/components/BottomUtils/index.js b/src/pages/Container/Map/components/BottomUtils/index.js index 8c98cd4..06ce84d 100644 --- a/src/pages/Container/Map/components/BottomUtils/index.js +++ b/src/pages/Container/Map/components/BottomUtils/index.js @@ -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); }} > @@ -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} > diff --git a/src/pages/Container/Map/index.js b/src/pages/Container/Map/index.js index 7f71a52..d9e6a1f 100644 --- a/src/pages/Container/Map/index.js +++ b/src/pages/Container/Map/index.js @@ -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) { diff --git a/src/pages/Container/Map/js/TrajectoryPolylineTrailLinkMaterialProperty.js b/src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js similarity index 100% rename from src/pages/Container/Map/js/TrajectoryPolylineTrailLinkMaterialProperty.js rename to src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js diff --git a/src/pages/Container/Map/js/WallPolylineTrailLinkMaterialProperty.js b/src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js similarity index 100% rename from src/pages/Container/Map/js/WallPolylineTrailLinkMaterialProperty.js rename to src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js diff --git a/src/pages/Container/Map/js/initMap.js b/src/pages/Container/Map/js/useInitMap.js similarity index 98% rename from src/pages/Container/Map/js/initMap.js rename to src/pages/Container/Map/js/useInitMap.js index 29ddde4..1c4606b 100644 --- a/src/pages/Container/Map/js/initMap.js +++ b/src/pages/Container/Map/js/useInitMap.js @@ -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; diff --git a/src/pages/Container/Map/js/mapMethods.js b/src/pages/Container/Map/js/useMapMethods.js similarity index 83% rename from src/pages/Container/Map/js/mapMethods.js rename to src/pages/Container/Map/js/useMapMethods.js index 970766e..ae51883 100644 --- a/src/pages/Container/Map/js/mapMethods.js +++ b/src/pages/Container/Map/js/useMapMethods.js @@ -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, }; } diff --git a/src/pages/Container/Map/js/usePeoplePosition.js b/src/pages/Container/Map/js/usePeoplePosition.js new file mode 100644 index 0000000..31de788 --- /dev/null +++ b/src/pages/Container/Map/js/usePeoplePosition.js @@ -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 }; +} diff --git a/src/pages/Container/Map/js/pointClickEvent.js b/src/pages/Container/Map/js/usePointClickEvent.js similarity index 100% rename from src/pages/Container/Map/js/pointClickEvent.js rename to src/pages/Container/Map/js/usePointClickEvent.js diff --git a/src/pages/Container/Map/js/utils.js b/src/pages/Container/Map/js/utils.js index 0d10164..0653a00 100644 --- a/src/pages/Container/Map/js/utils.js +++ b/src/pages/Container/Map/js/utils.js @@ -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"; + } +};