@@ -40,6 +40,10 @@ const app = Vue.createApp({
4040 // Cesium 增量更新:实体索引表,避免每次 removeAll 重建场景
4141 _entityIndex : { } , // id -> Cesium.Entity,管理节点(卫星、地面站、中继)
4242 _linkIndex : { } , // linkKey -> Cesium.Entity,管理链路 polyline
43+ // 链路动画状态:流动动画偏移量与 rAF 句柄
44+ _linkAnimOffset : 0 , // 当前动画时间偏移(0–1 循环,用于 dashPattern 驱动流动效果)
45+ _linkAnimFrame : null , // requestAnimationFrame 句柄,非 null 时表示动画循环运行
46+ _linkAnimRateMap : { } , // linkKey -> { rate, method },缓存链路传输速率与方式
4347 // 轨道轨迹索引:satId -> { orbitEntity, groundEntity }
4448 _trackIndex : { } ,
4549 _tracksLoading : false ,
@@ -258,6 +262,11 @@ const app = Vue.createApp({
258262 window . clearInterval ( this . refreshTimer ) ;
259263 this . refreshTimer = null ;
260264 }
265+ // 取消链路动画循环
266+ if ( this . _linkAnimFrame !== null ) {
267+ cancelAnimationFrame ( this . _linkAnimFrame ) ;
268+ this . _linkAnimFrame = null ;
269+ }
261270 window . removeEventListener ( 'resize' , this . resizeViewer ) ;
262271 if ( this . viewer && ! this . viewer . isDestroyed ( ) ) {
263272 this . viewer . destroy ( ) ;
@@ -267,6 +276,7 @@ const app = Vue.createApp({
267276 this . _linkIndex = { } ;
268277 this . _trackIndex = { } ;
269278 this . _coverageIndex = { } ;
279+ this . _linkAnimRateMap = { } ;
270280 } ,
271281
272282 methods : {
@@ -674,17 +684,25 @@ const app = Vue.createApp({
674684 . forEach ( ( req ) => {
675685 const satellite = satMap . get ( req . satellite_id ) ;
676686 if ( ! satellite ) return ;
677- this . updateRequestLinks ( req , satellite , geoMap , gsMap , linkColor , liveLinkKeys ) ;
687+ this . updateRequestLinks ( req , satellite , geoMap , gsMap , liveLinkKeys ) ;
678688 } ) ;
679689
680690 // 移除已消失的链路实体
681691 for ( const lkey of Object . keys ( this . _linkIndex ) ) {
682692 if ( ! liveLinkKeys . has ( lkey ) ) {
683693 this . viewer . entities . remove ( this . _linkIndex [ lkey ] ) ;
684694 delete this . _linkIndex [ lkey ] ;
695+ delete this . _linkAnimRateMap [ lkey ] ;
685696 }
686697 }
687698
699+ // 启动链路流动动画循环(若尚未运行)
700+ if ( liveLinkKeys . size > 0 ) {
701+ this . _startLinkAnimation ( ) ;
702+ } else {
703+ this . _stopLinkAnimation ( ) ;
704+ }
705+
688706 // ── 覆盖足迹与可见性圈 ────────────────────────────────────────
689707 this . updateCoverageFootprints ( ) ;
690708
@@ -697,8 +715,9 @@ const app = Vue.createApp({
697715 /**
698716 * 为单条请求按需增量更新/创建链路 polyline 实体。
699717 * liveLinkKeys 用于记录本轮活跃的链路 key,以便事后清理。
718+ * 链路颜色由 transmission_method 决定,线宽由 transmission_rate 归一化映射。
700719 */
701- updateRequestLinks ( req , satellite , geoMap , gsMap , color , liveLinkKeys ) {
720+ updateRequestLinks ( req , satellite , geoMap , gsMap , liveLinkKeys ) {
702721 const groundStation = req . selected_ground_station
703722 ? gsMap . get ( req . selected_ground_station )
704723 : null ;
@@ -709,54 +728,181 @@ const app = Vue.createApp({
709728 ? geoMap . get ( req . selected_relay2 )
710729 : null ;
711730
731+ const rate = Number ( req . transmission_rate || 0 ) ;
732+ const method = req . transmission_method || 'direct' ;
733+
712734 if ( firstRelay && secondRelay ) {
713- this . upsertLink ( `${ req . id } :sat-relay1` , satellite , firstRelay , color , liveLinkKeys ) ;
714- this . upsertLink ( `${ req . id } :relay1-relay2` , firstRelay , secondRelay , color , liveLinkKeys ) ;
735+ this . upsertLink ( `${ req . id } :sat-relay1` , satellite , firstRelay , rate , method , liveLinkKeys ) ;
736+ this . upsertLink ( `${ req . id } :relay1-relay2` , firstRelay , secondRelay , rate , method , liveLinkKeys ) ;
715737 if ( groundStation ) {
716- this . upsertLink ( `${ req . id } :relay2-gs` , secondRelay , groundStation , color , liveLinkKeys ) ;
738+ this . upsertLink ( `${ req . id } :relay2-gs` , secondRelay , groundStation , rate , method , liveLinkKeys ) ;
717739 }
718740 return ;
719741 }
720742
721743 if ( firstRelay ) {
722- this . upsertLink ( `${ req . id } :sat-relay1` , satellite , firstRelay , color , liveLinkKeys ) ;
744+ this . upsertLink ( `${ req . id } :sat-relay1` , satellite , firstRelay , rate , method , liveLinkKeys ) ;
723745 if ( groundStation ) {
724- this . upsertLink ( `${ req . id } :relay1-gs` , firstRelay , groundStation , color , liveLinkKeys ) ;
746+ this . upsertLink ( `${ req . id } :relay1-gs` , firstRelay , groundStation , rate , method , liveLinkKeys ) ;
725747 }
726748 return ;
727749 }
728750
729- if ( groundStation && req . transmission_method === 'direct' ) {
730- this . upsertLink ( `${ req . id } :sat-gs` , satellite , groundStation , color , liveLinkKeys ) ;
751+ if ( groundStation && method === 'direct' ) {
752+ this . upsertLink ( `${ req . id } :sat-gs` , satellite , groundStation , rate , method , liveLinkKeys ) ;
753+ }
754+ } ,
755+
756+ /**
757+ * 将传输速率(Mbps)归一化为链路线宽(1.5–6px)。
758+ * 使用对数映射,使低速率链路也有明显变化。
759+ * @param {number } rateMbps - 传输速率(Mbps)
760+ * @returns {number } 线宽(px)
761+ */
762+ _rateToLineWidth ( rateMbps ) {
763+ const MIN_W = 1.5 ;
764+ const MAX_W = 6.0 ;
765+ const MAX_RATE = 1000 ; // 参考最大速率 1000 Mbps
766+ if ( ! rateMbps || rateMbps <= 0 ) return MIN_W ;
767+ // 对数归一化:log(1 + rate) / log(1 + MAX_RATE)
768+ const norm = Math . log1p ( Math . min ( rateMbps , MAX_RATE ) ) / Math . log1p ( MAX_RATE ) ;
769+ return MIN_W + norm * ( MAX_W - MIN_W ) ;
770+ } ,
771+
772+ /**
773+ * 根据传输方式返回对应的链路颜色(Cesium.Color)。
774+ * - direct(直连):金黄色
775+ * - relay(单中继):青绿色
776+ * - multi_relay(多跳):紫色
777+ * @param {string } method - 传输方式
778+ * @returns {Cesium.Color }
779+ */
780+ _methodToLinkColor ( method ) {
781+ if ( ! window . Cesium ) return null ;
782+ switch ( method ) {
783+ case 'relay' : return Cesium . Color . fromCssColorString ( '#3dcfc4' ) ; // 青绿
784+ case 'multi_relay' : return Cesium . Color . fromCssColorString ( '#b67cf6' ) ; // 紫色
785+ default : return Cesium . Color . fromCssColorString ( '#f0c76b' ) ; // 金黄(直连)
731786 }
732787 } ,
733788
734789 /**
735790 * 按 linkKey 复用已有的 polyline 实体;不存在时新建并注册到 _linkIndex。
736- * 复用时更新两端点坐标,保持实体引用稳定。
791+ * 复用时更新两端点坐标与动画速率缓存,保持实体引用稳定。
792+ * 使用 PolylineGlowMaterialProperty 表现数据流向,线宽随传输速率变化。
793+ * @param {string } linkKey - 链路唯一键
794+ * @param {object } from - 起始节点 { lon, lat, alt }
795+ * @param {object } to - 终止节点 { lon, lat, alt }
796+ * @param {number } rateMbps - 传输速率(Mbps),用于映射线宽
797+ * @param {string } method - 传输方式(direct/relay/multi_relay),用于确定颜色
798+ * @param {Set<string> } liveLinkKeys - 本轮活跃链路集合
737799 */
738- upsertLink ( linkKey , from , to , color , liveLinkKeys ) {
800+ upsertLink ( linkKey , from , to , rateMbps , method , liveLinkKeys ) {
739801 liveLinkKeys . add ( linkKey ) ;
740802 const positions = [
741803 this . toCartesian ( from . lon , from . lat , from . alt || 0 ) ,
742804 this . toCartesian ( to . lon , to . lat , to . alt || 0 ) ,
743805 ] ;
806+ const width = this . _rateToLineWidth ( rateMbps ) ;
807+ const baseColor = this . _methodToLinkColor ( method || 'direct' ) ;
808+
809+ // 缓存当前链路速率与方式,供动画循环使用
810+ this . _linkAnimRateMap [ linkKey ] = { rate : rateMbps , method : method || 'direct' } ;
744811
745812 if ( this . _linkIndex [ linkKey ] ) {
746- // 更新两端点位置
747- this . _linkIndex [ linkKey ] . polyline . positions = positions ;
813+ // 更新两端点位置与线宽
814+ const poly = this . _linkIndex [ linkKey ] . polyline ;
815+ poly . positions = positions ;
816+ poly . width = width ;
817+ // 更新发光材质颜色(速率/方式可能在刷新中变化)
818+ if ( poly . material && poly . material instanceof Cesium . PolylineGlowMaterialProperty ) {
819+ poly . material = new Cesium . PolylineGlowMaterialProperty ( {
820+ glowPower : 0.25 ,
821+ taperPower : 0.9 ,
822+ color : baseColor . withAlpha ( 0.85 ) ,
823+ } ) ;
824+ }
748825 } else {
749826 const entity = this . viewer . entities . add ( {
750827 polyline : {
751828 positions,
752- width : 2.5 ,
753- material : color ,
829+ width,
830+ material : new Cesium . PolylineGlowMaterialProperty ( {
831+ glowPower : 0.25 ,
832+ taperPower : 0.9 ,
833+ color : baseColor . withAlpha ( 0.85 ) ,
834+ } ) ,
835+ arcType : Cesium . ArcType . NONE ,
754836 } ,
755837 } ) ;
756838 this . _linkIndex [ linkKey ] = entity ;
757839 }
758840 } ,
759841
842+ /**
843+ * 启动链路流动动画循环。
844+ * 通过持续调用 requestRender 驱动 Cesium 在 requestRenderMode 下不断重绘,
845+ * 同时利用 PolylineGlowMaterialProperty 的自发光效果模拟数据流动感。
846+ * 每帧递增 _linkAnimOffset 并对有速率信息的链路微调发光强度,产生脉冲效果。
847+ */
848+ _startLinkAnimation ( ) {
849+ if ( this . _linkAnimFrame !== null ) return ; // 已在运行
850+ if ( ! this . viewer || ! this . cesiumReady ) return ;
851+
852+ let lastTime = performance . now ( ) ;
853+ const tick = ( now ) => {
854+ const dt = Math . min ( ( now - lastTime ) / 1000 , 0.1 ) ; // 单帧增量(秒),上限 100ms
855+ lastTime = now ;
856+
857+ // 更新全局动画偏移(0–1 循环)
858+ this . _linkAnimOffset = ( this . _linkAnimOffset + dt * 0.5 ) % 1 ;
859+
860+ // 对每条活跃链路:按速率调整发光强度,营造脉冲流动感
861+ if ( window . Cesium ) {
862+ const t = this . _linkAnimOffset ;
863+ for ( const [ lkey , info ] of Object . entries ( this . _linkAnimRateMap ) ) {
864+ const entity = this . _linkIndex [ lkey ] ;
865+ if ( ! entity || ! entity . polyline ) continue ;
866+ // 速率越高脉冲越快;基础脉冲频率 1Hz,速率比例最高 3x
867+ const freq = 1 + Math . min ( 2 , ( info . rate || 0 ) / 200 ) ;
868+ const phase = ( t * freq ) % 1 ;
869+ // 发光强度在 0.15–0.55 间脉冲
870+ const glow = 0.15 + 0.40 * Math . abs ( Math . sin ( phase * Math . PI ) ) ;
871+ const baseColor = this . _methodToLinkColor ( info . method ) ;
872+ entity . polyline . material = new Cesium . PolylineGlowMaterialProperty ( {
873+ glowPower : glow ,
874+ taperPower : 0.9 ,
875+ color : baseColor . withAlpha ( 0.85 ) ,
876+ } ) ;
877+ }
878+ }
879+
880+ // 在 requestRenderMode 下触发重绘
881+ if ( this . viewer && ! this . viewer . isDestroyed ( ) ) {
882+ this . viewer . scene . requestRender ( ) ;
883+ }
884+
885+ // 若仍有活跃链路则继续循环
886+ if ( Object . keys ( this . _linkAnimRateMap ) . length > 0 ) {
887+ this . _linkAnimFrame = requestAnimationFrame ( tick ) ;
888+ } else {
889+ this . _linkAnimFrame = null ;
890+ }
891+ } ;
892+
893+ this . _linkAnimFrame = requestAnimationFrame ( tick ) ;
894+ } ,
895+
896+ /**
897+ * 停止链路流动动画循环(当无活跃传输链路时调用)。
898+ */
899+ _stopLinkAnimation ( ) {
900+ if ( this . _linkAnimFrame !== null ) {
901+ cancelAnimationFrame ( this . _linkAnimFrame ) ;
902+ this . _linkAnimFrame = null ;
903+ }
904+ } ,
905+
760906 makeLabel ( text , color , pixelOffsetY ) {
761907 return {
762908 text : String ( text || '' ) ,
@@ -790,9 +936,9 @@ const app = Vue.createApp({
790936 /**
791937 * @deprecated 由 updateRequestLinks 取代。
792938 */
793- drawRequestLinks ( req , satellite , geoMap , gsMap , color ) {
939+ drawRequestLinks ( req , satellite , geoMap , gsMap ) {
794940 const liveLinkKeys = new Set ( ) ;
795- this . updateRequestLinks ( req , satellite , geoMap , gsMap , color , liveLinkKeys ) ;
941+ this . updateRequestLinks ( req , satellite , geoMap , gsMap , liveLinkKeys ) ;
796942 } ,
797943
798944 /**
0 commit comments