core_temporal_TimeController.js - hiro-nyon/cesium-heatbox GitHub Wiki

Source: core/temporal/TimeController.js

日本語 | English

English

See also: Class: TimeController

import { TimeSlicer } from './TimeSlicer.js';

/**
 * Controller to synchronize Heatbox with Cesium Clock.
 * Cesium Clock と Heatbox を連携させるコントローラ。
 */
export class TimeController {
    /**
     * @param {Cesium.Viewer} viewer 
     * @param {Heatbox} heatbox 
     * @param {Object} options 
     */
    constructor(viewer, heatbox, options) {
        this._viewer = viewer;
        this._heatbox = heatbox;
        this._options = options;

        // Initialize TimeSlicer
        this._slicer = new TimeSlicer(options.data, options);

        this._lastUpdateTime = null;      // Last real time update (for throttling)
        this._lastEntry = null;           // Last data entry (for change detection)
        this._removeListener = null;      // Clock listener remover
        this._isActive = false;
    }

    /**
     * Activate the controller.
     * コントローラを有効化します。
     */
    activate() {
        if (this._isActive) return;
        this._isActive = true;

        // Global scope: calculate stats once
        if (this._options.classificationScope === 'global') {
            const heatboxOptions = this._heatbox.options || {};
            const valueProperty = heatboxOptions.valueProperty || 'weight';
            const globalStats = this._slicer.calculateGlobalStats(valueProperty);
            this._heatbox._globalStats = globalStats;
        }

        // Register clock listener
        this._removeListener = this._viewer.clock.onTick.addEventListener(
            this._onTick.bind(this)
        );

        // Initial update
        this._onTick(this._viewer.clock);
    }

    /**
     * Deactivate the controller.
     * コントローラを無効化します。
     */
    deactivate() {
        if (!this._isActive) return;
        this._isActive = false;

        if (this._removeListener) {
            this._removeListener();
            this._removeListener = null;
        }
    }

    /**
     * Handle clock tick.
     * Clock の更新を処理します。
     * @param {Cesium.Clock} clock 
     * @private
     */
    _onTick(clock) {
        const now = clock.currentTime;

        // Throttling check
        if (!this._shouldUpdate(now)) return;

        // Get data for current time
        const entry = this._slicer.getEntry(now);

        // Change detection
        // Allow null entries to propagate so outOfRangeBehavior can run
        if (entry !== null && entry === this._lastEntry) {
            return;
        }

        this._lastEntry = entry;
        this._updateHeatbox(entry);
    }

    /**
     * Check if update should proceed (throttling).
     * 更新すべきか判定します(スロットリング)。
     * @param {Cesium.JulianDate} now 
     * @returns {boolean}
     * @private
     */
    _shouldUpdate(_now) {
        const interval = this._options.updateInterval;

        if (interval === 'frame' || !interval) {
            return true;  // Update every frame
        }

        // Check elapsed time since last update
        const currentRealTime = Date.now();
        if (
            this._lastUpdateTime === null ||
            currentRealTime - this._lastUpdateTime >= interval
        ) {
            this._lastUpdateTime = currentRealTime;
            return true;
        }

        return false;
    }

    /**
     * Update Heatbox with new data.
     * Heatbox を新しいデータで更新します。
     * @param {Object|null} entry 
     * @private
     */
    _updateHeatbox(entry) {
        if (!entry) {
            // No data: check outOfRangeBehavior
            if (this._options.outOfRangeBehavior === 'clear') {
                this._heatbox.clear();
            }
            return;
        }

        // Update options
        const updateOptions = { _skipRebuild: false };

        // Global scope handling (Phase 3)
        if (this._options.classificationScope === 'global' && this._heatbox._globalStats) {
            updateOptions._externalStats = this._heatbox._globalStats;
        }

        this._heatbox.setData(entry.data, updateOptions);
    }
}

日本語

関連: TimeControllerクラス

import { TimeSlicer } from './TimeSlicer.js';

/**
 * Controller to synchronize Heatbox with Cesium Clock.
 * Cesium Clock と Heatbox を連携させるコントローラ。
 */
export class TimeController {
    /**
     * @param {Cesium.Viewer} viewer 
     * @param {Heatbox} heatbox 
     * @param {Object} options 
     */
    constructor(viewer, heatbox, options) {
        this._viewer = viewer;
        this._heatbox = heatbox;
        this._options = options;

        // Initialize TimeSlicer
        this._slicer = new TimeSlicer(options.data, options);

        this._lastUpdateTime = null;      // Last real time update (for throttling)
        this._lastEntry = null;           // Last data entry (for change detection)
        this._removeListener = null;      // Clock listener remover
        this._isActive = false;
    }

    /**
     * Activate the controller.
     * コントローラを有効化します。
     */
    activate() {
        if (this._isActive) return;
        this._isActive = true;

        // Global scope: calculate stats once
        if (this._options.classificationScope === 'global') {
            const heatboxOptions = this._heatbox.options || {};
            const valueProperty = heatboxOptions.valueProperty || 'weight';
            const globalStats = this._slicer.calculateGlobalStats(valueProperty);
            this._heatbox._globalStats = globalStats;
        }

        // Register clock listener
        this._removeListener = this._viewer.clock.onTick.addEventListener(
            this._onTick.bind(this)
        );

        // Initial update
        this._onTick(this._viewer.clock);
    }

    /**
     * Deactivate the controller.
     * コントローラを無効化します。
     */
    deactivate() {
        if (!this._isActive) return;
        this._isActive = false;

        if (this._removeListener) {
            this._removeListener();
            this._removeListener = null;
        }
    }

    /**
     * Handle clock tick.
     * Clock の更新を処理します。
     * @param {Cesium.Clock} clock 
     * @private
     */
    _onTick(clock) {
        const now = clock.currentTime;

        // Throttling check
        if (!this._shouldUpdate(now)) return;

        // Get data for current time
        const entry = this._slicer.getEntry(now);

        // Change detection
        // Allow null entries to propagate so outOfRangeBehavior can run
        if (entry !== null && entry === this._lastEntry) {
            return;
        }

        this._lastEntry = entry;
        this._updateHeatbox(entry);
    }

    /**
     * Check if update should proceed (throttling).
     * 更新すべきか判定します(スロットリング)。
     * @param {Cesium.JulianDate} now 
     * @returns {boolean}
     * @private
     */
    _shouldUpdate(_now) {
        const interval = this._options.updateInterval;

        if (interval === 'frame' || !interval) {
            return true;  // Update every frame
        }

        // Check elapsed time since last update
        const currentRealTime = Date.now();
        if (
            this._lastUpdateTime === null ||
            currentRealTime - this._lastUpdateTime >= interval
        ) {
            this._lastUpdateTime = currentRealTime;
            return true;
        }

        return false;
    }

    /**
     * Update Heatbox with new data.
     * Heatbox を新しいデータで更新します。
     * @param {Object|null} entry 
     * @private
     */
    _updateHeatbox(entry) {
        if (!entry) {
            // No data: check outOfRangeBehavior
            if (this._options.outOfRangeBehavior === 'clear') {
                this._heatbox.clear();
            }
            return;
        }

        // Update options
        const updateOptions = { _skipRebuild: false };

        // Global scope handling (Phase 3)
        if (this._options.classificationScope === 'global' && this._heatbox._globalStats) {
            updateOptions._externalStats = this._heatbox._globalStats;
        }

        this._heatbox.setData(entry.data, updateOptions);
    }
}