refactor(map): 迁移至 Cesium 模块化导入

master
LiuJiaNan 2026-08-21 15:03:50 +08:00
parent 9f3bf572fd
commit 6c3cac4dc1
11 changed files with 288 additions and 314 deletions

View File

@ -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: "<git-url>",
@ -9,7 +20,7 @@ module.exports = {
// 应用后端分支名称,部署上线需要
javaGitBranch: "<branch-name>",
// 接口服务地址
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}/`),
}),
],
},
};

View File

@ -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",

View File

@ -52,6 +52,4 @@
<% const { root } = $element; %>
<div id="<%= root.id %>" style="width: 100%; height: 100%; position: relative;overflow-y: auto;"></div>
</body>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</html>

View File

@ -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 };
}

View File

@ -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();

View File

@ -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"))}_`),
)

View File

@ -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, {
// Primitive 可以直接使用 Fabric 材质,无需向 Cesium 私有材质缓存注册类型。
export default function createTrajectoryPolylineTrailMaterial() {
return new Material({
fabric: {
type: Cesium.Material.CustomMaterialType,
uniforms: {
uTime: 0,
},
source: Cesium.Material.CustomMaterialSource,
source: materialSource,
},
});
Cesium.TrajectoryPolylineTrailLinkMaterialProperty
= TrajectoryPolylineTrailLinkMaterialProperty;
});
}

View File

@ -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;
},
},
);
});
}

View File

@ -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();

View File

@ -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,25 +329,39 @@ 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({
return new GeometryInstance({
id: createId(),
wall: {
geometry: new WallGeometry({
positions,
material: new Cesium.WallPolylineTrailLinkMaterialProperty(viewer),
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);
};
@ -332,7 +369,11 @@ export default function useMapMethods(viewerRef, request) {
// 删除墙
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),
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,
material: new Cesium.TrajectoryPolylineTrailLinkMaterialProperty(viewer),
clampToGround: true,
},
}),
}),
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;

View File

@ -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);
};