|
|
@@ -0,0 +1,433 @@
|
|
|
+<template>
|
|
|
+ <div class="cesium-transition-wrapper">
|
|
|
+ <div ref="cesiumContainer" class="cesium-container"></div>
|
|
|
+ <div class="ui-layer">
|
|
|
+ <div
|
|
|
+ ref="poiLabel"
|
|
|
+ class="html-label"
|
|
|
+ style="color: #00ffff; font-size: 20px; opacity: 0; display: none;"
|
|
|
+ >
|
|
|
+ 鲁谷西街与政达路交叉口
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+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',
|
|
|
+ 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._poi = { lon: 116.22405, lat: 39.90515, radarRadius: 200 };
|
|
|
+
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (!this.$refs.cesiumContainer) return;
|
|
|
+ this.initCesium();
|
|
|
+ this.waitForGlobeReady().then(() => {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ this._viewer.destroy();
|
|
|
+ this._viewer = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ waitForGlobeReady() {
|
|
|
+ return new Promise(resolve => {
|
|
|
+ const check = () => {
|
|
|
+ if (this._destroyed) { resolve(); return; }
|
|
|
+ if (this._viewer && this._viewer.scene.globe.tilesLoaded) {
|
|
|
+ resolve();
|
|
|
+ } else {
|
|
|
+ requestAnimationFrame(check);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ requestAnimationFrame(check);
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ initCesium() {
|
|
|
+ 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.DEEPSKYBLUE;
|
|
|
+
|
|
|
+ this._majorRoads = [
|
|
|
+ { name: "阜石路-阜成路快速路", width: 12, glowPower: 0.3, color: Cesium.Color.fromCssColorString('#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: Cesium.Color.fromCssColorString('#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: Cesium.Color.fromCssColorString('#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: Cesium.Color.fromCssColorString('#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: Cesium.Color.fromCssColorString('#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: Cesium.Color.fromCssColorString('#ffff00'), path: [[116.285, 39.94], [116.283, 39.91], [116.28, 39.88], [116.278, 39.85]] }
|
|
|
+ ];
|
|
|
+
|
|
|
+ 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,
|
|
|
+ imageryProvider: new Cesium.ArcGisMapServerImageryProvider({ url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' })
|
|
|
+ });
|
|
|
+
|
|
|
+ const cam = this._viewer.scene.screenSpaceCameraController;
|
|
|
+ cam.enableRotate = false;
|
|
|
+ cam.enableTranslate = false;
|
|
|
+ cam.enableZoom = false;
|
|
|
+ cam.enableTilt = false;
|
|
|
+ cam.enableLook = false;
|
|
|
+
|
|
|
+ this._viewer.cesiumWidget.creditContainer.style.display = "none";
|
|
|
+ this._viewer.scene.globe.enableLighting = true;
|
|
|
+ this._viewer.clock.currentTime = Cesium.JulianDate.fromDate(new Date('2024-01-01T23:30:00Z'));
|
|
|
+ this._viewer.clock.shouldAnimate = false;
|
|
|
+
|
|
|
+ const baseLayer = this._viewer.imageryLayers.get(0);
|
|
|
+ if (baseLayer) { baseLayer.brightness = 0.45; baseLayer.contrast = 1.3; }
|
|
|
+
|
|
|
+ // 微观区域高清卫星底图(本地静态图,秒加载,替代远程瓦片)
|
|
|
+ this._viewer.imageryLayers.addImageryProvider(
|
|
|
+ new Cesium.SingleTileImageryProvider({
|
|
|
+ url: './beijing-satellite.jpg',
|
|
|
+ rectangle: Cesium.Rectangle.fromDegrees(116.10, 39.80, 116.38, 39.98)
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ 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 || !this.$refs.poiLabel) return;
|
|
|
+ if (this.$refs.poiLabel.style.opacity === "0") return;
|
|
|
+ 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('./china.json').then(ds => {
|
|
|
+ if (this._destroyed) return;
|
|
|
+ this._viewer.dataSources.add(ds);
|
|
|
+ this._chinaDataSource = ds;
|
|
|
+ this._chinaDataSource.show = false;
|
|
|
+
|
|
|
+ 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 === '北京市') {
|
|
|
+ const pointCount = positions.length;
|
|
|
+ if (!provinceMainLand[entity.name] || pointCount > provinceMainLand[entity.name].pointCount) {
|
|
|
+ provinceMainLand[entity.name] = { pointCount, center: Cesium.BoundingSphere.fromPoints(positions).center };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 实体处理完毕后再添加到 viewer
|
|
|
+ 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;
|
|
|
+
|
|
|
+ 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),飞行0.8s后开始淡入边界
|
|
|
+ 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: 俯冲到微观 (2s),同时淡出边界
|
|
|
+ const targetSphere = Cesium.BoundingSphere.fromPoints([Cesium.Cartesian3.fromDegrees(this._poi.lon, this._poi.lat)]);
|
|
|
+ this._viewer.camera.flyToBoundingSphere(targetSphere, {
|
|
|
+ offset: new Cesium.HeadingPitchRange(0.0, Cesium.Math.toRadians(-45), 10000.0),
|
|
|
+ duration: 2, easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT
|
|
|
+ });
|
|
|
+ this.fadeMacroLayer(1.0, 0.0, 800);
|
|
|
+
|
|
|
+ // 阶段3: 显示微观雷达 + POI标签
|
|
|
+ await this._safeDelay(1500);
|
|
|
+ if (this._destroyed) return;
|
|
|
+ this._microEffectsSource.show = true;
|
|
|
+ if (this.$refs.poiLabel) this.$refs.poiLabel.style.opacity = 1;
|
|
|
+
|
|
|
+ // 阶段4: 路网生长 (每条间隔150ms),全部画完立即结束
|
|
|
+ await this._safeDelay(300);
|
|
|
+ if (this._destroyed) return;
|
|
|
+ const roadPromises = this._majorRoads.map((road, i) => this.animatePathGrowing(road, i * 150));
|
|
|
+ await Promise.all(roadPromises);
|
|
|
+ if (this._destroyed) return;
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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>
|