From 6c3cac4dc16eae773a278423f6aa6d43431e5926 Mon Sep 17 00:00:00 2001 From: LiuJiaNan <15703339975@163.com> Date: Fri, 21 Aug 2026 15:03:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor(map):=20=E8=BF=81=E7=A7=BB=E8=87=B3=20?= =?UTF-8?q?Cesium=20=E6=A8=A1=E5=9D=97=E5=8C=96=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jjb.config.js | 26 ++- package.json | 1 + public/index.html | 2 - .../Map/components/popup/js/Coord.js | 45 +++-- src/pages/Container/Map/index.js | 1 + src/pages/Container/Map/js/mapUtils.js | 74 +++---- ...ectoryPolylineTrailLinkMaterialProperty.js | 83 ++------ .../WallPolylineTrailLinkMaterialProperty.js | 116 +++-------- src/pages/Container/Map/js/useInitMap.js | 68 ++++--- src/pages/Container/Map/js/useMapMethods.js | 182 ++++++++++++------ src/utils/index.js | 4 +- 11 files changed, 288 insertions(+), 314 deletions(-) diff --git a/jjb.config.js b/jjb.config.js index c201a92..4c694c0 100644 --- a/jjb.config.js +++ b/jjb.config.js @@ -1,3 +1,14 @@ +const path = require("node:path"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); +const webpack = require("webpack"); + +const cesiumBuildPath = path.join( + path.dirname(require.resolve("cesium/package.json")), + "Build/Cesium", +); +const appIdentifier = "bi-h5"; +const cesiumStaticPath = `${appIdentifier}/static/cesium`; + module.exports = { // 应用后端git地址,部署上线需要 javaGit: "", @@ -9,7 +20,7 @@ module.exports = { // 应用后端分支名称,部署上线需要 javaGitBranch: "", // 接口服务地址 - API_HOST: "https://gbs-gateway.qhdsafety.com", + API_HOST: "http://192.168.198.8:30140", }, production: { // 应用后端分支名称,部署上线需要 @@ -19,7 +30,7 @@ module.exports = { }, }, // 应用唯一标识符 - appIdentifier: "bi-h5", + appIdentifier, // 应用上下文注入全局变量 contextInject: { // 应用Key @@ -71,5 +82,16 @@ module.exports = { // 自动注入编译后的文件到public/index.html中 inject: true, }, + plugins: [ + new CopyWebpackPlugin({ + patterns: ["Assets", "ThirdParty", "Widgets", "Workers"].map(name => ({ + from: path.join(cesiumBuildPath, name), + to: `${cesiumStaticPath}/${name}`, + })), + }), + new webpack.DefinePlugin({ + CESIUM_BASE_URL: JSON.stringify(`/${cesiumStaticPath}/`), + }), + ], }, }; diff --git a/package.json b/package.json index a99d504..1f1c37e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "animate.css": "^4.1.1", "antd": "6.5.0", "autofit.js": "^3.2.8", + "cesium": "1.144.0", "dayjs": "^1.11.7", "echarts": "^6.0.0", "immer": "^11.1.4", diff --git a/public/index.html b/public/index.html index cdeeb0e..3e2f7f9 100644 --- a/public/index.html +++ b/public/index.html @@ -52,6 +52,4 @@ <% const { root } = $element; %>
- - diff --git a/src/pages/Container/Map/components/popup/js/Coord.js b/src/pages/Container/Map/components/popup/js/Coord.js index 328e556..8919942 100644 --- a/src/pages/Container/Map/components/popup/js/Coord.js +++ b/src/pages/Container/Map/components/popup/js/Coord.js @@ -1,4 +1,13 @@ -const Cesium = window.Cesium; +import { + Cartesian2, + Cartographic, + Cesium3DTileFeature, + Cesium3DTileset, + Math as CesiumMath, + EllipsoidTerrainProvider, + Model, + SceneTransforms, +} from "cesium"; export class Coord { viewer; @@ -18,19 +27,19 @@ export class Coord { if (!Number.isNaN(Number(i))) { const pick = picks[i]; isOn3dtiles - = (pick && pick.primitive instanceof Cesium.Cesium3DTileFeature) - || (pick && pick.primitive instanceof Cesium.Cesium3DTileset) - || (pick && pick.primitive instanceof Cesium.Model); + = (pick && pick.primitive instanceof Cesium3DTileFeature) + || (pick && pick.primitive instanceof Cesium3DTileset) + || (pick && pick.primitive instanceof Model); if (isOn3dtiles) { viewer.scene.pick(position); cartesian3 = viewer.scene.pickPosition(position); if (cartesian3) { const cartographic - = Cesium.Cartographic.fromCartesian(cartesian3); + = Cartographic.fromCartesian(cartesian3); if (cartographic.height < 0) cartographic.height = 0; - const lon = Cesium.CesiumMath.toDegrees(cartographic.longitude); - const lat = Cesium.CesiumMath.toDegrees(cartographic.latitude); + const lon = CesiumMath.toDegrees(cartographic.longitude); + const lat = CesiumMath.toDegrees(cartographic.latitude); const height = cartographic.height; cartesian3 = this.lnglatToCartesian3(lon, lat, height); return cartesian3; @@ -42,7 +51,7 @@ export class Coord { // 不在模型上 if (!isOn3dtiles) { const isTerrain - = viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider; // 是否存在地形 + = viewer.terrainProvider instanceof EllipsoidTerrainProvider; // 是否存在地形 if (!isTerrain) { // 无地形 const ray = viewer.scene.camera.getPickRay(position); @@ -80,7 +89,7 @@ export class Coord { } cartesian3ToCartesian2(cartesian3) { - return Cesium.SceneTransforms.wgs84ToWindowCoordinates( + return SceneTransforms.worldToWindowCoordinates( this.viewer.scene, cartesian3, ); @@ -92,7 +101,7 @@ export class Coord { if (typeof extend === "undefined") { const coordToLonlat = (viewer, x, y) => { const { camera, scene } = viewer; - const d2 = new Cesium.Cartesian2(x, y); + const d2 = new Cartesian2(x, y); const ellipsoid = scene.globe.ellipsoid; // 2D转3D世界坐标 const d3 = camera.pickEllipsoid(d2, ellipsoid); @@ -101,10 +110,10 @@ export class Coord { const upperLeftCartographic = scene.globe.ellipsoid.cartesianToCartographic(d3); // 弧度转经纬度 - const lon = Cesium.CesiumMath.toDegrees( + const lon = CesiumMath.toDegrees( upperLeftCartographic.longitude, ); - const lat = Cesium.CesiumMath.toDegrees( + const lat = CesiumMath.toDegrees( upperLeftCartographic.latitude, ); return { lon, lat }; @@ -134,10 +143,10 @@ export class Coord { else { // 三维视图 bounds = [ - Cesium.CesiumMath.toDegrees(extend.west), - Cesium.CesiumMath.toDegrees(extend.south), - Cesium.CesiumMath.toDegrees(extend.east), - Cesium.CesiumMath.toDegrees(extend.north), + CesiumMath.toDegrees(extend.west), + CesiumMath.toDegrees(extend.south), + CesiumMath.toDegrees(extend.east), + CesiumMath.toDegrees(extend.north), ]; } return bounds; @@ -168,8 +177,8 @@ export class Coord { if (cartesian3) { const radians = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian3); - const latitude = Cesium.CesiumMath.toDegrees(radians.latitude); // 弧度转度 - const longitude = Cesium.CesiumMath.toDegrees(radians.longitude); + const latitude = CesiumMath.toDegrees(radians.latitude); // 弧度转度 + const longitude = CesiumMath.toDegrees(radians.longitude); const height = radians.height; return { longitude, latitude, height }; } diff --git a/src/pages/Container/Map/index.js b/src/pages/Container/Map/index.js index 8d9eefe..f96b2d3 100644 --- a/src/pages/Container/Map/index.js +++ b/src/pages/Container/Map/index.js @@ -23,6 +23,7 @@ import { resetBottomUtilsMittKey, } from "./js/mittKey"; import useInitMap from "./js/useInitMap"; +import "cesium/Build/Cesium/Widgets/widgets.css"; function Map(props) { const query = useGetUrlQuery(); diff --git a/src/pages/Container/Map/js/mapUtils.js b/src/pages/Container/Map/js/mapUtils.js index 1318839..81cca84 100644 --- a/src/pages/Container/Map/js/mapUtils.js +++ b/src/pages/Container/Map/js/mapUtils.js @@ -1,16 +1,25 @@ +import { + Cartesian3, + Cartographic, + Cesium3DTileset, + Math as CesiumMath, + createGuid, + CustomDataSource, + HeightReference, + HorizontalOrigin, + Matrix4, + VerticalOrigin, +} from "cesium"; import { getBillboardCanvases } from "./utils"; -const Cesium = window.Cesium; - -// 获取经纬度 +// 接口经纬度可能以字符串返回,统一在 Cesium 坐标入口转换为数值。 export const getPosition = (x, y, z = 0.0) => { - return Cesium.Cartesian3.fromDegrees(x, y, z); + return Cartesian3.fromDegrees(Number(x), Number(y), Number(z)); }; // 创建倾斜摄影 -export const createObliquePhotography = (url, viewer) => { - const tileset = new Cesium.Cesium3DTileset({ - url, +export const createObliquePhotography = async (url, viewer) => { + const tileset = await Cesium3DTileset.fromUrl(url, { skipLevelOfDetail: true, baseScreenSpaceError: 1024, maximumScreenSpaceError: 128, // 数值加大,能让最终成像变模糊 @@ -23,42 +32,32 @@ export const createObliquePhotography = (url, viewer) => { cullRequestsWhileMovingMultiplier: 10, // 值越小能够更快的剔除 preloadWhenHidden: true, preferLeaves: true, - maximumMemoryUsage: 128, // 内存分配变小有利于倾斜摄影数据回收,提升性能体验 + cacheBytes: 128 * 1024 * 1024, // 限制瓦片缓存,便于倾斜摄影数据及时回收。 progressiveResolutionHeightFraction: 0.5, // 数值偏于0能够让初始加载变得模糊 dynamicScreenSpaceErrorDensity: 0.5, // 数值加大,能让周边加载变快 dynamicScreenSpaceErrorFactor: 1, // 不知道起了什么作用没,反正放着吧先 dynamicScreenSpaceError: true, // 有了这个后,会在真正的全屏加载完之后才清晰化房屋 }); - tileset.readyPromise.then((tileset) => { - // 笛卡尔转换为弧度 - const cartographic = Cesium.Cartographic.fromCartesian( - tileset.boundingSphere.center, - ); - const lng = Cesium.Math.toDegrees(cartographic.longitude); // 使用经纬度和弧度的转换,将WGS84弧度坐标系转换到目标值,弧度转度 - const lat = Cesium.Math.toDegrees(cartographic.latitude); - // 计算中心点位置的地表坐标 - const surface = Cesium.Cartesian3.fromDegrees(lng, lat, 0); - // 偏移后的坐标 - const offset = Cesium.Cartesian3.fromDegrees(lng, lat, 5); - const translation = Cesium.Cartesian3.subtract( - offset, - surface, - new Cesium.Cartesian3(), - ); - tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation); - }); + // 新版 Cesium 通过 fromUrl 完成异步初始化,解析完成后再计算模型偏移。 + const cartographic = Cartographic.fromCartesian(tileset.boundingSphere.center); + const lng = CesiumMath.toDegrees(cartographic.longitude); + const lat = CesiumMath.toDegrees(cartographic.latitude); + const surface = Cartesian3.fromDegrees(lng, lat, 0); + const offset = Cartesian3.fromDegrees(lng, lat, 5); + const translation = Cartesian3.subtract(offset, surface, new Cartesian3()); + tileset.modelMatrix = Matrix4.fromTranslation(translation); viewer.scene.primitives.add(tileset); }; // 创建唯一Id export const createId = () => { - return Cesium.createGuid(); + return createGuid(); }; // 创建数据源 export const createEntityCollection = (name) => { - return new Cesium.CustomDataSource(`${name}_${createId()}`); + return new CustomDataSource(`${name}_${createId()}`); }; // 创建 billboard @@ -68,17 +67,24 @@ export const getBillboard = async ({ image, name = "" }) => { image: base64, width, height, - verticalOrigin: Cesium.VerticalOrigin.BOTTOM, - horizontalOrigin: Cesium.HorizontalOrigin.CENTER, - heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, + verticalOrigin: VerticalOrigin.BOTTOM, + horizontalOrigin: HorizontalOrigin.CENTER, + heightReference: HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, - clampToGround: true, }; }; +// DataSourceCollection 仅通过公开的 length/get 接口遍历,避免依赖内部数组结构。 +const getDataSources = (viewer) => { + return Array.from( + { length: viewer.dataSources.length }, + (_, index) => viewer.dataSources.get(index), + ); +}; + // 移除数据源 export const removeEntityCollection = (viewer, name) => { - viewer.dataSources._dataSources + getDataSources(viewer) .filter(ds => ds.name.startsWith(`${name}_`)) .forEach((item) => { viewer.dataSources.remove(item); @@ -88,7 +94,7 @@ export const removeEntityCollection = (viewer, name) => { // 合并数据源 export const createMergedEntityCollection = (viewer, name) => { const mergedDataSource = createEntityCollection(name); - viewer.dataSources._dataSources + getDataSources(viewer) .filter(ds => ds.name.startsWith(`${name.substring(0, name.indexOf("Merged"))}_`), ) diff --git a/src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js b/src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js index c7591db..f96e30d 100644 --- a/src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js +++ b/src/pages/Container/Map/js/material/TrajectoryPolylineTrailLinkMaterialProperty.js @@ -1,81 +1,28 @@ -const Cesium = window.Cesium; +import { Material } from "cesium"; -function TrajectoryPolylineTrailLinkMaterialProperty(viewer) { - this.viewer = viewer; - this._definitionChanged = new Cesium.Event(); - this._uTime = 0; -} - -Object.defineProperties(TrajectoryPolylineTrailLinkMaterialProperty.prototype, { - isConstant: { - get() { - return false; - }, - }, - definitionChanged: { - get() { - return this._definitionChanged; - }, - }, - uTime: Cesium.createPropertyDescriptor("uTime"), -}); - -TrajectoryPolylineTrailLinkMaterialProperty.prototype.getType = function () { - return "CustomMaterial"; -}; - -TrajectoryPolylineTrailLinkMaterialProperty.prototype.getValue = function ( - time, - result, -) { - if (!Cesium.defined(result)) { - result = {}; - } - - this._uTime += 0.005; - if (this._uTime > 1.0) { - this._uTime = 0; - } - - result.uTime = this._uTime; - this.viewer.scene.requestRender(); - return result; -}; - -TrajectoryPolylineTrailLinkMaterialProperty.prototype.equals = function ( - other, -) { - return ( - this === other - || (other instanceof TrajectoryPolylineTrailLinkMaterialProperty - && this._uTime === other._uTime) - ); -}; - -Cesium.Material.CustomMaterialType = "CustomMaterial"; -Cesium.Material.CustomMaterialSource = ` +const materialSource = ` czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); float progress = mod(1.0 - materialInput.st.s + uTime, 1.0); float head = smoothstep(0.95, 1.0, progress) * 2.0; - vec3 baseColor = vec3(1, 0, 0); + vec3 baseColor = vec3(1.0, 0.0, 0.0); float tailFade = smoothstep(0.3, 0.0, progress); - vec3 tailColor = mix(vec3(0), vec3(1), tailFade); + vec3 tailColor = mix(vec3(0.0), vec3(1.0), tailFade); material.diffuse = baseColor + tailColor * 0.8 + vec3(head); material.emission = material.diffuse * 0.5; + material.alpha = 1.0; return material; }`; -Cesium.Material._materialCache.addMaterial(Cesium.Material.CustomMaterialType, { - fabric: { - type: Cesium.Material.CustomMaterialType, - uniforms: { - uTime: 0, +// Primitive 可以直接使用 Fabric 材质,无需向 Cesium 私有材质缓存注册类型。 +export default function createTrajectoryPolylineTrailMaterial() { + return new Material({ + fabric: { + uniforms: { + uTime: 0, + }, + source: materialSource, }, - source: Cesium.Material.CustomMaterialSource, - }, -}); - -Cesium.TrajectoryPolylineTrailLinkMaterialProperty - = TrajectoryPolylineTrailLinkMaterialProperty; + }); +} diff --git a/src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js b/src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js index 2cc2440..7caffca 100644 --- a/src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js +++ b/src/pages/Container/Map/js/material/WallPolylineTrailLinkMaterialProperty.js @@ -1,102 +1,32 @@ +import { Color, Material } from "cesium"; import PolylineTrailLinkImage from "~/assets/images/map_bi/wall_img.png"; -const Cesium = window.Cesium; +const materialSource = ` +czm_material czm_getMaterial(czm_materialInput materialInput) +{ + czm_material material = czm_getDefaultMaterial(materialInput); + vec2 st = materialInput.st; + vec4 colorImage = texture(image, vec2(fract(st.t - time), st.t)); + vec4 fragColor = czm_gammaCorrect(color); + material.alpha = colorImage.a * color.a; + material.diffuse = color.rgb; + material.emission = fragColor.rgb; + return material; +}`; -function WallPolylineTrailLinkMaterialProperty( - viewer, - options = { - color: Cesium.Color.fromBytes(201, 118, 243).withAlpha(0.5), - duration: 2000, - }, +// Primitive 可以直接使用 Fabric 材质,无需向 Cesium 私有材质缓存注册类型。 +export default function createWallPolylineTrailMaterial( + color = Color.fromBytes(201, 118, 243).withAlpha(0.5), ) { - this.viewer = viewer; - this._definitionChanged = new Cesium.Event(); - this._color = undefined; - this._colorSubscription = undefined; - this.color = options.color; - this.duration = options.duration; - this._time = new Date().getTime(); -} - -Object.defineProperties(WallPolylineTrailLinkMaterialProperty.prototype, { - isConstant: { - get() { - return false; - }, - }, - definitionChanged: { - get() { - return this._definitionChanged; - }, - }, - color: Cesium.createPropertyDescriptor("color"), -}); -WallPolylineTrailLinkMaterialProperty.prototype.getType = function () { - return "PolylineTrailLink"; -}; -WallPolylineTrailLinkMaterialProperty.prototype.getValue = function ( - time, - result, -) { - if (!Cesium.defined(result)) { - result = {}; - } - result.color = Cesium.Property.getValueOrClonedDefault( - this._color, - time, - Cesium.Color.WHITE, - result.color, - ); - result.image = Cesium.Material.PolylineTrailLinkImage; - - if (this.duration) { - result.time - = ((new Date().getTime() - this._time) % this.duration) / this.duration; - } - this.viewer.scene.requestRender(); - return result; -}; -WallPolylineTrailLinkMaterialProperty.prototype.equals = function (other) { - return ( - this === other - || (other instanceof WallPolylineTrailLinkMaterialProperty - && Cesium.Property.equals(this._color, other._color)) - ); -}; -Cesium.WallPolylineTrailLinkMaterialProperty - = WallPolylineTrailLinkMaterialProperty; -Cesium.Material.PolylineTrailLinkType = "PolylineTrailLink"; -Cesium.Material.PolylineTrailLinkImage = PolylineTrailLinkImage; -Cesium.Material.PolylineTrailLinkSource = `czm_material czm_getMaterial(czm_materialInput - materialInput)\n\ - {\n\ - czm_material material = - czm_getDefaultMaterial(materialInput);\n\ - vec2 st = materialInput.st;\n\ - vec4 colorImage = texture2D(image, - vec2(fract(st.t - time), st.t));\n\ - vec4 fragColor;\n\ - fragColor.rgb = color.rgb / 1.0;\n\ - fragColor = czm_gammaCorrect(fragColor);\n\ - material.alpha = colorImage.a * color.a;\n\ - material.diffuse = color.rgb;\n\ - material.emission = fragColor.rgb;\n\ - return material;\n\ - }`; -Cesium.Material._materialCache.addMaterial( - Cesium.Material.PolylineTrailLinkType, - { + return new Material({ + translucent: true, fabric: { - type: Cesium.Material.PolylineTrailLinkType, uniforms: { - color: new Cesium.Color(1.0, 1.0, 1.0, 1), - image: Cesium.Material.PolylineTrailLinkImage, + color, + image: PolylineTrailLinkImage, time: 0, }, - source: Cesium.Material.PolylineTrailLinkSource, + source: materialSource, }, - translucent() { - return true; - }, - }, -); + }); +} diff --git a/src/pages/Container/Map/js/useInitMap.js b/src/pages/Container/Map/js/useInitMap.js index 2800741..538d01c 100644 --- a/src/pages/Container/Map/js/useInitMap.js +++ b/src/pages/Container/Map/js/useInitMap.js @@ -1,11 +1,18 @@ +import { + CameraEventType, + defined, + ImageryLayer, + ScreenSpaceEventHandler, + ScreenSpaceEventType, + Viewer, + WebMapTileServiceImageryProvider, +} from "cesium"; import { useRef } from "react"; import { createObliquePhotography } from "~/pages/Container/Map/js/mapUtils"; import useMapMethods from "./useMapMethods"; import usePointClickEvent from "./usePointClickEvent"; -const Cesium = window.Cesium; - export default function useInitMap(options) { // 页面和地图工具共用同一个 Cesium 引用,避免内外各维护一份实例。 const viewer = useRef(null); @@ -22,10 +29,11 @@ export default function useInitMap(options) { const getViewer = () => viewer.current; // 倾斜摄影 + // 当前按业务要求暂停初始化,保留入口以便服务恢复后重新启用。 + const initObliquePhotography = () => { const viewer = getViewer(); viewer.scene.globe.depthTestAgainstTerrain = true; - Cesium.ExperimentalFeatures.enableModelExperimental = true; createObliquePhotography( "http://192.168.192.215:8021/ware/upload/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/%E6%9B%B9%E5%A6%83%E7%94%B8%E6%B8%AF%E4%B8%9C/merge_tile.json", viewer, @@ -55,26 +63,36 @@ export default function useInitMap(options) { // 注册点击事件 const registerClickEvent = () => { const viewer = getViewer(); - viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); - viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); - const screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); + viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_DOUBLE_CLICK); + viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK); + const screenSpaceEventHandler = new ScreenSpaceEventHandler(viewer.scene.canvas); screenSpaceEventHandler.setInputAction((movement) => { const pick = viewer.scene.pick(movement.position); - if (Cesium.defined(pick) && pick.id?.id) { + if (defined(pick) && pick.id?.id) { pointClickEventRef.current.pointClickEvent(pick.id); } else { pointClickEventRef.current.closePopup(); } - }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + }, ScreenSpaceEventType.LEFT_CLICK); }; // 初始化地图 const initMap = () => { const tianDiTuKey = "06494bcf2d4b66df7f3c586cc65af0cb"; const subdomains = ["0", "1", "2", "3", "4", "5", "6", "7"]; - viewer.current = new Cesium.Viewer("cesiumContainer", { - // terrainProvider: Cesium.createWorldTerrain() + const imageryProvider = new WebMapTileServiceImageryProvider({ + // 影像底图 + url: + `https://t{s}.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${ + tianDiTuKey}`, + subdomains, + layer: "tdtImgLayer", + style: "default", + format: "image/jpeg", + tileMatrixSetID: "GoogleMapsCompatible", // 使用谷歌的瓦片切片方式 + }); + viewer.current = new Viewer("cesiumContainer", { animation: false, // 动画 homeButton: true, // home键 geocoder: true, // 地址编码 @@ -86,34 +104,23 @@ export default function useInitMap(options) { navigationInstructionsInitiallyVisible: false, // 导航指令 navigationHelpButton: false, // 帮助信息 selectionIndicator: false, // 选择 - imageryProvider: new Cesium.WebMapTileServiceImageryProvider({ - // 影像底图 - url: - `https://t{s}.tianditu.gov.cn/img_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=img&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default&format=tiles&tk=${ - tianDiTuKey}`, - subdomains, - layer: "tdtImgLayer", - style: "default", - format: "image/jpeg", - tileMatrixSetID: "GoogleMapsCompatible", // 使用谷歌的瓦片切片方式 - show: true, - }), + baseLayer: new ImageryLayer(imageryProvider), }); // 调整 Cesium 默认相机操作:左键旋转地图,右键调整俯仰角,实现从平面俯视到立体视角的切换。 const cameraController = viewer.current.scene.screenSpaceCameraController; - cameraController.rotateEventTypes = Cesium.CameraEventType.LEFT_DRAG; + cameraController.rotateEventTypes = CameraEventType.LEFT_DRAG; cameraController.tiltEventTypes = [ - Cesium.CameraEventType.RIGHT_DRAG, - Cesium.CameraEventType.MIDDLE_DRAG, - Cesium.CameraEventType.PINCH, + CameraEventType.RIGHT_DRAG, + CameraEventType.MIDDLE_DRAG, + CameraEventType.PINCH, ]; cameraController.zoomEventTypes = [ - Cesium.CameraEventType.WHEEL, - Cesium.CameraEventType.PINCH, + CameraEventType.WHEEL, + CameraEventType.PINCH, ]; - viewer.current._cesiumWidget._creditContainer.style.display = "none"; // 隐藏cesium ion + viewer.current.creditDisplay.container.style.display = "none"; // 隐藏 Cesium 版权信息容器 viewer.current.imageryLayers.addImageryProvider( - new Cesium.WebMapTileServiceImageryProvider({ + new WebMapTileServiceImageryProvider({ // 影像注记 url: `https://t{s}.tianditu.gov.cn/cia_w/wmts?service=wmts&request=GetTile&version=1.0.0&LAYER=cia&tileMatrixSet=w&TileMatrix={TileMatrix}&TileRow={TileRow}&TileCol={TileCol}&style=default.jpg&tk=${ @@ -123,7 +130,6 @@ export default function useInitMap(options) { style: "default", format: "image/jpeg", tileMatrixSetID: "GoogleMapsCompatible", - show: true, }), ); initObliquePhotography(); diff --git a/src/pages/Container/Map/js/useMapMethods.js b/src/pages/Container/Map/js/useMapMethods.js index 0ea53fc..e0b6964 100644 --- a/src/pages/Container/Map/js/useMapMethods.js +++ b/src/pages/Container/Map/js/useMapMethods.js @@ -1,3 +1,26 @@ +import { + Cartesian3, + ClassificationType, + ClockRange, + Color, + defined, + Entity, + ExtrapolationType, + GeometryInstance, + GroundPolylineGeometry, + GroundPolylinePrimitive, + HeightReference, + HorizontalOrigin, + JulianDate, + MaterialAppearance, + PinBuilder, + PolylineMaterialAppearance, + Primitive, + SampledPositionProperty, + SceneMode, + VerticalOrigin, + WallGeometry, +} from "cesium"; import { useRef } from "react"; import portEntityBillboardImage from "~/assets/images/map_bi/point/dianwei.png"; import branchOfficeEntityBillboardImage from "~/assets/images/map_bi/point/gongsidianwei.png"; @@ -17,11 +40,11 @@ import { getPosition, removeEntityCollection, } from "./mapUtils"; +import createTrajectoryPolylineTrailMaterial from "./material/TrajectoryPolylineTrailLinkMaterialProperty"; +import createWallPolylineTrailMaterial from "./material/WallPolylineTrailLinkMaterialProperty"; import mitt from "./mitt"; import { changeCoverMaskVisibleMittKey } from "./mittKey"; import { chunkedLoad, filterNull, formatPolygon } from "./utils"; -import "./material/TrajectoryPolylineTrailLinkMaterialProperty.js"; -import "./material/WallPolylineTrailLinkMaterialProperty"; const portPoint = [ { @@ -64,8 +87,6 @@ const peopleImg = { yellow: people_yellow, }; // 人员定位颜色图片 -const Cesium = window.Cesium; - export default function useMapMethods(viewerRef, request) { // 获取当前 Cesium 实例:初始化前为空,地图方法会在 initMap 完成后被调用。 const getViewer = () => viewerRef.current; @@ -97,6 +118,10 @@ export default function useMapMethods(viewerRef, request) { // 当前人员定位会话共用的采样起始时间 const peoplePositionStartTime = useRef(null); + // Primitive 不属于 Entity/DataSource,单独保存实例和动画注销函数以便完整清理。 + const wallPrimitivesRef = useRef([]); + const peopleTrajectoryPrimitiveRef = useRef(null); + // 设置中心点 const flyTo = ({ longitude, latitude, height } = defineCenterPoint) => { const viewer = getViewer(); @@ -123,7 +148,7 @@ export default function useMapMethods(viewerRef, request) { // 切换场景模式 const changeSceneMode = (type = "3d") => { const viewer = getViewer(); - viewer.scene.mode = { "2d": Cesium.SceneMode.COLUMBUS_VIEW, "3d": Cesium.SceneMode.SCENE3D }[type]; + viewer.scene.mode = { "2d": SceneMode.COLUMBUS_VIEW, "3d": SceneMode.SCENE3D }[type]; }; // 添加港口点 @@ -133,7 +158,7 @@ export default function useMapMethods(viewerRef, request) { viewer.dataSources.add(entityCollection); await chunkedLoad(portPoint, 50, async (item) => { entityCollection.entities.add( - new Cesium.Entity({ + new Entity({ id: item.id, name: item.name, position: getPosition(item.position.x, item.position.y), @@ -171,7 +196,7 @@ export default function useMapMethods(viewerRef, request) { viewer.dataSources.add(entityCollection); await chunkedLoad(branchOfficePoint, 50, async (item) => { entityCollection.entities.add( - new Cesium.Entity({ + new Entity({ id: item.id, name: item.corpName, position: getPosition(item.longitude, item.latitude), @@ -232,15 +257,15 @@ export default function useMapMethods(viewerRef, request) { itemsToRender.forEach((item) => { const latitudeAndLongitude = formatPolygon(item); entityCollection.entities.add( - new Cesium.Entity({ + new Entity({ id: createId(), polygon: { - hierarchy: Cesium.Cartesian3.fromDegreesArray(latitudeAndLongitude), + hierarchy: Cartesian3.fromDegreesArray(latitudeAndLongitude), extrudedHeight: item.stretchHeight, height: item.height, - material: Cesium.Color.fromCssColorString(item.color), + material: Color.fromCssColorString(item.color), outline: !!item.strokeColor, - outlineColor: Cesium.Color.fromCssColorString(item.strokeColor), + outlineColor: Color.fromCssColorString(item.strokeColor), }, }), ); @@ -263,15 +288,15 @@ export default function useMapMethods(viewerRef, request) { viewer.dataSources.add(entityCollection); areaList.forEach((area) => { entityCollection.entities.add( - new Cesium.Entity({ + new Entity({ id: createId(), name: area.closedAreaName, polygon: { - hierarchy: Cesium.Cartesian3.fromDegreesArray(formatPolygon(area)), - material: Cesium.Color.fromCssColorString("rgba(221,0,0,0.4)"), + hierarchy: Cartesian3.fromDegreesArray(formatPolygon(area)), + material: Color.fromCssColorString("rgba(221,0,0,0.4)"), outline: true, - outlineColor: Cesium.Color.fromCssColorString("rgba(51,35,108,0.63)"), - classificationType: Cesium.ClassificationType.BOTH, + outlineColor: Color.fromCssColorString("rgba(51,35,108,0.63)"), + classificationType: ClassificationType.BOTH, zIndex: 1, }, }), @@ -297,8 +322,6 @@ export default function useMapMethods(viewerRef, request) { const viewer = getViewer(); mitt.emit(changeCoverMaskVisibleMittKey, true); const currentEdge = edgeMap[id](); - const entityCollection = createEntityCollection("wallEntityCollection"); - viewer.dataSources.add(entityCollection); const wallList = currentEdge.wallList; if (!Array.isArray(wallList)) return; @@ -306,33 +329,51 @@ export default function useMapMethods(viewerRef, request) { // 筛选需要渲染的边界墙 const itemsToRender = wallList.filter(item => !corpionId || item.corpinfoId === corpionId); - // 统一渲染 - itemsToRender.forEach((item) => { + const geometryInstances = itemsToRender.map((item) => { const latitudeAndLongitude = formatPolygon(item); - const positions - = Cesium.Cartesian3.fromDegreesArray(latitudeAndLongitude); + const positions = Cartesian3.fromDegreesArray(latitudeAndLongitude); const pointCount = latitudeAndLongitude.length / 2; - entityCollection.entities.add( - new Cesium.Entity({ - id: createId(), - wall: { - positions, - material: new Cesium.WallPolylineTrailLinkMaterialProperty(viewer), - maximumHeights: Array.from({ length: pointCount }).fill(40), - minimumHeights: Array.from({ length: pointCount }).fill(0), - }, + return new GeometryInstance({ + id: createId(), + geometry: new WallGeometry({ + positions, + maximumHeights: Array.from({ length: pointCount }).fill(40), + minimumHeights: Array.from({ length: pointCount }).fill(0), + vertexFormat: MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat, }), - ); + }); }); + if (geometryInstances.length) { + const material = createWallPolylineTrailMaterial(); + const primitive = viewer.scene.primitives.add(new Primitive({ + geometryInstances, + appearance: new MaterialAppearance({ + material, + faceForward: true, + translucent: true, + }), + })); + const startTime = Date.now(); + const removeAnimation = viewer.scene.preUpdate.addEventListener(() => { + material.uniforms.time = ((Date.now() - startTime) % 2000) / 2000; + viewer.scene.requestRender(); + }); + wallPrimitivesRef.current.push({ primitive, removeAnimation }); + } + mitt.emit(changeCoverMaskVisibleMittKey, false); }; // 删除墙 const removeWall = () => { const viewer = getViewer(); - removeEntityCollection(viewer, "wallEntityCollection"); + wallPrimitivesRef.current.forEach(({ primitive, removeAnimation }) => { + removeAnimation(); + viewer.scene.primitives.remove(primitive); + }); + wallPrimitivesRef.current = []; }; // 点位聚合 @@ -342,26 +383,26 @@ export default function useMapMethods(viewerRef, request) { dataSource.clustering.minimumClusterSize = 5; // 设置最小的聚合点数目,超过此数目才能聚合 let removeListener; // 按聚合层级创建对应图标 - const pinBuilder = new Cesium.PinBuilder(); + const pinBuilder = new PinBuilder(); const pin100 = pinBuilder - .fromText("100+", Cesium.Color.BLUE, 48) + .fromText("100+", Color.BLUE, 48) .toDataURL(); - const pin50 = pinBuilder.fromText("50+", Cesium.Color.BLUE, 48).toDataURL(); - const pin40 = pinBuilder.fromText("40+", Cesium.Color.RED, 48).toDataURL(); - const pin30 = pinBuilder.fromText("30+", Cesium.Color.RED, 48).toDataURL(); - const pin20 = pinBuilder.fromText("20+", Cesium.Color.RED, 48).toDataURL(); - const pin10 = pinBuilder.fromText("10+", Cesium.Color.RED, 48).toDataURL(); + const pin50 = pinBuilder.fromText("50+", Color.BLUE, 48).toDataURL(); + const pin40 = pinBuilder.fromText("40+", Color.RED, 48).toDataURL(); + const pin30 = pinBuilder.fromText("30+", Color.RED, 48).toDataURL(); + const pin20 = pinBuilder.fromText("20+", Color.RED, 48).toDataURL(); + const pin10 = pinBuilder.fromText("10+", Color.RED, 48).toDataURL(); // 10以内聚合图标 const singleDigitPins = Array.from({ length: 8 }); for (let i = 0; i < singleDigitPins.length; ++i) { singleDigitPins[i] = pinBuilder - .fromText(`${i + 2}`, Cesium.Color.VIOLET, 48) + .fromText(`${i + 2}`, Color.VIOLET, 48) .toDataURL(); } customStyle(); function customStyle() { - if (Cesium.defined(removeListener)) { + if (defined(removeListener)) { removeListener(); removeListener = undefined; } @@ -371,10 +412,10 @@ export default function useMapMethods(viewerRef, request) { cluster.label.show = false; cluster.billboard.show = true; cluster.billboard.id = cluster.label.id; - cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM; - cluster.billboard.horizontalOrigin = Cesium.HorizontalOrigin.CENTER; + cluster.billboard.verticalOrigin = VerticalOrigin.BOTTOM; + cluster.billboard.horizontalOrigin = HorizontalOrigin.CENTER; cluster.billboard.heightReference - = Cesium.HeightReference.CLAMP_TO_GROUND; + = HeightReference.CLAMP_TO_GROUND; cluster.billboard.disableDepthTestDistance = Number.POSITIVE_INFINITY; const thresholdMap = [ @@ -422,7 +463,7 @@ export default function useMapMethods(viewerRef, request) { await chunkedLoad(filterNull(pointList), 50, async (item) => { const name = item[options.titleKey] || ""; entityCollection.entities.add( - new Cesium.Entity({ + new Entity({ id: createId(), name, position: getPosition(item.longitude || item.lng, item.latitude || item.lat), @@ -456,7 +497,12 @@ export default function useMapMethods(viewerRef, request) { // 查询新轨迹前先移除旧轨迹,确保地图上始终只展示当前筛选结果。 const removePeopleTrajectory = () => { const viewer = getViewer(); - viewer.entities.removeById("peopleTrajectoryEntity"); + const current = peopleTrajectoryPrimitiveRef.current; + if (!current) + return; + current.removeAnimation(); + viewer.scene.primitives.remove(current.primitive); + peopleTrajectoryPrimitiveRef.current = null; }; // 将接口返回的经纬高点位绘制为动态轨迹线,少于两个点时不创建无意义的线段。 @@ -466,26 +512,34 @@ export default function useMapMethods(viewerRef, request) { return; const viewer = getViewer(); - const coordinates = points.flatMap(point => [point.lon, point.lat, point.alt || 0]); - viewer.entities.add({ - id: "peopleTrajectoryEntity", - polyline: { - positions: Cesium.Cartesian3.fromDegreesArrayHeights(coordinates), - width: 5, - material: new Cesium.TrajectoryPolylineTrailLinkMaterialProperty(viewer), - clampToGround: true, - }, + const coordinates = points.flatMap(point => [Number(point.lon), Number(point.lat)]); + const material = createTrajectoryPolylineTrailMaterial(); + const primitive = viewer.scene.primitives.add(new GroundPolylinePrimitive({ + geometryInstances: new GeometryInstance({ + id: "peopleTrajectoryPrimitive", + geometry: new GroundPolylineGeometry({ + positions: Cartesian3.fromDegreesArray(coordinates), + width: 5, + }), + }), + appearance: new PolylineMaterialAppearance({ material }), + })); + const startTime = Date.now(); + const removeAnimation = viewer.scene.preUpdate.addEventListener(() => { + material.uniforms.uTime = ((Date.now() - startTime) % 3300) / 3300; + viewer.scene.requestRender(); }); + peopleTrajectoryPrimitiveRef.current = { primitive, removeAnimation }; }; // 人员定位使用 Viewer 的全局时钟;每次开启定位仅初始化一次,避免首批点位互相重置时钟。 const initPeoplePositionClock = () => { - const start = Cesium.JulianDate.now(); + const start = JulianDate.now(); const viewer = getViewer(); peoplePositionStartTime.current = start; viewer.clock.startTime = start.clone(); viewer.clock.currentTime = start.clone(); - viewer.clock.clockRange = Cesium.ClockRange.CLAMPED; + viewer.clock.clockRange = ClockRange.CLAMPED; viewer.clock.shouldAnimate = false; return start; }; @@ -499,13 +553,13 @@ export default function useMapMethods(viewerRef, request) { throw new Error("请传入markType(扎点类型)"); const viewer = getViewer(); const clonePoint = { ...point }; - point.property = new Cesium.SampledPositionProperty(); + point.property = new SampledPositionProperty(); // 下一条实时定位到达前保持最后有效位置,避免全局时钟前进后点位短暂消失。 - point.property.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD; + point.property.forwardExtrapolationType = ExtrapolationType.HOLD; point.property.forwardExtrapolationDuration = 0; // 首批人员必须共用同一采样起点,后续实体创建不会影响已存在人员的播放时间。 const start = peoplePositionStartTime.current || initPeoplePositionClock(); - const position = Cesium.Cartesian3.fromDegrees(point.x, point.y, 0); + const position = getPosition(point.x, point.y); point.property.addSample(start, position); point.lastTime = start; point.lastIconType = point.icon_type; @@ -542,11 +596,11 @@ export default function useMapMethods(viewerRef, request) { ).image; point.lastIconType = point.icon_type; } - const position = Cesium.Cartesian3.fromDegrees(point.x, point.y, 0); - const nextTime = Cesium.JulianDate.addSeconds( + const position = getPosition(point.x, point.y); + const nextTime = JulianDate.addSeconds( point.lastTime, 10, - new Cesium.JulianDate(), + new JulianDate(), ); point.property.addSample(nextTime, position); point.lastTime = nextTime; diff --git a/src/utils/index.js b/src/utils/index.js index df1fd43..8b3c818 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -55,8 +55,8 @@ export const closeWindow = () => { window.close(); setTimeout(() => { if (!window.closed && !window.opener) { - window.location.href = "https://gbs-gateway.qhdsafety.com/"; - // window.location.href = "http://192.168.198.8:30140/"; + // window.location.href = "https://gbs-gateway.qhdsafety.com/"; + window.location.href = "http://192.168.198.8:30140/"; } }, 500); };