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/CHANGELOG.md +398 -0
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/dist/cesium-heatbox.umd.min.js +2 -0
- package/dist/cesium-heatbox.umd.min.js.map +1 -0
- package/package.json +102 -0
- package/types/index.d.ts +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# CesiumJS Heatbox
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/hiro-nyon/cesium-heatbox/actions)
|
|
5
|
+
[](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
|