|
|
@@ -1,559 +0,0 @@
|
|
|
-<template>
|
|
|
- <div class="cesium-transition-wrapper">
|
|
|
- <div ref="cesiumContainer" class="cesium-container"></div>
|
|
|
- <img
|
|
|
- v-if="microImage"
|
|
|
- ref="microOverlay"
|
|
|
- :src="microImage"
|
|
|
- class="micro-overlay"
|
|
|
- />
|
|
|
- <div class="ui-layer">
|
|
|
- <div
|
|
|
- v-if="poi"
|
|
|
- ref="poiLabel"
|
|
|
- class="html-label"
|
|
|
- :style="{ color: poi.fontColor || '#00ffff', fontSize: (poi.fontSize || 20) + 'px', opacity: 0, display: 'none' }"
|
|
|
- >
|
|
|
- {{ poi.label }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<script>
|
|
|
-import CesiumPreloader from '@/utils/cesiumPreloader';
|
|
|
-const Cesium = window.Cesium;
|
|
|
-
|
|
|
-function createOuterRingTexture(color) {
|
|
|
- const size = 1024, cx = size / 2, cy = size / 2, radius = size / 2 - 20;
|
|
|
- const lineWidth = 16, baseColorStr = color.toCssColorString();
|
|
|
- const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d');
|
|
|
- const solidCanvas = document.createElement('canvas'); solidCanvas.width = size; solidCanvas.height = size; const solidCtx = solidCanvas.getContext('2d'); solidCtx.lineWidth = lineWidth; solidCtx.strokeStyle = baseColorStr; solidCtx.shadowBlur = 10; solidCtx.shadowColor = baseColorStr; solidCtx.beginPath(); solidCtx.arc(cx, cy, radius, 0, Math.PI * 2); solidCtx.stroke();
|
|
|
- const dashedCanvas = document.createElement('canvas'); dashedCanvas.width = size; dashedCanvas.height = size; const dashedCtx = dashedCanvas.getContext('2d'); dashedCtx.lineWidth = lineWidth; dashedCtx.strokeStyle = baseColorStr; dashedCtx.setLineDash([12, 12]); dashedCtx.shadowBlur = 10; dashedCtx.shadowColor = baseColorStr; dashedCtx.beginPath(); dashedCtx.arc(cx, cy, radius, 0, Math.PI * 2); dashedCtx.stroke();
|
|
|
- const solidMask = ctx.createConicGradient(0, cx, cy); solidMask.addColorStop(0.0, 'rgba(0,0,0,0)'); solidMask.addColorStop(0.10, 'rgba(0,0,0,0)'); solidMask.addColorStop(0.18, 'rgba(0,0,0,1)'); solidMask.addColorStop(0.48, 'rgba(0,0,0,1)'); solidMask.addColorStop(0.50, 'rgba(0,0,0,0)'); solidMask.addColorStop(0.60, 'rgba(0,0,0,0)'); solidMask.addColorStop(0.68, 'rgba(0,0,0,1)'); solidMask.addColorStop(0.98, 'rgba(0,0,0,1)'); solidMask.addColorStop(1.0, 'rgba(0,0,0,0)');
|
|
|
- const dashedMask = ctx.createConicGradient(0, cx, cy); dashedMask.addColorStop(0.0, 'rgba(0,0,0,0)'); dashedMask.addColorStop(0.02, 'rgba(0,0,0,1)'); dashedMask.addColorStop(0.10, 'rgba(0,0,0,1)'); dashedMask.addColorStop(0.18, 'rgba(0,0,0,0)'); dashedMask.addColorStop(0.50, 'rgba(0,0,0,0)'); dashedMask.addColorStop(0.52, 'rgba(0,0,0,1)'); dashedMask.addColorStop(0.60, 'rgba(0,0,0,1)'); dashedMask.addColorStop(0.68, 'rgba(0,0,0,0)'); dashedMask.addColorStop(1.0, 'rgba(0,0,0,0)');
|
|
|
- ctx.drawImage(solidCanvas, 0, 0); ctx.globalCompositeOperation = 'destination-in'; ctx.fillStyle = solidMask; ctx.fillRect(0, 0, size, size);
|
|
|
- const tempCanvas = document.createElement('canvas'); tempCanvas.width = size; tempCanvas.height = size; const tempCtx = tempCanvas.getContext('2d'); tempCtx.drawImage(dashedCanvas, 0, 0); tempCtx.globalCompositeOperation = 'destination-in'; tempCtx.fillStyle = dashedMask; tempCtx.fillRect(0, 0, size, size);
|
|
|
- ctx.globalCompositeOperation = 'lighter'; ctx.drawImage(tempCanvas, 0, 0);
|
|
|
- ctx.globalCompositeOperation = 'destination-over'; ctx.shadowBlur = 40; ctx.shadowColor = color.withAlpha(0.3).toCssColorString(); ctx.strokeStyle = color.withAlpha(0.08).toCssColorString(); ctx.lineWidth = 20; ctx.beginPath(); ctx.arc(cx, cy, radius, 0, Math.PI * 2); ctx.stroke();
|
|
|
- return canvas.toDataURL();
|
|
|
-}
|
|
|
-
|
|
|
-function createScannerTexture(color) {
|
|
|
- const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; const ctx = canvas.getContext('2d');
|
|
|
- const sharpColor = color.withAlpha(1.0).toCssColorString(); ctx.lineWidth = 3; ctx.strokeStyle = sharpColor; ctx.shadowBlur = 20; ctx.shadowColor = sharpColor; ctx.beginPath(); ctx.moveTo(256, 256); ctx.lineTo(506, 256); ctx.stroke(); return canvas.toDataURL();
|
|
|
-}
|
|
|
-
|
|
|
-function createLightBeamTexture(color) {
|
|
|
- const canvas = document.createElement('canvas'); canvas.width = 64; canvas.height = 256; const ctx = canvas.getContext('2d');
|
|
|
- const gradient = ctx.createLinearGradient(0, 256, 0, 0);
|
|
|
- gradient.addColorStop(0, color.withAlpha(0.6).toCssColorString()); gradient.addColorStop(0.4, color.withAlpha(0.1).toCssColorString()); gradient.addColorStop(1, color.withAlpha(0.0).toCssColorString());
|
|
|
- ctx.fillStyle = gradient; ctx.fillRect(0, 0, 64, 256); return canvas.toDataURL();
|
|
|
-}
|
|
|
-
|
|
|
-const TWO_PI = Math.PI * 2;
|
|
|
-
|
|
|
-export default {
|
|
|
- name: 'CesiumTransition',
|
|
|
- props: {
|
|
|
- // 要高亮的省份名称
|
|
|
- province: {
|
|
|
- type: String,
|
|
|
- default: '北京市'
|
|
|
- },
|
|
|
- // 微观标记点(null 则不显示POI)
|
|
|
- poi: {
|
|
|
- type: Object,
|
|
|
- default: null
|
|
|
- },
|
|
|
- // 区域中心坐标 [lon, lat](无POI时用于相机定位)
|
|
|
- districtCenter: {
|
|
|
- type: Array,
|
|
|
- default: null
|
|
|
- },
|
|
|
- // 路网数据
|
|
|
- roads: {
|
|
|
- type: Array,
|
|
|
- default: () => [
|
|
|
- { name: "阜石路-阜成路快速路", width: 12, glowPower: 0.3, color: '#00ffff', path: [[116.18, 39.93], [116.22, 39.93], [116.26, 39.928], [116.30, 39.925]] },
|
|
|
- { name: "石景山路-复兴路", width: 12, glowPower: 0.3, color: '#ff3333', path: [[116.18, 39.907], [116.224, 39.907], [116.25, 39.906], [116.30, 39.905]] },
|
|
|
- { name: "莲石东路快速路", width: 12, glowPower: 0.3, color: '#00ff00', path: [[116.18, 39.89], [116.22, 39.888], [116.26, 39.885], [116.30, 39.88]] },
|
|
|
- { name: "西五环路", width: 10, glowPower: 0.25, color: '#ffaa00', path: [[116.205, 39.95], [116.203, 39.92], [116.202, 39.89], [116.20, 39.86]] },
|
|
|
- { name: "玉泉路", width: 10, glowPower: 0.25, color: '#cc00ff', path: [[116.25, 39.94], [116.25, 39.907], [116.248, 39.88], [116.245, 39.86]] },
|
|
|
- { name: "西四环路", width: 10, glowPower: 0.25, color: '#ffff00', path: [[116.285, 39.94], [116.283, 39.91], [116.28, 39.88], [116.278, 39.85]] }
|
|
|
- ]
|
|
|
- },
|
|
|
- // 微观区域卫星底图
|
|
|
- satelliteImage: {
|
|
|
- type: Object,
|
|
|
- default: () => ({
|
|
|
- url: './beijing-satellite.jpg',
|
|
|
- bounds: [116.10, 39.80, 116.38, 39.98] // [west, south, east, north]
|
|
|
- })
|
|
|
- },
|
|
|
- // 中国边界 GeoJSON 路径
|
|
|
- boundaryUrl: {
|
|
|
- type: String,
|
|
|
- default: './china.json'
|
|
|
- },
|
|
|
- // 微观俯冲视角高度(米)
|
|
|
- microViewRange: {
|
|
|
- type: Number,
|
|
|
- default: 10000
|
|
|
- },
|
|
|
- // 微观覆盖图片路径(淡入淡出展示)
|
|
|
- microImage: {
|
|
|
- type: String,
|
|
|
- default: null
|
|
|
- }
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- isAnimating: false
|
|
|
- };
|
|
|
- },
|
|
|
- mounted() {
|
|
|
- this._viewer = null;
|
|
|
- this._preRenderListener = null;
|
|
|
- this._chinaDataSource = null;
|
|
|
- this._chinaEffectsSource = null;
|
|
|
- this._microEffectsSource = null;
|
|
|
- this._macroAlpha = 0.0;
|
|
|
- this._destroyed = false;
|
|
|
- this._pendingTimers = [];
|
|
|
-
|
|
|
- this._coords = { start: [-15.0, 35.86, 45000000], china: [104.19, 35.86, 14000000] };
|
|
|
-
|
|
|
- this.$nextTick(async () => {
|
|
|
- if (!this.$refs.cesiumContainer) return;
|
|
|
- // 尝试复用预加载的 Viewer
|
|
|
- const preloaded = await CesiumPreloader.acquire(this.$refs.cesiumContainer);
|
|
|
- this.initCesium(preloaded);
|
|
|
- if (!preloaded) {
|
|
|
- await this.waitForGlobeReady();
|
|
|
- }
|
|
|
- if (!this._destroyed) this.startTransition();
|
|
|
- });
|
|
|
- },
|
|
|
- beforeDestroy() {
|
|
|
- this._destroyed = true;
|
|
|
- this._pendingTimers.forEach(id => clearTimeout(id));
|
|
|
- this._pendingTimers = [];
|
|
|
- if (this._viewer) {
|
|
|
- if (this._preRenderListener) {
|
|
|
- this._viewer.scene.preRender.removeEventListener(this._preRenderListener);
|
|
|
- }
|
|
|
- // Cesium destroy() 内部会 removeChild,但 Vue 可能已移除 DOM,需要先确保容器在文档中
|
|
|
- const cesiumWidget = this._viewer.cesiumWidget && this._viewer.cesiumWidget.container;
|
|
|
- if (cesiumWidget && !cesiumWidget.parentNode) {
|
|
|
- document.body.appendChild(cesiumWidget);
|
|
|
- }
|
|
|
- try {
|
|
|
- this._viewer.destroy();
|
|
|
- } catch (e) {
|
|
|
- // 忽略 DOM 已被 Vue 移除导致的 removeChild 错误
|
|
|
- }
|
|
|
- this._viewer = null;
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- waitForGlobeReady() {
|
|
|
- return new Promise(resolve => {
|
|
|
- const startTime = Date.now();
|
|
|
- const maxWait = 3000; // 最多等待3秒,避免卡住
|
|
|
- const check = () => {
|
|
|
- if (this._destroyed) { resolve(); return; }
|
|
|
- if ((this._viewer && this._viewer.scene.globe.tilesLoaded) || (Date.now() - startTime > maxWait)) {
|
|
|
- resolve();
|
|
|
- } else {
|
|
|
- requestAnimationFrame(check);
|
|
|
- }
|
|
|
- };
|
|
|
- requestAnimationFrame(check);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- initCesium(preloadedViewer) {
|
|
|
- this._baseGold = Cesium.Color.GOLD;
|
|
|
- this._baseWhite = Cesium.Color.WHITE;
|
|
|
- this._baseBlack = Cesium.Color.BLACK;
|
|
|
- this._dynamicGold = this._baseGold.withAlpha(0);
|
|
|
- this._dynamicWhite = this._baseWhite.withAlpha(0);
|
|
|
- this._dynamicBlack = this._baseBlack.withAlpha(0);
|
|
|
- this._poiThemeColor = Cesium.Color.fromCssColorString(
|
|
|
- (this.poi && this.poi.themeColor) || '#00bfff'
|
|
|
- );
|
|
|
-
|
|
|
- // 将 props 中的 roads 转为 Cesium 颜色对象
|
|
|
- this._majorRoads = this.roads.map(r => ({
|
|
|
- ...r,
|
|
|
- color: Cesium.Color.fromCssColorString(r.color)
|
|
|
- }));
|
|
|
-
|
|
|
- if (preloadedViewer) {
|
|
|
- // 复用预加载的 Viewer(瓦片已渲染好)
|
|
|
- this._viewer = preloadedViewer;
|
|
|
- } else {
|
|
|
- // 降级:从头创建
|
|
|
- this._viewer = new Cesium.Viewer(this.$refs.cesiumContainer, {
|
|
|
- animation: false, timeline: false, baseLayerPicker: false, geocoder: false,
|
|
|
- homeButton: false, sceneModePicker: false, navigationHelpButton: false, infoBox: false,
|
|
|
- fullscreenButton: false, selectionIndicator: false, shadows: false, shouldAnimate: false,
|
|
|
- requestRenderMode: false,
|
|
|
- imageryProvider: new Cesium.UrlTemplateImageryProvider({
|
|
|
- url: './tiles/{z}/{y}/{x}.jpg',
|
|
|
- maximumLevel: 12
|
|
|
- })
|
|
|
- });
|
|
|
-
|
|
|
- this._viewer.cesiumWidget.creditContainer.style.display = "none";
|
|
|
- }
|
|
|
-
|
|
|
- const scene = this._viewer.scene;
|
|
|
- scene.fog.enabled = false;
|
|
|
- scene.skyAtmosphere.show = false;
|
|
|
- scene.globe.showGroundAtmosphere = false;
|
|
|
- scene.globe.enableLighting = true;
|
|
|
- scene.globe.tileCacheSize = 300;
|
|
|
- scene.globe.maximumScreenSpaceError = 1.5;
|
|
|
-
|
|
|
- this._viewer.clock.currentTime = Cesium.JulianDate.fromDate(new Date('2026-01-01T01:30:00Z'));
|
|
|
- this._viewer.clock.shouldAnimate = false;
|
|
|
-
|
|
|
- const baseLayer = this._viewer.imageryLayers.get(0);
|
|
|
- if (baseLayer) {
|
|
|
- baseLayer.brightness = 0.75; // 亮度:1.0 是原图,小于 1.0 变暗,大于 1.0 变亮
|
|
|
- baseLayer.contrast = 1.3; // 对比度
|
|
|
- baseLayer.gamma = 1; // 新增这一行:默认值是 1.0,调高可以显著提亮暗部环境
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- const cam = this._viewer.scene.screenSpaceCameraController;
|
|
|
- cam.enableRotate = false;
|
|
|
- cam.enableTranslate = false;
|
|
|
- cam.enableZoom = false;
|
|
|
- cam.enableTilt = false;
|
|
|
- cam.enableLook = false;
|
|
|
-
|
|
|
- // 微观区域高清卫星底图(有 microImage 覆盖层时跳过,避免重复)
|
|
|
- if (!this.microImage && this.satelliteImage && this.satelliteImage.url) {
|
|
|
- const b = this.satelliteImage.bounds;
|
|
|
- this._viewer.imageryLayers.addImageryProvider(
|
|
|
- new Cesium.SingleTileImageryProvider({
|
|
|
- url: this.satelliteImage.url,
|
|
|
- rectangle: Cesium.Rectangle.fromDegrees(b[0], b[1], b[2], b[3])
|
|
|
- })
|
|
|
- );
|
|
|
-
|
|
|
- // 针对本地大图的专项优化
|
|
|
- this._viewer.scene.globe.maximumScreenSpaceError = 1.0; // 降低误差,强制渲染高清
|
|
|
- this._viewer.scene.globe.tileCacheSize = 200; // 增加缓存
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- this._viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(...this._coords.start) });
|
|
|
-
|
|
|
- this.buildMacroLayer();
|
|
|
- this.buildMicroLayer();
|
|
|
-
|
|
|
- this._preRenderListener = this._viewer.scene.preRender.addEventListener(() => {
|
|
|
- if (!this._microEffectsSource || !this._microEffectsSource.show) return;
|
|
|
-
|
|
|
- // POI 标签跟踪
|
|
|
- if (this.poi && this.$refs.poiLabel && this.$refs.poiLabel.style.opacity !== "0") {
|
|
|
- const pos3D = Cesium.Cartesian3.fromDegrees(this.poi.lon, this.poi.lat);
|
|
|
- const screenPos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._viewer.scene, pos3D);
|
|
|
- if (screenPos && screenPos.y > 0) {
|
|
|
- this.$refs.poiLabel.style.display = 'flex';
|
|
|
- this.$refs.poiLabel.style.left = screenPos.x + 'px';
|
|
|
- this.$refs.poiLabel.style.top = (screenPos.y - 150) + 'px';
|
|
|
- } else {
|
|
|
- this.$refs.poiLabel.style.display = 'none';
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- buildMacroLayer() {
|
|
|
- this._chinaEffectsSource = new Cesium.CustomDataSource('chinaEffects');
|
|
|
- this._viewer.dataSources.add(this._chinaEffectsSource);
|
|
|
- this._chinaEffectsSource.show = false;
|
|
|
-
|
|
|
- Cesium.GeoJsonDataSource.load(this.boundaryUrl).then(ds => {
|
|
|
- if (this._destroyed) return;
|
|
|
- this._chinaDataSource = ds;
|
|
|
-
|
|
|
- const entities = ds.entities.values;
|
|
|
- const provinceMainLand = {};
|
|
|
-
|
|
|
- for (let i = 0; i < entities.length; i++) {
|
|
|
- const entity = entities[i];
|
|
|
- if (entity.polygon) {
|
|
|
- entity.polygon.fill = false;
|
|
|
- entity.polygon.outline = false;
|
|
|
- const positions = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;
|
|
|
- entity.polyline = new Cesium.PolylineGraphics({
|
|
|
- positions, width: 2,
|
|
|
- material: new Cesium.PolylineGlowMaterialProperty({
|
|
|
- glowPower: 0.05,
|
|
|
- color: new Cesium.CallbackProperty(() => this._dynamicGold, false)
|
|
|
- })
|
|
|
- });
|
|
|
- if (entity.name === this.province) {
|
|
|
- const pointCount = positions.length;
|
|
|
- if (!provinceMainLand[entity.name] || pointCount > provinceMainLand[entity.name].pointCount) {
|
|
|
- provinceMainLand[entity.name] = { pointCount, center: Cesium.BoundingSphere.fromPoints(positions).center };
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- this._viewer.dataSources.add(ds);
|
|
|
-
|
|
|
- for (const name in provinceMainLand) {
|
|
|
- const mainCenter = provinceMainLand[name].center;
|
|
|
- this._chinaEffectsSource.entities.add({
|
|
|
- position: mainCenter,
|
|
|
- label: {
|
|
|
- text: name, font: 'bold 28px Microsoft YaHei',
|
|
|
- fillColor: new Cesium.CallbackProperty(() => this._dynamicWhite, false),
|
|
|
- outlineColor: new Cesium.CallbackProperty(() => this._dynamicBlack, false),
|
|
|
- outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
|
- heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND,
|
|
|
- pixelOffset: new Cesium.Cartesian2(0, -10),
|
|
|
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 20000000)
|
|
|
- }
|
|
|
- });
|
|
|
- for (let i = 0; i < 3; i++) {
|
|
|
- const offset = (4000 / 3) * i, startTime = Date.now() + offset;
|
|
|
- this._chinaEffectsSource.entities.add({
|
|
|
- position: mainCenter,
|
|
|
- ellipse: {
|
|
|
- semiMinorAxis: new Cesium.CallbackProperty(() => 350000 * (((Date.now() - startTime) % 4000) / 4000), false),
|
|
|
- semiMajorAxis: new Cesium.CallbackProperty(() => 350000 * (((Date.now() - startTime) % 4000) / 4000), false),
|
|
|
- material: new Cesium.ColorMaterialProperty(new Cesium.CallbackProperty(() => {
|
|
|
- const t = ((Date.now() - startTime) % 4000) / 4000;
|
|
|
- return Cesium.Color.DEEPSKYBLUE.withAlpha((1.0 - t) * 0.8 * this._macroAlpha);
|
|
|
- }, false)), height: 5000 + i * 100
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- buildMicroLayer() {
|
|
|
- this._microEffectsSource = new Cesium.CustomDataSource('microEffects');
|
|
|
- this._viewer.dataSources.add(this._microEffectsSource);
|
|
|
- this._microEffectsSource.show = false;
|
|
|
-
|
|
|
- // POI 标记(可选)
|
|
|
- if (this.poi) {
|
|
|
- const pos3D = Cesium.Cartesian3.fromDegrees(this.poi.lon, this.poi.lat);
|
|
|
- const slowRingImg = createOuterRingTexture(this._poiThemeColor);
|
|
|
- const fastScannerImg = createScannerTexture(this._poiThemeColor);
|
|
|
- const beamImg = createLightBeamTexture(this._poiThemeColor);
|
|
|
-
|
|
|
- this._microEffectsSource.entities.add({ position: pos3D, ellipse: { semiMinorAxis: this.poi.radarRadius, semiMajorAxis: this.poi.radarRadius, material: new Cesium.ImageMaterialProperty({ image: slowRingImg, transparent: true }), stRotation: new Cesium.CallbackProperty(() => (Date.now() / 5000.0) % TWO_PI, false), height: 20 } });
|
|
|
- this._microEffectsSource.entities.add({ position: pos3D, ellipse: { semiMinorAxis: this.poi.radarRadius, semiMajorAxis: this.poi.radarRadius, material: new Cesium.ImageMaterialProperty({ image: fastScannerImg, transparent: true }), stRotation: new Cesium.CallbackProperty(() => (Date.now() / 300.0) % TWO_PI, false), height: 21 } });
|
|
|
- this._microEffectsSource.entities.add({ position: Cesium.Cartesian3.fromDegrees(this.poi.lon, this.poi.lat, 2000.0), cylinder: { length: 4000, topRadius: 20, bottomRadius: 200, material: new Cesium.ImageMaterialProperty({ image: beamImg, transparent: true }) } });
|
|
|
- this._microEffectsSource.entities.add({ position: Cesium.Cartesian3.fromDegrees(this.poi.lon, this.poi.lat, 50), point: { pixelSize: 15, color: Cesium.Color.WHITE, outlineColor: this._poiThemeColor, outlineWidth: 3 } });
|
|
|
- }
|
|
|
-
|
|
|
- },
|
|
|
-
|
|
|
- fadeMacroLayer(startAlpha, endAlpha, durationMs) {
|
|
|
- return new Promise(resolve => {
|
|
|
- if (startAlpha < endAlpha && this._chinaDataSource) {
|
|
|
- this._chinaDataSource.show = true;
|
|
|
- this._chinaEffectsSource.show = true;
|
|
|
- }
|
|
|
- const startTime = Date.now();
|
|
|
- const animateFade = () => {
|
|
|
- if (this._destroyed) { resolve(); return; }
|
|
|
- let p = (Date.now() - startTime) / durationMs; if (p >= 1.0) p = 1.0;
|
|
|
- this._macroAlpha = startAlpha + (endAlpha - startAlpha) * p;
|
|
|
- this._dynamicGold = this._baseGold.withAlpha(this._macroAlpha);
|
|
|
- this._dynamicWhite = this._baseWhite.withAlpha(this._macroAlpha);
|
|
|
- this._dynamicBlack = this._baseBlack.withAlpha(this._macroAlpha);
|
|
|
- if (p < 1.0) {
|
|
|
- requestAnimationFrame(animateFade);
|
|
|
- } else {
|
|
|
- if (endAlpha === 0 && this._chinaDataSource) {
|
|
|
- this._chinaDataSource.show = false;
|
|
|
- this._chinaEffectsSource.show = false;
|
|
|
- }
|
|
|
- resolve();
|
|
|
- }
|
|
|
- };
|
|
|
- requestAnimationFrame(animateFade);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- animatePathGrowing(roadObj, delayTime) {
|
|
|
- return new Promise(resolve => {
|
|
|
- const timerId = setTimeout(() => {
|
|
|
- if (this._destroyed) { resolve(); return; }
|
|
|
- const positions = roadObj.path.map(p => Cesium.Cartesian3.fromDegrees(p[0], p[1]));
|
|
|
- const distances = [0]; let totalLength = 0;
|
|
|
- for (let i = 1; i < positions.length; i++) {
|
|
|
- totalLength += Cesium.Cartesian3.distance(positions[i - 1], positions[i]);
|
|
|
- distances.push(totalLength);
|
|
|
- }
|
|
|
- let progress = 0.0, isFinished = false;
|
|
|
-
|
|
|
- this._microEffectsSource.entities.add({
|
|
|
- polyline: {
|
|
|
- positions: new Cesium.CallbackProperty(function () {
|
|
|
- if (isFinished) return positions;
|
|
|
- progress += 0.025;
|
|
|
- if (progress >= 1.0) { isFinished = true; resolve(); return positions; }
|
|
|
- const targetDist = progress * totalLength;
|
|
|
- const pts = [];
|
|
|
- for (let i = 1; i < positions.length; i++) {
|
|
|
- pts.push(positions[i - 1]);
|
|
|
- if (targetDist <= distances[i]) {
|
|
|
- const seg = (targetDist - distances[i - 1]) / (distances[i] - distances[i - 1]);
|
|
|
- const p = new Cesium.Cartesian3();
|
|
|
- Cesium.Cartesian3.lerp(positions[i - 1], positions[i], seg, p);
|
|
|
- pts.push(p); break;
|
|
|
- }
|
|
|
- }
|
|
|
- return pts;
|
|
|
- }, false),
|
|
|
- width: roadObj.width,
|
|
|
- material: new Cesium.PolylineGlowMaterialProperty({ glowPower: roadObj.glowPower, color: roadObj.color })
|
|
|
- }
|
|
|
- });
|
|
|
- }, delayTime);
|
|
|
- this._pendingTimers.push(timerId);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- _safeDelay(ms) {
|
|
|
- return new Promise(resolve => {
|
|
|
- const id = setTimeout(resolve, ms);
|
|
|
- this._pendingTimers.push(id);
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- async startTransition() {
|
|
|
- if (this.isAnimating) return;
|
|
|
- this.isAnimating = true;
|
|
|
-
|
|
|
- // 阶段1: 飞向中国 (2s)
|
|
|
- this._viewer.camera.flyTo({
|
|
|
- destination: Cesium.Cartesian3.fromDegrees(...this._coords.china), duration: 2
|
|
|
- });
|
|
|
- const fadeInId = setTimeout(() => {
|
|
|
- if (!this._destroyed) this.fadeMacroLayer(0.0, 1.0, 1000);
|
|
|
- }, 800);
|
|
|
- this._pendingTimers.push(fadeInId);
|
|
|
-
|
|
|
- await this._safeDelay(3000);
|
|
|
- if (this._destroyed) return;
|
|
|
-
|
|
|
- // 阶段2: 俯冲到微观
|
|
|
- const targetCenter = this.poi
|
|
|
- ? [this.poi.lon, this.poi.lat]
|
|
|
- : (this.districtCenter || [116.70, 39.80]);
|
|
|
- const targetSphere = Cesium.BoundingSphere.fromPoints([Cesium.Cartesian3.fromDegrees(targetCenter[0], targetCenter[1])]);
|
|
|
- this._viewer.camera.flyToBoundingSphere(targetSphere, {
|
|
|
- offset: new Cesium.HeadingPitchRange(0.0, Cesium.Math.toRadians(-45), this.microViewRange),
|
|
|
- duration: 2, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT
|
|
|
- });
|
|
|
- this.fadeMacroLayer(1.0, 0.0, 800);
|
|
|
-
|
|
|
- // 阶段3: 显示微观效果
|
|
|
- await this._safeDelay(1500);
|
|
|
- if (this._destroyed) return;
|
|
|
- this._microEffectsSource.show = true;
|
|
|
- if (this.poi && this.$refs.poiLabel) this.$refs.poiLabel.style.opacity = 1;
|
|
|
-
|
|
|
- // 阶段4: 微观图片淡入
|
|
|
- if (this.microImage && this.$refs.microOverlay) {
|
|
|
- await this._safeDelay(500);
|
|
|
- if (this._destroyed) return;
|
|
|
- this.$refs.microOverlay.style.opacity = 1;
|
|
|
- await this._safeDelay(2000); // 等待淡入完成 + 停留展示
|
|
|
- } else {
|
|
|
- await this._safeDelay(1000);
|
|
|
- }
|
|
|
- if (this._destroyed) return;
|
|
|
- this.$emit('complete');
|
|
|
- }
|
|
|
- }
|
|
|
-};
|
|
|
-</script>
|
|
|
-
|
|
|
-<style scoped>
|
|
|
-.cesium-transition-wrapper {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100vw;
|
|
|
- height: 100vh;
|
|
|
- z-index: 9999;
|
|
|
- background: #020813;
|
|
|
- overflow: hidden;
|
|
|
- font-family: "Microsoft YaHei", sans-serif;
|
|
|
-}
|
|
|
-
|
|
|
-.cesium-container {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- margin: 0;
|
|
|
- padding: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.micro-overlay {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- object-fit: cover;
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 1.5s ease;
|
|
|
- z-index: 2;
|
|
|
- pointer-events: none;
|
|
|
-}
|
|
|
-
|
|
|
-.ui-layer {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- pointer-events: none;
|
|
|
-}
|
|
|
-
|
|
|
-.html-label {
|
|
|
- position: absolute;
|
|
|
- padding: 4px 10px;
|
|
|
- border-radius: 2px;
|
|
|
- pointer-events: none;
|
|
|
- transition: opacity 0.5s;
|
|
|
- transform: translate(-50%, -100%);
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- white-space: nowrap;
|
|
|
- font-weight: bold;
|
|
|
- z-index: 5;
|
|
|
- border: 1px solid currentColor;
|
|
|
- background: rgba(0, 20, 30, 0.7);
|
|
|
- box-shadow: inset 0 0 10px currentColor;
|
|
|
- text-shadow: 0 0 5px #000;
|
|
|
-}
|
|
|
-
|
|
|
-.html-label::after {
|
|
|
- content: '';
|
|
|
- width: 2px;
|
|
|
- height: 35px;
|
|
|
- background: currentColor;
|
|
|
- margin-top: 5px;
|
|
|
- box-shadow: 0 0 5px currentColor;
|
|
|
-}
|
|
|
-</style>
|