You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular+ol-cesium 3D可视化操作时出现闪烁问题求助

Ol-Cesium + Angular 3D视图操作闪烁问题

环境版本

  • Angular v19
  • olcs 2.22.1
  • Cesium 1.123
  • ol 10.3.1

问题描述

3D视图下执行任意操作都会触发视图闪烁,2D视图无异常。涉及操作包括:移除并添加矢量图层、修改要素坐标、更新要素样式等。

应用结构简单:主组件(app.component.ts)通过按钮触发指令组件(cesium.directive.ts)中的方法,所有逻辑均在指令组件内。切换到3D视图后点击移动按钮,图标移动时视图会明显闪烁。

组件代码示例

主组件 app.component.ts

import { Component, ViewChildren, QueryList, AfterViewInit} from '@angular/core';
import { CesiumDirective } from './cesium.directive';

@Component({
    selector: 'app-root',
    template: `
    <div class="map-container">
      <div
        id="map"
        appCesium
        #cesiumDirective
      ></div>
      <button class="toggle-view-button" (click)="toggleView()">Toggle 2D/3D View</button>
      <button class="move-button" (click)="moveButton()">move</button>
    </div>
  `,
    styles: [`
    .map-container {
      width: 100%;
      height: 100vh;
      position: relative;
    }
    #map {
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
    }
    .toggle-view-button {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: 1000;
    }
    .move-button {
      position: absolute;
      top: 90px;
      right: 10px;
      z-index: 1000;
    }
  `],
    standalone: false
})
export class AppComponent implements AfterViewInit {
  @ViewChildren(CesiumDirective)
  cesiumDirectives!: QueryList<CesiumDirective>;
  private cesiumDirective!: CesiumDirective;

  ngAfterViewInit() {
    setTimeout(() => {
      this.cesiumDirective = this.cesiumDirectives.first;
      console.log('Cesium Directive found:', this.cesiumDirective);
    });
  }

  moveButton() {
    if(this.cesiumDirective) {
      this.cesiumDirective.moveButton();
    }
  }
}

指令组件 cesium.directive.ts

import { Directive, ElementRef, OnInit } from '@angular/core';
import OLCesium from 'olcs';
import Map from 'ol/Map';
import { Feature, View } from 'ol';
import TileLayer from 'ol/layer/Tile';
import { OSM } from 'ol/source';
import { Point } from 'ol/geom';
import {Fill, Icon, Stroke, Style, Text} from 'ol/style';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';


@Directive({
    selector: '[appCesium]',
    standalone: false,
})
export class CesiumDirective implements OnInit {
  private map!: Map;
  private ol3d: any;
  private vectorLayer!: VectorLayer;
  private is3DEnabled: boolean = false;
  private icon1Feature!: any;

  constructor(private el: ElementRef) {
  }

  ngOnInit(): void {
    this.initMap();
    this.addFeature();
  }

  private addFeature(): void {
    // 创建带图标的要素
    this.icon1Feature = new Feature({
      geometry: new Point([700000, 200000, 10000])
    });
    this.icon1Feature.setStyle(new Style({
      image: new Icon({
        anchor: [0.5, 1],
        src: 'assets/icon.png'
      }),
      text: new Text({
        text: 'Icon with anchor on the bottom center',
        stroke: new Stroke({
          color: 'black',
          width: 3
        }),
        fill: new Fill({
          color: 'white'
        })
      })
    }));

    // 创建矢量源和图层
    const vectorSource = new VectorSource({
      features: [this.icon1Feature]
    });

    this.vectorLayer = new VectorLayer({
      source: vectorSource
    });

    this.map.addLayer(this.vectorLayer);
  }

  private initMap(): void {
    
    try {
      this.map = new Map({
        target: this.el.nativeElement,
        layers: [
          new TileLayer({
            visible: true,
            preload: Infinity,
            source: new OSM(),
          })
        ],
        view: new View({
          zoom: 5,
          center: [6000000, 4000000]
        })
      });

      this.ol3d = new OLCesium({ 
        map: this.map,
        target: this.el.nativeElement,
      });
            
      const scene = this.ol3d.getCesiumScene();
      scene.debugShowFramesPerSecond = true;

      this.ol3d.setEnabled(false);

    } catch (error) {
      console.error('Error initializing map:', error);
    }
  }

  public toggleView(): void {
    try {
      this.is3DEnabled = !this.is3DEnabled;
      
      if (this.ol3d) {
        this.ol3d.setEnabled(this.is3DEnabled);
      }
    } catch (error) {
      console.error('Error in toggleView:', error);
    }
  }

  public moveButton() {
    const currentCoordinates = this.icon1Feature.getGeometry().getCoordinates();

    const deltaX = 100;
    const deltaY = 200;
    
    const newCoordinates = [currentCoordinates[0] + deltaX, currentCoordinates[1] + deltaY, currentCoordinates[2]];

    this.icon1Feature.setGeometry(new Point(newCoordinates));
  }

}

解决方案

1. 优化OL要素更新方式

避免每次创建新的Point对象,直接修改已有几何坐标,减少对象创建带来的同步开销:

public moveButton() {
  const geometry = this.icon1Feature.getGeometry() as Point;
  const currentCoordinates = geometry.getCoordinates();
  
  const deltaX = 100;
  const deltaY = 200;
  const newCoordinates = [currentCoordinates[0] + deltaX, currentCoordinates[1] + deltaY, currentCoordinates[2]];
  
  geometry.setCoordinates(newCoordinates);
}

2. 手动控制Cesium同步

使用OL-Cesium的FeatureOverlay手动同步单个要素,替代默认的全量更新:

public moveButton() {
  const geometry = this.icon1Feature.getGeometry() as Point;
  const currentCoordinates = geometry.getCoordinates();
  
  const deltaX = 100;
  const deltaY = 200;
  const newCoordinates = [currentCoordinates[0] + deltaX, currentCoordinates[1] + deltaY, currentCoordinates[2]];
  
  geometry.setCoordinates(newCoordinates);
  
  // 手动同步到Cesium
  const featureOverlay = this.ol3d.getFeatureOverlay(this.vectorLayer);
  featureOverlay.updateFeature(this.icon1Feature);
}

3. 隔离Angular变更检测

将Cesium相关操作放到NgZone外执行,避免Angular变更检测触发不必要的重绘:

import { NgZone } from '@angular/core';

// 构造函数注入NgZone
constructor(private el: ElementRef, private ngZone: NgZone) {}

public moveButton() {
  this.ngZone.runOutsideAngular(() => {
    const geometry = this.icon1Feature.getGeometry() as Point;
    const currentCoordinates = geometry.getCoordinates();
    
    const deltaX = 100;
    const deltaY = 200;
    const newCoordinates = [currentCoordinates[0] + deltaX, currentCoordinates[1] + deltaY, currentCoordinates[2]];
    
    geometry.setCoordinates(newCoordinates);
    
    const featureOverlay = this.ol3d.getFeatureOverlay(this.vectorLayer);
    featureOverlay.updateFeature(this.icon1Feature);
  });
}

4. 优化Cesium渲染设置

关闭调试选项,启用按需渲染和抗锯齿,减少闪烁感:

private initMap(): void {
  try {
    // ...已有地图初始化代码
    
    const scene = this.ol3d.getCesiumScene();
    scene.debugShowFramesPerSecond = false; // 关闭FPS显示
    scene.fxaa = true; // 开启FXAA抗锯齿
    scene.requestRenderMode = true; // 启用按需渲染
    scene.maximumRenderTimeChange = 0.0;
    
    this.ol3d.setEnabled(false);
  } catch (error) {
    console.error('Error initializing map:', error);
  }
}

整合优化后的完整moveButton方法

public moveButton() {
  this.ngZone.runOutsideAngular(() => {
    const geometry = this.icon1Feature.getGeometry() as Point;
    const currentCoordinates = geometry.getCoordinates();
    
    const deltaX = 100;
    const deltaY = 200;
    const newCoordinates = [currentCoordinates[0] + deltaX, currentCoordinates[1] + deltaY, currentCoordinates[2]];
    
    geometry.setCoordinates(newCoordinates);
    
    const featureOverlay = this.ol3d.getFeatureOverlay(this.vectorLayer);
    featureOverlay.updateFeature(this.icon1Feature);
  });
}

内容的提问来源于stack exchange,提问作者coman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:54:55