You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1098 lines
40 KiB

1 month ago
<template>
<div>
<div ref="cesiumContainerRef" class="cesium-container"></div>
<div class="btn-group">
<!-- <el-button class="get-view-btn" size="small" type="primary" @click="getCurrentViewInfo"
1 month ago
style="margin-right: 10px;">获取当前视角</el-button> -->
1 month ago
<!-- <el-button class="view-btn" size="small" type="success" @click="switchView('overview')"
style="margin-right: 10px;">概览视角</el-button> -->
<!-- <el-button class="view-btn" size="small" type="warning" @click="switchView('view1')"
style="margin-right: 10px;">视角1</el-button>
<el-button class="view-btn" size="small" type="warning" @click="switchView('view2')"
style="margin-right: 10px;">视角2</el-button> -->
</div>
</div>
</template>
<script setup>
4 weeks ago
import { onMounted, onBeforeUnmount, ref, watch } from 'vue';
1 month ago
import coordtransform from 'coordtransform';
import { MapApi } from "@/api/shorepower/map";
import { toRaw } from 'vue'
// ⚠️ 注意:通过 window 访问全局 Cesium 对象
const Cesium = window.Cesium;
4 weeks ago
const props = defineProps({
shorePowerList: {
type: Array,
required: true,
// 可选:添加自定义 validator(更严格)
validator(value) {
return Array.isArray(value) && value.every(item =>
typeof item === 'object' &&
item !== null &&
typeof item.position === 'string'
// 注意:无法验证是否符合 ShorePowerBerth 结构(因为 JS 没有接口)
);
}
},
shipDataList: {
type: Array,
required: true,
// 可选:添加自定义 validator(更严格)
validator(value) {
return Array.isArray(value) && value.every(item =>
typeof item === 'object' &&
item !== null &&
typeof item.shipName === 'string'
// 注意:无法验证是否符合 ShipRespVo 结构(因为 JS 没有接口)
);
}
},
1 week ago
distributionDataList: {
type: Array,
required: true,
// 可选:添加自定义 validator(更严格)
validator(value) {
return Array.isArray(value) && value.every(item =>
typeof item === 'object' &&
item !== null &&
typeof item.position === 'string'
// 注意:无法验证是否符合 ShorePowerBerth 结构(因为 JS 没有接口)
);
}
},
4 weeks ago
onInstanceClick: {
type: Function,
default: (data) => { }
1 week ago
},
getDeviceStatus: {
type: Function,
default: (deviceIds) => { }
4 weeks ago
}
})
4 weeks ago
// 自定义流动线材质类
class PolylineFlowMaterialProperty {
constructor(options) {
options = options || {};
this._definitionChanged = new Cesium.Event();
this._color = undefined;
this._colorSubscription = undefined;
this.color = options.color || Cesium.Color.YELLOW;
this.duration = options.duration || 2000; // 加快速度,从2000ms减少到1000ms
this.percent = options.percent || 0.1;
this.gradient = options.gradient || 0.01;
this._time = (new Date()).getTime();
}
get isConstant() {
return false;
}
get definitionChanged() {
return this._definitionChanged;
}
getType() {
return 'PolylineFlow';
}
getValue(time, result) {
if (!result) {
result = {};
}
result.color = Cesium.Property.getValueOrClonedDefault(this._color, time, Cesium.Color.WHITE, result.color);
result.image = undefined;
result.time = ((new Date()).getTime() - this._time) % this.duration / this.duration;
result.percent = this.percent;
result.gradient = this.gradient;
return result;
}
equals(other) {
return this === other ||
(other instanceof PolylineFlowMaterialProperty &&
Cesium.Property.equals(this._color, other._color) &&
this.duration === other.duration &&
this.percent === other.percent &&
this.gradient === other.gradient);
}
}
Object.defineProperties(PolylineFlowMaterialProperty.prototype, {
color: Cesium.createPropertyDescriptor('color')
});
// 注册自定义材质
Cesium.Material.PolylineFlowType = 'PolylineFlow';
Cesium.Material._materialCache.addMaterial(Cesium.Material.PolylineFlowType, {
fabric: {
type: Cesium.Material.PolylineFlowType,
uniforms: {
color: new Cesium.Color(1.0, 1.0, 0.0, 1.0),
time: 0,
percent: 0.1,
gradient: 0.01
},
source: `
czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
float t = time * 3.0; // 调整箭头速度
// 基础线段颜色,确保线条始终可见
vec3 baseColor = color.rgb;
// 创建箭头效果
float alpha = 0.2; // 基础透明度
// 创建多个移动的箭头
float arrowPos1 = mod(t - st.s * 5.0, 5.0);
float arrow1 = smoothstep(0.7, 0.9, arrowPos1) * (1.0 - smoothstep(0.9, 1.1, arrowPos1));
float arrowHead1 = smoothstep(0.4, 0.9, arrowPos1) * (1.0 - smoothstep(0.9, 1.3, arrowPos1));
float arrowPos2 = mod(t - st.s * 5.0 + 2.5, 5.0);
float arrow2 = smoothstep(0.7, 0.9, arrowPos2) * (1.0 - smoothstep(0.9, 1.1, arrowPos2));
float arrowHead2 = smoothstep(0.4, 0.9, arrowPos2) * (1.0 - smoothstep(0.9, 1.3, arrowPos2));
// 组合箭头效果
float arrowEffect = max(max(arrow1, arrowHead1), max(arrow2, arrowHead2));
// 应用箭头效果到透明度
alpha = 0.2 + arrowEffect * 0.8;
material.alpha = alpha;
material.diffuse = baseColor;
return material;
}
`
},
translucent: function (material) {
return true;
}
});
1 month ago
let viewer = null;
const cesiumContainerRef = ref(null); // 引用 DOM 元素
const history = ref(null); // 历史数据
const dataWithModels = ref([]); // 存储带有模型实例的数据列表
// 定义预设视角参数
const presetViews = {
overview: {
3 weeks ago
"longitude": 118.62637973547224,
"latitude": 38.75189077356901,
"height": 18603.526551007664,
"heading": 354.4493407804326,
"pitch": -33.907145214928995,
"roll": 359.9866190017743
1 month ago
}
};
1 month ago
// 岸电箱点击回调函数
let onElectricalBoxClick = null;
4 weeks ago
let globalModelInstance = null
1 month ago
// 设置岸电箱点击回调函数的方法
const setElectricalBoxClickCallback = (callback) => {
onElectricalBoxClick = callback;
};
1 month ago
const createView = (obj) => {
return {
destination: Cesium.Cartesian3.fromDegrees(obj.longitude, obj.latitude, obj.height),
orientation: {
heading: Cesium.Math.toRadians(obj.heading),
pitch: Cesium.Math.toRadians(obj.pitch),
roll: Cesium.Math.toRadians(obj.roll)
}
}
}
3 weeks ago
// 公用函数:创建带颜色和闪烁效果的标签(无缩放,有光晕效果)
const createPulsingLabel = (text, color) => {
return {
text: text,
font: '28px sans-serif',
fillColor: new Cesium.CallbackProperty(function (time, result) {
// t 会随着时间不断变化
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
// 通过改变透明度实现闪烁效果
const alpha = 0.7 + Math.sin(t * 4) * 0.3; // 透明度在 0.4-1.0 之间变化
return color.withAlpha(alpha);
}, false),
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
disableDepthTest: true,
scaleByDistance: new Cesium.NearFarScalar(1000, 1.0, 50000, 0.1)
// 移除了 scale 属性以保持文字大小不变
};
};
1 month ago
onMounted(async () => {
if (!Cesium) {
console.error("Cesium 对象未找到,请检查 index.html 文件!");
return;
}
// 步骤 1: 初始化 Viewer 并配置为使用高德地图影像和平坦地形
try {
viewer = new Cesium.Viewer(cesiumContainerRef.value, {
// **核心设置:禁用所有默认影像,我们将手动添加高德地图**
imageryProvider: false,
1 month ago
// infoBox: true,
1 month ago
// **使用平坦地形,避免依赖 Cesium Ion**
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
// sceneMode: Cesium.SceneMode.COLUMBUS_VIEW,
// 禁用所有不必要的UI控件
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
sceneModePicker: false,
navigationHelpButton: false,
infoBox: false,
fullscreenButton: false,
homeButton: false,
// 启用抗锯齿和其他渲染优化
contextOptions: {
webgl: {
antialias: true
}
}
});
// 禁用所有鼠标和键盘的相机控制操作
/* viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;
viewer.scene.screenSpaceCameraController.enableTilt = false;
viewer.scene.screenSpaceCameraController.enableLook = false; */
// 渲染优化设置
// 启用抗锯齿
viewer.scene.postProcessStages.fxaa.enabled = true;
// 启用深度测试,确保模型能够被地形正确遮挡
viewer.scene.globe.depthTestAgainstTerrain = true;
// 启用时钟动画,确保模型能够根据时间更新
viewer.clock.shouldAnimate = true;
1 month ago
// 相机控制设置
const controller = viewer.scene.screenSpaceCameraController;
// 设置最大缩放距离(相机到地球中心的最大距离)
// 500,000.0 米 = 500 公里
const MAX_DISTANCE = 500000.0;
// 启用缩放,然后设置其最大限制
controller.enableZoom = true;
controller.maximumZoomDistance = MAX_DISTANCE;
// (可选)同时限制最小缩放距离,防止滚轮进入模型内部
// 例如,限制最小距离为 1000.0 米
4 weeks ago
const MIN_HEIGHT = 1200;
controller.minimumZoomDistance = MIN_HEIGHT - 100;
const canvas = viewer.scene.canvas;
canvas.addEventListener(
"wheel",
(e) => {
const height = viewer.camera.positionCartographic.height;
// 只拦截“继续向内”
if (height <= MIN_HEIGHT && e.deltaY < 0) {
e.preventDefault();
e.stopPropagation();
}
},
{
passive: false,
capture: true
}
);
1 month ago
// viewer.scene.screenSpaceCameraController.minimumZoomDistance = 200;
1 month ago
// 步骤 2: 添加高德地图卫星影像底图
const gaodeImage = new Cesium.UrlTemplateImageryProvider({
url: "https://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ['1', '2', '3', '4'],
layer: 'gaodeImgLayer',
style: 'default',
format: 'image/png',
maximumLevel: 16
});
const dataInfo = history.value ? history.value : await MapApi.getAllData()
1 week ago
console.log(dataInfo)
4 weeks ago
const shipData = props.shipDataList
const shorePowerList = props.shorePowerList
1 week ago
const distributionDataList = props.distributionDataList
1 month ago
4 weeks ago
// 步骤 3: 将图层添加到 Viewer 中
viewer.imageryLayers.addImageryProvider(gaodeImage);
// const addStoreP
1 month ago
4 weeks ago
// 调用通用函数创建标记并获取带有模型实例的数据
// dataWithModels.value = addMarkersFromDataInfo(dataInfo);
const createMarker = () => {
const modelInstance = []
const labelInstance = []
const oneTitleInstance = []
const twoTitleInstance = []
2 weeks ago
const tempShorePowerInstance = []
1 week ago
const tempDistributionBoxInstance = []
2 weeks ago
const ShorePowerAndShipInstance = []
1 week ago
const distributionBoxAndShorePowerInstance = []
distributionDataList.forEach((item) => {
const itemDistribution = dataInfo.filter(mapitem => (item.id === mapitem.parentId) && mapitem.type === 3)
const result = itemDistribution
.filter(item => {
try {
const data = JSON.parse(item.data);
return data.type === 'icon';
} catch (e) {
return false; // 忽略无法解析的 data
}
})
.map(item => JSON.parse(item.data));
1 week ago
// console.log(result)
1 week ago
if (!result || result.length === 0) return
const dataObj = result[0]
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.gcj02towgs84(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
latitude = wgsLat;
longitude = wgsLon;
const xposition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 1);
console.log(xposition)
const xelectricalBoxModel = viewer.entities.add({
name: '配电箱',
position: xposition,
// 添加唯一标识用于识别点击事件
properties: {
modelType: 'distribution_box',
data: item
},
orientation: Cesium.Transforms.headingPitchRollQuaternion(
xposition,
new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(dataObj.rotationAngle), // 航向角 (绕Z轴旋转)
Cesium.Math.toRadians(0), // 俯仰角 (绕Y轴旋转)
Cesium.Math.toRadians(0) // 翻滚角 (绕X轴旋转)
)
),
model: {
uri: '/model/electrical_box.glb',
scale: 2,
enableDepthTest: true,
backFaceCulling: true
},
});
tempDistributionBoxInstance.push({ distributionId: item.id, distributionBoxModel: xelectricalBoxModel })
modelInstance.push({
model: xelectricalBoxModel,
type: 'shorepower_box',
data: item
})
const MODEL_HEIGHT_METERS = 80;
const cartographic = Cesium.Cartographic.fromCartesian(xposition);
const labelAltitude = cartographic.height + MODEL_HEIGHT_METERS;
const labelPosition = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
labelAltitude
);
const xelectricalBoxLabel = viewer.entities.add({
// 使用 3D 空间计算出的新位置
position: labelPosition,
properties: {
modelType: 'shorepower_box',
data: item
},
name: '岸电箱 Label',
label: {
text: item.name ? item.name.replace(/(.*)#(.*)/g, '$1#\n$2') : '',
font: '24px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// *** 关键设置 1:禁用深度测试,确保标签显示在模型上方 ***
// 确保它不会被模型或地球遮挡。
disableDepthTest: true,
// 关键设置 2:移除 pixelOffset(因为我们使用了 3D 位置偏移)
// pixelOffset: new Cesium.Cartesian2(0, 0),
// 关键设置 3:控制文字大小随距离缩放 (实现“真实大小”效果)
// 使用 near/far scalar 控制,避免缩放地图时文字过大
scaleByDistance: new Cesium.NearFarScalar(1000, 1.0, 50000, 0.1)
// ⚠️ 请根据您的场景调整这四个参数
}
});
})
console.log(distributionDataList)
4 weeks ago
shorePowerList.forEach((item) => {
1 week ago
// console.log(item.totalPowerDeviceId)
props.getDeviceStatus(item.totalPowerDeviceId)
4 weeks ago
const itemShipInfo = dataInfo.filter(mapitem => (item.id === mapitem.parentId) && mapitem.type === 5)
1 week ago
// console.log(item)
4 weeks ago
const result = itemShipInfo
.filter(item => {
1 month ago
try {
4 weeks ago
const data = JSON.parse(item.data);
return data.type === 'icon' && data.icon.includes('interface');
} catch (e) {
return false; // 忽略无法解析的 data
1 month ago
}
4 weeks ago
})
.map(item => JSON.parse(item.data));
if (!result || result.length === 0) return
const dataObj = result[0]
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
latitude = wgsLat;
longitude = wgsLon;
const xposition = Cesium.Cartesian3.fromDegrees(wgsCoords[0], wgsCoords[1], 1);
const xelectricalBoxModel = viewer.entities.add({
name: '岸电箱',
position: xposition,
// 添加唯一标识用于识别点击事件
properties: {
modelType: 'shorepower_box',
data: item
},
orientation: Cesium.Transforms.headingPitchRollQuaternion(
xposition,
new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(dataObj.rotationAngle), // 航向角 (绕Z轴旋转)
Cesium.Math.toRadians(0), // 俯仰角 (绕Y轴旋转)
Cesium.Math.toRadians(0) // 翻滚角 (绕X轴旋转)
)
),
model: {
uri: '/model/electrical_box.glb',
scale: 1.5,
enableDepthTest: true,
backFaceCulling: true
},
});
2 weeks ago
tempShorePowerInstance.push({ storePowerId: item.id, storePowerInstance: xelectricalBoxModel })
1 week ago
const foundShorePower = tempDistributionBoxInstance.find(tempDistributionBoxItem => tempDistributionBoxItem.distributionId === item.equipmentId);
if (foundShorePower) {
distributionBoxAndShorePowerInstance.push({
...foundShorePower,
storePowerInstance: xelectricalBoxModel,
storePowerId: item.id,
status: item.storePowerStatus == '在⽤' ? 1 : 0
});
4 weeks ago
}
1 month ago
4 weeks ago
modelInstance.push({
model: xelectricalBoxModel,
type: 'shorepower_box',
data: item
})
const MODEL_HEIGHT_METERS = 80;
const cartographic = Cesium.Cartographic.fromCartesian(xposition);
const labelAltitude = cartographic.height + MODEL_HEIGHT_METERS;
const labelPosition = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
labelAltitude
);
const xelectricalBoxLabel = viewer.entities.add({
// 使用 3D 空间计算出的新位置
position: labelPosition,
4 weeks ago
properties: {
modelType: 'shorepower_box',
data: item
},
4 weeks ago
name: '岸电箱 Label',
label: {
3 weeks ago
text: item.name ? item.name.replace(/(.*)#(.*)/g, '$1#\n$2') : '',
font: '20px sans-serif',
4 weeks ago
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
disableDepthTest: true,
scaleByDistance: new Cesium.NearFarScalar(1000, 1.0, 50000, 0.1)
1 month ago
}
4 weeks ago
});
1 week ago
/* let statusImage = null;
4 weeks ago
if ([3, 4, 6].includes(item.status)) {
// 50%概率:不显示状态图标
statusImage = null;
} else if ([1, 2, 8].includes(item.status)) {
// 25%概率:显示故障图标
statusImage = '/img/故障.png';
} else {
// 25%概率:显示离线图标
statusImage = '/img/离线.png';
}
// 只有当需要显示状态图标时才添加实体
if (statusImage) {
const overlayBillboard = viewer.entities.add({
position: xposition,
properties: {
modelType: 'shorepower_box',
data: item
},
billboard: {
image: statusImage,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scale: new Cesium.CallbackProperty(function (time, result) {
// t 会随着时间不断变化
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.2 + Math.sin(t * 4) * 0.1; // (基准0.6, 幅度±0.2)
return pulse;
}, false)
4 weeks ago
}
4 weeks ago
});
1 week ago
} */
4 weeks ago
labelInstance.push(xelectricalBoxLabel)
})
shipData.forEach(item => {
1 week ago
// console.log(item.shorePower.id)
4 weeks ago
const itemShipInfo = dataInfo.filter(shipItem => (item.shorePower.id === shipItem.parentId) && shipItem.type === 5)
const result = itemShipInfo
.filter(item => {
try {
const data = JSON.parse(item.data);
return data.type === 'icon' && data.icon.includes('ship_green');
} catch (e) {
return false; // 忽略无法解析的 data
4 weeks ago
}
4 weeks ago
})
.map(item => JSON.parse(item.data));
if (!result || result.length === 0) return
const dataObj = result[0]
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
latitude = wgsLat;
longitude = wgsLon;
const xposition = Cesium.Cartesian3.fromDegrees(wgsCoords[0], wgsCoords[1], 15);
const xelectricalBoxModel = viewer.entities.add({
// name: '岸电箱',
position: xposition,
// 添加唯一标识用于识别点击事件
properties: {
modelType: 'ship',
data: item
},
orientation: Cesium.Transforms.headingPitchRollQuaternion(
xposition,
new Cesium.HeadingPitchRoll(
Cesium.Math.toRadians(dataObj.rotationAngle), // 航向角 (绕Z轴旋转)
Cesium.Math.toRadians(0), // 俯仰角 (绕Y轴旋转)
Cesium.Math.toRadians(0) // 翻滚角 (绕X轴旋转)
)
),
model: {
uri: '/model/cargo_ship_07.glb',
scale: 1.5,
enableDepthTest: true,
backFaceCulling: true
},
});
2 weeks ago
const foundShorePower = tempShorePowerInstance.find(tempShorePowerItem => tempShorePowerItem.storePowerId === item.shorePower.id);
if (foundShorePower) {
ShorePowerAndShipInstance.push({
...foundShorePower,
shipInstance: xelectricalBoxModel,
shipId: item.shipBasicInfo.id,
status: item.shipStatus
});
}
4 weeks ago
modelInstance.push({
model: xelectricalBoxModel,
type: 'ship',
data: item
})
3 weeks ago
let labelColor = null
1 week ago
/* let statusImage = null;
3 weeks ago
if (['在用', '正常'].includes(item.shipStatus)) {
4 weeks ago
// statusImage = null;
4 weeks ago
} else if (item.shipStatus === '超容') {
4 weeks ago
statusImage = '/img/故障.png';
3 weeks ago
labelColor = Cesium.Color.YELLOW; // 超容时红色
4 weeks ago
} else {
2 weeks ago
statusImage = '/img/故障.png';
labelColor = Cesium.Color.YELLOW; // 未连接或离线时红色
4 weeks ago
}
3 weeks ago
if (item.applyInfo.reason !== 0) {
2 weeks ago
statusImage = '/img/未连接.png';
labelColor = Cesium.Color.RED;
3 weeks ago
}
4 weeks ago
// 只有当需要显示状态图标时才添加实体
if (statusImage) {
const overlayBillboard = viewer.entities.add({
position: xposition,
properties: {
modelType: 'ship',
data: item
},
billboard: {
image: statusImage,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scale: new Cesium.CallbackProperty(function (time, result) {
// t 会随着时间不断变化
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.3 + Math.sin(t * 4) * 0.1; // (基准0.6, 幅度±0.2)
return pulse;
}, false)
}
});
1 week ago
} */
4 weeks ago
3 weeks ago
const MODEL_HEIGHT_METERS = 80;
const cartographic = Cesium.Cartographic.fromCartesian(xposition);
const labelAltitude = cartographic.height + MODEL_HEIGHT_METERS;
const labelPosition = Cesium.Cartesian3.fromRadians(
cartographic.longitude,
cartographic.latitude,
labelAltitude
);
const xelectricalBoxLabel = viewer.entities.add({
// 使用 3D 空间计算出的新位置
position: labelPosition,
name: '船',
properties: {
modelType: 'ship',
data: item
},
label: !labelColor ? {
text: item.shipBasicInfo.name,
font: '28px sans-serif',
fillColor: Cesium.Color.LIME,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
disableDepthTest: true,
scaleByDistance: new Cesium.NearFarScalar(1000, 1.0, 50000, 0.1)
} : createPulsingLabel(item.shipBasicInfo.name, labelColor)
});
labelInstance.push(xelectricalBoxLabel)
4 weeks ago
})
// 其他信息
dataInfo.forEach(item => {
if (['华能曹妃甸码头名字', '华电储运码头名字', '国投曹妃甸码头名字'].includes(item.name)) {
const dataObj = JSON.parse(item.data)
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
// latitude = wgsLat;
// longitude = wgsLon;
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 0);
const label = viewer.entities.add({
position: labelPosition, // 船上方约10米
1 month ago
label: {
4 weeks ago
text: dataObj.name || `Marker-${item.id || index}`,
3 weeks ago
font: '24px sans-serif', // 增大字体
fillColor: Cesium.Color.WHITE, // 使用醒目的黄色
outlineColor: Cesium.Color.BLUE, // 使用红色边框
outlineWidth: 1, // 增加边框宽度
1 month ago
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
4 weeks ago
pixelOffset: new Cesium.Cartesian2(0, -30), // 偏移量,避免与模型重叠
disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见
1 month ago
}
4 weeks ago
});
twoTitleInstance.push(label)
1 month ago
4 weeks ago
}
})
dataInfo.forEach(item => {
if (['港区'].includes(item.name)) {
const dataObj = JSON.parse(item.data)
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
wgsLon = wgsCoords[0];
wgsLat = wgsCoords[1];
// latitude = wgsLat;
// longitude = wgsLon;
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 0);
const label = viewer.entities.add({
position: labelPosition, // 船上方约10米
label: {
text: dataObj.name || `Marker-${item.id || index}`,
4 weeks ago
font: `${dataObj?.fontSize || 16}px sans-serif`,
4 weeks ago
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -30), // 偏移量,避免与模型重叠
disableDepthTestDistance: Number.POSITIVE_INFINITY // 始终可见
}
});
oneTitleInstance.push(label)
4 weeks ago
4 weeks ago
}
})
return {
modelInstance,
labelInstance,
oneTitleInstance,
2 weeks ago
twoTitleInstance,
1 week ago
ShorePowerAndShipInstance,
distributionBoxAndShorePowerInstance,
4 weeks ago
}
4 weeks ago
}
1 week ago
const { modelInstance, labelInstance, oneTitleInstance, twoTitleInstance, ShorePowerAndShipInstance, distributionBoxAndShorePowerInstance } = createMarker()
4 weeks ago
globalModelInstance = modelInstance
// const xitemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
4 weeks ago
// 创建流动线材质
2 weeks ago
function createFlowMaterial(status) {
4 weeks ago
return new PolylineFlowMaterialProperty({
2 weeks ago
color: status ? Cesium.Color.LIME : Cesium.Color.YELLOW,
4 weeks ago
duration: 5000, // 动画持续时间(毫秒)
2 weeks ago
percent: 1, // 流动部分占整个线条的比例
gradient: 1 // 流动部分的渐变
4 weeks ago
});
}
2 weeks ago
function createShipAndShorePowerLine() {
ShorePowerAndShipInstance.forEach((entityItem, idx) => {
const shipEntity = entityItem.shipInstance
const shorePowerEntity = entityItem.storePowerInstance
// 检查实体是否存在以及是否有position属性
if (!shipEntity || !shorePowerEntity || !shipEntity.position || !shorePowerEntity.position) {
1 week ago
// console.warn('Missing entity or position property:', entityItem);
2 weeks ago
return;
}
if (entityItem.status !== '在用') return;
const lineEntity = viewer.entities.add({
name: `ship_shorePower_line_${idx}`,
polyline: {
positions: new Cesium.CallbackProperty(() => {
try {
// 获取主岸电箱的位置
const mainPosition = shorePowerEntity.position.getValue(viewer.clock.currentTime);
// 获取目标实体的位置
const targetPosition = shipEntity.position.getValue(viewer.clock.currentTime);
// 检查位置是否有效
if (!mainPosition || !targetPosition) {
return [];
}
// 返回包含两个点的数组(起点和终点)
return [mainPosition, targetPosition];
} catch (error) {
console.error('Error getting entity positions:', error);
return [];
}
}, false),
width: 3,
material: createFlowMaterial(entityItem.status === '在用' ? 1 : 0),
// 启用深度测试,使线条能够被地形遮挡
enableDepthTest: true
}
});
});
}
1 week ago
function createDistributionBoxAndShorePowerLine() {
distributionBoxAndShorePowerInstance.forEach((entityItem, idx) => {
1 week ago
// console.log('distributionBoxAndShorePowerInstance', entityItem)
1 week ago
const distributionBoxEntity = entityItem.distributionBoxModel
const shorePowerEntity = entityItem.storePowerInstance
// const entity = entityItem.xelectricalBoxModel
// 创建唯一的线实体ID
const lineEntity = viewer.entities.add({
name: `connection_line_${idx}`,
polyline: {
positions: new Cesium.CallbackProperty(() => {
// 获取主岸电箱的位置
const mainPosition = distributionBoxEntity.position.getValue(viewer.clock.currentTime);
// 获取目标实体的位置
const targetPosition = shorePowerEntity.position.getValue(viewer.clock.currentTime);
4 weeks ago
1 week ago
// 返回包含两个点的数组(起点和终点)
return [mainPosition, targetPosition];
}, false),
width: 3,
material: createFlowMaterial(0),
// 启用深度测试,使线条能够被地形遮挡
enableDepthTest: true
4 weeks ago
}
});
1 week ago
});
4 weeks ago
}
1 week ago
createShipAndShorePowerLine()
createDistributionBoxAndShorePowerLine()
4 weeks ago
1 month ago
// 设置初始视角
viewer.camera.flyTo(createView(presetViews.overview));
// 设置显示高度阈值
const SHOW_HEIGHT = 30000; // 高度 > 5000m 才显示模型等信息
viewer.camera.changed.addEventListener(() => {
// 这个函数会在相机的任何属性发生变化后立即被调用。
const height = viewer.camera.positionCartographic.height;
// console.log("相机高度(即缩放级别)已变化:", height.toFixed(2), "米");
// 控制模型显示/隐藏
const visible = height <= SHOW_HEIGHT; // 1000m内才显示模型等信息
4 weeks ago
twoTitleInstance.forEach(entity => {
1 month ago
if (entity.show !== visible) {
entity.show = visible;
}
});
4 weeks ago
oneTitleInstance.forEach(entity => {
if (entity.show === visible) {
entity.show = !visible;
}
});
1 month ago
// 其他缩放级别逻辑
if (height < 100000) {
// 放大到街景级别
console.log("当前处于近距离缩放级别。");
} else if (height > 5000000) {
// 缩小到全球级别
console.log("当前处于远距离缩放级别。");
}
});
4 weeks ago
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
const pickedObject = viewer.scene.pick(movement.position);
1 month ago
4 weeks ago
// 获取点击位置的经纬度
const cartesian = viewer.scene.pickPosition(movement.position);
if (Cesium.defined(cartesian)) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
1 week ago
const gcj02 = coordtransform.wgs84togcj02(longitude, latitude);
console.log(`当前点击位置的经纬度: 经度 ${gcj02[0]}°, 纬度 ${gcj02[1]}°`);
4 weeks ago
}
if (Cesium.defined(pickedObject)) {
const entity = pickedObject.id; // 获取实体对象
if (entity && entity.properties) {
// 从 properties 中读取自定义数据
const type = entity.properties.modelType.getValue();
console.log(`点击了一个实体。类型是: ${type}`);
1 week ago
if (type === 'shorepower_box' || type === 'ship' || type === 'distribution_box') {
4 weeks ago
console.log(entity.properties.data.getValue())
// onElectricalBoxClick(entity.properties.data.getValue())
// 触发自定义点击事件
props.onInstanceClick({
type,
data: entity.properties.data.getValue()
});
}
// 如果您存储了 data: itemWithModel (一个复杂对象)
// const fullData = entity.properties.data.getValue();
// ... 接着执行您的业务逻辑,如弹出信息窗口等 ...
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
1 month ago
// 确保启用场景的鼠标事件
viewer.scene.screenSpaceCameraController.enableInputs = true;
// 启用拾取透明物体
viewer.scene.useDepthPicking = true;
console.log('Cesium 高德地图 Viewer 初始化成功');
} catch (error) {
console.error('Cesium Viewer 初始化失败:', error);
}
});
4 weeks ago
// 监听shorePowerList的变化
watch(
() => props.shorePowerList,
(newList, oldList) => {
console.log('ShorePowerList updated:', newList);
if (newList && newList.length > 0) {
// 当shorePowerList更新时,可以在这里添加相应的处理逻辑
// 例如:更新地图上的岸电箱标记、刷新连接线等
console.log('处理shorePowerList数据:', newList);
// 这里可以调用相关的更新函数来处理新数据
}
},
{ deep: true } // 深度监听数组和对象的变化
);
1 month ago
// 视角切换方法
const switchView = (viewName) => {
const viewParams = presetViews[viewName];
if (viewParams && viewer) {
viewer.camera.flyTo({
...createView(viewParams),
duration: 2.0 // 动画持续时间(秒)
});
}
};
1 month ago
/**
* 切换到指定模型视图并允许设置自定义的飞离距离
* @param {Cesium.Entity} view - 要飞向的 Entity 实例
* @param {number} [extraDistance=15000.0] - 额外的飞离距离 ()
* @param {number} [estimatedRadius=500.0] - 用于计算视角范围的估计模型半径 ()
*/
4 weeks ago
const switchModelView = (item, extraDistance = 1000, estimatedRadius = 1000.0) => {
const clickItem = toRaw(item)
1 week ago
// console.log(item)
4 weeks ago
let entityInstance = null
if (clickItem.type === 'shorepower_box') {
console.log(clickItem.item)
const instance = globalModelInstance.filter((item) => item.type === 'shorepower_box').find(twoItem => twoItem.data
.id === clickItem.item.id)
console.log(instance)
if (instance.model) {
entityInstance = instance.model
}
} else if (clickItem.type === 'ship') {
console.log(clickItem.item)
console.log(globalModelInstance)
const instance = globalModelInstance.filter((item) => item.type === 'ship').find(twoItem => twoItem.data.shipBasicInfo
.id === clickItem.item.shipBasicInfo.id)
console.log(instance)
if (instance.model) {
entityInstance = instance.model
}
// 处理船舶点击
}
// return
1 month ago
console.log('目标模型实体:', entityInstance);
if (viewer && entityInstance && entityInstance.position) {
// 1. 获取模型当前的世界坐标
// 必须使用 getValue(viewer.clock.currentTime) 来获取当前准确的位置
const position = entityInstance.position.getValue(viewer.clock.currentTime);
if (!position) {
console.warn("无法获取模型位置,执行默认 flyTo。");
viewer.flyTo(entityInstance, { duration: 3.0 });
return;
}
// 2. 手动创建边界球 (因为 Entity 实例没有 readyPromise 或 boundingSphere 属性)
// 使用一个估计的半径来计算视角范围
const manualBoundingSphere = new Cesium.BoundingSphere(
position,
estimatedRadius // 使用估计半径
);
// 3. 定义相机相对于边界球的偏移量 (Heading, Pitch, Range)
const cameraOffset = new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(0.0), // Heading (航向), 0度朝北
Cesium.Math.toRadians(-45.0), // Pitch (俯仰), -45度斜向下看
manualBoundingSphere.radius + extraDistance // Range (距离) = 估计半径 + 额外距离
);
// 4. 执行飞行动作
viewer.camera.flyToBoundingSphere(
manualBoundingSphere,
{
offset: cameraOffset,
duration: 3.0 // 飞行时间
}
);
console.log(`飞往模型中心,距离模型中心 ${(manualBoundingSphere.radius + extraDistance) / 1000} 公里。`);
} else if (viewer && entityInstance) {
// 作为一个简单的 fallback 选项
viewer.flyTo(entityInstance, { duration: 3.0 });
console.warn("模型位置属性无效,执行默认 flyTo。");
} else {
console.error("Cesium Viewer 或模型实例无效。");
1 month ago
}
};
// 获取当前视角信息的方法
const getCurrentViewInfo = () => {
if (!viewer) {
console.warn('Viewer 尚未初始化');
return null;
}
const camera = viewer.camera;
const position = camera.position; // 世界笛卡尔坐标系 x,y,z
const cartographic = Cesium.Cartographic.fromCartesian(position);
console.log(
{
longitude: Cesium.Math.toDegrees(cartographic.longitude),
latitude: Cesium.Math.toDegrees(cartographic.latitude),
height: cartographic.height,
heading: Cesium.Math.toDegrees(camera.heading),
pitch: Cesium.Math.toDegrees(camera.pitch),
roll: Cesium.Math.toDegrees(camera.roll)
}
)
};
// 暴露方法和数据给父组件使用
defineExpose({
switchView,
dataWithModels,
1 month ago
switchModelView,
setElectricalBoxClickCallback
1 month ago
});
onBeforeUnmount(() => {
// 销毁 Viewer,释放 WebGL 资源
if (viewer) {
viewer.destroy();
viewer = null;
console.log('Cesium Viewer 已销毁');
}
});
</script>
<style scoped>
.cesium-container {
width: 100%;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
}
.btn-group {
position: absolute;
top: 10px;
z-index: 1000;
display: flex;
gap: 12rpx;
}
</style>