cesium-heatbox 0.1.9-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,272 @@
1
+ # CesiumJS Heatbox
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Build Status](https://github.com/hiro-nyon/cesium-heatbox/workflows/CI/badge.svg)](https://github.com/hiro-nyon/cesium-heatbox/actions)
5
+ [![Version](https://img.shields.io/github/package-json/v/hiro-nyon/cesium-heatbox?label=version)](https://github.com/hiro-nyon/cesium-heatbox/blob/main/package.json)
6
+
7
+ > **⚠️ 重要 / Important**: このライブラリは現在npm未登録です。GitHubから直接ダウンロードしてご利用ください。
8
+ > This library is currently not registered on npm. Please download directly from GitHub.
9
+
10
+ **日本語** | [English](#english)
11
+
12
+ ## デモ / Live Demo
13
+
14
+ - Playground: https://hiro-nyon.github.io/cesium-heatbox/
15
+ - 背景タイル: CartoDB Light (OSMベース)。高トラフィック時はタイルポリシーにご配慮ください。
16
+ - デモは `gh-pages` ブランチに静的ファイルのみを配置しています。
17
+
18
+ CesiumJS環境内の既存エンティティを対象とした3Dボクセルベースヒートマップ可視化ライブラリです。
19
+
20
+ **English**
21
+
22
+ A 3D voxel-based heatmap visualization library for existing entities in CesiumJS environments.
23
+
24
+ ## 特徴 / Features
25
+
26
+ ### 日本語
27
+ - **Entityベース**: 既存のCesium Entityから自動でデータを取得
28
+ - **自動範囲設定**: エンティティ分布から最適な直方体(AABB)範囲を自動計算
29
+ - **最小ボクセル数**: 指定された範囲を内包する最小限のボクセル数で効率的に処理
30
+ - **相対的色分け**: データ内の最小値・最大値に基づく動的色分け
31
+ - **パフォーマンス最適化**: バッチ描画によるスムーズな3D表示
32
+
33
+ ### English
34
+ - **Entity-based**: Automatically retrieves data from existing Cesium Entities
35
+ - **Automatic Range Setting**: Automatically calculates optimal axis-aligned box ranges from entity distribution
36
+ - **Minimal Voxel Count**: Efficient processing with minimum voxel count covering specified ranges
37
+ - **Relative Color Mapping**: Dynamic color mapping based on min/max values within data
38
+ - **Performance Optimization**: Smooth 3D display through batch rendering
39
+
40
+ ## 既存手法との比較 / Comparison with Existing Approaches
41
+
42
+ ### 日本語
43
+
44
+ **よくある代替手法**
45
+ - 2Dヒートマップ画像の貼り付け(例: heatmap.js を `ImageryLayer` として投影)
46
+ - 点群のクラスタリングやサマリ表示(Cesium の Entity クラスタリング)
47
+ - 他可視化フレームワークのレイヤー(例: deck.gl の HeatmapLayer など)
48
+
49
+ **Heatbox の強み**
50
+ - **真の3Dボクセル表現**: Z方向(高度)の分布を体積として可視化でき、2Dの塗りつぶしでは失われる高さ情報を保持
51
+ - **Entityベースのワークフロー**: 既存 `Cesium.Entity` から直接生成。事前のタイル化やサーバー処理が不要
52
+ - **自動ボクセルサイズ決定 (v0.1.4)**: `autoVoxelSize` によりデータ範囲と件数から最適サイズを自動計算。パフォーマンスと解像度のバランスを自動化
53
+ - **デバッグ境界制御 (v0.1.5)**: `debug.showBounds` でバウンディングボックス表示をON/OFF制御
54
+ - **知覚均等カラーマップ (v0.1.5)**: `viridis`、`inferno` カラーマップと二極性配色(blue-white-red)をサポート
55
+ - **TopN強調表示 (v0.1.5)**: 密度上位N個のボクセルを強調、他を淡色表示する `highlightTopN` オプション
56
+ - **枠線重なり対策 (v0.1.6)**: `voxelGap` による間隔調整と `outlineOpacity` による透明度制御で視認性向上
57
+ - **動的枠線制御 (v0.1.6)**: `outlineWidthResolver` 関数でボクセル毎の枠線太さを密度に応じて動的調整
58
+ - **太線エミュレーション拡張 (v0.1.6.2)**: `outlineEmulation` に 'all', 'non-topn' モード追加で WebGL 1px 制限を回避
59
+ - **厚い枠線表示 (v0.1.6.2)**: `enableThickFrames` で12個のフレームボックスによる視覚的に厚い枠線を実現
60
+ - **インセット枠線 (v0.1.6.1)**: `outlineInset` で枠線をボックス内側にオフセット(`outlineInsetMode` で TopN 限定も可)
61
+ - **Wiki自動同期 (v0.1.6)**: JSDoc → Markdown 変換による GitHub Wiki の自動更新
62
+ - **パフォーマンス制御**: `maxRenderVoxels` と内部検証(例: `validateVoxelCount`)で安定動作を担保
63
+ - **デバッグ/統計の取得**: `getStatistics()` と `getDebugInfo()` でレンダリング状態や調整内容を把握可能
64
+ - **表現の柔軟性**: `wireframeOnly`、`heightBased`、カラーマップ設定などで見やすさを調整
65
+
66
+ **適していないケース(指針)**
67
+ - 数十万〜数百万スケールの体積格子を恒常的に描画する用途 → 専用GPUベースのボリュームレンダリングや3D Tiles等を検討
68
+ - 連続体の科学可視化(例: 医用CT/流体のボリュームレンダリング) → 専用のボリュームレンダリング手法が適合
69
+
70
+ ### English
71
+
72
+ **Common Alternatives**
73
+ - Draped 2D heatmap textures (e.g., heatmap.js projected as an `ImageryLayer`)
74
+ - Point clustering/aggregation using Cesium Entity clustering
75
+ - Layers from other visualization frameworks (e.g., deck.gl HeatmapLayer)
76
+
77
+ **Strengths of Heatbox**
78
+ - **True 3D voxel representation**: Preserves vertical distribution (Z) as volumetric voxels, unlike 2D color fills
79
+ - **Entity-based workflow**: Builds directly from existing `Cesium.Entity` objects; no pre-tiling or server-side processing required
80
+ - **Automatic voxel sizing (v0.1.4)**: `autoVoxelSize` estimates optimal size from data extent and counts for balanced quality/performance
81
+ - **Debug boundary control (v0.1.5)**: `debug.showBounds` for bounding box display ON/OFF control
82
+ - **Perceptually uniform color maps (v0.1.5)**: `viridis`, `inferno` color maps and diverging color scheme (blue-white-red)
83
+ - **TopN highlighting (v0.1.5)**: `highlightTopN` option to emphasize top N density voxels
84
+ - **Outline overlap mitigation (v0.1.6)**: `voxelGap` for spacing and `outlineOpacity` for transparency control
85
+ - **Dynamic outline control (v0.1.6)**: `outlineWidthResolver` function for density-adaptive outline thickness
86
+ - **Extended outline emulation (v0.1.6.2)**: `outlineEmulation` 'all', 'non-topn' modes to bypass WebGL 1px limitation
87
+ - **Thick outline frames (v0.1.6.2)**: `enableThickFrames` creates visually thick outlines using 12 frame boxes
88
+ - **Inset outline (v0.1.6.1)**: `outlineInset` to draw outlines inset from faces (`outlineInsetMode` to limit to TopN)
89
+ - **Wiki auto-sync (v0.1.6)**: JSDoc → Markdown conversion for automated GitHub Wiki updates
90
+ - **Performance guard rails**: `maxRenderVoxels` and internal checks (e.g., `validateVoxelCount`) for stable rendering
91
+ - **Debugging and statistics**: Introspection via `getStatistics()` and `getDebugInfo()`
92
+ - **Flexible presentation**: `wireframeOnly`, `heightBased`, and color map presets for readability
93
+
94
+ **When this may not fit**
95
+ - Persistent rendering of hundreds of thousands to millions of voxels → consider GPU volume rendering or 3D Tiles-based approaches
96
+ - Scientific continuous volumes (e.g., CT/CFD) → dedicated volume rendering techniques are more suitable
97
+
98
+ ## インストール / Installation
99
+
100
+ ### 日本語
101
+
102
+ > **注意**: このライブラリは現在npm未登録のため、以下の方法でインストールしてください。
103
+
104
+ #### 方法1: GitHubから直接クローン
105
+
106
+ ```bash
107
+ git clone https://github.com/hiro-nyon/cesium-heatbox.git
108
+ cd cesium-heatbox
109
+ npm install
110
+ npm run build:umd
111
+ ```
112
+
113
+ #### 方法2: CDN経由で直接利用
114
+
115
+ ```html
116
+ <!-- UMDバンドルを直接読み込み -->
117
+ <script src="https://raw.githubusercontent.com/hiro-nyon/cesium-heatbox/main/dist/cesium-heatbox.umd.min.js"></script>
118
+ ```
119
+
120
+ #### 将来的なnpm対応
121
+
122
+ npm登録を検討中です。登録後は以下のコマンドでインストール可能になります:
123
+
124
+ ```bash
125
+ # 将来的に利用可能予定
126
+ npm install cesium-heatbox
127
+ ```
128
+
129
+ ### English
130
+
131
+ > **Note**: This library is currently not registered on npm. Please install using the following methods:
132
+
133
+ #### Method 1: Direct Clone from GitHub
134
+
135
+ ```bash
136
+ git clone https://github.com/hiro-nyon/cesium-heatbox.git
137
+ cd cesium-heatbox
138
+ npm install
139
+ npm run build:umd
140
+ ```
141
+
142
+ #### Method 2: Direct Use via CDN
143
+
144
+ ```html
145
+ <!-- Load UMD bundle directly -->
146
+ <script src="https://raw.githubusercontent.com/hiro-nyon/cesium-heatbox/main/dist/cesium-heatbox.umd.min.js"></script>
147
+ ```
148
+
149
+ #### Future npm Support
150
+
151
+ We are considering npm registration. After registration, installation will be possible with:
152
+
153
+ ```bash
154
+ # Will be available in the future
155
+ npm install cesium-heatbox
156
+ ```
157
+
158
+ ## 基本的な使用方法 / Basic Usage
159
+
160
+ ### 日本語
161
+
162
+ ```javascript
163
+ import Heatbox from 'cesium-heatbox';
164
+
165
+ // Viewerが初期化済みの状態で
166
+ const heatbox = new Heatbox(viewer, {
167
+ voxelSize: 20,
168
+ opacity: 0.8
169
+ });
170
+
171
+ // エンティティからヒートマップを作成
172
+ const entities = viewer.entities.values;
173
+ const statistics = await heatbox.createFromEntities(entities);
174
+
175
+ console.log('作成完了:', statistics);
176
+ ```
177
+
178
+ ### English
179
+
180
+ ```javascript
181
+ import Heatbox from 'cesium-heatbox';
182
+
183
+ // With initialized Viewer
184
+ const heatbox = new Heatbox(viewer, {
185
+ voxelSize: 20,
186
+ opacity: 0.8
187
+ });
188
+
189
+ // Create heatmap from entities
190
+ const entities = viewer.entities.values;
191
+ const statistics = await heatbox.createFromEntities(entities);
192
+
193
+ console.log('Creation completed:', statistics);
194
+ ```
195
+
196
+ ## API
197
+
198
+ ### 日本語
199
+
200
+ #### コンストラクタ
201
+
202
+ ```javascript
203
+ const heatbox = new Heatbox(viewer, options);
204
+ ```
205
+
206
+ **パラメータ**:
207
+ - `viewer` (Cesium.Viewer): CesiumJSビューワーインスタンス
208
+ - `options` (Object): 設定オプション
209
+
210
+ #### 主要メソッド
211
+
212
+ - `createFromEntities(entities)`: エンティティからヒートマップを作成
213
+ - `setVisible(show)`: 表示/非表示の切り替え
214
+ - `clear()`: ヒートマップをクリア
215
+ - `getStatistics()`: 統計情報を取得
216
+
217
+ ### English
218
+
219
+ #### Constructor
220
+
221
+ ```javascript
222
+ const heatbox = new Heatbox(viewer, options);
223
+ ```
224
+
225
+ **Parameters**:
226
+ - `viewer` (Cesium.Viewer): CesiumJS viewer instance
227
+ - `options` (Object): Configuration options
228
+
229
+ #### Main Methods
230
+
231
+ - `createFromEntities(entities)`: Create heatmap from entities
232
+ - `setVisible(show)`: Toggle visibility
233
+ - `clear()`: Clear heatmap
234
+ - `getStatistics()`: Get statistics
235
+
236
+ ## サンプル / Examples
237
+
238
+ ### 日本語
239
+ 基本的な使用例は `examples/` フォルダを参照してください。
240
+
241
+ ### English
242
+ Please refer to the `examples/` folder for basic usage examples.
243
+
244
+ ## ドキュメント / Documentation
245
+
246
+ 英語 → 日本語の順で掲載し、各ページ先頭に言語切替リンク([English](docs/API.md#english) | [日本語](docs/API.md#日本語))を用意しています。
247
+ Docs are structured English first, then Japanese. Each page includes a language switch at the top.
248
+
249
+ ### 日本語
250
+ - [API リファレンス](docs/API.md)
251
+ - [クイックスタート](docs/quick-start.md)
252
+ - [はじめに](docs/getting-started.md)
253
+ - [開発ガイド](docs/development-guide.md)
254
+
255
+ ### English
256
+ - [API Reference](docs/API.md)
257
+ - [Quick Start](docs/quick-start.md)
258
+ - [Getting Started](docs/getting-started.md)
259
+ - [Development Guide](docs/development-guide.md)
260
+
261
+ ## ライセンス / License
262
+
263
+ MIT License - 詳細は [LICENSE](LICENSE) を参照してください。
264
+ MIT License - See [LICENSE](LICENSE) for details.
265
+
266
+ ## 貢献 / Contributing
267
+
268
+ ### 日本語
269
+ プロジェクトへの貢献を歓迎します!詳細は [CONTRIBUTING.md](docs/contributing.md) を参照してください。
270
+
271
+ ### English
272
+ Contributions to the project are welcome! See [CONTRIBUTING.md](docs/contributing.md) for details.
@@ -0,0 +1,2 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("cesium")):"function"==typeof define&&define.amd?define(["cesium"],e):"object"==typeof exports?exports.CesiumHeatbox=e(require("cesium")):t.CesiumHeatbox=e(t.Cesium)}(this,t=>(()=>{"use strict";var e={50:e=>{e.exports=t}},i={};function o(t){var n=i[t];if(void 0!==n)return n.exports;var a=i[t]={exports:{}};return e[t](a,a.exports,o),a.exports}o.d=(t,e)=>{for(var i in e)o.o(e,i)&&!o.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:e[i]})},o.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var n={};o.d(n,{default:()=>w});var a=o(50);const s={voxelSize:20,opacity:.8,emptyOpacity:.03,showOutline:!0,showEmptyVoxels:!1,minColor:[0,32,255],maxColor:[255,64,0],maxRenderVoxels:5e4,batchMode:"auto",debug:!1,autoVoxelSize:!1,colorMap:"custom",diverging:!1,divergingPivot:0,highlightTopN:null,highlightStyle:{outlineWidth:4,boostOpacity:.2},voxelGap:0,outlineOpacity:1,outlineWidthResolver:null,outlineEmulation:"off",outlineInset:0,outlineInsetMode:"all",enableThickFrames:!1,outlineRenderMode:"standard",adaptiveOutlines:!1,outlineWidthPreset:"uniform",boxOpacityResolver:null,outlineOpacityResolver:null,adaptiveParams:{neighborhoodRadius:50,densityThreshold:5,cameraDistanceFactor:1,overlapRiskFactor:.3},renderLimitStrategy:"density",minCoverageRatio:.2,coverageBinsXY:"auto",autoVoxelSizeMode:"basic",autoVoxelTargetFill:.6,renderBudgetMode:"manual",autoView:!1,fitViewOptions:{paddingPercent:.1,pitch:-30,heading:0,altitudeStrategy:"auto"}},r=5e4,l=1e3,h=(Math.PI,"対象エンティティがありません");let d="undefined"!=typeof process&&process.env&&"true"===process.env.DEBUG?3:"undefined"!=typeof process&&process.env?1:3;const u={error(...t){d>=0&&console.error("[Heatbox ERROR]",...t)},warn(...t){d>=1&&console.warn("[Heatbox WARN]",...t)},info(...t){d>=2&&console.log("[Heatbox INFO]",...t)},debug(...t){d>=3&&console.log("[Heatbox DEBUG]",...t)},setLogLevel:t=>(t&&void 0!==t.debug&&("boolean"==typeof t.debug?d=t.debug?3:1:"object"==typeof t.debug&&null!==t.debug&&(d=3)),d)};function c(t){return!!Array.isArray(t)&&0!==t.length&&(t.length>5e3&&u.warn(`エンティティ数が推奨値(5000)を超えています: ${t.length}`),!0)}function m(t={}){const e={...t};if(e.batchMode&&e.debug&&u.warn("batchMode option is deprecated and will be removed in v1.0.0. It is currently ignored."),void 0!==e.voxelSize&&("number"!=typeof(i=e.voxelSize)||isNaN(i)||i<5||i>l))throw new Error(`ボクセルサイズが無効です: ${e.voxelSize}`);var i;if(void 0!==e.opacity&&(e.opacity=Math.max(0,Math.min(1,e.opacity))),void 0!==e.emptyOpacity&&(e.emptyOpacity=Math.max(0,Math.min(1,e.emptyOpacity))),e.minColor&&Array.isArray(e.minColor)&&3===e.minColor.length&&(e.minColor=e.minColor.map(t=>Math.max(0,Math.min(255,Math.floor(t))))),e.maxColor&&Array.isArray(e.maxColor)&&3===e.maxColor.length&&(e.maxColor=e.maxColor.map(t=>Math.max(0,Math.min(255,Math.floor(t))))),void 0!==e.colorMap&&(["custom","viridis","inferno"].includes(e.colorMap)||(u.warn(`Invalid colorMap: ${e.colorMap}. Using 'custom'.`),e.colorMap="custom")),void 0!==e.highlightTopN&&null!==e.highlightTopN&&("number"!=typeof e.highlightTopN||e.highlightTopN<=0)&&(u.warn(`Invalid highlightTopN: ${e.highlightTopN}. Must be a positive number.`),e.highlightTopN=null),void 0!==e.voxelGap&&(e.voxelGap=Math.max(0,Math.min(100,parseFloat(e.voxelGap)||0))),void 0!==e.outlineOpacity&&(e.outlineOpacity=Math.max(0,Math.min(1,parseFloat(e.outlineOpacity)||1))),void 0!==e.outlineWidthResolver&&null!==e.outlineWidthResolver&&"function"!=typeof e.outlineWidthResolver&&(u.warn("outlineWidthResolver must be a function. Ignoring."),e.outlineWidthResolver=null),void 0!==e.outlineEmulation&&(["off","topn","non-topn","all"].includes(e.outlineEmulation)||(u.warn(`Invalid outlineEmulation: ${e.outlineEmulation}. Using 'off'.`),e.outlineEmulation="off")),void 0!==e.outlineInset){const t=parseFloat(e.outlineInset);e.outlineInset=isNaN(t)||t<0?0:t}if(void 0!==e.outlineInsetMode&&(["all","topn"].includes(e.outlineInsetMode)||(u.warn(`Invalid outlineInsetMode: ${e.outlineInsetMode}. Using 'all'.`),e.outlineInsetMode="all")),void 0!==e.outlineInset){const t=parseFloat(e.outlineInset);e.outlineInset=Math.max(0,Math.min(100,isNaN(t)?0:t))}if(void 0!==e.outlineInsetMode&&(["all","topn"].includes(e.outlineInsetMode)||(u.warn(`Invalid outlineInsetMode: ${e.outlineInsetMode}. Using 'all'.`),e.outlineInsetMode="all")),void 0!==e.enableThickFrames&&(e.enableThickFrames=Boolean(e.enableThickFrames)),void 0!==e.renderLimitStrategy&&(["density","coverage","hybrid"].includes(e.renderLimitStrategy)||(u.warn(`Invalid renderLimitStrategy: ${e.renderLimitStrategy}. Using 'density'.`),e.renderLimitStrategy="density")),void 0!==e.minCoverageRatio){const t=parseFloat(e.minCoverageRatio);e.minCoverageRatio=isNaN(t)?.2:Math.max(0,Math.min(1,t))}if(void 0!==e.coverageBinsXY){const t=e.coverageBinsXY;if("auto"!==t){const i=parseInt(t,10);!Number.isFinite(i)||i<=0?(u.warn(`Invalid coverageBinsXY: ${t}. Using 'auto'.`),e.coverageBinsXY="auto"):e.coverageBinsXY=i}}if(void 0!==e.autoVoxelSizeMode&&(["basic","occupancy"].includes(e.autoVoxelSizeMode)||(u.warn(`Invalid autoVoxelSizeMode: ${e.autoVoxelSizeMode}. Using 'basic'.`),e.autoVoxelSizeMode="basic")),void 0!==e.autoVoxelTargetFill){const t=parseFloat(e.autoVoxelTargetFill);e.autoVoxelTargetFill=isNaN(t)?.6:Math.max(0,Math.min(1,t))}if(void 0!==e.renderBudgetMode&&(["manual","auto"].includes(e.renderBudgetMode)||(u.warn(`Invalid renderBudgetMode: ${e.renderBudgetMode}. Using 'manual'.`),e.renderBudgetMode="manual")),void 0!==e.fitViewOptions){const t=e.fitViewOptions||{},i=parseFloat(t.paddingPercent),o=parseFloat(t.pitch),n=parseFloat(t.heading),a=t.altitudeStrategy;e.fitViewOptions={paddingPercent:Number.isFinite(i)?Math.max(0,Math.min(1,i)):.1,pitch:Number.isFinite(o)?Math.max(-90,Math.min(0,o)):-30,heading:Number.isFinite(n)?n:0,altitudeStrategy:"manual"===a?"manual":"auto"}}return e}function p(t,e){const i=x(t),o=i.x*i.y*Math.max(i.z,10),n=e/o;let a;return a=n>.001?Math.max(10,Math.min(20,20/Math.sqrt(1e3*n))):n>1e-4?Math.max(20,Math.min(50,50/Math.sqrt(1e4*n))):Math.max(50,Math.min(100,100/Math.sqrt(1e5*n))),a=Math.max(5,Math.min(l,a)),u.debug(`Basic voxel size estimated: ${a}m (density: ${n}, volume: ${o})`),Math.round(a)}function x(t){try{const e=(t.minLat+t.maxLat)/2,i=Math.cos(e*Math.PI/180),o=111e3*(t.maxLon-t.minLon)*i,n=111e3*(t.maxLat-t.minLat),a=Math.max(t.maxAlt-t.minAlt,1);return{x:Math.max(o,1),y:Math.max(n,1),z:a}}catch(t){return u.warn("Data range calculation failed:",t),{x:1e3,y:1e3,z:100}}}u.debug,u.warn,u.error,u.info;const g={low:{min:8e3,max:12e3},mid:{min:2e4,max:35e3},high:{min:4e4,max:5e4}};class f{static calculateBounds(t){if(!Array.isArray(t)||0===t.length)throw new Error("エンティティが提供されていません");let e=1/0,i=-1/0,o=1/0,n=-1/0,s=1/0,r=-1/0,l=0;const h=a.JulianDate.now();if(t.forEach((t,d)=>{try{let d;if(t.position&&(d="function"==typeof t.position.getValue?t.position.getValue(h):t.position),!d)return;const u=a.Cartographic.fromCartesian(d);if(!u)return;const c=a.Math.toDegrees(u.longitude),m=a.Math.toDegrees(u.latitude),p=u.height;e=Math.min(e,c),i=Math.max(i,c),o=Math.min(o,m),n=Math.max(n,m),s=Math.min(s,p),r=Math.max(r,p),l++}catch(t){u.warn(`エンティティ ${d} の処理に失敗:`,t)}}),0===l)throw new Error("有効な位置情報を持つエンティティが見つかりません");return u.debug("座標範囲計算完了:",{validCount:l,bounds:{minLon:e,maxLon:i,minLat:o,maxLat:n,minAlt:s,maxAlt:r}}),{minLon:e,maxLon:i,minLat:o,maxLat:n,minAlt:s,maxAlt:r,centerLon:(e+i)/2,centerLat:(o+n)/2,centerAlt:(s+r)/2}}static voxelIndexToCoordinate(t,e,i,o,n){const{minLon:a,maxLon:s,minLat:r,maxLat:l,minAlt:h,maxAlt:d}=o,{numVoxelsX:u,numVoxelsY:c,numVoxelsZ:m}=n;return{lon:a+(t+.5)/u*(s-a),lat:r+(e+.5)/c*(l-r),alt:h+(i+.5)/m*(d-h)}}static coordinateToCartesian3(t,e,i){return a.Cartesian3.fromDegrees(t,e,i)}}class y{static createGrid(t,e){const i=(t.minLat+t.maxLat)/2,o=111e3*(t.maxLon-t.minLon)*Math.cos(i*Math.PI/180),n=111e3*(t.maxLat-t.minLat),a=t.maxAlt-t.minAlt,s=Math.max(1,Math.ceil(o/e)),r=Math.max(1,Math.ceil(n/e)),l=Math.max(1,Math.ceil(a/e)),h=s>0?o/s:e,d=r>0?n/r:e,c=l>0?Math.max(a/l,1):Math.max(e,1),m=s*r*l;return u.debug("VoxelGrid created:",{numVoxelsX:s,numVoxelsY:r,numVoxelsZ:l,totalVoxels:m,voxelSizeMeters:e,cellSizeX:h,cellSizeY:d,cellSizeZ:c,lonRangeMeters:o,latRangeMeters:n,altRangeMeters:a}),{numVoxelsX:s,numVoxelsY:r,numVoxelsZ:l,totalVoxels:m,voxelSizeMeters:e,cellSizeX:h,cellSizeY:d,cellSizeZ:c,lonRangeMeters:o,latRangeMeters:n,altRangeMeters:a}}static getVoxelKey(t,e,i){return`${t},${e},${i}`}static parseVoxelKey(t){const[e,i,o]=t.split(",").map(Number);return{x:e,y:i,z:o}}static iterateAllVoxels(t,e){const{numVoxelsX:i,numVoxelsY:o,numVoxelsZ:n}=t;for(let t=0;t<i;t++)for(let i=0;i<o;i++)for(let o=0;o<n;o++)e(t,i,o,this.getVoxelKey(t,i,o))}}class v{static classifyEntitiesIntoVoxels(t,e,i){const o=new Map;let n=0,s=0;u.debug(`Processing ${t.length} entities for classification`);const r=a.JulianDate.now();return t.forEach((t,l)=>{try{let l;if(t.position&&(l="function"==typeof t.position.getValue?t.position.getValue(r):t.position),!l)return void s++;const h=a.Cartographic.fromCartesian(l);if(!h)return void s++;const d=a.Math.toDegrees(h.longitude),u=a.Math.toDegrees(h.latitude),c=h.height;if(d<e.minLon-.001||d>e.maxLon+.001||u<e.minLat-.001||u>e.maxLat+.001||c<e.minAlt-1||c>e.maxAlt+1)return void s++;const m=e.maxLon-e.minLon,p=e.maxLat-e.minLat,x=e.maxAlt-e.minAlt,g=0===m?0:Math.floor((d-e.minLon)/m*i.numVoxelsX),f=0===p?0:Math.floor((u-e.minLat)/p*i.numVoxelsY),v=0===x?0:Math.floor((c-e.minAlt)/x*i.numVoxelsZ);if(g>=0&&g<i.numVoxelsX&&f>=0&&f<i.numVoxelsY&&v>=0&&v<i.numVoxelsZ){const e=y.getVoxelKey(g,f,v);o.has(e)||o.set(e,{x:g,y:f,z:v,entities:[],count:0});const i=o.get(e);i.entities.push(t),i.count++,n++}else s++}catch(t){u.warn(`エンティティ ${l} の処理に失敗:`,t),s++}}),u.info(`${n}個のエンティティを${o.size}個のボクセルに分類(${s}個はスキップ)`),o}static calculateStatistics(t,e){if(0===t.size)return{totalVoxels:e.totalVoxels,renderedVoxels:0,nonEmptyVoxels:0,emptyVoxels:e.totalVoxels,totalEntities:0,minCount:0,maxCount:0,averageCount:0,autoAdjusted:!1,originalVoxelSize:null,finalVoxelSize:null,adjustmentReason:null};const i=Array.from(t.values()).map(t=>t.count),o=i.reduce((t,e)=>t+e,0),n={totalVoxels:e.totalVoxels,renderedVoxels:0,nonEmptyVoxels:t.size,emptyVoxels:e.totalVoxels-t.size,totalEntities:o,minCount:Math.min(...i),maxCount:Math.max(...i),averageCount:o/t.size,autoAdjusted:!1,originalVoxelSize:null,finalVoxelSize:null,adjustmentReason:null};return u.debug("統計情報計算完了:",n),n}static getTopNVoxels(t,e){if(0===t.size||e<=0)return[];const i=Array.from(t.values()).sort((t,e)=>e.count-t.count);return i.slice(0,Math.min(e,i.length))}}const M={viridis:[[68,1,84],[71,44,122],[59,81,139],[44,113,142],[33,144,141],[39,173,129],[92,200,99],[170,220,50],[253,231,37],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],inferno:[[0,0,4],[31,12,72],[85,15,109],[136,34,106],[186,54,85],[227,89,51],[249,142,8],[252,187,17],[245,219,76],[252,255,164],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255],[255,255,255]],diverging:[[0,0,255],[32,64,255],[64,128,255],[96,160,255],[128,192,255],[160,224,255],[192,240,255],[224,248,255],[255,255,255],[255,248,224],[255,240,192],[255,224,160],[255,192,128],[255,160,96],[255,128,64],[255,64,32],[255,0,0]]};class b{constructor(t,e={}){this.viewer=t,this.options={minColor:[0,0,255],maxColor:[255,0,0],opacity:.8,emptyOpacity:.03,showOutline:!0,showEmptyVoxels:!1,wireframeOnly:!1,heightBased:!1,outlineWidth:2,outlineInset:0,outlineInsetMode:"all",outlineRenderMode:"standard",adaptiveOutlines:!1,outlineWidthPreset:"uniform",boxOpacityResolver:null,outlineOpacityResolver:null,adaptiveParams:{neighborhoodRadius:50,densityThreshold:5,cameraDistanceFactor:1,overlapRiskFactor:.3},...e},this.voxelEntities=[],u.debug("VoxelRenderer initialized with options:",this.options)}_calculateAdaptiveParams(t,e,i,o){if(!this.options.adaptiveOutlines)return{outlineWidth:null,boxOpacity:null,outlineOpacity:null,shouldUseEmulation:!1};const{x:n,y:a,z:s,count:r}=t,l=o.maxCount>o.minCount?(r-o.minCount)/(o.maxCount-o.minCount):0;let h=0,d=0;const u=Math.max(1,Math.floor(this.options.adaptiveParams.neighborhoodRadius/20));for(let t=-u;t<=u;t++)for(let e=-u;e<=u;e++)for(let o=-u;o<=u;o++){if(0===t&&0===e&&0===o)continue;const r=`${n+t},${a+e},${s+o}`,l=i.get(r);l&&(h+=l.count,d++)}const c=(d>0?h/d:0)>this.options.adaptiveParams.densityThreshold,m=Math.min(1,1)*this.options.adaptiveParams.cameraDistanceFactor,p=c?this.options.adaptiveParams.overlapRiskFactor:0;let x,g,f;switch(this.options.outlineWidthPreset){case"adaptive-density":x=c?Math.max(.5,this.options.outlineWidth*(.5+.5*l)):this.options.outlineWidth,g=c?.8*this.options.opacity:this.options.opacity,f=c?.6:1;break;case"topn-focus":x=e?this.options.outlineWidth*(1.5+.5*l):Math.max(.5,.7*this.options.outlineWidth),g=e?this.options.opacity:.6*this.options.opacity,f=e?1:.4;break;default:x=this.options.outlineWidth,g=this.options.opacity,f=this.options.outlineOpacity||1}return x*=m,f=Math.max(.2,f*(1-p)),{outlineWidth:Math.max(.5,x),boxOpacity:Math.max(.1,Math.min(1,g)),outlineOpacity:Math.max(.2,Math.min(1,f)),shouldUseEmulation:c||x>2&&"standard"!==this.options.outlineRenderMode}}render(t,e,i,o){this.clear(),u.debug("VoxelRenderer.render - Starting render with simplified approach",{voxelDataSize:t.size,bounds:e,grid:i,statistics:o}),this._shouldShowBounds()&&this._renderBoundingBox(e);let n=[];const s=new Set;if(this.options.showEmptyVoxels){const e=Math.min(i.totalVoxels,this.options.maxRenderVoxels||1e4);u.debug(`Generating grid for up to ${e} voxels`);for(let o=0;o<i.numVoxelsX;o++){for(let a=0;a<i.numVoxelsY;a++){for(let s=0;s<i.numVoxelsZ;s++){const i=`${o},${a},${s}`,r=t.get(i)||{x:o,y:a,z:s,count:0};if(n.push({key:i,info:r}),n.length>=e){u.debug(`Reached maximum voxel limit of ${e}`);break}}if(n.length>=e)break}if(n.length>=e)break}}else if(n=Array.from(t.entries()).map(([t,e])=>({key:t,info:e})),this.options.maxRenderVoxels&&n.length>this.options.maxRenderVoxels){const t=this._selectVoxelsForRendering(n,this.options.maxRenderVoxels,e,i);n=t.selectedVoxels,this._selectionStats={strategy:t.strategy,clippedNonEmpty:t.clippedNonEmpty,coverageRatio:t.coverageRatio||0},u.debug(`Applied ${t.strategy} strategy: ${n.length} voxels selected, ${t.clippedNonEmpty} clipped`)}this.options.highlightTopN&&this.options.highlightTopN>0&&([...n].sort((t,e)=>e.info.count-t.info.count).slice(0,this.options.highlightTopN).forEach(t=>s.add(t.key)),u.debug(`TopN highlight enabled: ${s.size} voxels will be highlighted`)),u.debug(`Rendering ${n.length} voxels`);let r=0;return n.forEach(({key:n,info:l})=>{try{const{x:h,y:d,z:c}=l,m=e.minLon+(h+.5)*(e.maxLon-e.minLon)/i.numVoxelsX,p=e.minLat+(d+.5)*(e.maxLat-e.minLat)/i.numVoxelsY,x=e.minAlt+(c+.5)*(e.maxAlt-e.minAlt)/i.numVoxelsZ,g=s.has(n),f=this._calculateAdaptiveParams(l,g,t,o);let y,v;if(0===l.count)y=a.Color.LIGHTGRAY,v=this.options.emptyOpacity;else{const t=o.maxCount>o.minCount?(l.count-o.minCount)/(o.maxCount-o.minCount):0;if(y=this.interpolateColor(t,l.count),this.options.boxOpacityResolver&&"function"==typeof this.options.boxOpacityResolver){const e={voxel:{x:h,y:d,z:c,count:l.count},isTopN:g,normalizedDensity:t,statistics:o,adaptiveParams:f};try{const t=this.options.boxOpacityResolver(e);v=isNaN(t)?this.options.opacity:Math.max(0,Math.min(1,t))}catch(t){u.warn("boxOpacityResolver error, using fallback:",t),v=f.boxOpacity||this.options.opacity}}else v=f.boxOpacity||this.options.opacity;!this.options.highlightTopN||g||this.options.boxOpacityResolver||(v*=1-(this.options.highlightStyle?.boostOpacity||.2))}let M=i.cellSizeX||(i.lonRangeMeters?i.lonRangeMeters/i.numVoxelsX:i.voxelSizeMeters),b=i.cellSizeY||(i.latRangeMeters?i.latRangeMeters/i.numVoxelsY:i.voxelSizeMeters),w=i.cellSizeZ||(i.altRangeMeters?Math.max(i.altRangeMeters/Math.max(i.numVoxelsZ,1),1):Math.max(i.voxelSizeMeters,1));this.options.voxelGap>0&&(M=Math.max(M-this.options.voxelGap,.1*M),b=Math.max(b-this.options.voxelGap,.1*b),w=Math.max(w-this.options.voxelGap,.1*w));let V,z,R=w;if(this.options.heightBased&&l.count>0&&(R=w*(.1+.9*(o.maxCount>o.minCount?(l.count-o.minCount)/(o.maxCount-o.minCount):0))),this.options.outlineWidthResolver&&"function"==typeof this.options.outlineWidthResolver){const t=o.maxCount>o.minCount?(l.count-o.minCount)/(o.maxCount-o.minCount):0,e={voxel:{x:h,y:d,z:c,count:l.count},isTopN:g,normalizedDensity:t,statistics:o,adaptiveParams:f};try{V=this.options.outlineWidthResolver(e),isNaN(V)&&(V=f.outlineWidth||this.options.outlineWidth)}catch(t){u.warn("outlineWidthResolver error, using fallback:",t),V=f.outlineWidth||this.options.outlineWidth}}else V=this.options.adaptiveOutlines&&null!==f.outlineWidth?f.outlineWidth:g&&this.options.highlightTopN&&this.options.highlightStyle?.outlineWidth||this.options.outlineWidth;if(this.options.outlineOpacityResolver&&"function"==typeof this.options.outlineOpacityResolver){const t=o.maxCount>o.minCount?(l.count-o.minCount)/(o.maxCount-o.minCount):0,e={voxel:{x:h,y:d,z:c,count:l.count},isTopN:g,normalizedDensity:t,statistics:o,adaptiveParams:f};try{const t=this.options.outlineOpacityResolver(e);z=isNaN(t)?this.options.outlineOpacity??1:Math.max(0,Math.min(1,t))}catch(t){u.warn("outlineOpacityResolver error, using fallback:",t),z=f.outlineOpacity||(this.options.outlineOpacity??1)}}else z=f.outlineOpacity||(this.options.outlineOpacity??1);const S=y.withAlpha(z);let C=!0,_=!1,L=!1;switch(this.options.outlineRenderMode){case"standard":C=this.options.showOutline,_=this.options.outlineInset>0;break;case"inset":C=!1,_=!0;break;case"emulation-only":C=!1,_=!1,L=!0}let E=L;L||("topn"===this.options.outlineEmulation?E=g&&(V||1)>1:"non-topn"===this.options.outlineEmulation?E=!g&&(V||1)>1:"all"===this.options.outlineEmulation?E=(V||1)>1:this.options.adaptiveOutlines&&f.shouldUseEmulation&&(E=!0));const $={position:a.Cartesian3.fromDegrees(m,p,x),box:{dimensions:new a.Cartesian3(M,b,R),outline:C&&!E,outlineColor:S,outlineWidth:Math.max(V||1,0)},properties:{type:"voxel",key:n,count:l.count,x:h,y:d,z:c},description:this.createVoxelDescription(l,n)};this.options.wireframeOnly?($.box.material=a.Color.TRANSPARENT,$.box.fill=!1):($.box.material=y.withAlpha(v),$.box.fill=!0);const A=this.viewer.entities.add($);if(this.voxelEntities.push(A),_&&this._shouldApplyInsetOutline(g))try{const t=this.options.outlineInset>0?this.options.outlineInset:1;this._createInsetOutline(m,p,x,M,b,R,S,Math.max(V||1,1),n,t)}catch(t){u.warn("Failed to create inset outline:",t)}if(E)try{const t=A.position.getValue(a.JulianDate.now()),e=.2*M,i=.2*b,o=.2*R,n=this.options.outlineInset&&this.options.outlineInset>0?this.options.outlineInset:0,s=.05*M,r=.05*b,l=.05*R,h=Math.min(n>0?n:s,e),d=Math.min(n>0?n:r,i),u=Math.min(n>0?n:l,o),c=Math.max(M-h,.1*M),m=Math.max(b-d,.1*b),p=Math.max(R-u,.1*R);this._addEdgePolylines(t,c,m,p,S,Math.max(V,1))}catch(t){u.warn("Failed to add emulated thick outline polylines:",t)}r++}catch(t){u.warn("Error rendering voxel:",t)}}),u.info(`Successfully rendered ${r} voxels`),r}_renderBoundingBox(t){if(t)try{const e=(t.minLon+t.maxLon)/2,i=(t.minLat+t.maxLat)/2,o=(t.minAlt+t.maxAlt)/2,n=111e3*(t.maxLon-t.minLon)*Math.cos(i*Math.PI/180),s=111e3*(t.maxLat-t.minLat),r=t.maxAlt-t.minAlt,l=this.viewer.entities.add({position:a.Cartesian3.fromDegrees(e,i,o),box:{dimensions:new a.Cartesian3(n,s,r),material:a.Color.YELLOW.withAlpha(.1),outline:!0,outlineColor:a.Color.YELLOW.withAlpha(.3),outlineWidth:2},description:`バウンディングボックス<br>サイズ: ${n.toFixed(1)} x ${s.toFixed(1)} x ${r.toFixed(1)} m`});this.voxelEntities.push(l),u.debug("Debug bounding box added:",{center:{lon:e,lat:i,alt:o},size:{width:n,depth:s,height:r}})}catch(t){u.warn("Failed to render bounding box:",t)}}_addEdgePolylines(t,e,i,o,n,s){try{const r=e/2,l=i/2,h=o/2,d=a.Transforms.eastNorthUpToFixedFrame(t),u=(t,e,i)=>{const o=new a.Cartesian3(t,e,i);return a.Matrix4.multiplyByPoint(d,o,new a.Cartesian3)},c=[u(-r,-l,-h),u(r,-l,-h),u(r,l,-h),u(-r,l,-h),u(-r,-l,h),u(r,-l,h),u(r,l,h),u(-r,l,h)];[[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]].forEach(([t,e])=>{const i=this.viewer.entities.add({polyline:{positions:[c[t],c[e]],width:s,material:n,arcType:a.ArcType.NONE}});this.voxelEntities.push(i)})}catch(t){u.warn("Edge polyline creation failed:",t)}}interpolateColor(t,e=null){if(this.options.diverging&&null!==e&&("number"==typeof this.options.divergingPivot?this.options.divergingPivot:0)>0)return this._interpolateDivergingColor(e);if(this.options.colorMap&&"custom"!==this.options.colorMap)return this._interpolateFromColorMap(t,this.options.colorMap);const[i,o,n]=this.options.minColor,[s,r,l]=this.options.maxColor,h=Math.round(i+(s-i)*t),d=Math.round(o+(r-o)*t),u=Math.round(n+(l-n)*t);return a.Color.fromBytes(h,d,u)}_interpolateFromColorMap(t,e){const i=M[e];if(!i)return u.warn(`Unknown color map: ${e}. Falling back to custom.`),this.interpolateColor(t);const o=t*(i.length-1),n=Math.floor(o),s=Math.min(n+1,i.length-1),r=o-n,[l,h,d]=i[n],[c,m,p]=i[s],x=Math.round(l+(c-l)*r),g=Math.round(h+(m-h)*r),f=Math.round(d+(p-d)*r);return a.Color.fromBytes(x,g,f)}_interpolateDivergingColor(t){const e=this.options.divergingPivot||0;let i;return t<=e?(i=t/e*.5,i=Math.max(0,Math.min(.5,i))):(i=.5+(t-e)/e*.5,i=Math.max(.5,Math.min(1,i))),this._interpolateFromColorMap(i,"diverging")}createVoxelDescription(t,e){return`\n <div style="padding: 10px; font-family: Arial, sans-serif;">\n <h3 style="margin-top: 0;">ボクセル [${t.x}, ${t.y}, ${t.z}]</h3>\n <table style="width: 100%;">\n <tr><td><b>エンティティ数:</b></td><td>${t.count}</td></tr>\n <tr><td><b>ID:</b></td><td>${e}</td></tr>\n </table>\n </div>\n `}clear(){u.debug("VoxelRenderer.clear - Removing",this.voxelEntities.length,"entities"),this.voxelEntities.forEach(t=>{try{const e="function"==typeof t.isDestroyed&&t.isDestroyed();t&&!e&&this.viewer.entities.remove(t)}catch(t){u.warn("Entity removal error:",t)}}),this.voxelEntities=[]}_shouldShowBounds(){return!!this.options.debug&&("boolean"==typeof this.options.debug?this.options.debug:"object"==typeof this.options.debug&&null!==this.options.debug&&!0===this.options.debug.showBounds)}_shouldApplyInsetOutline(t){return"topn"!==(this.options.outlineInsetMode||"all")||t}_createInsetOutline(t,e,i,o,n,s,r,l,h,d=null){const c=.2*o,m=.2*n,p=.2*s,x=null!==d?d:this.options.outlineInset,g=Math.min(x,c),f=Math.min(x,m),y=Math.min(x,p),v=Math.max(o-2*g,.1*o),M=Math.max(n-2*f,.1*n),b=Math.max(s-2*y,.1*s),w=this.viewer.entities.add({position:a.Cartesian3.fromDegrees(t,e,i),box:{dimensions:new a.Cartesian3(v,M,b),fill:!1,outline:!0,outlineColor:r,outlineWidth:Math.max(l||1,0)},properties:{type:"voxel-inset-outline",parentKey:h,insetSize:{x:v,y:M,z:b}}});this.voxelEntities.push(w),this.options.enableThickFrames&&(g>.1||f>.1||y>.1)&&this._createThickOutlineFrames(t,e,i,o,n,s,v,M,b,r,h),u.debug(`Inset outline created for voxel ${h}:`,{originalSize:{x:o,y:n,z:s},insetSize:{x:v,y:M,z:b},effectiveInset:{x:g,y:f,z:y}})}_createThickOutlineFrames(t,e,i,o,n,s,r,l,h,d,c){const m=(o-r)/2,p=(n-l)/2,x=(s-h)/2,g=o/2,f=n/2,y=s/2,v=r/2,M=l/2,b=h/2;u.debug(`Frame bounds for ${c}:`,{frameThick:{x:m,y:p,z:x},outerBound:{x:g,y:f,z:y},innerBound:{x:v,y:M,z:b}});const w=[{pos:[0,(f+M)/2,y-x/2],size:[r,p,x],name:"top-back"},{pos:[0,-(f+M)/2,y-x/2],size:[r,p,x],name:"top-front"},{pos:[(g+v)/2,0,y-x/2],size:[m,n,x],name:"top-right"},{pos:[-(g+v)/2,0,y-x/2],size:[m,n,x],name:"top-left"},{pos:[0,(f+M)/2,x/2-y],size:[r,p,x],name:"bottom-back"},{pos:[0,-(f+M)/2,x/2-y],size:[r,p,x],name:"bottom-front"},{pos:[(g+v)/2,0,x/2-y],size:[m,n,x],name:"bottom-right"},{pos:[-(g+v)/2,0,x/2-y],size:[m,n,x],name:"bottom-left"},{pos:[(g+v)/2,(f+M)/2,0],size:[m,p,h],name:"vertical-back-right"},{pos:[(g+v)/2,-(f+M)/2,0],size:[m,p,h],name:"vertical-front-right"},{pos:[-(g+v)/2,(f+M)/2,0],size:[m,p,h],name:"vertical-back-left"},{pos:[-(g+v)/2,-(f+M)/2,0],size:[m,p,h],name:"vertical-front-left"}];w.forEach(o=>{if(o.size[0]>.1&&o.size[1]>.1&&o.size[2]>.1)try{const n=1/(111e3*Math.cos(e*Math.PI/180)),s=1/111e3,r=t+o.pos[0]*n,l=e+o.pos[1]*s,h=i+o.pos[2],u=this.viewer.entities.add({position:a.Cartesian3.fromDegrees(r,l,h),box:{dimensions:new a.Cartesian3(o.size[0],o.size[1],o.size[2]),material:d.withAlpha(.8),outline:!1,fill:!0},properties:{type:"voxel-outline-frame",parentKey:c,frameName:o.name}});this.voxelEntities.push(u)}catch(t){u.warn(`Failed to create outline frame ${o.name}:`,t)}}),u.debug(`Thick outline frames created for voxel ${c}: ${w.length} frames`)}setVisible(t){u.debug("VoxelRenderer.setVisible:",t),this.voxelEntities.forEach(e=>{try{const i="function"==typeof e.isDestroyed&&e.isDestroyed();e&&!i&&(e.show=t)}catch(t){u.warn("Entity visibility error:",t)}})}_selectVoxelsForRendering(t,e,i,o){const n=this.options.renderLimitStrategy||"density",a=new Set;this.options.highlightTopN&&this.options.highlightTopN>0&&[...t].sort((t,e)=>e.info.count-t.info.count).slice(0,this.options.highlightTopN).forEach(t=>a.add(t.key));let s,r,l=null;switch(n){case"coverage":s=this._selectByCoverage(t,e,o,a).selected,r=t.length-s.length;break;case"hybrid":{const i=this._selectByHybrid(t,e,o,a);s=i.selected,r=t.length-s.length,l=i.coverageRatio;break}default:s=this._selectByDensity(t,e,a).selected,r=t.length-s.length}return{selectedVoxels:s,strategy:n,clippedNonEmpty:r,coverageRatio:l}}_selectByDensity(t,e,i=new Set){const o=[...t].sort((t,e)=>e.info.count-t.info.count),n=[],a=new Set;return o.forEach(t=>{i.has(t.key)&&n.length<e&&(n.push(t),a.add(t.key))}),o.forEach(t=>{!a.has(t.key)&&n.length<e&&(n.push(t),a.add(t.key))}),{selected:n}}_selectByCoverage(t,e,i,o=new Set){const n=[],a=new Set;t.forEach(t=>{o.has(t.key)&&n.length<e&&(n.push(t),a.add(t.key))});const s="auto"===this.options.coverageBinsXY?Math.ceil(Math.sqrt(e/4)):this.options.coverageBinsXY,r=new Map;t.filter(t=>!a.has(t.key)).forEach(t=>{const e=`${Math.max(0,Math.min(s-1,Math.floor(t.info.x/Math.max(1,i.numVoxelsX)*s)))},${Math.max(0,Math.min(s-1,Math.floor(t.info.y/Math.max(1,i.numVoxelsY)*s)))}`;r.has(e)||r.set(e,[]),r.get(e).push(t)});const l=Array.from(r.keys());let h=0;for(;n.length<e&&h<10*l.length;){const t=l[h%l.length],e=r.get(t);if(e&&e.length>0){e.sort((t,e)=>e.info.count-t.info.count);const i=e.shift();a.has(i.key)||(n.push(i),a.add(i.key)),0===e.length&&(r.delete(t),l.splice(l.indexOf(t),1))}h++}return{selected:n}}_selectByHybrid(t,e,i,o=new Set){const n=this.options.minCoverageRatio||.2,a=[],s=new Set;t.forEach(t=>{o.has(t.key)&&a.length<e&&(a.push(t),s.add(t.key))});const r=e-a.length,l=Math.floor(r*n),h=r-l;l>0&&this._selectByCoverage(t.filter(t=>!s.has(t.key)),l,i,new Set).selected.forEach(t=>{a.length<e&&!s.has(t.key)&&(a.push(t),s.add(t.key))}),h>0&&this._selectByDensity(t.filter(t=>!s.has(t.key)),h,new Set).selected.forEach(t=>{a.length<e&&!s.has(t.key)&&(a.push(t),s.add(t.key))});const d=l>0?(a.length-o.size-h)/(a.length-o.size):0;return{selected:a,coverageRatio:d}}getSelectionStats(){return this._selectionStats||null}}const w=class{constructor(t,e={}){if(!function(t){if(!t)return!1;if(!t.scene||!t.entities||!t.scene.canvas)return!1;const e=t.scene.canvas;return!!(e.getContext("webgl2")||e.getContext("webgl")||e.getContext("experimental-webgl"))}(t))throw new Error("CesiumJS Viewerが無効です");this.viewer=t;const i={...s,...e};this.options=m(function(t){if("auto"!==t.renderBudgetMode&&"auto"!==t.maxRenderVoxels)return t;const e=function(){try{const t=function(){try{const t=document.createElement("canvas"),e=t.getContext("webgl2")||t.getContext("webgl");if(!e)return{webgl2:!1,maxTextureSize:0,maxRenderbufferSize:0};const i={webgl2:!!t.getContext("webgl2"),maxTextureSize:e.getParameter(e.MAX_TEXTURE_SIZE),maxRenderbufferSize:e.getParameter(e.MAX_RENDERBUFFER_SIZE)};return t.remove(),i}catch(t){return u.warn("WebGL info detection failed:",t),{webgl2:!1,maxTextureSize:0,maxRenderbufferSize:0}}}(),e=function(){try{return{deviceMemory:navigator.deviceMemory||null,hardwareConcurrency:navigator.hardwareConcurrency||null,devicePixelRatio:window.devicePixelRatio||1,screenPixels:screen.width*screen.height*Math.pow(window.devicePixelRatio||1,2),userAgent:navigator.userAgent}}catch(t){return u.warn("Device info detection failed:",t),{deviceMemory:null,hardwareConcurrency:null,devicePixelRatio:1,screenPixels:2073600,userAgent:""}}}();let i="mid",o="fallback";if(null!==e.deviceMemory)i=e.deviceMemory<=4?"low":e.deviceMemory<=8?"mid":"high",o="deviceMemory";else if(null!==e.hardwareConcurrency){const t=e.hardwareConcurrency*Math.min(e.screenPixels/2073600,2);i=t<=4?"low":t<=8?"mid":"high",o="hardwareConcurrency+resolution"}(t.maxTextureSize<4096||!t.webgl2)&&(i="high"===i?"mid":"low",o+="+webglLimits");const n=g[i],a=Math.min(Math.floor((n.min+n.max)/2),r);return u.debug(`Device tier detected: ${i} (${o}), maxRenderVoxels: ${a}`),{tier:i,maxRenderVoxels:a,detectionMethod:o,deviceInfo:e,webglInfo:t}}catch(t){return u.warn("Device tier detection failed, using default mid tier:",t),{tier:"mid",maxRenderVoxels:Math.min(25e3,r),detectionMethod:"error-fallback",deviceInfo:null,webglInfo:null}}}(),i={...t,maxRenderVoxels:e.maxRenderVoxels,_autoRenderBudget:{tier:e.tier,detectionMethod:e.detectionMethod,autoMaxRenderVoxels:e.maxRenderVoxels}};return u.info(`Auto Render Budget applied: ${e.tier} tier, maxRenderVoxels: ${e.maxRenderVoxels}`),i}(i)),u.setLogLevel(this.options),this.renderer=new b(this.viewer,this.options),this._bounds=null,this._grid=null,this._voxelData=null,this._statistics=null,this._eventHandler=null,this._initializeEventListeners()}async setData(t){if(c(t))try{if(u.debug("Heatbox.setData - 処理開始:",t.length,"個のエンティティ"),u.debug("Step 1: 境界計算"),this._bounds=f.calculateBounds(t),!this._bounds)return u.error("境界計算に失敗"),void this.clear();u.debug("境界計算完了:",this._bounds);let e=this.options.voxelSize||s.voxelSize,i=null;if(this.options.autoVoxelSize&&!this.options.voxelSize)try{u.debug("自動ボクセルサイズ調整開始");const o={autoVoxelSizeMode:this.options.autoVoxelSizeMode,autoVoxelTargetFill:this.options.autoVoxelTargetFill,maxRenderVoxels:this.options.maxRenderVoxels},n=function(t,e,i={}){try{return"occupancy"===(i.autoVoxelSizeMode||"basic")?function(t,e,i){const o=x(t),n=i.maxRenderVoxels||5e4,a=i.autoVoxelTargetFill||.6;let s=p(t,e);u.debug(`Starting occupancy-based estimation: N=${e}, target=${a}, maxVoxels=${n}`);for(let t=0;t<10;t++){const i=Math.ceil(o.x/s)*Math.ceil(o.y/s)*Math.ceil(o.z/s),r=i*(1-Math.exp(-e/i)),h=Math.min(r/n,1);if(u.debug(`Iteration ${t}: size=${s.toFixed(1)}m, totalVoxels=${i}, expectedOccupied=${r.toFixed(0)}, fill=${h.toFixed(3)}`),Math.abs(h-a)<.05){u.debug(`Converged at iteration ${t}: size=${s.toFixed(1)}m, fill=${h.toFixed(3)}`);break}s*=Math.pow(h/a,.3),s=Math.max(5,Math.min(l,s))}const r=Math.round(s);return u.info(`Occupancy-based voxel size: ${r}m (target fill: ${a})`),r}(t,e,i):p(t,e)}catch(t){return u.warn("Initial voxel size estimation failed:",t),20}}(this._bounds,t.length,o),a=y.createGrid(this._bounds,n),s=function(t,e){const i={valid:!0,warning:!1,error:null,recommendedSize:null};return t>r?(i.valid=!1,i.error="ボクセル数が上限を超えています",i.recommendedSize=Math.ceil(e*Math.pow(t/r,1/3))):t>3e4&&(i.warning=!0,i.error="推定メモリ使用量が警告値を超えています"),i}(a.totalVoxels,n);!s.valid&&s.recommendedSize?(e=s.recommendedSize,i={enabled:!0,mode:this.options.autoVoxelSizeMode,originalSize:n,finalSize:e,adjusted:!0,reason:`Performance limit exceeded: ${a.totalVoxels} > 50000`},u.info(`Auto-adjusted voxelSize: ${n}m → ${e}m (${a.totalVoxels} voxels)`)):(e=n,i={enabled:!0,mode:this.options.autoVoxelSizeMode,originalSize:n,finalSize:e,adjusted:!1,reason:null},u.info(`Auto-determined voxelSize: ${e}m`))}catch(t){u.warn("Auto voxel size adjustment failed, using default:",t),e=s.voxelSize,i={enabled:!0,adjusted:!1,reason:"Estimation failed, using default size",originalSize:null,finalSize:e}}u.debug("Step 2: グリッド生成 (サイズ:",e,"m)"),this._grid=y.createGrid(this._bounds,e),u.debug("グリッド生成完了:",this._grid),u.debug("Step 3: エンティティ分類"),this._voxelData=v.classifyEntitiesIntoVoxels(t,this._bounds,this._grid),u.debug("エンティティ分類完了:",this._voxelData.size,"個のボクセル"),u.debug("Step 4: 統計計算"),this._statistics=v.calculateStatistics(this._voxelData,this._grid),u.debug("統計情報:",this._statistics),i&&(this._statistics.autoAdjusted=i.adjusted,this._statistics.originalVoxelSize=i.originalSize,this._statistics.finalVoxelSize=i.finalSize,this._statistics.adjustmentReason=i.reason),u.debug("Step 5: 描画");const o=this.renderer.render(this._voxelData,this._bounds,this._grid,this._statistics);if(this._statistics.renderedVoxels=o,u.info("描画完了 - 実際の描画数:",o),this.options.autoView)try{u.debug("Auto view adjustment triggered"),await this.fitView(),u.debug("Auto view adjustment completed")}catch(t){u.warn("Auto view adjustment failed:",t)}u.debug("Heatbox.setData - 処理完了")}catch(t){throw u.error("ヒートマップ作成エラー:",t),this.clear(),t}else this.clear()}async createFromEntities(t){if(!c(t))throw new Error(h);return await this.setData(t),this.getStatistics()}setVisible(t){this.renderer.setVisible(t)}clear(){this.renderer.clear(),this._bounds=null,this._grid=null,this._voxelData=null,this._statistics=null}destroy(){this.clear(),this._eventHandler&&!this._eventHandler.isDestroyed()&&this._eventHandler.destroy(),this._eventHandler=null}dispose(){this.destroy()}getOptions(){return{...this.options}}updateOptions(t){if(this.options=m({...this.options,...t}),this.renderer.options=this.options,this._voxelData){const t=this.renderer.render(this._voxelData,this._bounds,this._grid,this._statistics);this._statistics.renderedVoxels=t}}_initializeEventListeners(){this._eventHandler=new a.ScreenSpaceEventHandler(this.viewer.scene.canvas),this._eventHandler.setInputAction(t=>{const e=this.viewer.scene.pick(t.position);if(a.defined(e)&&e.id&&e.id.properties&&"voxel"===e.id.properties.type){const t=e.id.properties.key,i={x:e.id.properties.x,y:e.id.properties.y,z:e.id.properties.z,count:e.id.properties.count},o=new a.Entity({id:`voxel-${t}`,description:this.renderer.createVoxelDescription(i,t)});this.viewer.selectedEntity=o}},a.ScreenSpaceEventType.LEFT_CLICK)}getStatistics(){if(!this._statistics)return null;const t={...this._statistics},e=this.renderer.getSelectionStats();return e&&(t.selectionStrategy=e.strategy,t.clippedNonEmpty=e.clippedNonEmpty,t.coverageRatio=e.coverageRatio??0),this.options._autoRenderBudget&&(t.renderBudgetTier=this.options._autoRenderBudget.tier,t.autoMaxRenderVoxels=this.options._autoRenderBudget.autoMaxRenderVoxels),"number"==typeof this.options.maxRenderVoxels&&this.options.maxRenderVoxels>0?t.occupancyRatio=Math.min(1,Math.max(0,(t.renderedVoxels||0)/this.options.maxRenderVoxels)):t.occupancyRatio=null,t}getBounds(){return this._bounds}getDebugInfo(){const t={options:{...this.options},bounds:this._bounds,grid:this._grid,statistics:this._statistics};return this.options.autoVoxelSize&&(t.autoVoxelSizeInfo={enabled:this.options.autoVoxelSize,originalSize:this._statistics?.originalVoxelSize,finalSize:this._statistics?.finalVoxelSize,adjusted:this._statistics?.autoAdjusted||!1,reason:this._statistics?.adjustmentReason,dataRange:this._bounds?x(this._bounds):null,estimatedDensity:this._bounds&&this._statistics?this._statistics.totalEntities/(x(this._bounds).x*x(this._bounds).y*x(this._bounds).z):null}),t}async fitView(t=null,e={}){try{const i=t||this._bounds;if(!i)return void u.warn("No bounds available for fitView");if(!this._isValidBounds(i))return void u.warn("Invalid bounds provided to fitView:",i);const o={...this.options.fitViewOptions,...e};u.debug("fitView called with bounds:",i,"options:",o);const n=(i.minLon+i.maxLon)/2,a=(i.minLat+i.maxLat)/2,s=(i.minAlt+i.maxAlt)/2,r=x(i),l=Math.max(r.x,r.y,r.z);if(l<10)return u.debug("Very small data range detected, applying minimum scale"),this._handleMinimalDataRange(n,a,s,o);if(l>1e5)return u.debug("Very large data range detected, applying maximum scale"),this._handleLargeDataRange(i,o);const h=Math.max(.05,Math.min(.5,o.paddingPercent))*l,d=this._calculateOptimalCameraHeight(l,h,o);return this._executeCameraMovement(n,a,s,d,o,l,h)}catch(t){throw u.error("fitView failed:",t),t}}_isValidBounds(t){return t&&"number"==typeof t.minLon&&!isNaN(t.minLon)&&"number"==typeof t.maxLon&&!isNaN(t.maxLon)&&"number"==typeof t.minLat&&!isNaN(t.minLat)&&"number"==typeof t.maxLat&&!isNaN(t.maxLat)&&"number"==typeof t.minAlt&&!isNaN(t.minAlt)&&"number"==typeof t.maxAlt&&!isNaN(t.maxAlt)&&t.minLon<=t.maxLon&&t.minLat<=t.maxLat&&t.minAlt<=t.maxAlt}async _handleMinimalDataRange(t,e,i,o){u.debug("Handling minimal data range");const n=a.Cartesian3.fromDegrees(t,e,i+2e3),s=a.Math.toRadians(o.heading),r=a.Math.toRadians(o.pitch);return this.viewer.camera.flyTo({destination:n,orientation:{heading:s,pitch:r,roll:0},duration:1.5})}async _handleLargeDataRange(t,e){u.debug("Handling large data range with bounding sphere");const i=(t.minLon+t.maxLon)/2,o=(t.minLat+t.maxLat)/2,n=(t.minAlt+t.maxAlt)/2,s=x(t),r=Math.max(s.x,s.y,s.z),l=new a.BoundingSphere(a.Cartesian3.fromDegrees(i,o,n),r/2),h=a.Math.toRadians(e.heading),d=a.Math.toRadians(e.pitch);return this.viewer.camera.flyToBoundingSphere(l,{duration:2.5,offset:new a.HeadingPitchRange(h,d,0)})}_calculateOptimalCameraHeight(t,e,i){if("auto"!==i.altitudeStrategy)return i.altitude||5e3;try{const o=a.Math.toRadians(i.pitch),n=this.viewer.camera.frustum.fovy||a.Math.toRadians(60),s=(t+e)/(2*Math.tan(n/2)),r=Math.abs(o);let l=s*Math.max(.5,Math.sin(Math.PI/2-r)+.3);const h=t/Math.min(t,100);h>5&&(l*=Math.log10(h)+1);const d=Math.max(500,.1*t),c=Math.min(1e5,10*t);return l=Math.max(d,Math.min(c,l)),u.debug(`Camera height calculated: ${l.toFixed(0)}m (range: ${t.toFixed(0)}m, pitch: ${i.pitch}°)`),l}catch(e){return u.warn("Camera height calculation failed, using fallback:",e),Math.max(2e3,2*t)}}async _executeCameraMovement(t,e,i,o,n,s,r){try{const l=a.Cartesian3.fromDegrees(t,e,i+o),h=a.Math.toRadians(n.heading),d=a.Math.toRadians(n.pitch),c={heading:h,pitch:d,roll:0};u.debug(`Camera target: position=${t.toFixed(6)},${e.toFixed(6)},${(i+o).toFixed(0)}, heading=${n.heading}°, pitch=${n.pitch}°`);const m=Math.max(1,Math.min(3,.8*Math.log10(s))),p=this.viewer.camera.flyTo({destination:l,orientation:c,duration:m,complete:()=>{u.debug("fitView camera movement completed")},cancel:()=>{u.debug("fitView camera movement cancelled")}});if(p)await p;else{u.debug("Using fallback: flyToBoundingSphere");const o=new a.BoundingSphere(a.Cartesian3.fromDegrees(t,e,i),s/2+r);await this.viewer.camera.flyToBoundingSphere(o,{duration:m,offset:new a.HeadingPitchRange(h,d,0)})}u.info("fitView completed successfully")}catch(t){throw u.error("Camera movement execution failed:",t),t}}static filterEntities(t,e){return Array.isArray(t)&&"function"==typeof e?t.filter(e):[]}};return u.info("CesiumJS Heatbox v0.1.9 loaded"),n.default})());
2
+ //# sourceMappingURL=cesium-heatbox.umd.min.js.map