@3clear/basegis 0.1.3 → 0.1.5

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 CHANGED
@@ -1,33 +1,313 @@
1
1
  # @3clear/basegis
2
2
 
3
- `@3clear/basegis` 是 3clear 一张图项目抽出的 GIS 能力包。它提供一个统一入口 `BaseGIS`,把 Cesium / Leaflet 的差异收敛到适配层里;业务页面优先只操作 `BaseGIS`,复杂图层能力通过 `methods` 控制器组合 `BaseGIS` 公共方法完成。
4
- 把leaflet cesium 常用方法做了二次封装统一了api 使同一套代码可以适配2套地图引擎
3
+ `@3clear/basegis` 是 3clear 一张图项目抽出的 GIS 能力包,通过统一入口 `BaseGIS` 封装 Cesium / Leaflet 常用 API。业务页面可复用同一套地图逻辑,复杂图层通过 `methods` 控制器组合公共方法;三维专有能力的支持范围见[能力支持说明](#能力支持说明)。
4
+
5
5
  当前包包含:
6
6
 
7
- - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、Cesium / Leaflet 统一粒子风场、GPU 粒子风场、三维体渲染、三维切片/剖面渲染等基础能力。
8
- - `methods`:图片图层、网格图层、海量点、点位抽稀、等值线、等压线、风场、图形组等高级控制器。
7
+ - `BaseGIS`:初始化、引擎切换、视角与底图、[地图截图导出](#map-screenshot)、基础图形、Canvas 图标与扩散 Marker、GeoJSON、DEM、点击事件,以及统一风场、源解析传输、GPU 风场、三维体与剖面。
8
+ - `methods`:多地图实时视角联动、独立线图层、台风路径、图片图层、网格图层、海量点、点位聚合、点位抽稀、等值线、风场等高级控制器。
9
9
  - `layers`:天地图、GeoServer 金字塔瓦片、WMS、WMTS 图层配置快捷构造器。
10
- - `assets`:GIS 示例资源。
10
+ - `assets`:站点、工厂、信息标记和台风中心 SVG 资源。
11
11
  - `style.css`:Leaflet、GIS 图层和标注相关基础样式。
12
12
 
13
13
  ## 能力总览
14
14
 
15
- 下表以当前 npm 包的真实出口和本项目源码为准。使用位置列展示原测试路由,点击后会在新标签页打开代码演练场,并通过 `path` 参数自动选中对应路由;没有独立页面的辅助函数与其所属控制器合并展示。
16
-
17
- | <small>导出 / 能力</small> | <small>功能</small> | <small>在本项目中的使用位置或路由</small> |
18
- | --- | --- | --- |
19
- | <small>[`BaseGIS`](#base-gis)</small> | <small>Cesium / Leaflet 统一入口,负责地图初始化、引擎切换、视角控制、场景模式、底图、DEM、绘制、点击事件,以及 GeoJSON、风场、三维体渲染和剖面渲染等能力。</small> | <small>基础能力:<a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a><br>专项能力:<a href="http://61.50.111.214:31166/lgmap/test-page-14" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-15" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-20" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-21" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-23" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
20
- | <small>[`GraphicGroupController`](#graphic-group-controller)</small> | <small>统一装载和清理一组点、线、面、文字及 Marker 图形。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
21
- | <small>[`ImageLayerController` / `toImageLayerArea`](#image-layer-controller)</small> | <small>加载普通图片、GeoTIFF、灰度图着色和多时次栅格,并提供范围参数转换。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
22
- | <small>[`GridLayerController`](#grid-layer-controller)</small> | <small>加载灰度图或 GeoTIFF 数值网格,支持着色、探针、网格注记和生命周期控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
23
- | <small>[`PointLargeLayerController`](#point-large-layer-controller)</small> | <small>面向海量点位的异步构建、样式规则、点击、高亮、删除和显隐控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-5" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
24
- | <small>[`PointDensityController`](#point-density-controller)</small> | <small>点位视野裁剪和屏幕网格抽稀,支持动态图标、点击回调、配置更新和状态统计。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-8" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-9" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-13" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
25
- | <small>[`PressureContourLayer` / `createPressureDataResolver` / `buildPressureRenderData`](#pressure-contour-layer)</small> | <small>解析气压数据并生成等值线、数值标签及高低压中心标注。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-7" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
26
- | <small>[`ContourLayerController`](#contour-layer-controller)</small> | <small>统一管理等值线图层的加载、更新、显隐、引擎切换和销毁。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
27
- | <small>[`RasterContourController`](#raster-contour-controller)</small> | <small>从 GeoTIFF、灰度图或数值网格提取并渲染等值线。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
28
- | <small>[`WindFieldMethods`](#wind-field-methods)</small> | <small>提供 Cesium / Leaflet 风场数据加载、粒子动画、速度底图、交互和生命周期控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
29
- | <small>[`createTiandituLayer` / `createGeoserverPyramidLayer` / `createWmsLayer` / `createWmtsLayer`](#layer-config-builders)</small> | <small>构造天地图、GeoServer 金字塔瓦片、WMS 和 WMTS 图层配置。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
30
- | <small>[`gisMarkerSample`](#gis-marker-sample)</small> | <small>可直接导入的 GIS Marker 示例资源。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
15
+ 以下按类型展示 `@3clear/basegis` 的主要业务能力;工具型辅助函数不单独作为能力卡展示。点击名称可跳转到下方详细说明,完整导入方式见[出口](#出口)。
16
+
17
+ ### Core 核心(1)
18
+
19
+ <table>
20
+ <tbody>
21
+ <tr>
22
+ <td>
23
+ <a href="#base-gis"><strong>BaseGIS</strong></a><br>
24
+ <small>Cesium / Leaflet 统一 GIS 主入口 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
25
+ </td>
26
+ </tr>
27
+ </tbody>
28
+ </table>
29
+
30
+ ### DEM 地形
31
+
32
+ <table>
33
+ <tbody>
34
+ <tr>
35
+ <td>
36
+ <a href="#dem-terrain"><strong>DEM 地形</strong></a><br>
37
+ <small>Cesium 椭球地形、World Terrain 与地形夸张</small>
38
+ </td>
39
+ </tr>
40
+ </tbody>
41
+ </table>
42
+
43
+ ### 地图截图导出
44
+
45
+ <table>
46
+ <tbody>
47
+ <tr>
48
+ <td>
49
+ <a href="#map-screenshot"><strong>BaseGIS.screenshot</strong></a><br>
50
+ <small>当前视口、框选及指定范围截图 · <a href="http://61.50.111.214:31166/lgmap/test-page-40" target="_blank" rel="noopener noreferrer">查看示例</a></small>
51
+ </td>
52
+ </tr>
53
+ </tbody>
54
+ </table>
55
+
56
+ ### 多地图联动(双屏联动)
57
+
58
+ <table>
59
+ <tbody>
60
+ <tr>
61
+ <td>
62
+ <a href="#map-view-link-controller"><strong>MapViewLinkController</strong></a><br>
63
+ <small>多地图实例注册与移动、缩放、视角实时联动 · <a href="http://61.50.111.214:31166/lgmap/test-page-36" target="_blank" rel="noopener noreferrer">查看示例</a></small>
64
+ </td>
65
+ </tr>
66
+ </tbody>
67
+ </table>
68
+
69
+ ### Graphics 图形与点位(4)
70
+
71
+ <table>
72
+ <tbody>
73
+ <tr>
74
+ <td width="50%">
75
+ <a href="#basic-graphics"><strong>绘制点、线、面、文字和 Marker</strong></a><br>
76
+ <small>BaseGIS 基础绘图与清理,支持 Cesium / Leaflet · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
77
+ </td>
78
+ <td width="50%">
79
+ <a href="#point-large-layer-controller"><strong>PointLargeLayerController</strong></a><br>
80
+ <small>海量点 · <a href="http://61.50.111.214:31166/lgmap/test-page-5" target="_blank" rel="noopener noreferrer">查看示例</a></small>
81
+ </td>
82
+ </tr>
83
+ <tr>
84
+ <td width="50%">
85
+ <a href="#point-cluster-controller"><strong>PointClusterController</strong></a><br>
86
+ <small>点位聚合 · <a href="http://61.50.111.214:31166/lgmap/test-page-29" target="_blank" rel="noopener noreferrer">查看示例</a></small>
87
+ </td>
88
+ <td width="50%">
89
+ <a href="#point-density-controller"><strong>PointDensityController</strong></a><br>
90
+ <small>点位视野裁剪抽稀 · <a href="http://61.50.111.214:31166/lgmap/test-page-8" target="_blank" rel="noopener noreferrer">查看示例</a></small>
91
+ </td>
92
+ </tr>
93
+ </tbody>
94
+ </table>
95
+
96
+ ### geojson
97
+
98
+ <table>
99
+ <tbody>
100
+ <tr>
101
+ <td>
102
+ <a href="#geojson-layer"><strong>BaseGIS.upsertGeoJsonLayer</strong></a><br>
103
+ <small>GeoJSON 点、线、面与按要素设置样式 · <a href="http://61.50.111.214:31166/lgmap/test-page-23" target="_blank" rel="noopener noreferrer">查看示例</a></small>
104
+ </td>
105
+ </tr>
106
+ </tbody>
107
+ </table>
108
+
109
+ ### 图标
110
+
111
+ <table>
112
+ <tbody>
113
+ <tr>
114
+ <td>
115
+ <a href="#marker-icons"><strong>图标</strong></a><br>
116
+ <small>提供了一些项目中常用的图标及告警扩散效果图标 · <a href="http://61.50.111.214:31166/lgmap/test-page-41" target="_blank" rel="noopener noreferrer">查看示例</a></small>
117
+ </td>
118
+ </tr>
119
+ </tbody>
120
+ </table>
121
+
122
+ ### Line 线图层(1)
123
+
124
+ <table>
125
+ <tbody>
126
+ <tr>
127
+ <td>
128
+ <a href="#line-layer-controller"><strong>LineLayerController</strong></a><br>
129
+ <small>独立线图层、实线/虚线、固定/流动渐变与沿线动画 · <a href="http://61.50.111.214:31166/lgmap/test-page-34" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
130
+ <small>支持:逐顶点色、时间裁剪和图标引领的逐步出线;走航轨迹见 <a href="http://61.50.111.214:31166/lgmap/test-page-43" target="_blank" rel="noopener noreferrer">走航示例</a></small>
131
+ </td>
132
+ </tr>
133
+ </tbody>
134
+ </table>
135
+
136
+ ### Typhoon 台风路径(1)
137
+
138
+ <table>
139
+ <tbody>
140
+ <tr>
141
+ <td>
142
+ <a href="#typhoon-path-controller"><strong>TyphoonPathController</strong></a><br>
143
+ <small>实况路径、强度节点、当前中心、四象限风圈、多机构预报与路径播放 · <a href="http://61.50.111.214:31166/lgmap/test-page-10" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
144
+ <small>支持:Cesium / Leaflet 同一份规范化台风数据与引擎切换恢复</small>
145
+ </td>
146
+ </tr>
147
+ </tbody>
148
+ </table>
149
+
150
+ ### Image 图片图层(1)
151
+
152
+ <table>
153
+ <tbody>
154
+ <tr>
155
+ <td>
156
+ <a href="#image-layer-controller"><strong>ImageLayerController</strong></a><br>
157
+ <small>图片覆盖层与时次切换 · <a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
158
+ <small>支持:PNG / JPG / WebP 等浏览器图片、灰度图;可配套 GeoTIFF 取值</small>
159
+ </td>
160
+ </tr>
161
+ </tbody>
162
+ </table>
163
+
164
+ ### Grid 数值网格(1)
165
+
166
+ <table>
167
+ <tbody>
168
+ <tr>
169
+ <td>
170
+ <a href="#grid-layer-controller"><strong>GridLayerController</strong></a><br>
171
+ <small>栅格数值注记与鼠标探针 · <a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
172
+ <small>支持:GeoTIFF、灰度图、直接数值网格</small>
173
+ </td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
177
+
178
+ ### Contour 通用等值线与中心标注(2)
179
+
180
+ <table>
181
+ <tbody>
182
+ <tr>
183
+ <td width="50%">
184
+ <a href="#contour-layer-controller"><strong>ContourLayerController</strong></a><br>
185
+ <small>已有等值线、线值标签与中心标注 · <a href="http://61.50.111.214:31166/lgmap/test-page-19" target="_blank" rel="noopener noreferrer">查看示例</a></small>
186
+ </td>
187
+ <td width="50%">
188
+ <a href="#raster-contour-controller"><strong>RasterContourController</strong></a><br>
189
+ <small>从栅格追线,可选自动检测 H/L 中心 · <a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small>
190
+ </td>
191
+ </tr>
192
+ </tbody>
193
+ </table>
194
+
195
+ ### Source 源解析传输(1)
196
+
197
+ <table>
198
+ <tbody>
199
+ <tr>
200
+ <td>
201
+ <a href="#source-transport"><strong>BaseGIS.upsertSourceTransportLayer</strong></a><br>
202
+ <small>Cesium 专有;贡献弧线、灰色烟羽点云、移动烟团与目标汇聚体 · <a href="http://61.50.111.214:31166/lgmap/test-page-42" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
203
+ <small>页面最少只传 target + flows,体云复用 BaseGIS 现有体渲染</small>
204
+ </td>
205
+ </tr>
206
+ </tbody>
207
+ </table>
208
+
209
+ ### Volume 三维体渲染(1)
210
+
211
+ <table>
212
+ <tbody>
213
+ <tr>
214
+ <td>
215
+ <a href="#volume-rendering"><strong>BaseGIS.upsertVolumeLayer</strong></a><br>
216
+ <small>Cesium 专有;三维标量场体积采样与裁切 · <a href="http://61.50.111.214:31166/lgmap/test-page-14" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
217
+ <small>支持:一维体数据 + rows / cols / heights 网格维度、原位更新体数据与屏幕位置取值</small>
218
+ </td>
219
+ </tr>
220
+ </tbody>
221
+ </table>
222
+
223
+ ### Section 三维切片 / 剖面(1)
224
+
225
+ <table>
226
+ <tbody>
227
+ <tr>
228
+ <td>
229
+ <a href="#section-rendering"><strong>BaseGIS.upsertSectionLayer</strong></a><br>
230
+ <small>Cesium 专有;按经度、纬度、气压层切片 · <a href="http://61.50.111.214:31166/lgmap/test-page-15" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
231
+ <small>支持:{ Bound, DataAry } 三维格点数据</small>
232
+ </td>
233
+ </tr>
234
+ </tbody>
235
+ </table>
236
+
237
+ ### Wind 风场(2)
238
+
239
+ 风场分为 GPU 和 Canvas 两种渲染方式。推荐使用 [BaseGIS.upsertWindLayer](#unified-wind-layer) 统一入口:Cesium 自动使用 GPU,Leaflet 自动使用 Canvas,切换引擎时自动恢复风场。
240
+
241
+ <table>
242
+ <tbody>
243
+ <tr>
244
+ <td>
245
+ <a href="#gpu-wind-layer"><strong>GPU 风场</strong></a><br>
246
+ <small>Cesium 专用,使用 GPU 绘制风场粒子,支持地形采样 · <a href="http://61.50.111.214:31166/lgmap/test-page-21" target="_blank" rel="noopener noreferrer">查看示例</a></small>
247
+ </td>
248
+ </tr>
249
+ <tr>
250
+ <td>
251
+ <a href="#wind-field-methods"><strong>Canvas 风场</strong></a><br>
252
+ <small>通过 WindFieldMethods 在 Cesium / Leaflet 上绘制风场粒子,支持风速底图 · <a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small>
253
+ </td>
254
+ </tr>
255
+ </tbody>
256
+ </table>
257
+
258
+ ### Layers 图层构造器(4)
259
+
260
+ <table>
261
+ <tbody>
262
+ <tr>
263
+ <td width="50%">
264
+ <a href="#layer-config-builders"><strong>createTiandituLayer</strong></a><br>
265
+ <small>天地图图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
266
+ </td>
267
+ <td width="50%">
268
+ <a href="#layer-config-builders"><strong>createGeoserverPyramidLayer</strong></a><br>
269
+ <small>GeoServer 金字塔瓦片配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
270
+ </td>
271
+ </tr>
272
+ <tr>
273
+ <td width="50%">
274
+ <a href="#layer-config-builders"><strong>createWmsLayer</strong></a><br>
275
+ <small>WMS 图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
276
+ </td>
277
+ <td width="50%">
278
+ <a href="#layer-config-builders"><strong>createWmtsLayer</strong></a><br>
279
+ <small>WMTS 图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
280
+ </td>
281
+ </tr>
282
+ </tbody>
283
+ </table>
284
+
285
+ ### Assets 示例资源(4)
286
+
287
+ <table>
288
+ <tbody>
289
+ <tr>
290
+ <td width="50%">
291
+ <a href="#gis-marker-sample"><strong>gisMarkerSample</strong></a><br>
292
+ <small>GIS Marker 示例资源 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
293
+ </td>
294
+ <td width="50%">
295
+ <a href="#typhoon-path-controller"><strong>typhoonPathIcon</strong></a><br>
296
+ <small>台风路径默认中心 SVG · <a href="http://61.50.111.214:31166/lgmap/test-page-10" target="_blank" rel="noopener noreferrer">查看示例</a></small>
297
+ </td>
298
+ </tr>
299
+ <tr>
300
+ <td width="50%">
301
+ <a href="#gis-marker-sample"><strong>gisFactoryMarker</strong></a><br>
302
+ <small>工厂标记 SVG,可配合图片与数值图标使用</small>
303
+ </td>
304
+ <td width="50%">
305
+ <a href="#gis-marker-sample"><strong>gisInfoMarker</strong></a><br>
306
+ <small>信息标记 SVG,可配合图片与名称图标使用</small>
307
+ </td>
308
+ </tr>
309
+ </tbody>
310
+ </table>
31
311
 
32
312
  ## 安装
33
313
 
@@ -35,7 +315,7 @@
35
315
  npm install @3clear/basegis leaflet axios
36
316
  ```
37
317
 
38
- `d3-contour`、`pixi.js` 和 `leaflet-pixi-overlay` 已随 BaseGIS 构建产物发布,业务项目不需要单独安装。其中 Pixi 相关代码只在首次使用 Leaflet 海量点能力时按需加载。
318
+ `d3-contour`、`pixi.js`、`leaflet-pixi-overlay` 和 `html2canvas` 已随 BaseGIS 构建产物发布,业务项目不需要单独安装。其中 Pixi 相关代码只在首次使用 Leaflet 海量点能力时按需加载,`html2canvas` 只在首次调用 Leaflet 截图时按需加载。
39
319
 
40
320
  使用样式:
41
321
 
@@ -43,8 +323,19 @@ npm install @3clear/basegis leaflet axios
43
323
  import '@3clear/basegis/style.css'
44
324
  ```
45
325
 
46
- Cesium 当前不随 npm 包发布,项目需要按原项目方式把 Cesium 静态资源放到 `public/lib/Cesium`,并保证初始化前能访问 `window.Cesium`。
47
- 如果需要加载 GeoTIFF 网格,需要项目提前提供 `window.GeoTIFF`。
326
+ 外部依赖版本以 `package.json` 为准:当前为 `leaflet ^1.9.4`、`axios ^1.15.1`。仅使用 Leaflet 时,不需要加载 Cesium
327
+
328
+ Cesium 不随 npm 包发布。使用 Cesium 时,宿主项目需自行加载 `Cesium.js` 和 `Widgets/widgets.css`,并保留 `Workers`、`Assets` 等完整静态目录;初始化前必须能访问 `window.Cesium`。`config.engine.cesium.scriptUrl/cssUrl` 不会自动注入脚本和样式。
329
+
330
+ 例如,将完整 Cesium 资源放入 `public/lib/Cesium` 后,在 Vite 的 `index.html` 中、应用入口脚本之前添加:
331
+
332
+ ```html
333
+ <script>window.CESIUM_BASE_URL = '%BASE_URL%lib/Cesium/'</script>
334
+ <link rel="stylesheet" href="%BASE_URL%lib/Cesium/Widgets/widgets.css">
335
+ <script src="%BASE_URL%lib/Cesium/Cesium.js"></script>
336
+ ```
337
+
338
+ 使用 GeoTIFF 数据时,宿主还需提前提供 `window.GeoTIFF`。本文 `/data/...`、`/mock/...` 均为示例数据地址,不包含在 npm 包中,请替换为项目真实地址;部署在子路径时,应结合宿主的 `import.meta.env.BASE_URL` 生成静态资源 URL。
48
339
 
49
340
  ## 出口
50
341
 
@@ -54,29 +345,36 @@ import { BaseGIS } from '@3clear/basegis'
54
345
 
55
346
  // 高级能力控制器
56
347
  import {
57
- GraphicGroupController,
348
+ LineLayerController,
349
+ MapViewLinkController,
350
+ TyphoonPathController,
58
351
  ImageLayerController,
352
+ toImageLayerArea,
59
353
  GridLayerController,
60
354
  PointLargeLayerController,
355
+ PointClusterController,
61
356
  PointDensityController,
62
357
  ContourLayerController,
63
358
  RasterContourController,
64
- PressureContourLayer,
65
- createPressureDataResolver,
66
- buildPressureRenderData,
67
359
  WindFieldMethods,
68
360
  } from '@3clear/basegis/methods'
69
361
 
70
362
  // 图层配置构造器
71
363
  import {
72
364
  createTiandituLayer,
365
+ createTiandituTileSource,
73
366
  createGeoserverPyramidLayer,
74
367
  createWmsLayer,
75
368
  createWmtsLayer,
76
369
  } from '@3clear/basegis/layers'
77
370
 
78
371
  // 示例资源
79
- import { gisMarkerSample } from '@3clear/basegis/assets'
372
+ import {
373
+ gisMarkerSample,
374
+ gisFactoryMarker,
375
+ gisInfoMarker,
376
+ typhoonPathIcon,
377
+ } from '@3clear/basegis/assets'
80
378
  ```
81
379
 
82
380
  ## 快速开始
@@ -132,17 +430,19 @@ onBeforeUnmount(() => {
132
430
  })
133
431
  </script>
134
432
 
135
- <style scoped>
433
+ <style scoped lang="scss">
136
434
  .map {
137
435
  width: 100%;
138
- height: 100%;
436
+ height: 100vh;
139
437
  }
140
438
  </style>
141
439
  ```
142
440
 
441
+ 以下 API 示例默认 `mapCore` 已初始化成功;示例中的业务数据、图片和服务地址需由页面准备。组件卸载时先销毁控制器、移除页面监听,再调用 `mapCore.destroy()`。
442
+
143
443
  ## 返回值约定
144
444
 
145
- 多数方法返回统一结果对象:
445
+ 操作方法通常返回统一结果对象;图片、GeoJSON、风场等异步加载方法应使用 `await`:
146
446
 
147
447
  ```js
148
448
  {
@@ -160,9 +460,12 @@ onBeforeUnmount(() => {
160
460
  message: 'error message',
161
461
  code: 'NOT_INITIALIZED'
162
462
  }
163
-
164
463
  ```
165
464
 
465
+ `getEngineType()`、`getConfig()`、`getMapInstance()` 等读取方法直接返回值;`createMarkerIcon()` 直接返回图标参数或 `null`。控制器的 `getState()` 也返回状态对象,不能一律按 `result.data` 读取。
466
+
467
+ ## 配置与底图
468
+
166
469
  说明:
167
470
 
168
471
  - `containerId` 和 `container` 二选一即可;`init()` 时也可以再次传入。
@@ -192,17 +495,6 @@ basemap: {
192
495
  | `tianditu-vector-label` | 天地图矢量注记 | `annotation` | `true` | `wmts` | `tianditu` | `vectorLabel` | 内置 `defaultAnnotationId` | Cesium / Leaflet |
193
496
  | `tianditu-terrain-label` | 天地图地形注记 | `annotation` | `true` | `wmts` | `tianditu` | `terrainLabel` | 可作为 `defaultAnnotationId` | Cesium / Leaflet |
194
497
 
195
- 可用于 `defaultVisibleId` 的内置底图 id:
196
-
197
- - `tianditu-vector`
198
- - `tianditu-imagery`
199
- - `tianditu-terrain`
200
-
201
- 可用于 `defaultAnnotationId` 的内置注记 id:
202
-
203
- - `tianditu-vector-label`
204
- - `tianditu-terrain-label`
205
-
206
498
  说明:
207
499
 
208
500
  - `defaultVisibleId` 应指向 `category: 'basemap'` 的底图。
@@ -211,7 +503,7 @@ basemap: {
211
503
  - `geoserver-wmts-sample` 和 `geoserver-wms-sample` 默认 `enabled: false`,只是配置格式示例;如果要作为默认底图,需要替换真实服务地址并改为 `enabled: true`。
212
504
  - 天地图 provider 内置资源还包括 `imageryLabel`;默认 `basemap.list` 没有单独注册影像注记 id,但 `resourceKey: 'imagery'` 会自动推断使用 `imageryLabel` 注记。
213
505
 
214
- ### 底图切换
506
+ ### 底图切换
215
507
 
216
508
  底图可以来自 `config.basemap.list`,也可以直接传配置对象。当前适配层支持:
217
509
 
@@ -329,9 +621,11 @@ const basemapList = [
329
621
  id: 'weather-wmts',
330
622
  name: '气象 WMTS',
331
623
  category: 'basemap',
332
- url: 'https://example.com/wmts',
333
- layer: 'weather',
334
- tileMatrixSet: 'EPSG:3857',
624
+ engineSupport: ['cesium'],
625
+ url: 'https://example.com/geoserver/gwc/service/wmts?' +
626
+ 'SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=workspace:weather&' +
627
+ 'STYLE=default&TILEMATRIXSET=EPSG:3857&FORMAT=image/png&' +
628
+ 'TILEMATRIX=EPSG:3857:{z}&TILEROW={y}&TILECOL={x}',
335
629
  }),
336
630
  ]
337
631
 
@@ -348,11 +642,17 @@ const mapCore = new BaseGIS({
348
642
 
349
643
  四个构造器都会保留额外传入字段,便于继续配置 `engineSupport`、缩放级别、注记资源或服务参数。
350
644
 
645
+ `createWmtsLayer()` 只生成配置,不会根据 `layer / tileMatrixSet` 自动拼接请求。自定义 WMTS 需要完整的 GetTile URL 模板,矩阵标识应按服务实际配置填写;XYZ 瓦片同样使用包含 `{z}/{x}/{y}` 的 URL。
646
+
647
+ 模板瓦片不会自动重投影。上面的 `EPSG:3857` WMTS 示例限定用于 Cesium;当前 Leaflet 使用 `EPSG:4326`,自定义瓦片需匹配地图的坐标系和瓦片矩阵。
648
+
649
+ `layers` 还导出辅助函数 `createTiandituTileSource(resourceKey = 'imagery')`,返回 `{ url, subdomains }`,资源不可用时返回 `null`。它供需要直接操作引擎的特殊页面使用,普通页面仍优先调用 `createTiandituLayer()` 和 `BaseGIS`。
650
+
351
651
  <a id="base-gis"></a>
352
652
 
353
653
  ## BaseGIS 基础能力
354
654
 
355
- 这一节列的是 `BaseGIS` 主入口直接提供的基础能力。业务页面优先调用这些方法;图片图层、网格图层、海量点、点位抽稀、等值线、风场等更复杂能力,建议使用后文 `methods` 中对应的 Controller。
655
+ 这一节列的是 `BaseGIS` 主入口直接提供的基础能力。业务页面优先调用这些方法;图片图层、网格图层、海量点、点位聚合、点位抽稀、等值线、风场等更复杂能力,建议使用后文 `methods` 中对应的 Controller。
356
656
 
357
657
  ### 1. 生命周期、实例与引擎切换
358
658
 
@@ -414,6 +714,8 @@ const mapCore = new BaseGIS({
414
714
  | `getEngineType()` | 无 | 返回当前引擎类型。 |
415
715
  | `getConfig()` | 无 | 返回当前运行时配置。 |
416
716
  | `getMapInstance()` | 无 | 返回底层地图实例:Cesium `viewer` 或 Leaflet `map`。 |
717
+ | `getMapContainer()` | 无 | 返回统一结果,`data.container` 为地图容器 DOM。 |
718
+ | `resize()` | 无 | 在容器尺寸改变、隐藏面板重新显示后刷新地图尺寸。 |
417
719
 
418
720
  #### 推荐的引擎切换写法
419
721
 
@@ -449,25 +751,28 @@ if (result.success) {
449
751
 
450
752
  `switchEngine()` 作为兼容别名保留,行为与 `setEngine()` 一致。
451
753
 
754
+ <a id="managed-layer-restore"></a>
755
+
452
756
  #### 切换后的图层处理
453
757
 
454
758
  引擎切换不是把 Cesium 图层对象“搬到” Leaflet,也不是把 Leaflet 图层对象“搬到” Cesium。BaseGIS 会保存托管图层的业务参数,并在新 adapter 中重新创建图层。
455
759
 
456
- - 自动恢复范围包括图片、网格、海量点、点位抽稀、等值线和三维体图层,对应 `upsert*Layer` 方法及 Controller。
760
+ - 自动恢复范围包括图片、网格、海量点、点位聚合、点位抽稀、独立线、源解析传输、台风路径、等值线、统一风场和三维体图层。
457
761
  - 图层最新的数据参数、显隐、清空、删除、海量点删除和高亮状态会同步到 BaseGIS 快照。
458
762
  - 快照只保存业务参数引用,不复制大数组,不保存任何底层引擎对象。
459
- - 三维体图层切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
763
+ - 三维体、源解析传输切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
460
764
  - Cesium 专有能力在 Leaflet 下不可用,例如 DEM、三维体渲染、三维切片/剖面渲染。
461
- - 一次性绘制对象、点击监听、风场、剖面图层和页面直接操作底层引擎创建的对象不在托管范围内,需要业务自行恢复。
765
+ - 基础绘制对象、GeoJSON、点击/视图监听、GPU 专用风场、独立 `WindFieldMethods`、剖面图层和页面直接创建的底层引擎对象不在托管范围内,需要业务重新加载或绑定。
462
766
 
463
767
  切换结果中可以查看恢复明细:
464
768
 
465
769
  ```js
466
770
  const result = await mapCore.setEngine('leaflet')
467
- console.log(result.data.restore.restored)
468
- console.log(result.data.restore.failed)
771
+ console.log(result.data?.restore?.restored)
772
+ console.log(result.data?.restore?.failed)
469
773
  ```
470
774
 
775
+ `result.success` 表示引擎初始化/切换是否成功,不保证每个图层都恢复成功;应同时检查 `data.restore.failed`。不要在切换前先调用 `destroy()`,它会清空托管快照。
471
776
 
472
777
  ### 2. 视角控制与场景模式
473
778
 
@@ -523,6 +828,13 @@ const viewStateResult = mapCore.getViewState()
523
828
  // Cesium 通常包含 center / bounds / height / heading / pitch / roll / engineType。
524
829
  // Leaflet 通常包含 center / bounds / zoom / engineType。
525
830
 
831
+ // 让地图适配指定经纬度范围,可用于多地图首次统一范围。
832
+ mapCore.fitViewBounds({
833
+ bounds: { west: 73, south: 18, east: 135, north: 54 },
834
+ animate: false,
835
+ padding: 0,
836
+ })
837
+
526
838
  // 经纬度转地图容器像素坐标,常用于自定义 HTML 浮层定位。
527
839
  const pointResult = mapCore.projectToContainerPoint({
528
840
  longitude: 104,
@@ -543,6 +855,10 @@ const viewListener = mapCore.onViewChange({
543
855
  // 缩放过程中要实时刷新点位样式时,优先用 onChange。
544
856
  onChange() {},
545
857
 
858
+ // Cesium 交互期间逐渲染帧检测相机变化;适合多地图实时联动。
859
+ // 默认 false,普通业务监听无需开启。
860
+ continuous: true,
861
+
546
862
  // Cesium: camera.moveEnd / morphComplete;Leaflet: moveend / zoomend / resize。
547
863
  onEnd() {},
548
864
 
@@ -565,13 +881,98 @@ viewListener.data?.off?.()
565
881
  | `setSceneMode(payload)` | `'2d'/'2.5d'/'3d'` 或 `{ mode, duration, preserveView }` | 切换场景模式。 |
566
882
  | `getSceneMode()` | 无 | 获取当前场景模式。 |
567
883
  | `getViewBounds()` | 无 | 获取当前视图经纬度边界。 |
568
- | `getViewState()` | | 获取当前视图状态。 |
569
- | `onViewChange(payload)` | `{ onStart, onChange, onEnd, endDelay }` | 监听视图变化,返回 `{ off }`。 |
884
+ | `getViewState(payload)` | 可选 `{includeBounds:false}` | 获取当前视图状态;实时同引擎联动可关闭较重的范围采样。 |
885
+ | `fitViewBounds(payload)` | `{bounds:{west,south,east,north},animate?,padding?,duration?}` | 适配指定经纬度范围;`duration` 仅用于 Cesium 动画。 |
886
+ | `onViewChange(payload)` | `{ onStart, onChange, onEnd, endDelay, continuous? }` | 监听视图变化,返回 `{ off }`;Cesium 开启 `continuous` 后交互期间逐帧检测。 |
570
887
  | `projectToContainerPoint(payload)` | `{ longitude, latitude, height }` | 经纬度投影到地图容器像素坐标。 |
571
888
 
889
+ <a id="map-screenshot"></a>
890
+
891
+ ### 3. 地图截图与导出
892
+
893
+ `screenshot()` 用于导出当前 Cesium 或 Leaflet 地图,两个引擎使用完全相同的调用方式、参数和结果对象。默认截取当前可视范围(地图视口)并下载 PNG,不会自动拼接视口外尚未渲染的地图内容;只截取地图内容,不包含页面工具栏、弹窗等地图容器外的 DOM 浮层。
894
+
895
+ 框选遮罩样式包含在 `@3clear/basegis/style.css` 中,使用 npm 包时应按安装章节导入该样式文件。
896
+
897
+ 在线示例:[地图截图与 PNG 导出](http://61.50.111.214:31166/lgmap/test-page-40)。
898
+
899
+ #### 直接下载 PNG
900
+
901
+ 默认 `download: true`,页面不需要自己创建下载链接或处理 Blob。`fileName` 可指定文件名;不传时使用前缀和时间戳自动命名。
902
+
903
+ ```js
904
+ // 截取当前可视范围并直接下载。
905
+ const result = await mapCore.screenshot({ fileName: 'weather-map.png' })
906
+ if (!result.success) {
907
+ console.warn('截图导出失败', result.code, result.message)
908
+ }
909
+
910
+ // 左键拖拽框选区域,松开后下载;右键或 Esc 取消。
911
+ const selectResult = await mapCore.screenshot({
912
+ mode: 'select',
913
+ namePrefix: 'map-select',
914
+ })
915
+ if (!selectResult.success && selectResult.code !== 'CANCELLED') {
916
+ console.warn('框选导出失败', selectResult.message)
917
+ }
918
+ ```
919
+
920
+ #### 按范围裁剪导出
921
+
922
+ ```js
923
+ // 按当前视野中的经纬度范围裁剪。
924
+ await mapCore.screenshot({
925
+ namePrefix: 'east-china',
926
+ bounds: { west: 115, south: 25, east: 123, north: 36 },
927
+ })
928
+
929
+ // 按相对当前地图视口左上角的 CSS 像素区域裁剪。
930
+ await mapCore.screenshot({
931
+ rect: { left: 120, top: 80, width: 640, height: 360 },
932
+ })
933
+ ```
934
+
935
+ #### 获取 Blob,不自动下载
936
+
937
+ ```js
938
+ const captureResult = await mapCore.screenshot({
939
+ download: false,
940
+ fileName: 'weather-map.png',
941
+ })
942
+ if (captureResult.success) {
943
+ const { blob, fileName, width, height } = captureResult.data
944
+ // 页面可将 blob 交给自己的预览或上传逻辑,BaseGIS 不会自动上传。
945
+ console.log(blob, fileName, width, height)
946
+ }
947
+ ```
948
+
949
+ #### 方法与参数
950
+
951
+ | 方法 | 参数 | 说明 |
952
+ | --- | --- | --- |
953
+ | `screenshot(payload)` | `{ mode?, download?, fileName?, namePrefix?, bounds?, area?, rect?, screenRect? }` | 截取 Cesium / Leaflet 地图;`mode: 'select'` 进入交互框选,`download` 默认为 `true`。 |
954
+ | `cancelScreenshotSelection(reason?)` | 可选取消原因字符串 | 主动取消正在进行的框选;返回布尔值,表示是否取消了现有框选。 |
955
+
956
+ | 参数 | 默认值 | 说明 |
957
+ | --- | --- | --- |
958
+ | `mode` | 省略 | 默认直接截图;`'select'` 进入鼠标框选,也可简写为 `screenshot('select')`。 |
959
+ | `download` | `true` | 自动触发浏览器下载;传 `false` 只生成截图结果。 |
960
+ | `fileName` | 自动生成 | 优先于 `namePrefix`;未以 `.png` 结尾时自动追加扩展名。当前仅导出 PNG。 |
961
+ | `namePrefix` | `map-screenshot` | 自动文件名前缀;未指定名称的框选截图默认使用 `map-select`。 |
962
+ | `bounds` / `area` | 当前视口 | 经纬度范围别名,支持 `{ west, south, east, north }` 或 `{ startLon, startLat, endLon, endLat }`。 |
963
+ | `rect` / `screenRect` | 当前视口 | 屏幕区域别名,支持 `{ left, top, width, height }`,也可用 `x / y` 代替 `left / top`;单位为相对地图视口左上角的 CSS 像素。 |
964
+
965
+ 取消框选时,原 `screenshot()` Promise 返回 `success: false`、`code: 'CANCELLED'` 和 `data.cancelled: true`;页面通常无需按错误弹窗处理。
966
+
967
+ 成功结果的 `data` 包含 `blob`、`width`、`height`、`mimeType`、`fileName`、`downloaded`、`screenRect` 和 `pixelRect`。Cesium 读取 WebGL 场景画布;Leaflet 在首次调用时按需加载 DOM 渲染器,合成瓦片、SVG、Canvas 和 DOM Marker,并排除 Leaflet 自带控件。跨域地图资源需要服务端允许 CORS,否则对应资源可能缺失或截图返回 `FAILED`。为保证 WebGL 截图可靠,Cesium 初始化时会保留 drawing buffer;在超高分辨率大屏上会增加一定显存占用。
968
+
969
+ `width / height / pixelRect` 使用导出图片的实际像素,可能与 CSS 像素不同;`downloaded: true` 仅表示已触发下载,不代表浏览器已确认文件保存到磁盘。导出范围始终限制在当前视口内,不是整页长截图,也不支持自动加载并拼接屏幕外瓦片。
970
+
971
+ <a id="basic-graphics"></a>
972
+
572
973
  ### 4. 绘制点、线、面、文字和 Marker
573
974
 
574
- 这些方法用于轻量绘制和样例验证。大量点位或复杂业务图层请优先使用后文的 `PointLargeLayerController`、`PointDensityController`、`GraphicGroupController` 等控制器。
975
+ 这些方法用于轻量绘制和样例验证。大量点位请优先使用后文的 `PointLargeLayerController`、`PointClusterController`、`PointDensityController` 等控制器。
575
976
 
576
977
  点:
577
978
 
@@ -662,11 +1063,188 @@ mapCore.addMarker({
662
1063
  })
663
1064
  ```
664
1065
 
1066
+ <a id="marker-icons"></a>
1067
+
1068
+ #### Canvas 数据图标与扩散效果
1069
+
1070
+ `mapCore.createMarkerIcon(options, data)` 支持圆点、“数值 + 名称”、气象风向、“图片 + 名称”和“图片 + 数值 + 名称”五种 Canvas 样式。圆点用法:
1071
+
1072
+ ```js
1073
+ const station = {
1074
+ id: 'station-1',
1075
+ name: '示例站点',
1076
+ position: [104, 35],
1077
+ value: 28.6,
1078
+ status: 'normal',
1079
+ windDirection: 90,
1080
+ windSpeed: 6,
1081
+ }
1082
+
1083
+ const markerIcon = mapCore.createMarkerIcon({
1084
+ type: 'dot',
1085
+ size: 18,
1086
+ color: '#64748b',
1087
+ getColor(data) {
1088
+ if (data.status === 'normal') return '#00e600'
1089
+ if (data.status === 'warning') return '#a67824'
1090
+ return '#6b7280'
1091
+ },
1092
+ }, station)
1093
+
1094
+ mapCore.addMarker({
1095
+ id: station.id,
1096
+ name: station.name,
1097
+ position: station.position,
1098
+ data: station,
1099
+ ...markerIcon,
1100
+ })
1101
+ ```
1102
+
1103
+ “数值 + 名称”用法:
1104
+
1105
+ ```js
1106
+ const markerIcon = mapCore.createMarkerIcon({
1107
+ type: 'value-label',
1108
+ value: station.value,
1109
+ label: station.name,
1110
+ showLabel: station.showName !== false,
1111
+ color: '#64748b',
1112
+ getColor(data) {
1113
+ if (data.status === 'normal') return '#00e600'
1114
+ if (data.status === 'warning') return '#a67824'
1115
+ return '#6b7280'
1116
+ },
1117
+ }, station)
1118
+
1119
+ mapCore.addMarker({
1120
+ id: station.id,
1121
+ name: station.name,
1122
+ position: station.position,
1123
+ data: station,
1124
+ ...markerIcon,
1125
+ })
1126
+ ```
1127
+
1128
+ 气象风向用法:
1129
+
1130
+ ```js
1131
+ const markerIcon = mapCore.createMarkerIcon({
1132
+ type: 'wind',
1133
+ direction: station.windDirection,
1134
+ label: station.name,
1135
+ showLabel: station.showName !== false,
1136
+ color: '#64748b',
1137
+ getColor(data) {
1138
+ if (data.windSpeed >= 17.2) return '#e5484d'
1139
+ if (data.windSpeed >= 10.8) return '#f59e0b'
1140
+ if (data.windSpeed >= 5.5) return '#2f80ed'
1141
+ return '#00e600'
1142
+ },
1143
+ }, station)
1144
+
1145
+ mapCore.addMarker({
1146
+ id: station.id,
1147
+ position: station.position,
1148
+ data: station,
1149
+ ...markerIcon,
1150
+ })
1151
+ ```
1152
+
1153
+ “图片 + 名称”用法:
1154
+
1155
+ ```js
1156
+ import { gisInfoMarker } from '@3clear/basegis/assets'
1157
+
1158
+ const stationImage = new Image()
1159
+ // 跨域图片需由服务端允许 CORS,并在设置 src 前配置 crossOrigin。
1160
+ stationImage.src = gisInfoMarker
1161
+ await stationImage.decode()
1162
+
1163
+ const markerIcon = mapCore.createMarkerIcon({
1164
+ type: 'image-label',
1165
+ image: stationImage,
1166
+ imageSize: [20, 20],
1167
+ label: station.name,
1168
+ showLabel: station.showName !== false,
1169
+ }, station)
1170
+
1171
+ mapCore.addMarker({
1172
+ id: station.id,
1173
+ position: station.position,
1174
+ data: station,
1175
+ ...markerIcon,
1176
+ })
1177
+ ```
1178
+
1179
+ “图片 + 数值 + 名称”用法:
1180
+
1181
+ ```js
1182
+ import { gisFactoryMarker } from '@3clear/basegis/assets'
1183
+
1184
+ const company = { id: 'factory-1', name: '示例企业', emissionValue: 12.5 }
1185
+ const factoryImage = new Image()
1186
+ factoryImage.src = gisFactoryMarker
1187
+ await factoryImage.decode()
1188
+
1189
+ const markerIcon = mapCore.createMarkerIcon({
1190
+ type: 'image-value-label',
1191
+ image: factoryImage,
1192
+ imageSize: [14, 14],
1193
+ value: company.emissionValue,
1194
+ label: company.name,
1195
+ showValue: company.showValue !== false,
1196
+ showLabel: company.showName !== false,
1197
+ }, company)
1198
+
1199
+ mapCore.addMarker({
1200
+ id: company.id,
1201
+ position: [104.2, 35.1],
1202
+ data: company,
1203
+ ...markerIcon,
1204
+ })
1205
+ ```
1206
+
1207
+ `getColor(data)` 同步接收 `createMarkerIcon` 第二个参数传入的原始数据并返回 CSS 颜色。回调异常、返回非法颜色或空值时使用 `options.color`,再无有效静态颜色时使用默认灰色。圆点 `size` 默认 18px,允许 4~96px,并使用中心锚点。`value-label` 的 `value` 位于上方彩色色块,`label` 位于下方白色名称块;`showLabel: false` 时只显示数值块,图标宽高和锚点会自动更新。`wind` 的 `direction` 使用角度,`0` 指向正上方(北)、`90` 指向右侧(东),按顺时针方向增加;`label` 位于下方白色名称块,`showLabel: false` 时可隐藏,风速颜色分级完全由业务回调决定。`image-label` 的 `image` 接收已加载的 CanvasImageSource,`imageSize` 控制图片尺寸;URL 图片应先加载一次再传入,以保持抽稀回调同步。`image-value-label` 在同一规则上增加右侧 `value` 和蓝色圆角色块;`showValue: false` 时不绘制数值底板,只保留图片本身,`showLabel: false` 可独立隐藏名称。两者都隐藏时得到纯图片图标,画布尺寸和锚点会按照 `imageSize` 自动收缩。该类型使用整数锚点减少低 DPR 下的文字模糊。返回的 `iconUrl/iconSize/iconAnchor` 可用于普通 Marker、点位抽稀及其他图片点位能力。
1208
+
1209
+ Cesium / Leaflet 双引擎扩散 Marker:
1210
+
1211
+ ```js
1212
+ mapCore.addMarker({
1213
+ id: 'alarm-station',
1214
+ name: '告警站点',
1215
+ position: [121.48, 31.23],
1216
+ data: station,
1217
+ iconUrl: '/marker.png',
1218
+ iconSize: [34, 40],
1219
+ pulse: {
1220
+ color: '#22d3ee',
1221
+ size: 88,
1222
+ duration: 2,
1223
+ ringCount: 2,
1224
+ ringWidth: 2,
1225
+ ringOpacity: 0.72,
1226
+ },
1227
+ clampToGround: true,
1228
+ onClick({ data }) {
1229
+ console.log(data)
1230
+ },
1231
+ })
1232
+ ```
1233
+
1234
+ `pulse` 只是图标下方的效果层,不会生成中心点。`pulse.size` 是最大扩散直径(像素),`ringWidth` 是独立的屏幕像素线宽,`ringOpacity` 是圆环不透明度;`duration` 是单个圆环的周期(秒),`ringCount` 支持 1~3。页面不需要判断当前引擎;Leaflet 使用 SVG 动画,Cesium 使用 Billboard 缩放动画,两端保持相同的半径、线宽、透明度和相位变化曲线。只传 `pulse` 时仅显示扩散环;与 `iconUrl` 同时传入时,图标显示在扩散环上层。
1235
+
665
1236
  <a id="gis-marker-sample"></a>
666
1237
 
667
- #### 使用 gisMarkerSample 示例资源
1238
+ #### 使用 SVG 资源
668
1239
 
669
- `gisMarkerSample` 是可直接导入的 Marker 图片资源,适合快速验证 `addMarker()` 或控制器图标配置。
1240
+ `assets` 导出的资源均为可直接用于 `iconUrl` SVG URL;传给 Canvas 图标的 `image` 时则需要先加载为图片对象。
1241
+
1242
+ | 导出 | 用途 |
1243
+ | --- | --- |
1244
+ | `gisMarkerSample` | 基础 Marker 示例。 |
1245
+ | `gisFactoryMarker` | 工厂标记,适合企业排放数据。 |
1246
+ | `gisInfoMarker` | 信息标记,适合站点名称与数值。 |
1247
+ | `typhoonPathIcon` | 台风路径默认中心图标。 |
670
1248
 
671
1249
  ```js
672
1250
  import { gisMarkerSample } from '@3clear/basegis/assets'
@@ -697,13 +1275,105 @@ mapCore.clearGraphics()
697
1275
  | `drawLine(payload)` | 线配置 | 绘制线。 |
698
1276
  | `drawPolygon(payload)` | 面配置 | 绘制面。 |
699
1277
  | `drawText(payload)` | 文字配置 | 绘制文字。 |
700
- | `addMarker(payload)` | Marker 配置 | 绘制图片 Marker;未传 `iconUrl` 时降级为点。 |
1278
+ | `createMarkerIcon(options, data)` | 图标配置、原始数据 | 直接返回 `iconUrl/iconSize/iconAnchor`;未知 `type` 返回 `null`。 |
1279
+ | `addMarker(payload)` | Marker 配置 | 绘制图片或 `pulse` 扩散 Marker;未传图片或扩散参数时,Cesium 使用点、Leaflet 使用默认 Marker。 |
701
1280
  | `removeGraphic(payload)` | 图形 id 或 `{ id }` | 删除指定图形。 |
702
1281
  | `clearGraphics()` | 无 | 清空通过基础绘制方法创建的图形。 |
703
1282
 
704
1283
 
705
1284
 
706
- ### 6. 点击事件
1285
+ <a id="geojson-layer"></a>
1286
+
1287
+ ### 5. GeoJSON 图层
1288
+
1289
+ `upsertGeoJsonLayer()` 用同一个 `layerId` 创建或替换 GeoJSON 点、线、面图层,两个引擎共用数据与样式参数。坐标按 GeoJSON 顺序传入 `[经度, 纬度, 可选高度]`。
1290
+
1291
+ ```js
1292
+ const result = await mapCore.upsertGeoJsonLayer({
1293
+ layerId: 'region-boundaries',
1294
+ data: {
1295
+ type: 'FeatureCollection',
1296
+ features: [{
1297
+ type: 'Feature',
1298
+ id: 'region-1',
1299
+ properties: { name: '示例区域', level: 'warning' },
1300
+ geometry: {
1301
+ type: 'Polygon',
1302
+ coordinates: [[[103, 34], [105, 34], [105, 36], [103, 36], [103, 34]]],
1303
+ },
1304
+ }],
1305
+ },
1306
+ style: { color: '#2f80ff', weight: 2, fillOpacity: 0.2 },
1307
+ styleCallback(feature) {
1308
+ return feature?.properties?.level === 'warning'
1309
+ ? { color: '#ff4d4f', fillColor: '#ff4d4f' }
1310
+ : {}
1311
+ },
1312
+ onClick({ feature, properties }) {
1313
+ console.log(properties.name, feature)
1314
+ },
1315
+ })
1316
+
1317
+ if (result.success && result.data.bounds) {
1318
+ mapCore.fitViewBounds({ bounds: result.data.bounds, animate: false })
1319
+ }
1320
+ ```
1321
+
1322
+ | 参数 | 说明 |
1323
+ | --- | --- |
1324
+ | `layerId` | 必填,图层唯一 id。 |
1325
+ | `data` | 必填;支持 FeatureCollection、Feature、Geometry、Feature 数组、JSON 字符串或 URL。也兼容 `geojson / geoJson / source / url`。 |
1326
+ | `fetchOptions` | 使用 URL 时传给 Fetch 的请求参数。业务接口通常由宿主 `api/modules` 获取后再传入 `data`。 |
1327
+ | `style` | 统一样式:`color / weight / opacity / fillColor / fillOpacity / stroke / fill`;Leaflet 圆点大小用 `radius`,Cesium 标记大小用 `markerSize`。 |
1328
+ | `styleCallback(feature, index)` | 按要素返回样式覆盖;异常时使用基础样式。 |
1329
+ | `onClick(event)` | 返回 `layerId / feature / properties / data / event / target / engineType`;`data` 为原始 Feature。 |
1330
+ | `visible` | 默认 `true`。 |
1331
+ | `clampToGround` | Cesium 是否贴地,默认 `true`。 |
1332
+
1333
+ ```js
1334
+ mapCore.hideGeoJsonLayer({ layerId: 'region-boundaries' })
1335
+ mapCore.showGeoJsonLayer({ layerId: 'region-boundaries' })
1336
+ const stateResult = mapCore.getGeoJsonLayerState({ layerId: 'region-boundaries' })
1337
+ console.log(stateResult.data) // layerId / layerType / visible / featureCount / bounds
1338
+
1339
+ mapCore.clearGeoJsonLayer({ layerId: 'region-boundaries' }) // 清空要素,保留图层。
1340
+ mapCore.removeGeoJsonLayer({ layerId: 'region-boundaries' }) // 删除图层。
1341
+ ```
1342
+
1343
+ 上述管理方法也可直接传 id 字符串;方法名兼容大写 `GeoJSON`,例如 `upsertGeoJSONLayer()`。更新时需重新提供完整数据和样式。GeoJSON 当前不在自动恢复清单中,切换引擎后需重新调用 `upsertGeoJsonLayer()`。
1344
+
1345
+ <a id="dem-terrain"></a>
1346
+
1347
+ ### 6. DEM 地形
1348
+
1349
+ 仅 Cesium 支持。默认使用 `ellipsoid-flat` 椭球地形;`tianditu-terrain` 是底图瓦片,不会提供高程。当前 DEM 接口支持椭球和 Cesium World Terrain,配置中的 `tianditu-dem` 仍是占位项,不能作为已实现地形源使用。
1350
+
1351
+ ```js
1352
+ // 使用 World Terrain 前,宿主需准备 Cesium Ion 等所需凭据与网络访问。
1353
+ const result = mapCore.loadDEMById('cesium-world-terrain')
1354
+ if (!result.success) {
1355
+ console.warn(result.message)
1356
+ } else if (result.data.loading) {
1357
+ // 某些 Cesium 版本异步加载地形;调用返回成功不等于地形已经就绪。
1358
+ const provider = await result.data.readyPromise
1359
+ if (!provider) console.warn('World Terrain 加载失败')
1360
+ }
1361
+
1362
+ mapCore.setTerrainExaggeration({ factor: 2 })
1363
+ console.log(mapCore.getDEMState().data)
1364
+ ```
1365
+
1366
+ | 方法 | 说明 |
1367
+ | --- | --- |
1368
+ | `loadDEMById(id)` | 从 `config.dem.list` 查找地形配置,常用 `ellipsoid-flat / cesium-world-terrain`。 |
1369
+ | `loadDEM(payload)` | 直接传配置;支持 `{ id, sourceType: 'ellipsoid-flat' }` 或 `{ id, sourceType: 'cesium-official', factory: 'worldTerrain' }`。 |
1370
+ | `loadDefaultDEM()` | 按当前 `config.dem` 加载默认地形。 |
1371
+ | `getDEMState()` | `data` 返回 `{ id, enabled, exaggeration }`;`enabled` 按 `id !== 'ellipsoid-flat'` 判断,不代表异步地形已就绪。 |
1372
+ | `setTerrainExaggeration(payload)` | 传数字或 `{ factor }`,范围 `1~10`。 |
1373
+
1374
+ 恢复平面地形可调用 `mapCore.loadDEMById('ellipsoid-flat')`。Leaflet 不提供 DEM;调用失败时应检查 `success / code / message`,不要直接假定结果包含地形状态。
1375
+
1376
+ ### 7. 点击事件
707
1377
 
708
1378
  `onClick` 注册地图点击事件;`offClick` 移除当前点击监听。当前每个 adapter 只保留一个基础点击监听,重复调用 `onClick` 会先移除旧监听。
709
1379
 
@@ -767,117 +1437,521 @@ Leaflet 当前返回坐标点击:
767
1437
  说明:
768
1438
 
769
1439
  - 基础 `onClick` 适合地图空白点击、简单 Entity 点击。
770
- - 海量点、点位抽稀等图层自己的点击事件,应使用对应 Controller 的 `onClick` 参数。
1440
+ - 海量点、点位聚合、点位抽稀等图层自己的点击事件,应使用对应 Controller 的点击回调参数。
771
1441
 
772
1442
 
773
- <a id="graphic-group-controller"></a>
1443
+ <a id="map-view-link-controller"></a>
774
1444
 
775
- ## GraphicGroupController
1445
+ ## 多地图联动(双屏联动)
776
1446
 
777
- 用于一次性加载一组点、线、面、文字、marker。它只调用 `BaseGIS.drawPoint / drawLine / drawPolygon / drawText / addMarker`。
1447
+ `MapViewLinkController` 注册多个已经初始化的 BaseGIS,并实时同步拖动、缩放和视角。每个地图仍可独立加载不同模式、时次和图层;控制器只负责视角,不创建地图,也不会在 `unregisterMap()`、`clear()` `destroy()` 时销毁 BaseGIS。
1448
+
1449
+ 在线示例:[查看分屏布局中的页面地图联动](http://61.50.111.214:31166/lgmap/test-page-36)。布局可混合地图、图表和表格,地图实例与联动由页面管理。
778
1450
 
779
1451
  ```js
780
- import { GraphicGroupController } from '@3clear/basegis/methods'
1452
+ import { BaseGIS } from '@3clear/basegis'
1453
+ import { MapViewLinkController } from '@3clear/basegis/methods'
781
1454
 
782
- const graphics = new GraphicGroupController({ mapCore })
1455
+ const mapA = new BaseGIS({
1456
+ engineType: 'leaflet',
1457
+ containerId: 'map-a',
1458
+ })
1459
+ const mapB = new BaseGIS({
1460
+ engineType: 'leaflet',
1461
+ containerId: 'map-b',
1462
+ })
1463
+ mapA.init()
1464
+ mapB.init()
1465
+
1466
+ const viewLinks = new MapViewLinkController({
1467
+ enabled: true,
1468
+ realtime: true,
1469
+ mode: 'all', // all | leader | group
1470
+ strategy: 'auto', // auto | view | bounds
1471
+ leaderId: 'map-a',
1472
+ syncInterval: 32, // 实时同步间隔,约 30fps
1473
+ })
783
1474
 
784
- graphics.load([
785
- {
786
- type: 'point',
787
- id: 'p1',
788
- longitude: 104,
789
- latitude: 35,
790
- },
791
- {
792
- type: 'line',
793
- id: 'l1',
794
- positions: [
795
- [103, 34],
796
- [105, 36],
797
- ],
798
- },
799
- {
800
- type: 'text',
801
- id: 't1',
802
- longitude: 104,
803
- latitude: 35,
804
- text: '站点',
805
- },
806
- ])
1475
+ viewLinks.registerMap('map-a', mapA, { group: 'forecast' })
1476
+ viewLinks.registerMap('map-b', mapB, { group: 'forecast' })
807
1477
 
808
- graphics.clear()
809
- graphics.getState()
1478
+ // 获取 BaseGIS、底层地图实例和全部 id -> BaseGIS 映射。
1479
+ const registeredMapA = viewLinks.getMap('map-a')
1480
+ const leafletMapA = viewLinks.getNativeMap('map-a')
1481
+ const allMaps = viewLinks.getMaps()
1482
+
1483
+ // 以 map-a 为操作源放大,并实时同步其他地图。
1484
+ viewLinks.zoomIn({ mapId: 'map-a', step: 1 })
1485
+
1486
+ // 程序化控制全部注册地图。
1487
+ viewLinks.setView({ center: [104, 34], zoom: 5, height: 5000000 })
1488
+ viewLinks.fitBounds({ bounds: { west: 73, south: 18, east: 135, north: 54 } })
1489
+
1490
+ // 页面卸载:控制器先解绑,地图仍由页面自己销毁。
1491
+ viewLinks.destroy()
1492
+ mapA.destroy()
1493
+ mapB.destroy()
810
1494
  ```
811
1495
 
812
- <a id="image-layer-controller"></a>
1496
+ 构造参数:
813
1497
 
814
- ## ImageLayerController
1498
+ | 参数 | 类型 | 默认值 | 说明 |
1499
+ | --- | --- | --- | --- |
1500
+ | `enabled` | `Boolean` | `true` | 是否开启联动。 |
1501
+ | `realtime` | `Boolean` | `true` | `true` 按动画帧同步移动/缩放;`false` 只在操作结束时同步。 |
1502
+ | `mode` | `String` | `'all'` | `all` 任意图控制;`leader` 仅主图;`group` 仅同组地图。 |
1503
+ | `strategy` | `String` | `'auto'` | `auto` 自动选择;`view` 优先中心和层级/高度;`bounds` 使用视图范围。 |
1504
+ | `leaderId` | `String` | `''` | 主图 id;空值会使用首个注册地图。 |
1505
+ | `syncInterval` | `Number` | `32` | 实时同步的最小间隔,单位 ms;默认约 30fps,降低多 Cesium 实例同时渲染的压力。 |
1506
+ | `suppressionMs` | `Number` | `220` | 被同步地图的事件抑制时长,避免反向循环。 |
1507
+ | `endDelay` | `Number` | `80` | Cesium 操作结束事件延迟,单位 ms。 |
1508
+ | `onStateChange` | `Function` | `null` | 注册、模式、主图和操作结束后的回调,参数为 `{ reason, state }`。 |
1509
+
1510
+ 联动与实例方法:
1511
+
1512
+ | 方法 | 参数 | 返回 / 说明 |
1513
+ | --- | --- | --- |
1514
+ | `registerMap(id,mapCore,options)` | `options:{group?,enabled?,replace?}` | 注册已初始化的 BaseGIS 并绑定实时视图监听。 |
1515
+ | `unregisterMap(id)` | 地图 id | 解除监听和注册,不销毁 BaseGIS。 |
1516
+ | `refreshMap(id)` | 地图 id | `BaseGIS.setEngine()` 后重新绑定新 adapter 的监听。 |
1517
+ | `getMap(id)` | 地图 id | 返回 BaseGIS 或 `null`。 |
1518
+ | `getNativeMap(id)` | 地图 id | 返回 Cesium Viewer / Leaflet Map 或 `null`。 |
1519
+ | `getMaps()` | 无 | 返回新的 `Map<id, BaseGIS>`,修改它不会改变控制器注册表。 |
1520
+ | `getMapIds()` / `hasMap(id)` | 可选 id | 查询注册状态。 |
1521
+ | `forEachMap(callback)` | `(mapCore,id,entry)` | 遍历已注册 BaseGIS。 |
1522
+ | `getLeaderMap()` / `getActiveMap()` | 无 | 获取主图或最近操作地图。 |
1523
+ | `setLeader(id)` | 地图 id | 设置主图。 |
1524
+ | `setMode(mode)` | `all/leader/group` | 更新联动模式。 |
1525
+ | `setStrategy(strategy)` | `auto/view/bounds` | 更新视角映射策略。 |
1526
+ | `setMapGroup(id,group)` | 地图 id、组名 | 更新地图所在联动组。 |
1527
+ | `setMapEnabled(id,enabled)` | 地图 id、布尔值 | 单独启停某张地图的联动。 |
1528
+ | `setEnabled(enabled)` / `pause()` / `resume()` | 可选布尔值 | 整体启停联动。 |
1529
+ | `setRealtime(realtime)` | 布尔值 | 切换实时或操作结束后同步。 |
1530
+ | `syncFrom(id,options)` | 地图 id、`force/strategy` 等 | 立即把指定地图视角同步给目标地图。 |
1531
+
1532
+ 统一视角操作:
815
1533
 
816
- 用于管理一个图片覆盖层。所有更新都走 `update(payload)`;页面有很多时次数据时,自己维护数据列表,然后把当前时次的数据传给 `update`。
1534
+ | 方法 | 参数 | 说明 |
1535
+ | --- | --- | --- |
1536
+ | `zoomIn(payload)` | `{mapId?,step?,distance?}` | 按显式 `mapId`、最近活动地图、主图的顺序选取操作源,再按联动规则同步。 |
1537
+ | `zoomOut(payload)` | 同上 | 缩小并同步。 |
1538
+ | `setView(payload)` | `center,zoom,height,heading,pitch,roll` | 设置全部注册地图视角。 |
1539
+ | `fitBounds(payload)` | `{bounds,animate?,padding?,duration?}` | 让全部地图适配相同范围。 |
1540
+ | `resetView(payload)` | 可选视角 | 重置全部地图。 |
1541
+ | `resizeAll(payload)` | 可选参数 | 刷新全部地图容器尺寸。 |
1542
+ | `getState()` | 无 | 返回模式、主图、注册地图、最近同步来源等状态。 |
1543
+ | `clear()` / `destroy()` | 无 | 解绑全部监听并释放引用,不销毁 BaseGIS。 |
817
1544
 
818
- `toImageLayerArea()` 可以把经纬度数组、Leaflet Bounds 或常见范围对象统一转换成 `{ startLon, startLat, endLon, endLat }`:
1545
+ `setView / fitBounds / resetView / resizeAll` 始终作用于所有注册地图,不受联动分组或暂停状态限制。
819
1546
 
820
- ```js
821
- import {
822
- ImageLayerController,
823
- toImageLayerArea,
824
- } from '@3clear/basegis/methods'
1547
+ 同为 Leaflet 时会同步 `center + zoom`;同为 Cesium 时会直接同步相机经纬度、`height + heading/pitch/roll`;两种视图模型无法直接对应时,`auto` 才会计算并使用 `bounds`。Cesium 交互期间会逐帧检测相机变化,再按 `syncInterval` 合并为最新状态写入目标地图,避免目标地图追赶稀疏跳点,也避免四五个 Cesium 实例每帧重复计算完整视域。一次拖动期间会锁定唯一交互源,目标地图的程序化相机事件不会反向接管并形成反馈循环。
825
1548
 
826
- const area = toImageLayerArea({
827
- west: 73,
828
- south: 18,
829
- east: 135,
830
- north: 54,
831
- })
832
- ```
1549
+ > BaseGIS 切换引擎会重建 adapter,原视图监听随旧 adapter 销毁。切换完成后调用 `viewLinks.refreshMap(id)`;控制器不会劫持或改写 `BaseGIS.setEngine()`。
833
1550
 
834
- 先创建控制器。这里是控制器级默认参数,后续每次 `update(payload)` 都可以覆盖这些默认值:
1551
+
1552
+ <a id="line-layer-controller"></a>
1553
+
1554
+ ## LineLayerController
1555
+
1556
+ 独立管理一组折线,支持整体更新、显隐、清空、销毁和引擎切换恢复。每条线必须提供唯一 `id` 和至少两个 `[longitude, latitude, height?]` 坐标点;同一控制器同一时刻只播放一条线的逐步出线动画。
1557
+
1558
+ ### 逐顶点色专题线与时间裁剪
1559
+
1560
+ `lines[].colors` 是六位 HEX 数组,长度必须与 `positions` 完全一致,颜色沿每两个相邻顶点连续插值,优先于 `style.color`:
835
1561
 
836
1562
  ```js
837
- import { ImageLayerController } from '@3clear/basegis/methods'
1563
+ import { LineLayerController } from '@3clear/basegis/methods'
1564
+
1565
+ const lineLayer = new LineLayerController({ mapCore, layerId: 'sampling-tracks' })
1566
+ const startTime = Date.parse('2026-08-28T08:00:00+08:00')
1567
+ await lineLayer.load({
1568
+ lines: [{
1569
+ id: 'sampling-track',
1570
+ positions: [[113.30, 22.60], [113.31, 22.61]],
1571
+ colors: ['#00FF00', '#FF3B30'],
1572
+ times: [startTime, startTime + 60000],
1573
+ }],
1574
+ style: { width: 5, opacity: 1, clampToGround: false },
1575
+ animation: { enabled: false },
1576
+ currentTime: startTime,
1577
+ })
838
1578
 
839
- const imageLayer = new ImageLayerController({
840
- // 必填:BaseGIS 实例。也可以写 baseGIS: mapCore。
841
- mapCore,
1579
+ lineLayer.setTime(startTime + 30000) // 显示前 30 秒已走过的路线。
1580
+ lineLayer.setTime(null) // 恢复完整路线。
1581
+ ```
842
1582
 
843
- // 选填:图层 id。不传时默认 image-layer-default。
844
- // 后续 show / hide / destroy 会按这个 id 找图层。
845
- layerId: 'radar-image',
1583
+ 需要行驶回放时,为各条线附加 `times: [起点毫秒时间戳, 终点毫秒时间戳, ...]`,长度与坐标相同且严格递增。`load` 可设置 `currentTime`,加载后调用 `lineLayer.setTime(time)` 裁掉未来部分;`setTime(null)` 恢复完整路线。不带 `times` 的线不参与裁剪。成功返回 `{ success: true, data: { layerId, currentTime } }`,非法时间返回友好失败结果。
846
1584
 
847
- // 选填:默认透明度,范围通常是 0 - 1。不传时默认 0.85。
848
- opacity: 0.85,
1585
+ Cesium 使用一个批量 Primitive 做顶点色插值,时间用相对秒数存入顶点,回放每帧仅改材质参数。Leaflet 使用共享 Canvas 重绘已走过的线段,不重新投影或创建图层。这是实线专题叠加,不能与虚线、流动、单线 reveal 或贴地混用;Cesium 关闭深度测试,带时间的采样按直线连接,不用于地形遮挡/贴合,位置必须不同。数据/配色更新仍为全量替换,显隐和引擎切换保留时间。浓度映射、分车/断线、时钟和小车属于业务层;参考项目 `/test-page-43`。
849
1586
 
850
- // 选填:默认图片四至范围。
851
- // 如果 update(payload) 里传 imageUrl,但没有传 area,就会使用这里的 defaultArea。
852
- defaultArea: {
853
- startLon: 73,
854
- startLat: 18,
855
- endLon: 135,
856
- endLat: 54,
857
- },
1587
+ ### 实线、虚线与纯色
858
1588
 
859
- // 选填:默认图片类型。color 表示图片已经填色;grayscale 表示灰度图需要运行时着色。
860
- imageSourceType: 'color',
1589
+ ```js
1590
+ import { LineLayerController } from '@3clear/basegis/methods'
861
1591
 
862
- // 选填:默认小数位,网格标注或探针显示数值时使用。
863
- decimalPlaces: 2,
1592
+ const lineLayer = new LineLayerController({
1593
+ mapCore,
1594
+ layerId: 'flight-route',
1595
+ style: {
1596
+ color: '#00d8ff',
1597
+ width: 4,
1598
+ opacity: 0.9,
1599
+ pattern: 'dashed', // solid | dashed
1600
+ dashLength: 14,
1601
+ gapLength: 8,
1602
+ },
1603
+ })
1604
+
1605
+ await lineLayer.load({
1606
+ lines: [{
1607
+ id: 'route-1',
1608
+ positions: [
1609
+ [116.4, 39.9],
1610
+ [117.8, 37.5],
1611
+ [121.5, 31.2],
1612
+ ],
1613
+ }],
864
1614
  })
865
1615
  ```
866
1616
 
867
- ### 1. 普通图片
1617
+ 单条线的 `style` 可以覆盖控制器的顶层默认样式。
868
1618
 
869
- 只显示一张已经处理好的 PNG/JPG。适合雷达图、云图、已填色格点图。
1619
+ ### 固定渐变与流动渐变
1620
+
1621
+ `style.color` 可以是 CSS 颜色字符串,也可以是渐变配置。`stops` 的 `offset` 按 `0~1` 升序排列;配置 `flow` 后渐变会沿线路循环流动。
870
1622
 
871
1623
  ```js
872
- await imageLayer.update({
873
- // 选填:当前数据 id,用于控制器内部记录 activeItem。
874
- id: 'radar-202607010800',
1624
+ const gradientStyle = {
1625
+ width: 5,
1626
+ pattern: 'solid',
1627
+ color: {
1628
+ type: 'gradient',
1629
+ stops: [
1630
+ { offset: 0, color: '#00e5ff' },
1631
+ { offset: 0.5, color: '#2563eb' },
1632
+ { offset: 1, color: '#a855f7' },
1633
+ ],
1634
+ flow: {
1635
+ enabled: true,
1636
+ durationMs: 2400,
1637
+ direction: 'forward', // forward | reverse
1638
+ },
1639
+ },
1640
+ }
1641
+ ```
875
1642
 
876
- // 选填:当前数据名称,用于状态展示。
877
- name: '08:00 雷达',
1643
+ 渐变与虚线可以同时配置。Cesium 和 Leaflet 的底层绘制机制不同,虚线端点和颜色交界处可能有轻微视觉差异。
878
1644
 
879
- // 必填:图片地址。普通图片模式必须传 imageUrl。
880
- imageUrl: '/data/radar/202607010800.png',
1645
+ ### 飞机引领的逐步出线
1646
+
1647
+ 飞机不是单独的类,而是通用 `animation.icon` 配置。省略 `icon` 时只播放线路逐步出现。
1648
+
1649
+ ```js
1650
+ // BaseGIS 不内置飞机图片,替换成业务项目自己的资源地址。
1651
+ const planeIconUrl = '/your-app/plane.svg'
1652
+
1653
+ const flightLine = new LineLayerController({
1654
+ mapCore,
1655
+ layerId: 'flight-route',
1656
+ style: gradientStyle,
1657
+ animation: {
1658
+ enabled: true,
1659
+ lineId: 'route-1',
1660
+ mode: 'reveal',
1661
+ durationMs: 12000,
1662
+ autoplay: true,
1663
+ loop: true,
1664
+ icon: {
1665
+ url: planeIconUrl,
1666
+ size: [36, 36],
1667
+ rotateToPath: true,
1668
+ rotationOffsetDeg: 0,
1669
+ },
1670
+ },
1671
+ })
1672
+
1673
+ await flightLine.load({
1674
+ lines: [{
1675
+ id: 'route-1',
1676
+ positions: [
1677
+ [116.4, 39.9],
1678
+ [117.8, 37.5],
1679
+ [121.5, 31.2],
1680
+ ],
1681
+ }],
1682
+ })
1683
+
1684
+ flightLine.pauseAnimation()
1685
+ flightLine.playAnimation()
1686
+ flightLine.restartAnimation()
1687
+ ```
1688
+
1689
+ 动画按照各段实际地理距离插值。`hide()` 暂停帧更新并记录播放状态,`show()` 恢复隐藏前正在运行的动画;`clear()` 和 `destroy()` 会取消动画。切换引擎会恢复播放/暂停意图,但不会保存逐帧进度,动画从起点重新计算。
1690
+
1691
+ ### 参数
1692
+
1693
+ | 参数 | 默认值 | 说明 |
1694
+ | --- | --- | --- |
1695
+ | `layerId` | `line-default` | 独立线图层 id。 |
1696
+ | `lines` | `[]` | 折线数组,每项包含唯一 `id` 和至少两个 `positions` 坐标。 |
1697
+ | `lines[].colors` | - | 与坐标等长的六位 HEX 数组,优先于 `style.color`;用于实线专题叠加。 |
1698
+ | `lines[].times` | - | 与坐标等长、严格递增的有限毫秒时间戳;必须与 `colors` 配合。 |
1699
+ | `currentTime` | `null` | 路线时间裁剪位置;`null` 显示全部。 |
1700
+ | `visible` | `true` | 初始是否可见。 |
1701
+ | `style.color` | `#2f80ff` | 纯色字符串或渐变对象。 |
1702
+ | `style.width` / `style.opacity` | `3` / `1` | 线宽和透明度。 |
1703
+ | `style.pattern` | `solid` | `solid` 或 `dashed`。 |
1704
+ | `style.dashLength` / `gapLength` | `12` / `8` | 虚线实段和间隔长度。 |
1705
+ | `animation.enabled` | `false` | 是否启用逐步出线。 |
1706
+ | `animation.lineId` | 单线时自动选择 | 多条线启用动画时必填。 |
1707
+ | `animation.durationMs` | `10000` | 从起点到终点的时长。 |
1708
+ | `animation.autoplay` / `loop` | `true` / `false` | 自动播放与循环。 |
1709
+ | `animation.icon` | - | 可选的飞机、车辆或船舶图标配置。 |
1710
+
1711
+ ### 方法
1712
+
1713
+ | 方法 | 说明 |
1714
+ | --- | --- |
1715
+ | `mount(mapCore)` | 后挂载 BaseGIS。 |
1716
+ | `load(payload, options?)` / `update(payload)` | 加载或更新线、样式和动画。 |
1717
+ | `setTime(currentTime)` | 更新路线时间裁剪;传 `null` 恢复完整路线,不重建全部线数据。 |
1718
+ | `show()` / `hide()` / `toggle()` | 控制显隐。 |
1719
+ | `playAnimation()` / `pauseAnimation()` / `restartAnimation()` | 控制沿线动画。 |
1720
+ | `clear()` | 清空线和动画,保留控制器。 |
1721
+ | `refreshState()` / `getState()` | 获取线数量、点数和动画状态。 |
1722
+ | `destroy()` | 删除图层、动画和引擎恢复快照。 |
1723
+
1724
+ 页面卸载时先执行 `lineLayer.destroy()`,再执行 `mapCore.destroy()`。
1725
+
1726
+ 在线示例:[查看 LineLayerController 双引擎应用 Demo](http://61.50.111.214:31166/lgmap/test-page-34)。
1727
+
1728
+ <a id="typhoon-path-controller"></a>
1729
+
1730
+ ## TyphoonPathController
1731
+
1732
+ 把已经整理好的台风实况和预报数据渲染为独立专题图层,统一支持 Cesium / Leaflet。控制器负责实况路径、强度着色节点、生命周期标签、移动中心、当前点 7/10/12 级四象限风圈、多机构预报、不确定性圆和播放定位。
1733
+
1734
+ BaseGIS 不请求台风接口,也不推算强度、风圈、预报或预警等级。分页筛选、接口字段转换、ECharts 和业务面板仍由应用层负责。24/48 小时警戒线属于普通业务折线,推荐与 `LineLayerController` 组合使用。
1735
+
1736
+ ### 最小用法
1737
+
1738
+ ```js
1739
+ import { TyphoonPathController } from '@3clear/basegis/methods'
1740
+
1741
+ const typhoonLayer = new TyphoonPathController({
1742
+ mapCore,
1743
+ layerId: 'typhoon-senlac',
1744
+ })
1745
+
1746
+ await typhoonLayer.load({
1747
+ typhoon: { id: '2604', code: '2604', name: '森拉克' },
1748
+ tracks: [
1749
+ {
1750
+ id: 'track-1',
1751
+ time: '2026-04-10T18:00:00Z',
1752
+ position: [136.2, 10.5],
1753
+ level: 'TD',
1754
+ stage: '热带低压',
1755
+ windSpeed: 16,
1756
+ pressure: 1002,
1757
+ windCircles: {
1758
+ r7: { ne: 120, se: 140, sw: 90, nw: 110 },
1759
+ },
1760
+ label: '生成 4/10 18时',
1761
+ },
1762
+ {
1763
+ id: 'track-2',
1764
+ time: '2026-04-11T12:00:00Z',
1765
+ position: [133.2, 12],
1766
+ level: 'TY',
1767
+ stage: '台风',
1768
+ windSpeed: 35,
1769
+ pressure: 970,
1770
+ windCircles: {
1771
+ r7: { ne: 300, se: 320, sw: 200, nw: 220 },
1772
+ r10: { ne: 120, se: 140, sw: 80, nw: 100 },
1773
+ },
1774
+ label: '加强 4/11 12时',
1775
+ },
1776
+ ],
1777
+ playback: {
1778
+ enabled: true,
1779
+ durationMs: 12000,
1780
+ autoplay: false,
1781
+ loop: true,
1782
+ },
1783
+ onCurrentChange({ index, point, progress }) {
1784
+ console.log(index, point, progress)
1785
+ },
1786
+ })
1787
+
1788
+ typhoonLayer.play()
1789
+ ```
1790
+
1791
+ 坐标固定为 `[longitude, latitude, height?]`。风圈半径单位是公里,`ne / se / sw / nw` 分别表示东北、东南、西南、西北四个象限;没有某一级风圈时直接省略对应的 `r7 / r10 / r12`。
1792
+
1793
+ ### 已准备好的预报路径
1794
+
1795
+ `forecastPaths` 支持同时传入多个机构结果。BaseGIS 只渲染传入结果,不在前端生成预报。
1796
+
1797
+ ```js
1798
+ await typhoonLayer.update({
1799
+ forecastPaths: [{
1800
+ id: 'cma',
1801
+ name: '中央气象台',
1802
+ style: {
1803
+ color: '#ffdf4d',
1804
+ width: 2.5,
1805
+ opacity: 0.96,
1806
+ dashLength: 12,
1807
+ gapLength: 8,
1808
+ },
1809
+ points: [
1810
+ { id: 'f1', time: '2026-04-13T12:00:00Z', position: [127.4, 19.2], level: 'STY' },
1811
+ {
1812
+ id: 'f2',
1813
+ time: '2026-04-14T00:00:00Z',
1814
+ position: [122.8, 20.5],
1815
+ level: 'TY',
1816
+ uncertaintyRadiusKm: 90,
1817
+ },
1818
+ ],
1819
+ }],
1820
+ })
1821
+ ```
1822
+
1823
+ ### 路径点格式
1824
+
1825
+ | 字段 | 必填 | 说明 |
1826
+ | --- | --- | --- |
1827
+ | `id` | 否 | 点 id;未传时使用时次或数组索引生成内部 id。 |
1828
+ | `time` | 建议 | ISO 字符串或浏览器可解析时间;播放优先按真实时距插值。 |
1829
+ | `position` | 是 | `[longitude, latitude, height?]`。 |
1830
+ | `level` | 否 | 强度代码,默认 `TD`;内置 `TD / TS / STS / TY / STY / SuperTY` 颜色。 |
1831
+ | `stage` / `label` | 否 | 中心状态文字和静态生命周期标签。 |
1832
+ | `windSpeed` / `pressure` | 否 | 中心标签可展示的风速和气压业务值。 |
1833
+ | `windCircles` | 否 | `r7 / r10 / r12` 四象限半径对象,单位 km。 |
1834
+ | `data` | 否 | 点击回调透传的业务数据。 |
1835
+
1836
+ ### 样式与播放配置
1837
+
1838
+ | 参数 | 默认值 | 说明 |
1839
+ | --- | --- | --- |
1840
+ | `visible` | `true` | 图层初始显隐。 |
1841
+ | `style.trackLine` | 白色、`width: 3` | 实况路径颜色、宽度、透明度及描边。 |
1842
+ | `style.forecastLine` | 白色虚线、`width: 2` | 未给单条预报路径样式时的默认值。 |
1843
+ | `style.point.radius` | `5` | 实况和预报节点半径。 |
1844
+ | `style.label.visible` | `true` | 是否显示传入的生命周期标签。 |
1845
+ | `style.center.iconUrl` | 包内台风 SVG | 可覆盖为业务 GIF、PNG 或 SVG。 |
1846
+ | `style.center.iconSize` | `[36, 36]` | 当前中心图标尺寸。 |
1847
+ | `style.center.rotate` | `true` | 是否旋转中心图标。 |
1848
+ | `style.center.showLabel` | `true` | 是否显示台风编号、名称、强度和风速。 |
1849
+ | `style.windCircle.visible` | `true` | 是否显示当前点的 7/10/12 级风圈。 |
1850
+ | `style.intensityColors` | 内置强度色 | 按 `level` 覆盖节点颜色。 |
1851
+ | `playback.enabled` | `true` | 是否允许路径播放。 |
1852
+ | `playback.durationMs` | `15000` | 从首点到末点的总时长。 |
1853
+ | `playback.autoplay` / `loop` | `false` / `false` | 自动播放与循环。 |
1854
+ | `playback.currentIndex` | `0` | 初始活动路径点。 |
1855
+
1856
+ `onCurrentChange` 只在活动点索引变化或显式定位时触发,不会在每一帧刷业务状态。`onPointClick` 在点击实况点、预报点或当前中心时返回 `{ targetType, point, pointIndex, forecastPathId, coordinate, containerPoint, engineType }`,弹窗由业务页面实现。
1857
+
1858
+ ### 方法
1859
+
1860
+ | 方法 | 说明 |
1861
+ | --- | --- |
1862
+ | `mount(mapCore)` | 后挂载已经初始化的 BaseGIS。 |
1863
+ | `load(payload, options?)` / `update(payload)` | 加载或局部更新路径、预报、样式和播放配置。 |
1864
+ | `show()` / `hide()` / `toggle()` | 控制专题显隐;隐藏时暂停,重新显示后恢复原播放意图。 |
1865
+ | `play()` / `pause()` / `restart({ autoplay })` | 控制台风中心播放。 |
1866
+ | `seek(index)` / `seek({ time })` | 按点索引或时次定位。 |
1867
+ | `flyTo(options?)` | 定位到实况和预报路径范围。 |
1868
+ | `clear()` | 清空实况和预报数据,保留空图层及控制器。 |
1869
+ | `refreshState()` / `getState()` | 获取点数、预报数、当前索引、进度和播放状态。 |
1870
+ | `destroy()` | 删除图层、动画、点击监听和引擎恢复快照。 |
1871
+
1872
+ 切换引擎会恢复数据、样式、显隐、播放/暂停意图和最后一次显式 `seek` 的位置;不会保存正在播放的逐帧进度。页面卸载时先执行 `typhoonLayer.destroy()`,再执行 `mapCore.destroy()`。
1873
+
1874
+ 在线示例:[查看 TyphoonPathController 双引擎应用 Demo](http://61.50.111.214:31166/lgmap/test-page-10)。
1875
+
1876
+ <a id="image-layer-controller"></a>
1877
+
1878
+ ## ImageLayerController
1879
+
1880
+ 用于管理一个图片覆盖层。所有更新都走 `update(payload)`;页面有很多时次数据时,自己维护数据列表,然后把当前时次的数据传给 `update`。
1881
+
1882
+ ### 支持的数据格式
1883
+
1884
+ | 数据模式 | 核心字段 | 用途 |
1885
+ | --- | --- | --- |
1886
+ | 普通图片 | `imageUrl + area` | 浏览器可解码的图片,常用 PNG、JPG/JPEG、WebP;用作雷达、云图或已填色格点图覆盖层。 |
1887
+ | 灰度图 | `imageUrl + area + imageSourceType: 'grayscale' + colorize` | 把像素灰度映射成业务值,再按色带生成可见图片。 |
1888
+ | 图片 + GeoTIFF | `imageUrl + tifUrl + area` | 图片负责显示,GeoTIFF 为网格注记和鼠标探针提供数值。 |
1889
+
1890
+ `ImageLayerController` 不会把单独的 GeoTIFF 渲染成彩色图片底图。只有 TIF 数值数据时,优先使用 [`GridLayerController`](#grid-layer-controller);需要由栅格生成等值线时使用 [`RasterContourController`](#raster-contour-controller)。
1891
+
1892
+ `toImageLayerArea()` 可以把经纬度数组、Leaflet Bounds 或常见范围对象统一转换成 `{ startLon, startLat, endLon, endLat }`:
1893
+
1894
+ ```js
1895
+ import {
1896
+ ImageLayerController,
1897
+ toImageLayerArea,
1898
+ } from '@3clear/basegis/methods'
1899
+
1900
+ const area = toImageLayerArea({
1901
+ west: 73,
1902
+ south: 18,
1903
+ east: 135,
1904
+ north: 54,
1905
+ })
1906
+ ```
1907
+
1908
+ 先创建控制器。这里是控制器级默认参数,后续每次 `update(payload)` 都可以覆盖这些默认值:
1909
+
1910
+ ```js
1911
+ import { ImageLayerController } from '@3clear/basegis/methods'
1912
+
1913
+ const imageLayer = new ImageLayerController({
1914
+ // 必填:BaseGIS 实例。也可以写 baseGIS: mapCore。
1915
+ mapCore,
1916
+
1917
+ // 选填:图层 id。不传时默认 image-layer-default。
1918
+ // 后续 show / hide / destroy 会按这个 id 找图层。
1919
+ layerId: 'radar-image',
1920
+
1921
+ // 选填:默认透明度,范围通常是 0 - 1。不传时默认 0.85。
1922
+ opacity: 0.85,
1923
+
1924
+ // 选填:默认图片四至范围。
1925
+ // 如果 update(payload) 里传 imageUrl,但没有传 area,就会使用这里的 defaultArea。
1926
+ defaultArea: {
1927
+ startLon: 73,
1928
+ startLat: 18,
1929
+ endLon: 135,
1930
+ endLat: 54,
1931
+ },
1932
+
1933
+ // 选填:默认图片类型。color 表示图片已经填色;grayscale 表示灰度图需要运行时着色。
1934
+ imageSourceType: 'color',
1935
+
1936
+ // 选填:默认小数位,网格标注或探针显示数值时使用。
1937
+ decimalPlaces: 2,
1938
+ })
1939
+ ```
1940
+
1941
+ ### 1. 普通图片
1942
+
1943
+ 只显示一张已经处理好的 PNG/JPG。适合雷达图、云图、已填色格点图。
1944
+
1945
+ ```js
1946
+ await imageLayer.update({
1947
+ // 选填:当前数据 id,用于控制器内部记录 activeItem。
1948
+ id: 'radar-202607010800',
1949
+
1950
+ // 选填:当前数据名称,用于状态展示。
1951
+ name: '08:00 雷达',
1952
+
1953
+ // 必填:图片地址。普通图片模式必须传 imageUrl。
1954
+ imageUrl: '/data/radar/202607010800.png',
881
1955
 
882
1956
  // 必填:图片四至范围。只要传 imageUrl,就必须能确定 area。
883
1957
  area: {
@@ -996,57 +2070,26 @@ await imageLayer.update({
996
2070
 
997
2071
  Leaflet 会对经度跨度达到 360° 的全球图片按当前视野自动补充相邻世界副本,全球 TIF / 灰度格点的注记和鼠标探针也会使用同一套经度循环,因此业务页面不需要手动创建 `±360°` 图片层。原始图片按 `0° ~ 360°` 排列时传 `isSplit: true`,适配层会在交换左右半幅的同时把显示范围统一为 `-180° ~ 180°`,确保图片与格点值落在同一经度位置。
998
2072
 
999
- `loadGrayImage` 的灰度值换算规则:
1000
-
1001
- ```js
1002
- 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue) * (maxValue - minValue) + minValue
1003
- ```
1004
-
1005
- 所以灰度图要表达真实业务值时,建议显式传 `minValue / maxValue`。如果只写 `colorize: true`,会使用默认范围 `minValue: 0`、`maxValue: 1`、`grayMinValue: 1`、`grayMaxValue: 254`,只适合临时预览,不适合正式业务图层。
1006
-
1007
- `loadGrayImage` 支持两种写法:
1008
-
1009
- ```js
1010
- await gridLayer.loadGrayImage({
1011
- imageUrl: '/data/grid/temp-gray.png',
1012
- area,
1013
- colorize: {
1014
- minValue: -20,
1015
- maxValue: 50,
1016
- colors: ['#2639a7', '#2faaf3', '#8ee5b2', '#fd9919', '#fb422d'],
1017
- },
1018
- })
1019
-
1020
- await gridLayer.loadGrayImage({
1021
- imageUrl: '/data/grid/temp-gray.png',
1022
- area,
1023
- minValue: -20,
1024
- maxValue: 50,
1025
- grayMinValue: 1,
1026
- grayMaxValue: 254,
1027
- colors: ['#2639a7', '#2faaf3', '#8ee5b2', '#fd9919', '#fb422d'],
1028
- })
1029
- ```
1030
- ### 4. 纯 TIF
2073
+ ### 4. 仅 TIF 数值层
1031
2074
 
1032
- 只传 TIF,不传图片。适合数据本身带地理范围、需要按 TIF 渲染的场景。
2075
+ 只传 TIF 时不会生成彩色图片底图,仅能使用 TIF 数值显示网格注记或鼠标探针。这种场景更推荐直接使用 `GridLayerController`。
1033
2076
 
1034
2077
  ```js
1035
2078
  await imageLayer.update({
1036
2079
  // 选填:当前数据 id。
1037
2080
  id: 'rain-tif',
1038
2081
 
1039
- // 必填建议:纯 TIF 模式下显式清空 imageUrl,避免沿用上一次图片。
2082
+ // 必填建议:仅 TIF 模式下显式清空 imageUrl,避免沿用上一次图片。
1040
2083
  imageUrl: '',
1041
2084
 
1042
2085
  // 必填:TIF 地址。
1043
2086
  tifUrl: '/data/rain.tif',
1044
2087
 
1045
- // 选填:是否按默认色带着色。需要更精细控制时传 colorize 对象。
1046
- colorize: true,
1047
-
1048
2088
  // 选填:是否开启鼠标探针。
1049
2089
  showProbe: true,
2090
+
2091
+ // 选填:是否显示网格数值注记。
2092
+ showLabel: true,
1050
2093
  })
1051
2094
  ```
1052
2095
 
@@ -1138,7 +2181,7 @@ imageLayer.destroy({ layerId: 'radar-image' })
1138
2181
  | 方法 | 参数 | 返回值 | 说明 |
1139
2182
  | --- | --- | --- | --- |
1140
2183
  | `mount(target)` | `BaseGIS` 实例 | `Result` | 绑定 `BaseGIS`。构造时已传 `mapCore/baseGIS` 时通常不需要手动调用。 |
1141
- | `update(payload)` | 图片图层数据 | `Promise<Result>` | 推荐主入口。创建或更新当前图片图层,支持普通图片、图片+TIF、灰度图+色带、纯 TIF |
2184
+ | `update(payload)` | 图片图层数据 | `Promise<Result>` | 推荐主入口。创建或更新普通图片、灰度图或图片 + TIF;仅 TIF 时只有数值注记/探针。 |
1142
2185
  | `show(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 显示图片图层。成功后控制器 `visible` 变为 `true`。 |
1143
2186
  | `hide(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 隐藏图片图层。成功后控制器 `visible` 变为 `false`。 |
1144
2187
  | `destroy(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 移除图片图层,并清空 `items / activeIndex / layerType / currentPayload`。如果地图已销毁,只清本地状态。 |
@@ -1179,35 +2222,43 @@ imageLayer.destroy({ layerId: 'radar-image' })
1179
2222
  | `defaultArea` | 选填 | 默认图片范围,`update` 未传 `area` 时使用。 |
1180
2223
  | `id` | 选填 | 当前数据 id,便于状态记录。 |
1181
2224
  | `name` | 选填 | 当前数据名称,便于页面展示。 |
1182
- | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。纯 TIF 模式建议传空字符串清掉旧图片。 |
1183
- | `tifUrl` | TIF、图片+TIF 必填 | GeoTIFF 地址,用于渲染、探针或网格注记。 |
2225
+ | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。仅 TIF 模式建议传空字符串清掉旧图片。 |
2226
+ | `tifUrl` | TIF、图片+TIF 必填 | GeoTIFF 数值地址,用于鼠标探针或网格注记,不会单独生成彩色图片底图。 |
1184
2227
  | `area` | 传 `imageUrl` 时必填 | 图片范围:`startLon / startLat / endLon / endLat`。 |
2228
+ | `sourceProjection` | 选填 | 源图投影;当前支持将 `EPSG:3857` 图片重采样为经纬度图片,不是任意投影转换器。 |
2229
+ | `sourceArea` | 选填 | 源图片范围,默认使用 `area`;支持墨卡托米坐标范围或经纬度范围。 |
2230
+ | `targetProjection` | 选填 | 默认从地图 CRS 推断;源投影与目标相同则不重采样。 |
1185
2231
  | `imageSourceType` | 灰度图必填,其他选填 | `color` 表示已填色图片;`grayscale` 表示灰度图。 |
1186
- | `colorize` | 灰度图必填,TIF 着色选填 | 色带配置,或传 `true` 使用默认着色。 |
2232
+ | `colorize` | 灰度图必填 | 灰度图业务值和色带配置。 |
1187
2233
  | `colorize.minValue` | 灰度图必填 | 色带映射最小值。 |
1188
2234
  | `colorize.maxValue` | 灰度图必填 | 色带映射最大值。 |
1189
2235
  | `colorize.noDataValue` | 选填 | 无效值。 |
1190
2236
  | `colorize.colors` | 灰度图必填 | 色带颜色数组。 |
1191
2237
  | `opacity` | 选填 | 透明度,通常 0 - 1。 |
2238
+ | `zIndex` | 选填 | Leaflet 图片叠放层级,默认 `650`。 |
1192
2239
  | `visible` | 选填 | 是否显示图层。 |
1193
- | `showProbe` | 选填 | 是否开启鼠标探针,通常依赖 `tifUrl`。 |
1194
- | `showLabel` | 选填 | 是否显示网格注记,通常依赖 `tifUrl`。 |
2240
+ | `showProbe` | 选填 | 是否开启鼠标探针,需要 `tifUrl` 或灰度图数值。 |
2241
+ | `showLabel` | 选填 | 是否显示网格注记,需要 `tifUrl` 或灰度图数值。 |
1195
2242
  | `decimalPlaces` | 选填 | 探针或注记数值小数位。 |
1196
2243
  | `isSplit` | 选填 | 是否切片处理大图。 |
1197
2244
  | `gridTotal` | 选填 | 切片网格数量。 |
1198
2245
  | `tileSize` | 选填 | 切片尺寸。 |
1199
2246
 
1200
- 兼容方法:
1201
-
1202
- - `load(items, { index, visible })`:兼容旧的图片集合加载方式,会把 `items[index]` 写入当前图层。
1203
- - `switchTo(idOrIndex)`:在已加载的 `items` 内切换到某一项。
1204
- - `next() / prev()`:只适合本地小数组演示,不推荐作为业务时次更新主路径。
2247
+ 图片重采样依赖 Canvas 读取像素,跨域图片需要服务端允许 CORS。
1205
2248
 
1206
2249
  <a id="grid-layer-controller"></a>
1207
2250
 
1208
2251
  ## GridLayerController
1209
2252
 
1210
- 用于 TIF 网格、灰度图网格、外部数值网格。
2253
+ 用于 GeoTIFF、灰度图和外部数值网格的采样、数值注记和鼠标探针。它不绘制彩色栅格底图;`showLabel` 和 `showProbe` 都为 `false` 时,数据会被读取,但地图上没有可见内容。
2254
+
2255
+ ### 支持的数据格式
2256
+
2257
+ | 数据模式 | 核心字段 | 数据契约 |
2258
+ | --- | --- | --- |
2259
+ | GeoTIFF | `tifUrl` | `.tif/.tiff` 地址;读取第一个栅格波段及文件地理范围。 |
2260
+ | 灰度图 | `imageUrl/grayImageUrl + area` | 浏览器可解码的灰度图;通过 `grayMinValue/grayMaxValue/minValue/maxValue/noDataValue` 换算业务值。 |
2261
+ | 直接数值网格 | `imageGridData` | `{ values, width, height, area }` 或 `{ values, width, height, bbox }`;`values` 必须是长度不小于 `width * height` 的一维数组或 TypedArray。 |
1211
2262
 
1212
2263
  ```js
1213
2264
  import { GridLayerController } from '@3clear/basegis/methods'
@@ -1222,7 +2273,6 @@ const gridLayer = new GridLayerController({
1222
2273
 
1223
2274
  await gridLayer.loadTif({
1224
2275
  tifUrl: '/data/grid/temp.tif',
1225
- colorize: true,
1226
2276
  })
1227
2277
 
1228
2278
  await gridLayer.loadGrayImage({
@@ -1233,7 +2283,7 @@ await gridLayer.loadGrayImage({
1233
2283
  endLon: 135,
1234
2284
  endLat: 54,
1235
2285
  },
1236
- // 灰度图需要把灰度值映射成业务值,再按业务值找色带。
2286
+ // 灰度图需要把灰度值映射成业务值。
1237
2287
  colorize: {
1238
2288
  // 必填建议:业务值最小值。
1239
2289
  minValue: -20,
@@ -1249,27 +2299,59 @@ await gridLayer.loadGrayImage({
1249
2299
  })
1250
2300
 
1251
2301
  await gridLayer.loadData({
1252
- imageGridData: [
1253
- [12.1, 13.4],
1254
- [14.2, 15.6],
1255
- ],
1256
- area: {
1257
- startLon: 100,
1258
- startLat: 30,
1259
- endLon: 110,
1260
- endLat: 40,
2302
+ imageGridData: {
2303
+ values: [12.1, 13.4, 14.2, 15.6],
2304
+ width: 2,
2305
+ height: 2,
2306
+ area: {
2307
+ startLon: 100,
2308
+ startLat: 30,
2309
+ endLon: 110,
2310
+ endLat: 40,
2311
+ },
1261
2312
  },
1262
2313
  })
1263
2314
 
1264
- gridLayer.update({ opacity: 0.7 })
2315
+ await gridLayer.update({ decimalPlaces: 2 })
1265
2316
  gridLayer.hide()
1266
2317
  gridLayer.show()
1267
2318
  gridLayer.destroy()
1268
2319
  ```
1269
2320
 
1270
- ## 统一粒子风场
2321
+ 灰度值换算规则:
1271
2322
 
1272
- 同一组 API 会根据当前引擎自动选择渲染器:Cesium 使用 GPU 粒子风场,Leaflet 使用迁自 one-map 的 Canvas 粒子风场。
2323
+ ```text
2324
+ 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue)
2325
+ * (maxValue - minValue) + minValue
2326
+ ```
2327
+
2328
+ 灰度图要表达真实业务值时,应显式传入 `minValue / maxValue`。数值图层不使用色带绘制背景;如果需要同时显示彩色图片,请使用 `ImageLayerController`。
2329
+
2330
+ ### 方法
2331
+
2332
+ | 方法 | 说明 |
2333
+ | --- | --- |
2334
+ | `mount(mapCore)` | 后挂载 BaseGIS。 |
2335
+ | `load(payload)` | 加载 `tifUrl / imageUrl / grayImageUrl / imageGridData` 数据源。 |
2336
+ | `loadTif(payload)` / `loadGrayImage(payload)` / `loadData(payload)` | 对应三种数据源的快捷加载方法。 |
2337
+ | `update(payload)` | 合并更新已加载数据和参数;首次尚未加载时返回 `NOT_LOADED`。 |
2338
+ | `show()` / `hide()` | 控制显隐。 |
2339
+ | `getState()` | 返回挂载状态、引擎、图层 id、显隐及当前 payload。 |
2340
+ | `destroy()` | 移除网格图层并清理控制器数据。 |
2341
+
2342
+ <a id="unified-wind-layer"></a>
2343
+
2344
+ ## 风场统一入口(推荐)
2345
+
2346
+ 风场有 GPU 和 Canvas 两种渲染方式,三个调用入口的区别如下:
2347
+
2348
+ | 调用入口 | Cesium | Leaflet | 切换引擎后自动恢复 |
2349
+ | --- | --- | --- | --- |
2350
+ | `BaseGIS.upsertWindLayer()`(推荐) | GPU | Canvas | 支持 |
2351
+ | [`BaseGIS.upsertGpuWindLayer()`](#gpu-wind-layer) | GPU | 不支持 | 不支持 |
2352
+ | [`WindFieldMethods`](#wind-field-methods) | Canvas | Canvas | 不支持 |
2353
+
2354
+ 一般使用 `upsertWindLayer()` 即可,它会根据当前引擎自动选择渲染器。需要在 Cesium 上使用 Canvas,或需要叠加风速底图时,使用 `WindFieldMethods`。
1273
2355
 
1274
2356
  ```js
1275
2357
  const windData = await fetch('/mock/uv.json').then((response) => response.json())
@@ -1307,9 +2389,13 @@ mapCore.removeWindLayer({ layerId: 'surface-wind' })
1307
2389
 
1308
2390
  跨引擎统一使用 `Bound / DataAry` 数据格式。公共参数包括 `maxParticles`、`speedFactor`、`lineWidth`、`fadeOpacity`、`particleOpacity`、`color` 和 `visible`;适配层会统一速度、帧率拖尾、屏幕线宽、设备像素比和粒子视觉密度。`maxParticles` 表示跨引擎视觉预算:Cesium 的实际粒子数会按 GPU 纹理向上取整为整数平方,Leaflet 会按 Canvas 拖尾覆盖率换算实际粒子数,均可通过图层状态查看;算法和投影不同,因此不保证逐像素完全一致。Leaflet 还支持 `particleGap`、`maxAge`、`frameRate`、`minSpeed`。统一风场属于 BaseGIS 托管图层,`setEngine()` 切换引擎时会自动使用原数据和最新参数恢复。
1309
2391
 
1310
- ## GPU 粒子风场
2392
+ <a id="gpu-wind-layer"></a>
2393
+
2394
+ ## GPU 风场
1311
2395
 
1312
- GPU 粒子风场是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用同名方法时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
2396
+ `upsertGpuWindLayer()` 与统一入口在 Cesium 下使用同一套 GPU 实现。本节接口直接使用 GPU 参数,不参与 BaseGIS 的引擎切换恢复;统一入口还会换算线宽等参数,因此两组接口的参数值不宜直接照搬。
2397
+
2398
+ GPU 风场仅支持 Cesium。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用本节接口时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
1313
2399
 
1314
2400
  Cesium 由宿主通过 `window.Cesium` 提供。启用 `terrainEnabled` 后,图层会采样当前 `viewer.terrainProvider`,让轨迹高度随地形变化;如果当前使用椭球地形,采样高度为 0。
1315
2401
 
@@ -1349,9 +2435,9 @@ if (!result.success) {
1349
2435
  const windData = {
1350
2436
  // 经度最小值、纬度最小值、经向格点数、纬向格点数、
1351
2437
  // 经度跨度、纬度跨度、数值缩放倍数。
1352
- Bound: [100, 10, 181, 91, 80, 40, 10],
2438
+ Bound: [100, 10, 2, 2, 10, 10, 10],
1353
2439
  // 每个格点按 U、V 交错;纬度行从南向北排列。
1354
- DataAry: [u0, v0, u1, v1],
2440
+ DataAry: [30, 10, 40, 15, 20, 10, 35, 5],
1355
2441
  }
1356
2442
  ```
1357
2443
 
@@ -1369,7 +2455,7 @@ const windData = {
1369
2455
  },
1370
2456
  u: new Float32Array(181 * 91),
1371
2457
  v: new Float32Array(181 * 91),
1372
- // index-gpu.vue 解码结果从北向南排列,因此使用 north-to-south。
2458
+ // 仅当数据行从北向南排列时设置;默认按南到北排列。
1373
2459
  rowOrder: 'north-to-south',
1374
2460
  }
1375
2461
  ```
@@ -1437,6 +2523,78 @@ mapCore.removeGpuWindLayer({ layerId: 'surface-gpu-wind' })
1437
2523
 
1438
2524
  GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后,业务应重新调用 `upsertGpuWindLayer`;需要自动恢复时改用 `upsertWindLayer`。核心渲染流程改编自 [RaymanNg/3D-Wind-Field](https://github.com/RaymanNg/3D-Wind-Field),遵循源码目录内 `LICENSE-RaymanNg.txt` 的 MIT License。
1439
2525
 
2526
+ <a id="source-transport"></a>
2527
+
2528
+ ## 源解析传输
2529
+
2530
+ 源解析传输是 Cesium 专有复合层,包含彩色贡献弧线、灰色烟羽 Billboard、移动烟团、来源/目标节点、路径选择和目标汇聚体。业务接口、城市聚合、污染物字段与时间轴由页面处理,整理好 `target + flows` 后一次调用即可:
2531
+
2532
+ ```js
2533
+ const sources = [
2534
+ { code: 'source-1', name: '来源一', position: [118.1, 24.5], value: 12, percent: 37.5 },
2535
+ { code: 'source-2', name: '来源二', position: [120.6, 27.9], value: 20, percent: 62.5 },
2536
+ ]
2537
+
2538
+ const result = mapCore.upsertSourceTransportLayer({
2539
+ layerId: 'source-analysis',
2540
+ target: {
2541
+ name: '福州市',
2542
+ position: [119.331, 26.0639],
2543
+ label: '福州市 汇聚中心\n累积浓度 32.00 μg/m³',
2544
+ },
2545
+ flows: sources.map(source => ({
2546
+ id: source.code,
2547
+ name: source.name,
2548
+ start: source.position,
2549
+ value: source.value,
2550
+ percent: source.percent,
2551
+ label: source.label,
2552
+ data: source,
2553
+ })),
2554
+ onSelect(flow) {
2555
+ console.log('选中来源', flow)
2556
+ },
2557
+ onHover(flow, screenPosition) {
2558
+ console.log('悬停来源', flow, screenPosition)
2559
+ },
2560
+ })
2561
+
2562
+ if (result.success) {
2563
+ mapCore.flyToSourceTransportLayer({ layerId: 'source-analysis' })
2564
+ }
2565
+ ```
2566
+
2567
+ | 参数 | 是否必填 | 说明 |
2568
+ | --- | --- | --- |
2569
+ | `layerId` | 否 | 默认 `source-transport-default`。 |
2570
+ | `target.position` | 创建时必填 | 目标 `[longitude, latitude]`;可附带 `name/label/data`。 |
2571
+ | `flows` | 创建时必填 | 每项至少传 `start`(兼容 `position`);`end` 默认目标位置。 |
2572
+ | `flow.id/code` | 建议 | 稳定且不重复的来源标识。 |
2573
+ | `flow.value/intensity` | 否 | `intensity` 范围 `0~1`;不传时按本批有效 `value` 做最小最大归一化,同值时为 `0.5`。 |
2574
+ | `flow.percent` | 否 | 百分数值,例如 `37.5` 表示 `37.5%`,参与弧高与烟羽速度计算。 |
2575
+ | `flow.bend/arcHeight/color/lineWidth/label/smoke/data` | 否 | 单来源覆盖。 |
2576
+ | `colors` | 否 | 来源颜色调色板,可由每条 flow 的 `color` 覆盖。 |
2577
+ | `visible/running` | 否 | 初始显隐与动画状态,默认均为 `true`。 |
2578
+ | `path/line/smoke/sourceNode/targetNode/volume/interaction/fog/camera` | 否 | 单层视觉配置,可覆盖 BaseGIS 实例级默认值。 |
2579
+ | `onSelect/onHover` | 否 | 分别接收 `flow/null` 和 `flow/null, screenPosition/null`。 |
2580
+
2581
+ `target.volume: false` 或 `volume: { enabled: false }` 可关闭目标汇聚体;也可通过 `target.volume.data/option/parameters/colorRamp` 传真实体数据。未传数据时会生成示意汇聚云,底层复用 `upsertVolumeLayer`,该云形不代表业务浓度的真实三维分布。
2582
+
2583
+ `updateSourceTransportLayer()` 支持部分更新:`target` 对象会合并,`flows` 数组整体替换。只更新 `target.volume` 时,不会重建来源弧线:
2584
+
2585
+ ```js
2586
+ mapCore.updateSourceTransportLayer({
2587
+ layerId: 'source-analysis',
2588
+ target: { volume: { parameters: { opacity: 0.5, brightness: 1.1 } } },
2589
+ })
2590
+ ```
2591
+
2592
+ 目标体云创建失败时,其余部分仍可能加载成功。除 `success` 外,还应检查 `result.data.degraded`(可能包含 `'targetVolume'`)和 `result.data.state.volumeError`。
2593
+
2594
+ 生命周期方法:`updateSourceTransportLayer`、`showSourceTransportLayer`、`hideSourceTransportLayer`、`playSourceTransportLayer`、`pauseSourceTransportLayer`、`flyToSourceTransportLayer`、`clearSourceTransportLayer`、`getSourceTransportLayerState`、`removeSourceTransportLayer`。`clear` 会一起清掉来源、目标和内部体云;Leaflet 返回 `UNSUPPORTED_CAPABILITY` 且不抛异常,切回 Cesium 后自动恢复托管快照。
2595
+
2596
+ <a id="volume-rendering"></a>
2597
+
1440
2598
  ## 三维体渲染
1441
2599
 
1442
2600
  三维体渲染是 Cesium 专有能力,直接通过 `BaseGIS` 调用。Leaflet 调用时会返回不支持结果,不会抛出破坏页面的异常。
@@ -1446,11 +2604,17 @@ GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后
1446
2604
  ### 基础用法
1447
2605
 
1448
2606
  ```js
2607
+ // 3 个经度 × 2 个纬度 × 2 个高度层,X 最快变化,随后是 Y、Z。
2608
+ const volumeValues = new Uint8Array([
2609
+ 20, 80, 120, 60, 160, 220,
2610
+ 40, 100, 160, 80, 180, 240,
2611
+ ])
2612
+
1449
2613
  const result = mapCore.upsertVolumeLayer({
1450
2614
  // 建议必填:图层唯一 id。再次使用同一个 layerId 调用会覆盖旧体渲染层。
1451
2615
  layerId: 'volume-demo',
1452
2616
 
1453
- // 必填:体数据一维数组,长度通常为 rows * cols * heights。
2617
+ // 必填:0~255 编码的一维数组,长度必须等于 rows * cols * heights。
1454
2618
  data: volumeValues,
1455
2619
 
1456
2620
  // 必填:体数据范围和网格尺寸。
@@ -1461,19 +2625,20 @@ const result = mapCore.upsertVolumeLayer({
1461
2625
  // 纬度范围。
1462
2626
  ymin: 30,
1463
2627
  ymax: 40,
1464
- // 高度范围,单位按业务数据约定。
2628
+ // 高度范围,单位米。
1465
2629
  zmin: 0,
1466
2630
  zmax: 10000,
1467
- // 纬向、经向、高度向网格数量。
1468
- rows: 100,
1469
- cols: 100,
1470
- heights: 30,
2631
+ // 经度 X、纬度 Y、高度 Z 的网格数量,均为不小于 2 的整数。
2632
+ rows: 3,
2633
+ cols: 2,
2634
+ heights: 2,
1471
2635
  },
1472
2636
 
1473
2637
  // 选填:体渲染 shader 参数,可后续单独更新。
1474
2638
  parameters: {
1475
- // 采样阈值。值越小,通常显示范围越少;具体效果和数据归一化有关。
1476
- threshold: 0.3,
2639
+ // 使用 0~255 编码直接定位色带,不再采用旧的 0~15 映射。
2640
+ normalizedColorRamp: 1,
2641
+ opacity: 0.72,
1477
2642
  // 光线步进采样次数。越大越细腻,也越耗性能。
1478
2643
  steps: 100,
1479
2644
  // x/y/z 三个方向裁切位置,默认 -0.5 表示不裁切。
@@ -1482,6 +2647,8 @@ const result = mapCore.upsertVolumeLayer({
1482
2647
  zCut: -0.5,
1483
2648
  },
1484
2649
 
2650
+ colorRamp: ['#2f80ed', '#22c55e', '#facc15', '#ef4444'],
2651
+
1485
2652
  // 选填:初始是否显示。
1486
2653
  visible: true,
1487
2654
  })
@@ -1493,13 +2660,13 @@ if (!result.success) {
1493
2660
 
1494
2661
  ### 更新参数
1495
2662
 
1496
- 更新透明阈值、步进数、裁切面时,不需要重新加载体数据,直接更新参数即可。
2663
+ 更新透明度、密度、步进数或裁切面时,不需要重新加载体数据,直接更新参数即可。
1497
2664
 
1498
2665
  ```js
1499
2666
  mapCore.updateVolumeLayerParameters({
1500
2667
  layerId: 'volume-demo',
1501
2668
  parameters: {
1502
- threshold: 0.5,
2669
+ opacity: 0.5,
1503
2670
  steps: 180,
1504
2671
  zCut: 0.1,
1505
2672
  },
@@ -1508,27 +2675,46 @@ mapCore.updateVolumeLayerParameters({
1508
2675
 
1509
2676
  ### 更新数据
1510
2677
 
1511
- 更新数据或空间范围时,继续使用同一个 `layerId` 调用 `upsertVolumeLayer`。适配层会先移除旧 Primitive,再创建新的体渲染层。
2678
+ 空间范围和网格尺寸不变时,使用 `updateVolumeLayerData()` 更新同一 Primitive 的体纹理:
1512
2679
 
1513
2680
  ```js
1514
- mapCore.upsertVolumeLayer({
2681
+ mapCore.updateVolumeLayerData({
1515
2682
  layerId: 'volume-demo',
1516
- data: nextVolumeValues,
1517
- option: nextVolumeOption,
1518
- parameters: currentParameters,
2683
+ data: Uint8Array.from(volumeValues, (value) => Math.min(255, value + 10)),
1519
2684
  })
1520
2685
  ```
1521
2686
 
2687
+ 新数据长度必须与原网格一致。此方法会恢复显示;如果需要保持隐藏,更新后再调用 `hideVolumeLayer()`。空间范围或网格尺寸改变时,使用同一 `layerId` 重新调用 `upsertVolumeLayer({ data, option, ... })`。新 Primitive 创建并加入场景后才释放旧图层,创建失败时保留旧图层。
2688
+
2689
+ ### 按屏幕位置取值
2690
+
2691
+ `sampleVolumeLayerValue()` 沿屏幕位置的相机射线采样,选取对可见体云贡献最大的点,并对数据做三线性插值。`position` 为相对地图画布左上角的 CSS 像素坐标,不是经纬度:
2692
+
2693
+ ```js
2694
+ const sampleResult = mapCore.sampleVolumeLayerValue({
2695
+ layerId: 'volume-demo',
2696
+ position: { x: 320, y: 180 },
2697
+ steps: 120,
2698
+ // 可选 data:与纹理同长度、同顺序的原始业务值数组。
2699
+ // 不传则返回渲染数据值;命中位置始终由可见体云决定。
2700
+ })
2701
+ if (sampleResult.success) {
2702
+ console.log(sampleResult.data) // { value, lon, lat, height },height 单位米。
2703
+ }
2704
+ ```
2705
+
2706
+ 图层隐藏、未命中或被地表遮挡时返回失败结果。取值采样步数限制为 `32~400`,与渲染步数上限不同。
2707
+
1522
2708
  ### 显隐、定位和移除
1523
2709
 
1524
2710
  ```js
1525
2711
  mapCore.hideVolumeLayer({ layerId: 'volume-demo' })
1526
2712
  mapCore.showVolumeLayer({ layerId: 'volume-demo' })
1527
2713
  mapCore.flyToVolumeLayer({ layerId: 'volume-demo', duration: 0.8 })
1528
- mapCore.removeVolumeLayer({ layerId: 'volume-demo' })
1529
2714
 
1530
2715
  const stateResult = mapCore.getVolumeLayerState({ layerId: 'volume-demo' })
1531
2716
  console.log(stateResult.data)
2717
+ mapCore.removeVolumeLayer({ layerId: 'volume-demo' })
1532
2718
  ```
1533
2719
 
1534
2720
  ### 方法总表
@@ -1536,58 +2722,68 @@ console.log(stateResult.data)
1536
2722
  | 方法 | 参数 | 说明 |
1537
2723
  | --- | --- | --- |
1538
2724
  | `upsertVolumeLayer(payload)` | 体渲染配置 | 创建或更新体渲染层。同 `layerId` 会覆盖旧图层。 |
2725
+ | `updateVolumeLayerData(payload)` | `{ layerId, data }` | 同范围、同网格尺寸时更新体纹理,不重建 Primitive。 |
1539
2726
  | `updateVolumeLayerParameters(payload)` | `{ layerId, parameters }` | 更新体渲染 shader 参数,不重新加载体数据。 |
1540
2727
  | `showVolumeLayer(payload)` | `{ layerId }` | 显示体渲染层。 |
1541
2728
  | `hideVolumeLayer(payload)` | `{ layerId }` | 隐藏体渲染层。 |
1542
2729
  | `removeVolumeLayer(payload)` | `{ layerId }` | 移除体渲染层并释放 Primitive。 |
1543
2730
  | `flyToVolumeLayer(payload)` | `{ layerId, duration }` | 飞到体渲染层经纬度范围。 |
1544
2731
  | `getVolumeLayerState(payload)` | `{ layerId }` | 获取图层状态。 |
2732
+ | `sampleVolumeLayerValue(payload)` | `{ layerId, position, data?, steps? }` | 按屏幕位置取值;可用 `windowPosition`、`values` 作为别名。 |
1545
2733
 
1546
2734
  ### 参数总表
1547
2735
 
1548
2736
  | 参数 | 是否必填 | 说明 |
1549
2737
  | --- | --- | --- |
1550
2738
  | `layerId` / `volumeLayerId` / `id` | 建议必填 | 体渲染图层 id,不传默认 `volume-default`。 |
1551
- | `data` / `value` / `values` | 必填 | 体数据一维数组,通常按层、行、列展开。 |
2739
+ | `data` / `value` / `values` | 必填 | `0~255` 字节编码,长度为 `rows * cols * heights`;索引为 `z * rows * cols + y * rows + x`。业务浮点值需先编码,不会自动归一化。 |
1552
2740
  | `option` | 必填 | 体数据空间范围和网格尺寸。 |
1553
2741
  | `option.xmin / xmax` | 必填 | 经度最小值和最大值。 |
1554
2742
  | `option.ymin / ymax` | 必填 | 纬度最小值和最大值。 |
1555
- | `option.zmin / zmax` | 必填 | 高度最小值和最大值。 |
1556
- | `option.rows` | 必填 | 纬向网格数量。 |
1557
- | `option.cols` | 必填 | 经向网格数量。 |
1558
- | `option.heights` | 必填 | 高度层数量。 |
1559
- | `parameters.threshold` | 选填 | 采样阈值。 |
1560
- | `parameters.steps` | 选填 | 光线步进采样次数。 |
2743
+ | `option.zmin / zmax` | 必填 | 高度最小值和最大值,单位米。 |
2744
+ | `option.rows` | 必填 | X / 经度方向数量,不小于 `2` 的整数。 |
2745
+ | `option.cols` | 必填 | Y / 纬度方向数量,不小于 `2` 的整数。 |
2746
+ | `option.heights` | 必填 | Z / 高度层数量,不小于 `2` 的整数。 |
2747
+ | `parameters.threshold` | 选填 | 默认 `1.2`;仅累积归一化纹理值小于该阈值的样本,不是整体透明度。 |
2748
+ | `parameters.steps` | 选填 | 默认 `1200`;渲染步进数限制在 `1~4000`。 |
2749
+ | `parameters.densityThreshold` | 选填 | 默认 `0.08`,按色带位置过滤低密度样本的起点。 |
2750
+ | `parameters.densitySoftness` | 选填 | 默认 `0.47`,低密度过滤的过渡宽度。 |
2751
+ | `parameters.densityScale` | 选填 | 默认 `0.34`,每步透明度累积强度。 |
2752
+ | `parameters.opacity / brightness` | 选填 | 默认 `0.72 / 1`,整体透明度和颜色亮度倍率。 |
2753
+ | `parameters.normalizedColorRamp` | 选填 | 默认 `0`;设为 `1` 时用 `data / 255` 定位色带。 |
1561
2754
  | `parameters.xCut / yCut / zCut` | 选填 | 三个方向的裁切位置,默认 `-0.5`。 |
1562
- | `colorRamp` / `colors` / `colorKeys` | 选填 | 自定义色带配置。 |
2755
+ | `colorRamp` | 选填 | CSS 颜色数组、RGBA 数组列表或展平的 RGBA 字节数组。 |
1563
2756
  | `geometry` / `dim` | 选填 | 自定义体渲染几何或维度,普通业务通常不需要传。 |
1564
2757
  | `visible` | 选填 | 初始是否显示,默认显示。 |
1565
2758
 
2759
+ <a id="section-rendering"></a>
2760
+
1566
2761
  ## 三维切片 / 剖面渲染
1567
2762
 
1568
2763
  三维切片/剖面渲染也是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它把三维网格数据按 X、Y、Z 三个方向切出剖面,适合气象温度、湿度、风场标量、污染物浓度等三维格点数据查看。
1569
2764
 
1570
- 这里的“切片”指三维数据剖面,不是 `ImageLayerController` 里的大图切片。
2765
+ 这里的“切片”指三维数据剖面,不是 `ImageLayerController` 里的大图切片。当前 Z 轴按 hPa 气压处理,内部将气压换算为高度后放大 10 倍显示,不接受任意米制高度层。
1571
2766
 
1572
2767
  ### 数据格式
1573
2768
 
1574
2769
  ```js
1575
2770
  const sectionData = {
1576
2771
  Bound: [
1577
- 1000, // 0: LayerMax,最高气压层或起始层值。
2772
+ 1000, // 0: LayerMax,起始气压值(hPa),较大气压在下方。
1578
2773
  108.68, // 1: LonMin,经度最小值。
1579
2774
  28.59, // 2: LatMin,纬度最小值。
1580
2775
  10, // 3: LayerNums,层数。
1581
2776
  147, // 4: LonNums,经向格点数。
1582
2777
  198, // 5: LatNums,纬向格点数。
1583
- 900, // 6: DLayer LayerMin 相关值,按数据生成规则提供。
2778
+ 900, // 6: DLayer,气压跨度;LayerMin = LayerMax - DLayer。
1584
2779
  16.74, // 7: DLon,经度跨度。
1585
2780
  16.71, // 8: DLat,纬度跨度。
1586
- 1, // 9: ValueScale,数值缩放。
1587
- 1, // 10: 预留/数据标记。
2781
+ 1, // 9: ValueScale,业务值 = 原始值 / ValueScale。
2782
+ 1, // 10: Stride,每个格点的分量数;标量为 1。
1588
2783
  [1000, 925, 850, 700, 600, 500, 400, 300, 200, 100], // 11: LayerList,气压层列表。
1589
2784
  ],
1590
- // 必填:三维格点值一维数组,长度通常为 LayerNums * LatNums * LonNums。
2785
+ // 长度必须等于 LayerNums * LatNums * LonNums * Stride
2786
+ // 每层从西南角开始,经度最快变化,再纬度,最后气压层。
1591
2787
  DataAry: valueList,
1592
2788
  }
1593
2789
 
@@ -1606,6 +2802,8 @@ const optionData = {
1606
2802
  }
1607
2803
  ```
1608
2804
 
2805
+ `Bound[11]` 可传 `null` 表示等间距气压层;传列表时长度必须等于 `LayerNums`,首项必须等于 `Bound[0]`。`Stride > 1` 时当前实现计算各分量的模长。
2806
+
1609
2807
  ### 加载或更新数据
1610
2808
 
1611
2809
  同一个 `layerId` 重复调用 `upsertSectionLayer` 就是更新数据。适配层会清理旧切片,再加载新数据。
@@ -1629,15 +2827,16 @@ const result = mapCore.upsertSectionLayer({
1629
2827
 
1630
2828
  // 选填:是否显示坐标轴和刻度。
1631
2829
  showAxis: false,
2830
+ // 选填:有 DEM 时避免近地切片被地形裁断,默认 false。
2831
+ ignoreTerrainDepth: true,
1632
2832
  })
1633
2833
 
1634
- if (!result.success) {
2834
+ if (result.success) {
2835
+ const state = result.data.state
2836
+ console.log(state.XRange, state.YRange, state.ZRange)
2837
+ } else {
1635
2838
  console.warn(result.message)
1636
- return
1637
2839
  }
1638
-
1639
- const state = result.data.state
1640
- console.log(state.XRange, state.YRange, state.ZRange)
1641
2840
  ```
1642
2841
 
1643
2842
  ### 渲染 X / Y / Z 切片
@@ -1657,7 +2856,7 @@ mapCore.renderSectionLayer({
1657
2856
  value: 39.9,
1658
2857
  })
1659
2858
 
1660
- // Z 高度/气压方向切片。
2859
+ // Z 气压方向切片,单位 hPa。
1661
2860
  mapCore.renderSectionLayer({
1662
2861
  layerId: 'section-demo',
1663
2862
  sectionType: 2,
@@ -1671,7 +2870,7 @@ mapCore.renderSectionLayer({
1671
2870
  | --- | --- | --- |
1672
2871
  | `0` | 经度方向剖面 | 经度值。 |
1673
2872
  | `1` | 纬度方向剖面 | 纬度值。 |
1674
- | `2` | 高度/气压方向剖面 | 气压层或高度层值。 |
2873
+ | `2` | 气压方向剖面 | 气压值,单位 hPa。 |
1675
2874
  | `3` | 全量剖面 | 可不传 `value`。 |
1676
2875
 
1677
2876
  ### 显隐、移除和定位
@@ -1687,16 +2886,16 @@ mapCore.removeSectionLayer({
1687
2886
  sectionType: 2,
1688
2887
  })
1689
2888
 
1690
- // 移除整个切片图层。
1691
- mapCore.removeSectionLayer({ layerId: 'section-demo' })
1692
-
1693
2889
  // 飞到切片数据范围。
1694
2890
  mapCore.flyToSectionLayer({ layerId: 'section-demo', duration: 0.8 })
2891
+
2892
+ // 移除整个切片图层。
2893
+ mapCore.removeSectionLayer({ layerId: 'section-demo' })
1695
2894
  ```
1696
2895
 
1697
2896
  ### hover 取值
1698
2897
 
1699
- 页面先用 Cesium 拾取得到鼠标所在三维点,再交给 `sampleSectionLayerValue` 计算当前切片上的数值。
2898
+ 页面先用 Cesium 拾取得到鼠标所在三维点,再将经纬度与对应气压交给 `sampleSectionLayerValue`。`hpa` 不是 Cesium 的 `cartographic.height`;应先还原 10 倍高度显示比例,再按剖面使用的气压高度关系换算。
1700
2899
 
1701
2900
  ```js
1702
2901
  const result = mapCore.sampleSectionLayerValue({
@@ -1740,9 +2939,10 @@ if (result.success) {
1740
2939
  | `colorInfo.rgbAry` | 建议必填 | 色标 RGB 数组。 |
1741
2940
  | `boxInfo` | 选填 | 自定义剖面盒子范围。不传时根据 `dataInfo` 自动生成。 |
1742
2941
  | `showAxis` | 选填 | 是否显示坐标轴和刻度。 |
1743
- | `sectionType` | 渲染、显隐、移除单个切片时必填 | `0` 经度,`1` 纬度,`2` 高度/气压,`3` 全量。 |
2942
+ | `ignoreTerrainDepth` | 选填 | 默认 `false`;设为 `true` 时,可见剖面暂时关闭 globe 地形深度检测,不改变切片自身深度写入。 |
2943
+ | `sectionType` | 渲染、显隐、移除单个切片时必填 | `0` 经度,`1` 纬度,`2` 气压,`3` 全量。 |
1744
2944
  | `value` / `param` | `sectionType` 为 `0/1/2` 时必填 | 切片位置值。 |
1745
- | `lon / lat / hpa` | hover 取值必填 | Cesium 拾取得到的经度、纬度、气压高度。 |
2945
+ | `lon / lat / hpa` | hover 取值必填 | 经度、纬度及换算后的气压(hPa),不是米制高度。 |
1746
2946
  | `showZLayer` | hover 取值选填 | Z 切片是否参与 hover 命中,默认参与。 |
1747
2947
 
1748
2948
  <a id="point-large-layer-controller"></a>
@@ -1755,160 +2955,54 @@ Leaflet 模式使用 BaseGIS 内置的 `pixi.js` 和 `leaflet-pixi-overlay` 异
1755
2955
 
1756
2956
  如果业务目标是“地图缩小时按屏幕网格抽稀,只显示代表点”,应使用后文的 `PointDensityController`。
1757
2957
 
1758
- ### Demo 位置
1759
-
1760
- 仓库内已有对应 demo:
1761
-
1762
- - 路由:`/test-page-5`
1763
- - 页面:`src/views/test-page-5/index.vue`
1764
- - 当前 demo 覆盖能力:加载、更新、显隐、高亮、取消高亮、删除点位、清空、销毁、状态读取、视角复位。
1765
-
1766
- demo 源码内部使用:
1767
-
1768
- ```js
1769
- import { BaseGIS } from '@/gis'
1770
- import { PointLargeLayerController } from '@/gis/methods'
1771
- ```
2958
+ ### 基础用法
1772
2959
 
1773
- npm 包外部项目使用:
2960
+ 地图初始化成功后创建控制器,再调用 `load()` 加载点位。以下使用内置圆点,无需先准备图片资源;实际项目可通过 `icon / highlightIcon` 替换普通与高亮图标。
1774
2961
 
1775
2962
  ```js
1776
- import { BaseGIS } from '@3clear/basegis'
1777
2963
  import { PointLargeLayerController } from '@3clear/basegis/methods'
1778
- ```
1779
-
1780
- ### 使用方式一:构造时传入 mapCore
1781
-
1782
- 这是业务页面里最常用的写法。地图初始化完成后创建控制器,再调用 `load` 加载点位。
1783
-
1784
- ```js
1785
- const mapCore = new BaseGIS({
1786
- engineType: 'leaflet',
1787
- containerId: 'map',
1788
- })
1789
-
1790
- const initResult = mapCore.init()
1791
- if (!initResult.success) {
1792
- console.warn(initResult.message)
1793
- }
1794
2964
 
1795
2965
  const pointLayer = new PointLargeLayerController({
1796
- // 必填:BaseGIS 实例。
1797
- mapCore,
1798
- // 建议必填:图层唯一 ID,后续显隐、高亮、删除都基于这个图层。
1799
- layerId: 'station-large',
1800
- // 选填:点位唯一值字段,不传默认使用 id。
1801
- idKey: 'stationCode',
1802
-
1803
- // 点位数据。也可以使用 data/items 字段;推荐统一使用 points。
1804
- points,
1805
-
1806
- // 初始是否显示图层。不传时默认 true。
1807
- visible: true,
1808
-
1809
- // 默认普通状态图标。支持图片 URL、base64、data URL。
1810
- icon: normalPointIcon,
1811
-
1812
- // 默认高亮状态图标。调用 setHighlight(id) 后使用。
1813
- highlightIcon: highlightPointIcon,
1814
-
1815
- // 样式规则读取的字段名。下面 styleRules 里的 gt/default 会基于 value 判断。
1816
- styleField: 'value',
1817
-
1818
- // 点位样式规则。从上到下匹配,命中后使用该规则里的 icon/highlightIcon/style 等配置。
1819
- styleRules: [
1820
- {
1821
- // value < 300 时命中
1822
- lt: 300,
1823
- icon: lowPointIcon,
1824
- highlightIcon: highlightPointIcon,
1825
- },
1826
- {
1827
- // value > 780 时命中
1828
- gt: 780,
1829
- icon: warningPointIcon,
1830
- highlightIcon: highlightPointIcon,
1831
- },
1832
- {
1833
- // 300 <= value < 780
1834
- gte: 300,
1835
- lt: 780,
1836
- icon: normalPointIcon,
1837
- highlightIcon: highlightPointIcon,
1838
- },
1839
- {
1840
- // 兜底规则。没有命中前面规则的点位使用普通图标。
1841
- default: true,
1842
- icon: normalPointIcon,
1843
- highlightIcon: highlightPointIcon,
1844
- },
1845
- ],
1846
-
1847
- // 普通图标宽度,单位像素。
1848
- width: 18,
1849
-
1850
- // 普通图标高度,单位像素。
1851
- height: 18,
1852
-
1853
- // 普通图标缩放比例。
1854
- scale: 1,
1855
-
1856
- // 未传 icon 时,可以使用内置圆点图标。下面这些配置用于控制圆点样式。
1857
- // color: '#19d3a2',
1858
- // outlineColor: '#ffffff',
1859
- // outlineWidth: 2,
1860
-
1861
- // 未传 highlightIcon 时,可以使用内置高亮圆点图标。下面这些配置用于控制高亮圆点样式。
1862
- // highlightColor: '#ffcf33',
1863
- // highlightOutlineColor: '#ffffff',
1864
- // highlightWidth: 20,
1865
- // highlightHeight: 20,
1866
- // highlightScale: 1.18,
1867
-
1868
- // 分帧构建批量大小。点位很多时可以调大或调小,默认 5000。
1869
- // chunkSize: 10000,
1870
-
1871
- // Cesium 专用:禁用深度检测距离。需要点位不被地形或模型遮挡时可使用。
1872
- // disableDepthTestDistance: Number.POSITIVE_INFINITY,
1873
-
1874
- // Cesium 专用:按相机距离缩放图标。
1875
- // 数组含义:[近距离, 近距离缩放, 远距离, 远距离缩放]。
1876
- // scaleByDistance: [50000, 1, 9000000, 0.34],
1877
-
1878
- // 点击点位回调。event.data 是原始点位数据。
1879
- onClick: ({ id, data }) => {`点击点位:${data || id}。`},
1880
-
2966
+ mapCore,
2967
+ layerId: 'station-large',
2968
+ idKey: 'stationCode',
2969
+ width: 18,
2970
+ height: 18,
2971
+ color: '#ffffff', // 白色基础纹理,供分级规则着色。
2972
+ styleField: 'value',
2973
+ // 从上到下匹配,首个命中的规则生效。
2974
+ styleRules: [
2975
+ { gte: 150, style: { color: '#ff4d4f' } },
2976
+ { gte: 75, style: { color: '#faad14' } },
2977
+ { default: true, style: { color: '#19d3a2' } },
2978
+ ],
2979
+ onClick({ id, data }) {
2980
+ console.log('点击点位', id, data)
2981
+ },
1881
2982
  })
1882
2983
 
1883
2984
  await pointLayer.load({
1884
2985
  points: [
1885
- {
1886
- stationCode: 'A001',
1887
- name: '站点 A001',
1888
- longitude: 104,
1889
- latitude: 35,
1890
- value: 86,
1891
- },
2986
+ { stationCode: 'A001', name: '站点 A001', longitude: 104, latitude: 35, value: 86 },
2987
+ { stationCode: 'A002', name: '站点 A002', longitude: 105, latitude: 35, value: 42 },
2988
+ { stationCode: 'A003', name: '站点 A003', longitude: 106, latitude: 35, value: 168 },
1892
2989
  ],
1893
2990
  })
1894
2991
  ```
1895
2992
 
1896
2993
  `styleRules` 常用匹配方式:
1897
2994
 
1898
- | 写法 | 说明 |
1899
- |---------------------------------------------------| --- |
1900
- | `{ field: 'aqi', min: 51, max: 100 }` | 指定字段并按区间匹配。 |
1901
- | `{ gt: 780 }` | 使用 `styleField` 指定的字段做大于判断。 |
1902
- | `{ gte: 51, lt: 101 }` | 使用 `styleField` 指定的字段做区间判断。 |
1903
- | `{ values: ['优', '良'] }` | 命中指定值集合。 |
1904
- | `{ operator: '>', value: 100 }` | 使用操作符匹配,支持 `> / >= / < / <= / == / === / != / !== / in`。 |
1905
- | `{ when:(point, index, record) { return true } }` | 完全自定义匹配函数。 |
1906
- | `{ default: true }` | 兜底规则。 |
1907
-
1908
-
1909
-
2995
+ | 写法 | 说明 |
2996
+ | --- | --- |
2997
+ | `{ field: 'aqi', min: 51, max: 100 }` | 指定字段并按区间匹配。 |
2998
+ | `{ gt: 150 }` | 使用 `styleField` 指定的字段做大于判断。 |
2999
+ | `{ gte: 51, lt: 101 }` | 使用 `styleField` 指定的字段做区间判断。 |
3000
+ | `{ values: ['优', '良'] }` | 命中指定值集合。 |
3001
+ | `{ operator: '>', value: 100 }` | 支持 `> / >= / < / <= / == / === / != / !== / in`。 |
3002
+ | `{ when: (point, index, record) => point.value > 100 }` | 自定义匹配函数。 |
3003
+ | `{ default: true }` | 兜底规则。 |
1910
3004
 
1911
- ### 使用方式:更新数据或样式
3005
+ ### 更新数据或样式
1912
3006
 
1913
3007
  时间轴、实时刷新等场景中,建议复用同一个控制器实例,不要每次刷新都重新 `new PointLargeLayerController()`。
1914
3008
 
@@ -2045,69 +3139,378 @@ pointLayer.getState()
2045
3139
  | `destroy(payload)` | 销毁图层并释放资源。别名:`removeLayer`。 |
2046
3140
  | `getState()` | 读取控制器状态。 |
2047
3141
 
2048
- ### 构造和加载参数
3142
+ ### 构造和加载参数
3143
+
3144
+ | 参数 | 是否必填 | 默认值 | 说明 |
3145
+ | --- | --- | --- | --- |
3146
+ | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。也可以后续通过 `mount(mapCore)` 挂载。 |
3147
+ | `layerId` / `pointLargeLayerId` / `largeLayerId` / `id` | 否 | `point-large-default` | 海量点图层 ID。 |
3148
+ | `visible` | 否 | `true` | 初始是否显示。 |
3149
+ | `idKey` | 否 | `id` | 点位唯一值字段。高亮、删除单点依赖该值。 |
3150
+ | `points` / `data` / `items` | 加载时必填其一 | - | 点位数组。 |
3151
+ | `longitudeKeys` | 否 | `['longitude', 'lon', 'lng', 'x']` | 经度字段候选列表。 |
3152
+ | `latitudeKeys` | 否 | `['latitude', 'lat', 'y']` | 纬度字段候选列表。 |
3153
+ | `heightKeys` | 否 | `['height', 'altitude', 'z']` | 高度字段候选列表。 |
3154
+ | `image` / `icon` / `iconUrl` / `imageUrl` | 否 | - | 普通状态图标。未传时使用内置圆点图标。 |
3155
+ | `highlightImage` / `highlightIcon` / `highlightIconUrl` / `highlightImageUrl` | 否 | - | 高亮状态图标。未传时使用高亮圆点图标。 |
3156
+ | `width` / `height` | 否 | `32` | 普通图标尺寸。 |
3157
+ | `scale` | 否 | `1` | 普通图标缩放比例。 |
3158
+ | `color` / `outlineColor` / `outlineWidth` | 否 | `#1e88e5` / `#fff` / `2` | 未传图标时的内置圆点样式。 |
3159
+ | `highlightColor` / `highlightOutlineColor` / `highlightOutlineWidth` | 否 | `#ffcc00` / `#fff` / `3` | 未传高亮图标时的高亮圆点样式。 |
3160
+ | `highlightWidth` / `highlightHeight` / `highlightScale` | 否 | `36` / `36` / `scale * 1.35` | 高亮图标尺寸和缩放。 |
3161
+ | `styleField` / `valueKey` | 否 | `value` | `styleRules` 默认读取的业务值字段。 |
3162
+ | `styleRules` | 否 | `[]` | 点位分级样式规则。 |
3163
+ | `imageCallback` | 否 | - | 自定义返回点位图标。 |
3164
+ | `styleCallback` | 否 | - | 自定义返回普通状态样式。 |
3165
+ | `highlightStyleCallback` | 否 | - | 自定义返回高亮状态样式。 |
3166
+ | `showTooltip` | 否 | `true` | 是否显示内置悬浮提示。 |
3167
+ | `tooltipFormatter` | 否 | - | 自定义悬浮提示内容。 |
3168
+ | `tooltipOffset` | 否 | `[14, 14]` | 悬浮提示偏移。 |
3169
+ | `tooltipClassName` | 否 | - | 悬浮提示 DOM class。 |
3170
+ | `onClick` / `onHover` / `onHoverIn` / `onHoverOut` | 否 | - | 点位事件回调。 |
3171
+ | `chunkSize` | 否 | `5000` | 分帧构建时每批处理数量。 |
3172
+ | `chunkFrameBudget` | 否 | `0` | 分帧构建每帧预算,单位毫秒。 |
3173
+ | `pickRadius` | 否 | `10` | Leaflet / Pixi 模式下的拾取半径。 |
3174
+ | `keepSize` | 否 | `true` | Cesium 下是否尽量保持屏幕像素尺寸。 |
3175
+ | `clampToGround` | 否 | `false` | Cesium 点位是否贴地。 |
3176
+ | `disableDepthTestDistance` | 否 | - | Cesium 深度检测距离。 |
3177
+ | `pixelOffset` / `eyeOffset` | 否 | - | Cesium 图标偏移。 |
3178
+ | `scaleByDistance` | 否 | - | Cesium 按距离缩放。 |
3179
+ | `translucencyByDistance` | 否 | - | Cesium 按距离透明。 |
3180
+ | `distanceDisplayCondition` | 否 | - | Cesium 按距离显示隐藏。 |
3181
+ | `useParticleContainer` | 否 | `false` | Leaflet 下是否使用 Pixi 粒子容器能力。 |
3182
+ | `pixiOptions` | 否 | - | Leaflet / Pixi 渲染参数。 |
3183
+
3184
+ ### 状态返回
3185
+
3186
+ ```js
3187
+ const state = pointLayer.getState()
3188
+
3189
+ console.log(state)
3190
+ // {
3191
+ // mounted: true,
3192
+ // engineType: 'leaflet',
3193
+ // layerType: 'leaflet-point-large-layer',
3194
+ // visible: true,
3195
+ // layerId: 'station-large',
3196
+ // total: 20000,
3197
+ // renderedCount: 20000,
3198
+ // highlightedId: 'A001',
3199
+ // building: false,
3200
+ // destroyed: false,
3201
+ // payload: {}
3202
+ // }
3203
+ ```
3204
+
3205
+
3206
+ <a id="point-cluster-controller"></a>
3207
+
3208
+ ## PointClusterController 点位聚合
3209
+
3210
+ ### 能力介绍
3211
+
3212
+ 用于点位聚合。地图移动、缩放或数据更新后,会把当前视野内落入同一屏幕网格的邻近点合成一个带数量的聚合圆;放大后,聚合圆会继续拆分为更小的聚合组或独立点。
3213
+
3214
+ Cesium / Leaflet 使用相同的点位解析、屏幕网格和状态字段。页面只创建 `BaseGIS` 与 `PointClusterController`,不需要判断当前引擎。
3215
+
3216
+ 一次聚合过程会:
3217
+
3218
+ 1. 根据当前地图视野筛选可见点位。
3219
+ 2. 把经纬度投影到屏幕坐标,并按 `pixelRange` 划分网格。
3220
+ 3. 网格内点数达到 `minimumClusterSize` 时渲染聚合圆,否则渲染独立点。
3221
+ 4. 在地图移动、缩放或数据更新后重新计算,并通过 `onStateChange` 返回最新统计。
3222
+
3223
+ 聚合只改变当前视野的渲染方式,不修改传入的原始点位数据。它适合站点、设备、告警等密集点位;如果需要显示全部点位或只保留代表点,应分别使用 `PointLargeLayerController` 或 `PointDensityController`。
3224
+
3225
+ 它和另外两种点位能力的区别:
3226
+
3227
+ - `PointLargeLayerController`:尽量显示全部有效点位。
3228
+ - `PointClusterController`:邻近点合成一个带数量的聚合圆,可点击继续展开。
3229
+ - `PointDensityController`:邻近点只保留一个代表点,不显示合计数量。
3230
+
3231
+ ### Demo 位置
3232
+
3233
+ - 路由:`/test-page-29`
3234
+ - 页面:`src/views/test-page-29/index.vue`
3235
+ - 数据量:默认生成 10,000 个点位。
3236
+ - 覆盖能力:Cesium / Leaflet 切换、聚合参数更新、数据更新、聚合点击放大、单点点击、显隐、清空、删除和状态读取。
3237
+
3238
+ ### Vue 页面完整用法
3239
+
3240
+ ```vue
3241
+ <template>
3242
+ <div id="station-cluster-map" class="station-cluster-map"></div>
3243
+ </template>
3244
+
3245
+ <script setup>
3246
+ import { onBeforeUnmount, onMounted } from 'vue'
3247
+ import { BaseGIS } from '@3clear/basegis'
3248
+ import { PointClusterController } from '@3clear/basegis/methods'
3249
+ import '@3clear/basegis/style.css'
3250
+
3251
+ let mapCore = null
3252
+ let stationCluster = null
3253
+
3254
+ const points = [
3255
+ { id: 'A001', name: '站点 A', position: [121.4700, 31.2300] },
3256
+ { id: 'A002', name: '站点 B', position: [121.4705, 31.2304] },
3257
+ { id: 'A003', name: '站点 C', position: [121.4710, 31.2308] },
3258
+ ]
3259
+
3260
+ onMounted(async () => {
3261
+ mapCore = new BaseGIS({
3262
+ engineType: 'cesium',
3263
+ containerId: 'station-cluster-map',
3264
+ config: {
3265
+ view: {
3266
+ initialView: {
3267
+ center: [121.4737, 31.2304],
3268
+ height: 420000,
3269
+ zoom: 8,
3270
+ pitch: -90,
3271
+ },
3272
+ },
3273
+ },
3274
+ })
3275
+
3276
+ const initResult = mapCore.init()
3277
+ if (!initResult.success) {
3278
+ console.warn(initResult.message)
3279
+ return
3280
+ }
3281
+
3282
+ stationCluster = new PointClusterController({
3283
+ mapCore,
3284
+ layerId: 'station-cluster',
3285
+
3286
+ // 聚合网格大小,单位屏幕像素。值越大,点越容易聚在一起。
3287
+ pixelRange: 72,
3288
+
3289
+ // 同一网格至少有 3 个点时才显示聚合圆。
3290
+ minimumClusterSize: 3,
3291
+
3292
+ pointColor: '#36cfc9',
3293
+ pointOutlineColor: '#e6fffb',
3294
+ pointSize: 14,
3295
+ clusterColor: '#1677ff',
3296
+ clusterTextColor: '#ffffff',
3297
+ clusterSize: 42,
3298
+
3299
+ // 默认 true。点击聚合圆后,当前引擎会自动向聚合中心放大。
3300
+ zoomOnClusterClick: true,
3301
+
3302
+ onClick(point) {
3303
+ console.log('点击单点:', point)
3304
+ },
3305
+
3306
+ onClusterClick(cluster) {
3307
+ console.log(`点击了包含 ${cluster.count} 个点的聚合圆`)
3308
+ console.log(cluster.center, cluster.ids, cluster.points)
3309
+ },
3310
+
3311
+ onStateChange(state) {
3312
+ console.log('源点、聚合组、渲染对象:', {
3313
+ sourceCount: state.sourceCount,
3314
+ clusterCount: state.clusterCount,
3315
+ renderedCount: state.renderedCount,
3316
+ })
3317
+ },
3318
+ })
3319
+
3320
+ const loadResult = await stationCluster.load({
3321
+ points,
3322
+ visible: true,
3323
+ })
3324
+ if (!loadResult.success) {
3325
+ console.warn(loadResult.message)
3326
+ }
3327
+ })
3328
+
3329
+ onBeforeUnmount(() => {
3330
+ stationCluster?.destroy()
3331
+ stationCluster = null
3332
+ mapCore?.destroy()
3333
+ mapCore = null
3334
+ })
3335
+ </script>
3336
+
3337
+ <style scoped lang="scss">
3338
+ .station-cluster-map {
3339
+ width: 100%;
3340
+ height: 100vh;
3341
+ }
3342
+ </style>
3343
+ ```
3344
+
3345
+ `load` 也支持直接传数组或 GeoJSON FeatureCollection:
3346
+
3347
+ ```js
3348
+ await stationCluster.load(points)
3349
+ await stationCluster.load(featureCollection)
3350
+ ```
3351
+
3352
+ 点位既可以使用 `position: [经度, 纬度, 高度]`、`coordinates` 或直接坐标数组,也可以使用以下默认字段:
3353
+
3354
+ - 经度:`longitude / lon / lng / x`
3355
+ - 纬度:`latitude / lat / y`
3356
+ - 高度:`height / altitude / z`
3357
+ - 唯一值:默认 `id`,可通过 `idKey` 指定
3358
+
3359
+ ### 更新聚合参数与数据
3360
+
3361
+ ```js
3362
+ // 让点位更容易形成聚合。
3363
+ await stationCluster.setConfig({
3364
+ pixelRange: 96,
3365
+ minimumClusterSize: 2,
3366
+ })
3367
+
3368
+ // 替换数据,保留图层和其他配置。
3369
+ await stationCluster.update({
3370
+ points: nextPoints,
3371
+ })
3372
+ ```
3373
+
3374
+ `clusterRadius` 是 `pixelRange` 的别名。两者同时传入时,以 `pixelRange` 为准。
3375
+
3376
+ ### 切换 Cesium / Leaflet
3377
+
3378
+ 聚合图层由 `BaseGIS` 托管。切换引擎时直接调用 `setEngine()`,无需在页面里重建 Cesium / Leaflet 分支;切换成功后,现有聚合数据和配置会通过托管快照自动恢复。
3379
+
3380
+ ```js
3381
+ const result = await mapCore.setEngine('leaflet')
3382
+ if (!result.success) {
3383
+ console.warn(result.message)
3384
+ }
3385
+ ```
3386
+
3387
+ ### 点击回调
3388
+
3389
+ 单点点击的第一个参数是原始点位数据:
3390
+
3391
+ ```js
3392
+ await stationCluster.setConfig({
3393
+ onClick(point, engineObject, event) {
3394
+ console.log(point.id)
3395
+ },
3396
+ })
3397
+ ```
3398
+
3399
+ 聚合点击的第一个参数结构如下:
3400
+
3401
+ ```js
3402
+ {
3403
+ layerId: 'station-cluster',
3404
+ key: 'cluster:12:8',
3405
+ count: 36,
3406
+ center: [121.48, 31.23, 0],
3407
+ ids: ['A001', 'A002'],
3408
+ points: [/* 原始点位数据 */],
3409
+ }
3410
+ ```
3411
+
3412
+ `engineObject` 和 `event` 是当前引擎的拾取对象与事件,仅在确实需要底层信息时使用。通用业务逻辑应优先只读取第一个参数。
3413
+
3414
+ ### 显隐、清空和销毁
3415
+
3416
+ ```js
3417
+ stationCluster.hide()
3418
+ stationCluster.show()
3419
+ stationCluster.toggle()
3420
+ stationCluster.toggle(true)
3421
+ stationCluster.refreshState()
3422
+
3423
+ // 清空数据但保留图层实例,后续仍可 update/load。
3424
+ stationCluster.clear()
3425
+
3426
+ // 删除图层并释放监听与渲染资源。
3427
+ stationCluster.destroy()
3428
+ ```
3429
+
3430
+ | 方法 | 说明 |
3431
+ | --- | --- |
3432
+ | `mount(mapCore)` | 挂载 `BaseGIS`。构造时已传 `mapCore/baseGIS` 时无需重复调用。 |
3433
+ | `load(payload, options)` | 创建或覆盖聚合点数据,支持数组、FeatureCollection 或 `{ points }`。 |
3434
+ | `upsert(payload, options)` | `load` 的别名。 |
3435
+ | `update(payload)` | 合并现有配置,更新数据、样式或聚合参数。 |
3436
+ | `setConfig(payload)` | `update` 的别名,适合只表达参数更新。 |
3437
+ | `show()` / `hide()` / `toggle()` | 控制图层显隐。 |
3438
+ | `clear()` | 清空点位,保留图层实例。 |
3439
+ | `refreshState()` | 从当前引擎重新读取状态。 |
3440
+ | `destroy()` / `removeLayer()` | 删除图层并释放资源。 |
3441
+ | `getState()` | 获取控制器与引擎侧聚合状态。 |
3442
+
3443
+ ### 参数总表
2049
3444
 
2050
3445
  | 参数 | 是否必填 | 默认值 | 说明 |
2051
3446
  | --- | --- | --- | --- |
2052
- | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。也可以后续通过 `mount(mapCore)` 挂载。 |
2053
- | `layerId` / `pointLargeLayerId` / `largeLayerId` / `id` | 否 | `point-large-default` | 海量点图层 ID。 |
3447
+ | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。 |
3448
+ | `layerId` / `pointClusterLayerId` / `clusterLayerId` / `id` | 否 | `point-cluster-default` | 聚合图层 ID。 |
3449
+ | `points` / `data` / `items` | 首次加载必填其一 | - | 点位数组或 GeoJSON FeatureCollection。 |
3450
+ | `idKey` | 否 | `id` | 点位唯一值字段。 |
3451
+ | `longitudeKeys` / `latitudeKeys` / `heightKeys` | 否 | 内置常用字段 | 自定义坐标字段候选名。 |
2054
3452
  | `visible` | 否 | `true` | 初始是否显示。 |
2055
- | `idKey` | 否 | `id` | 点位唯一值字段。高亮、删除单点依赖该值。 |
2056
- | `points` / `data` / `items` | 加载时必填其一 | - | 点位数组。 |
2057
- | `longitudeKeys` | 否 | `['longitude', 'lon', 'lng', 'x']` | 经度字段候选列表。 |
2058
- | `latitudeKeys` | 否 | `['latitude', 'lat', 'y']` | 纬度字段候选列表。 |
2059
- | `heightKeys` | 否 | `['height', 'altitude', 'z']` | 高度字段候选列表。 |
2060
- | `image` / `icon` / `iconUrl` / `imageUrl` | | - | 普通状态图标。未传时使用内置圆点图标。 |
2061
- | `highlightImage` / `highlightIcon` / `highlightIconUrl` / `highlightImageUrl` | 否 | - | 高亮状态图标。未传时使用高亮圆点图标。 |
2062
- | `width` / `height` | 否 | `32` | 普通图标尺寸。 |
2063
- | `scale` | 否 | `1` | 普通图标缩放比例。 |
2064
- | `color` / `outlineColor` / `outlineWidth` | 否 | `#1e88e5` / `#fff` / `2` | 未传图标时的内置圆点样式。 |
2065
- | `highlightColor` / `highlightOutlineColor` / `highlightOutlineWidth` | 否 | `#ffcc00` / `#fff` / `3` | 未传高亮图标时的高亮圆点样式。 |
2066
- | `highlightWidth` / `highlightHeight` / `highlightScale` | 否 | `36` / `36` / `scale * 1.35` | 高亮图标尺寸和缩放。 |
2067
- | `styleField` / `valueKey` | 否 | `value` | `styleRules` 默认读取的业务值字段。 |
2068
- | `styleRules` | 否 | `[]` | 点位分级样式规则。 |
2069
- | `imageCallback` | 否 | - | 自定义返回点位图标。 |
2070
- | `styleCallback` | 否 | - | 自定义返回普通状态样式。 |
2071
- | `highlightStyleCallback` | 否 | - | 自定义返回高亮状态样式。 |
2072
- | `showTooltip` | 否 | `true` | 是否显示内置悬浮提示。 |
2073
- | `tooltipFormatter` | 否 | - | 自定义悬浮提示内容。 |
2074
- | `tooltipOffset` | 否 | `[14, 14]` | 悬浮提示偏移。 |
2075
- | `tooltipClassName` | 否 | - | 悬浮提示 DOM class。 |
2076
- | `onClick` / `onHover` / `onHoverIn` / `onHoverOut` | | - | 点位事件回调。 |
2077
- | `chunkSize` | 否 | `5000` | 分帧构建时每批处理数量。 |
2078
- | `chunkFrameBudget` | 否 | `0` | 分帧构建每帧预算,单位毫秒。 |
2079
- | `pickRadius` | 否 | `10` | Leaflet / Pixi 模式下的拾取半径。 |
2080
- | `keepSize` | 否 | `true` | Cesium 下是否尽量保持屏幕像素尺寸。 |
2081
- | `clampToGround` | 否 | `false` | Cesium 点位是否贴地。 |
2082
- | `disableDepthTestDistance` | 否 | - | Cesium 深度检测距离。 |
2083
- | `pixelOffset` / `eyeOffset` | 否 | - | Cesium 图标偏移。 |
2084
- | `scaleByDistance` | 否 | - | Cesium 按距离缩放。 |
2085
- | `translucencyByDistance` | 否 | - | Cesium 按距离透明。 |
2086
- | `distanceDisplayCondition` | 否 | - | Cesium 按距离显示隐藏。 |
2087
- | `useParticleContainer` | 否 | `false` | Leaflet 下是否使用 Pixi 粒子容器能力。 |
2088
- | `pixiOptions` | 否 | - | Leaflet / Pixi 渲染参数。 |
3453
+ | `pixelRange` / `clusterRadius` | 否 | `64` | 屏幕聚合网格大小,单位像素。 |
3454
+ | `minimumClusterSize` | | `2` | 形成聚合圆所需的最少点数,最小为 2。 |
3455
+ | `pointSize` | 否 | Cesium `16` / Leaflet `14` | 未传图片时的圆点尺寸。 |
3456
+ | `pointWidth` / `pointHeight` | 否 | `pointSize` | 单点图片显示尺寸。 |
3457
+ | `pointColor` | 否 | `#3aa7ff` | 默认单点填充色。 |
3458
+ | `pointOutlineColor` / `pointOutlineWidth` | | 白色 / `2` | 默认单点描边。 |
3459
+ | `image` / `pointImage` / `icon` / `iconUrl` | 否 | - | 自定义单点图片。 |
3460
+ | `imageCallback` | 否 | - | 按点位返回图片地址或图标配置。 |
3461
+ | `clusterSize` | 否 | `42` | 聚合圆基础尺寸;数量增大时会轻微放大。 |
3462
+ | `clusterColor` | 否 | `#1677ff` | 聚合圆背景色。 |
3463
+ | `clusterTextColor` | 否 | `#ffffff` | 聚合数量文字颜色。 |
3464
+ | `clusterOutlineColor` / `clusterOutlineWidth` | 否 | 白色 / `3` | 聚合圆描边。 |
3465
+ | `clusterImageCallback` | 否 | - | 默认图标由双引擎共用 Canvas 生成;可返回自定义图片地址、Canvas 或图标配置。 |
3466
+ | `zoomOnClusterClick` | 否 | `true` | 点击聚合圆后是否自动放大。 |
3467
+ | `throttleTime` | 否 | `100` | 地图变化后重新聚合的节流时间,毫秒。 |
3468
+ | `onClick` | 否 | - | 单点点击回调。 |
3469
+ | `onClusterClick` | 否 | - | 聚合点击回调。 |
3470
+ | `onStateChange` | 否 | - | 聚合状态变化回调。 |
3471
+ | `viewportBuffer` | 否 | `80` | Cesium 专用:屏幕视野缓冲像素。 |
3472
+ | `cullByGlobe` | 否 | `true` | Cesium 专用:剔除地球背面点。 |
3473
+ | `clampToGround` | 否 | `false` | Cesium 专用:单点是否贴地。 |
3474
+ | `clusterZoomFactor` | | `0.45` | Cesium 专用:每次点击后的目标高度比例。 |
3475
+ | `clusterZoomStep` | 否 | `2` | Leaflet 专用:每次点击增加的缩放级别。 |
3476
+ | `maxClusterZoom` | 否 | `Infinity` | Leaflet 专用:聚合点击最大缩放级别。 |
3477
+ | `zIndexOffset` | 否 | `0` | Leaflet 专用:Marker 层级偏移。 |
2089
3478
 
2090
3479
  ### 状态返回
2091
3480
 
2092
3481
  ```js
2093
- const state = pointLayer.getState()
3482
+ const state = stationCluster.getState()
2094
3483
 
2095
3484
  console.log(state)
2096
3485
  // {
2097
- // mounted: true,
2098
3486
  // engineType: 'leaflet',
2099
- // layerType: 'leaflet-point-large-layer',
3487
+ // layerType: 'leaflet-point-cluster-layer',
3488
+ // sourceCount: 10000,
3489
+ // inViewCount: 9800,
3490
+ // outOfViewCount: 200,
3491
+ // clusterCount: 185,
3492
+ // clusteredPointCount: 9600,
3493
+ // visiblePointCount: 200,
3494
+ // renderedCount: 385,
3495
+ // pixelRange: 72,
3496
+ // minimumClusterSize: 3,
2100
3497
  // visible: true,
2101
- // layerId: 'station-large',
2102
- // total: 20000,
2103
- // renderedCount: 20000,
2104
- // highlightedId: 'A001',
2105
- // building: false,
2106
3498
  // destroyed: false,
2107
- // payload: {}
2108
3499
  // }
2109
3500
  ```
2110
3501
 
3502
+ 其中 `renderedCount = clusterCount + visiblePointCount`;被聚合的原始点数量单独记录在 `clusteredPointCount`,不会和聚合圆数量混淆。
3503
+
3504
+ 底层统一方法也可直接通过 `BaseGIS` 调用:
3505
+
3506
+ ```js
3507
+ mapCore.upsertPointClusterLayer(payload)
3508
+ mapCore.showPointClusterLayer({ layerId: 'station-cluster' })
3509
+ mapCore.hidePointClusterLayer({ layerId: 'station-cluster' })
3510
+ mapCore.clearPointClusterLayer({ layerId: 'station-cluster' })
3511
+ mapCore.getPointClusterLayerState({ layerId: 'station-cluster' })
3512
+ mapCore.removePointClusterLayer({ layerId: 'station-cluster' })
3513
+ ```
2111
3514
 
2112
3515
  <a id="point-density-controller"></a>
2113
3516
 
@@ -2121,9 +3524,9 @@ console.log(state)
2121
3524
 
2122
3525
  仓库内已有多个点位抽稀 demo:
2123
3526
 
2124
- - `/test-page-6`:站点 ICON 抽稀,使用 `imageCallback` 按数据绘制站点图标。
2125
3527
  - `/test-page-8`:空气质量站点抽稀,使用固定 `image` 图标。
2126
3528
  - `/test-page-13`:空气质量业务页里的城市点位抽稀。
3529
+ - `/test-page-41`:`imageCallback` 同步返回 Canvas 图标和 `pulse` 告警扩散参数。
2127
3530
 
2128
3531
  ### 基础用法
2129
3532
 
@@ -2149,12 +3552,31 @@ const density = new PointDensityController({
2149
3552
  // 同一网格内保留哪个点。字段值越大越优先显示。
2150
3553
  priorityKey: 'level',
2151
3554
 
2152
- // 默认图标。
2153
- image: '/icons/station.png',
2154
-
2155
- // 图标尺寸。
2156
- width: 28,
2157
- height: 28,
3555
+ // 直接复用 BaseGIS 内置 Canvas 图标。
3556
+ imageCallback(point) {
3557
+ const icon = mapCore.createMarkerIcon({
3558
+ type: 'image-value-label',
3559
+ image: factoryImage,
3560
+ imageSize: [24, 24],
3561
+ showValue: false,
3562
+ showLabel: false,
3563
+ }, point)
3564
+
3565
+ return {
3566
+ ...icon,
3567
+ iconAnchor: 'center',
3568
+ pulse: point.status === 'alarm'
3569
+ ? {
3570
+ color: '#ef4444',
3571
+ size: 72,
3572
+ duration: 1.6,
3573
+ ringCount: 2,
3574
+ ringWidth: 2,
3575
+ ringOpacity: 0.75,
3576
+ }
3577
+ : false,
3578
+ }
3579
+ },
2158
3580
 
2159
3581
  // 点击点位回调。第一个参数是原始点位数据。
2160
3582
  onClick(point) {
@@ -2169,122 +3591,13 @@ const density = new PointDensityController({
2169
3591
 
2170
3592
  await density.load({
2171
3593
  points: [
2172
- { staNum: 'A001', longitude: 104, latitude: 35, level: 5 },
2173
- { staNum: 'A002', longitude: 104.01, latitude: 35.01, level: 3 },
3594
+ { staNum: 'A001', name: '站点一', longitude: 104, latitude: 35, level: 5, value: 22, status: 'normal' },
3595
+ { staNum: 'A002', name: '站点二', longitude: 104.01, latitude: 35.01, level: 3, value: 68, status: 'alarm' },
2174
3596
  ],
2175
3597
  })
2176
3598
  ```
2177
3599
 
2178
- ### 完整配置模板
2179
-
2180
- 下面的配置接近 `test-page-6` 的写法,字段已加注释。业务中按需保留即可。
2181
-
2182
- ```js
2183
- const density = new PointDensityController({
2184
- // BaseGIS 实例。控制器只通过 BaseGIS 调用当前引擎能力,不直接依赖 Cesium / Leaflet。
2185
- mapCore,
2186
-
2187
- // 图层唯一标识。后续 update/show/hide/clear/destroy 都按该 id 定位图层。
2188
- layerId: 'station-density',
2189
-
2190
- // 点位唯一值字段。默认 id;如果点位唯一值在 properties 中,也会尝试读取 properties[idKey]。
2191
- idKey: 'staNum',
2192
-
2193
- // 初始是否显示图层。不传默认 true。
2194
- visible: true,
2195
-
2196
- // 原始点位数据。也可以不在构造时传,后续 density.load({ points }) 传入。
2197
- points: stationList,
2198
-
2199
- // 经度字段候选列表。默认已支持 longitude/lon/lng/x。
2200
- longitudeKeys: ['longitude', 'lon', 'lng', 'x'],
2201
-
2202
- // 纬度字段候选列表。默认已支持 latitude/lat/y。
2203
- latitudeKeys: ['latitude', 'lat', 'y'],
2204
-
2205
- // 高度字段候选列表。默认已支持 height/altitude/z。
2206
- heightKeys: ['height', 'altitude', 'z'],
2207
-
2208
- // 是否开启抽稀。true 时会按屏幕网格去重;false 时只做视野过滤,不做网格碰撞去重。
2209
- enableThinning: true,
2210
-
2211
- // 抽稀网格大小,单位屏幕像素。值越大,保留点越少;值越小,保留点越多。
2212
- gridSize: 150,
2213
-
2214
- // gridSize 的别名。如果同时传 gridSize 和 pixelRange,以 gridSize 为准。
2215
- // pixelRange: 150,
2216
-
2217
- // 最多显示点数量。超过后会按优先级截断。不传默认 Infinity。
2218
- maxCount: 500,
2219
-
2220
- // 同一网格内点位优先级字段。字段值越大越优先保留。
2221
- priorityKey: 'level',
2222
-
2223
- // 自定义优先级函数。返回值越大越优先保留;传了它后优先级逻辑可完全由业务决定。
2224
- // priorityCallback(point, markerOrBillboard, index) {
2225
- // return point.level * 100 + point.value
2226
- // },
2227
-
2228
- // 默认图标地址。支持图片 URL、base64、data URL。
2229
- image: '/icons/station.png',
2230
-
2231
- // 图标地址别名,和 image 作用一致。
2232
- // icon: '/icons/station.png',
2233
- // iconUrl: '/icons/station.png',
2234
-
2235
- // 按点位动态生成图标。适合空气质量、告警等级等需要每个点图标不同的场景。
2236
- // 返回值可以是图片地址、base64、data URL。
2237
- // imageCallback(point, index) {
2238
- // return createStationIcon(point)
2239
- // },
2240
-
2241
- // 图标宽度,单位像素。
2242
- width: 28,
2243
-
2244
- // 图标高度,单位像素。
2245
- height: 28,
2246
-
2247
- // 地图移动、缩放后重新计算抽稀的节流时间,单位毫秒。
2248
- throttleTime: 120,
2249
-
2250
- // Cesium 专用:视野过滤缓冲,单位屏幕像素。
2251
- // 值越大,视野边缘附近的点越不容易在移动时频繁出现/消失。
2252
- viewportBuffer: 100,
2253
-
2254
- // Cesium 专用:3D 场景下是否剔除地球背面的点。
2255
- cullByGlobe: true,
2256
-
2257
- // Cesium 专用:Billboard 是否贴地。
2258
- clampToGround: false,
2259
-
2260
- // Cesium 专用:Billboard 缩放比例。
2261
- scale: 1,
2262
-
2263
- // Cesium 专用:禁用深度检测距离。
2264
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
2265
-
2266
- // Cesium 专用:平滑更新,减少刷新抽稀结果时的突兀感。
2267
- smoothUpdate: true,
2268
- smoothUpdateFrames: 2,
2269
-
2270
- // Leaflet 专用:视野 bounds 扩展比例。
2271
- // 例如 0.1 表示在当前视野基础上向外扩展 10% 后再过滤点位。
2272
- boundsBufferRatio: 0,
2273
-
2274
- // Leaflet 专用:Marker 层级偏移。
2275
- zIndexOffset: 0,
2276
-
2277
- // 点位点击回调。第一个参数是原始点位数据。
2278
- onClick(point) {
2279
- console.log(point)
2280
- },
2281
-
2282
- // 状态变化回调。每次抽稀刷新、显隐、清空后会尽量触发。
2283
- onStateChange(state) {
2284
- console.log(state.visibleCount, state.hiddenCount)
2285
- },
2286
- })
2287
- ```
3600
+ 上例中的 `factoryImage` 是创建抽稀层前已加载完成的 `Image`。`pulse` 使用与普通 Marker 相同的参数,只为抽稀后实际保留的点创建;点被替换、移出视野、隐藏、清空或删除时,扩散圈会随图标一起回收。
2288
3601
 
2289
3602
  ### 点位数据格式
2290
3603
 
@@ -2430,7 +3743,7 @@ density.destroy()
2430
3743
  | `priorityKey` | 否 | - | 同一网格内点位优先级字段,值越大越优先显示。 |
2431
3744
  | `priorityCallback` | 否 | - | 自定义优先级函数,返回值越大越优先显示。 |
2432
3745
  | `image` / `icon` / `iconUrl` | 否 | - | 默认图标地址。 |
2433
- | `imageCallback` | 否 | - | 图标生成函数,适合每个点图标不同的场景。 |
3746
+ | `imageCallback` | 否 | - | 图标生成函数;配置对象可附带 `pulse` 告警扩散参数。 |
2434
3747
  | `width` | 否 | `32` | 图标宽度,单位像素。 |
2435
3748
  | `height` | 否 | `32` | 图标高度,单位像素。 |
2436
3749
  | `throttleTime` | 否 | `120` | 地图移动、缩放后刷新抽稀的节流时间,单位毫秒。 |
@@ -2470,11 +3783,35 @@ console.log(state)
2470
3783
  // }
2471
3784
  ```
2472
3785
 
3786
+ <a id="contour-guide"></a>
3787
+
3788
+ ## 等值线与中心标注能力选择
3789
+
3790
+ 已有线坐标或中心坐标时用 `ContourLayerController`,输入栅格时用 `RasterContourController`。线值标签与中心标注均使用通用 Contour 渲染配置。
3791
+
3792
+ | 手里的数据 / 目标 | 使用入口 | 负责内容 |
3793
+ | --- | --- | --- |
3794
+ | 已经计算好的等值线,可选已有中心点 | [`ContourLayerController`](#contour-layer-controller) | 渲染线、线值标签和传入的 `centers`,并管理统一生命周期。 |
3795
+ | GeoTIFF、灰度图或数值网格 | [`RasterContourController`](#raster-contour-controller) | 读取栅格、追踪等值线;显式开启后可从网格检测局部 H/L 中心。 |
3796
+
3797
+ 两条数据链路:
3798
+
3799
+ ```text
3800
+ 已有 isolines + 可选 centers ──> ContourLayerController ──> 线 + 标签 + 中心
3801
+ GeoTIFF / 灰度图 / 数值网格 ──> RasterContourController ──> ContourLayerController ──> 地图
3802
+ ```
3803
+
3804
+ 特别注意:
3805
+
3806
+ - `ContourLayerController` 只渲染传入的 `centers`,不会从已有线坐标反推极值中心。
3807
+ - `RasterContourController.centerDetection` 默认关闭;只有气压等确实需要 H/L 语义的数值网格才应显式开启。
3808
+ - 两个控制器都不转换业务单位或 Windy 原始格式;传入值就是最终计算和展示值。
3809
+
2473
3810
  <a id="contour-layer-controller"></a>
2474
3811
 
2475
- ## ContourLayerController
3812
+ ## ContourLayerController:渲染已有等值线与中心标注
2476
3813
 
2477
- 用于等值线图层,支持加载、更新、显隐、清空和销毁。
3814
+ 负责渲染已经计算好的线和业务传入的中心坐标,不读取栅格,也不执行插值或等值线追踪。Cesium / Leaflet 共用同一套数据、样式和生命周期 API。
2478
3815
 
2479
3816
  ```js
2480
3817
  import { ContourLayerController } from '@3clear/basegis/methods'
@@ -2483,6 +3820,10 @@ const contour = new ContourLayerController({
2483
3820
  mapCore,
2484
3821
  layerId: 'temperature-contour',
2485
3822
  visible: true,
3823
+ color: '#ff4d4f',
3824
+ width: 2,
3825
+ showLabel: true,
3826
+ labelFormatter: (item) => `${item.value}℃`,
2486
3827
  })
2487
3828
 
2488
3829
  await contour.load({
@@ -2490,6 +3831,7 @@ await contour.load({
2490
3831
  {
2491
3832
  value: 20,
2492
3833
  lines: [
3834
+ // 默认坐标顺序是 [纬度, 经度]。
2493
3835
  [
2494
3836
  [30, 104],
2495
3837
  [31, 105],
@@ -2498,18 +3840,11 @@ await contour.load({
2498
3840
  ],
2499
3841
  },
2500
3842
  ],
2501
- style: {
2502
- color: '#ff4d4f',
2503
- width: 2,
2504
- },
2505
- showLabel: true,
2506
- labelFormatter: (item) => String(item.value),
2507
3843
  })
2508
3844
 
2509
3845
  await contour.update({
2510
- style: {
2511
- color: '#1677ff',
2512
- },
3846
+ color: '#1677ff',
3847
+ width: 1.5,
2513
3848
  })
2514
3849
 
2515
3850
  contour.hide()
@@ -2518,13 +3853,63 @@ contour.clear()
2518
3853
  contour.destroy()
2519
3854
  ```
2520
3855
 
2521
- 数据别名支持 `contours / isolines / isoline / lines / data / items`。开启 `showLabel` 后,Cesium Leaflet 都会根据当前屏幕范围重新选择可见线段上的位置。Leaflet 传入经度跨度达到 360° 的 `worldCopyArea` 后,还会按当前视野动态更新等值线路径和标签的世界副本;这个过程只重建可见路径,不重新解析栅格或追踪等值线。`RasterContourController` 会自动把栅格范围作为 `worldCopyArea` 传入。
3856
+ 样式参数必须放在配置顶层,或通过 `styleCallback` 按线返回;`style: { color, width }` 不会生效。
3857
+
3858
+ ### 输入数据
3859
+
3860
+ 推荐使用 `{ value, lines }` 结构:
3861
+
3862
+ ```js
3863
+ const contours = [
3864
+ {
3865
+ value: 20,
3866
+ label: '20℃',
3867
+ lines: [
3868
+ // 一条等值线;默认每个坐标是 [纬度, 经度]。
3869
+ [[30, 104], [31, 105], [32, 106]],
3870
+ ],
3871
+ },
3872
+ ]
3873
+ ```
3874
+
3875
+ 还支持:
3876
+
3877
+ - `{ value, coordinates: [[lat, lon], ...] }`
3878
+ - `{ value, points: [lat, lon, lat, lon, ...] }`
3879
+ - GeoJSON `LineString` / `MultiLineString` Feature;GeoJSON 坐标按标准 `[经度, 纬度]` 解析
3880
+ - 原始线数组,以及顶层字段别名 `contours / isolines / isoline / lines / data / items`
3881
+
3882
+ 传 GeoJSON FeatureCollection 时,请传 `featureCollection.features` 或 `{ contours: featureCollection.features }`。
3883
+
3884
+ <a id="contour-layer-parameters"></a>
3885
+
3886
+ ### 核心参数
3887
+
3888
+ | 参数 | 类型 | 默认值 | 说明 |
3889
+ | --- | --- | --- | --- |
3890
+ | `mapCore/baseGIS` | `BaseGIS` | - | 当前 `BaseGIS` 实例;构造时不传则后续调用 `mount(mapCore)`。 |
3891
+ | `layerId/contourLayerId` | `string` | `contour-default` | 图层唯一 ID。 |
3892
+ | `coordinateOrder` | `latlng/lnglat` | `latlng` | `lines/coordinates` 的嵌套坐标顺序。 |
3893
+ | `flatOrder` | `latlng/lnglat` | `latlng` | `points` 平铺数组的坐标顺序。 |
3894
+ | `color` | `string` | `rgba(255,255,255,0.82)` | 默认线色。 |
3895
+ | `width/weight` | `number` | `1.15` | 线宽;两种写法用于兼容双引擎。 |
3896
+ | `opacity` | `number` | `1` | 线透明度。 |
3897
+ | `styleCallback(rawItem, line, lineIndex, record)` | `Function` | - | 按原始记录和线段返回动态样式。 |
3898
+ | `splitWrappedLine` | `boolean` | `true` | 是否拆分跨日期变更线的异常跳线。 |
3899
+ | `worldCopyOffsets` | `number[]` | Cesium `[0]`;Leaflet `[-360,0,360]` | 世界副本经度偏移。 |
3900
+ | `visible` | `boolean` | `true` | 初始是否显示。 |
3901
+
3902
+ Cesium 专用参数包括 `height / clampToGround / granularity / lineThinStep`;Leaflet 专用参数包括 `smoothFactor / lineCap / lineJoin / interactive / pane`。
3903
+
3904
+ ### 双引擎渲染说明
3905
+
3906
+ 开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的标签位置。Leaflet 传入经度跨度达到 360° 的 `worldCopyArea` 后,还会按当前视野动态更新等值线路径和标签的世界副本;这个过程只重建可见路径,不重新解析栅格或追踪等值线。`RasterContourController` 会自动把栅格范围作为 `worldCopyArea` 传入。
2522
3907
 
2523
3908
  Cesium 在默认椭球地表上使用 `PolylineCollection` 批量落图;真正启用 DEM 地形后才使用 `GroundPolylinePrimitive`。新批次就绪后再替换旧批次,不为每条线创建独立 Entity。标签定位可通过 `labelLineThinStep` 抽样投影点,不会改变实际绘制的等值线。
2524
3909
 
2525
3910
  Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做屏幕空间简化:缩小时清理挤在同一像素内的折点,放大后自动保留曲线细节,避免全局 `preferCanvas` 放大细线锯齿。线位于 `dt-contour-pane`(层级 `450`),稳定显示在图片图层上方,并保持在 `markerPane` 数值标签下方;显式传入 `pane` 时仍以业务配置为准。
2526
3911
 
2527
- 屏幕标注参数:
3912
+ ### 通用线值标签参数
2528
3913
 
2529
3914
  | 参数 | 类型 | 默认值 | 说明 |
2530
3915
  | --- | --- | --- | --- |
@@ -2546,13 +3931,28 @@ Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做
2546
3931
 
2547
3932
  `getState()` 的 `labelCount` 是当前视野实际显示的标签数量。Cesium 状态额外包含 `lineRenderer/lineBuildElapsed/labelLayoutElapsed/primitiveReadyElapsed`,用于区分线构建、标签排版和异步就绪耗时。`onStateChange(state)` 会在视野重排后回传新状态。页面存在侧栏等遮挡时,可通过 `labelViewportPadding` 排除对应区域。
2548
3933
 
3934
+ ### 生命周期与状态
3935
+
3936
+ | 方法 | 说明 |
3937
+ | --- | --- |
3938
+ | `mount(mapCore)` | 后挂载 `BaseGIS`;构造时已传入则无需调用。 |
3939
+ | `load(payload, options)` / `upsert(...)` | 首次创建或覆盖图层,必须包含等值线数据或 `centers`。 |
3940
+ | `update(payload)` / `setConfig(...)` | 合并数据或渲染参数并更新已经加载的图层。 |
3941
+ | `show()` / `hide()` / `toggle(visible)` | 控制图层显隐。 |
3942
+ | `clear()` | 清空线、线值标签和中心标注,但保留图层实例,可再次加载。 |
3943
+ | `refreshState()` | 从当前引擎重新读取状态。 |
3944
+ | `getState()` | 返回普通状态对象,包括 `levelCount / lineCount / centerCount / lineLabelCount / centerLabelCount / labelCount / visible`。 |
3945
+ | `destroy()` / `removeLayer()` | 删除图层、释放资源,并移除 `BaseGIS` 托管快照。 |
3946
+
3947
+ 推荐顺序:`BaseGIS.init()` 成功后创建控制器并 `load()`;页面卸载时先 `contour.destroy()`,再 `mapCore.destroy()`。
3948
+
2549
3949
  <a id="raster-contour-controller"></a>
2550
3950
 
2551
- ## RasterContourController
3951
+ ## RasterContourController:从栅格生成等值线
2552
3952
 
2553
- 用于从 GeoTIFF、灰度图或数值网格直接计算并绘制等值线。内部统一使用 `d3-contour` 追踪等值线,输出仍交给 `ContourLayerController`,所以页面不需要区分 Cesium 和 Leaflet;调用 `BaseGIS.setEngine()` 时,已经生成的等值线会随托管图层自动恢复。
3953
+ 负责完整的“读取数据源 标准化数值网格 → 追踪等值线 → 双引擎渲染”链路。内部使用 `d3-contour` 生成线,再交给 `ContourLayerController` 渲染;页面不需要区分 Cesium 和 Leaflet。调用 `BaseGIS.setEngine()` 时,已经生成的线会随托管图层自动恢复。
2554
3954
 
2555
- ### 1. GeoTIFF 生成等值线
3955
+ ### GeoTIFF
2556
3956
 
2557
3957
  ```js
2558
3958
  import { RasterContourController } from '@3clear/basegis/methods'
@@ -2598,7 +3998,7 @@ await rasterContour.update({ interval: 2 })
2598
3998
 
2599
3999
  GeoTIFF 依赖宿主页面提供 `window.GeoTIFF`。控制器会读取波段、尺寸、坐标系和范围;当前原生支持 EPSG:4326、EPSG:3857。其他投影可先转换为 WGS84,或传入 WGS84 `area` 覆盖原始范围。
2600
4000
 
2601
- ### 2. 灰度图生成等值线
4001
+ ### 灰度图
2602
4002
 
2603
4003
  ```js
2604
4004
  await rasterContour.loadGrayImage({
@@ -2622,7 +4022,9 @@ await rasterContour.loadGrayImage({
2622
4022
 
2623
4023
  灰度图片本身没有地理范围和业务值含义,因此 `area` 必填;要表达真实数据,还应传正确的灰度范围与业务值范围。已经着色的彩色 PNG 不能反推出原始业务值,应使用对应 TIF 或直接传数值网格。
2624
4024
 
2625
- ### 3. 数值网格生成等值线
4025
+ 上例虽然使用了气压值域,但 `centerDetection` 默认关闭,因此只生成普通等压线和线值标签。需要 H/L 中心时,按下方“自动检测 H/L 中心”配置显式开启。
4026
+
4027
+ ### 数值网格
2626
4028
 
2627
4029
  ```js
2628
4030
  await rasterContour.loadGrid({
@@ -2644,13 +4046,13 @@ await rasterContour.loadGrid({
2644
4046
  })
2645
4047
  ```
2646
4048
 
2647
- ### 4. 参数
4049
+ ### 参数
2648
4050
 
2649
4051
  数据源参数:
2650
4052
 
2651
4053
  | 参数 | 类型 | 必填 | 默认值 | 说明 |
2652
4054
  | --- | --- | --- | --- | --- |
2653
- | `mapCore/baseGIS` | `BaseGIS` | 构造时必填 | `null` | 当前 BaseGIS 实例。 |
4055
+ | `mapCore/baseGIS` | `BaseGIS` | | `null` | 当前 BaseGIS 实例;构造时不传则后续调用 `mount(mapCore)`。 |
2654
4056
  | `layerId/contourLayerId` | `string` | 否 | `raster-contour-default` | 等值线图层 id。 |
2655
4057
  | `sourceType` | `geotiff/grayscale/grid` | 否 | 自动判断 | `loadGeoTiff/loadGrayImage/loadGrid` 会自动补齐。 |
2656
4058
  | `tifUrl/tiffUrl` | `string` | GeoTIFF 必填 | `''` | GeoTIFF 地址。 |
@@ -2681,74 +4083,136 @@ await rasterContour.loadGrid({
2681
4083
  | `minimumLinePoints` | `number` | 否 | `3` | 最短线点数,用于过滤栅格四角的两点补边。 |
2682
4084
  | `coordinatePrecision` | `number` | 否 | `6` | 输出经纬度小数位数。 |
2683
4085
  | `visible` | `boolean` | 否 | `true` | 初始显隐。 |
2684
- | 其他渲染参数 | - | 否 | - | `ContourLayerController` 相同,如 `color/width/weight/styleCallback/showLabel/labelViewportPadding`。 |
4086
+ | 其他渲染参数 | - | 否 | - | 复用 [`ContourLayerController` 核心参数](#contour-layer-parameters),如 `color/weight/styleCallback/showLabel/labelViewportPadding`;顶层 `width/height` 为栅格尺寸,线宽请用 `weight` 或 `styleCallback` 返回的 `width/weight`。 |
4087
+
4088
+ 级别选择优先级为 `thresholds` → `interval` → `thresholdCount`。显式传入 `thresholds` 时,只保留实际值域内的级别。
2685
4089
 
2686
- ### 5. 方法和状态
4090
+ ### 自动检测 H/L 中心
4091
+
4092
+ 该能力默认关闭。它使用标准化后的数值网格查找局部极大值(H)和局部极小值(L),而不是根据已经生成的线反推。因此只应用于确实存在 H/L 语义的栅格数据。
4093
+
4094
+ ```js
4095
+ await rasterContour.loadGrid({
4096
+ values: pressureValues,
4097
+ width,
4098
+ height,
4099
+ area: [70, 10, 140, 60],
4100
+ interval: 4,
4101
+ showLabel: true,
4102
+ centerDetection: {
4103
+ enabled: true,
4104
+ radius: 6,
4105
+ minProminence: 2,
4106
+ minGridDistance: 20,
4107
+ maxCount: 12,
4108
+ },
4109
+ centerAnnotation: {
4110
+ visible: true,
4111
+ collision: true,
4112
+ color: '#e3f8ff',
4113
+ valueColor: '#ffffff',
4114
+ },
4115
+ })
4116
+ ```
4117
+
4118
+ | `centerDetection` 参数 | 默认值 | 说明 |
4119
+ | --- | --- | --- |
4120
+ | `enabled` | `false` | 开启 H/L 自动检测。 |
4121
+ | `radius` | `6` | 计算局部显著度的网格半径。 |
4122
+ | `minProminence` | `2` | 中心值与邻域平均值的最小差异,单位与输入网格一致。 |
4123
+ | `minGridDistance` | `20` | 同类中心的最小网格距离,距离过近时合并。 |
4124
+ | `maxCount` | `12` | H/L 合计最大输出数。 |
4125
+ | `includeHigh/includeLow` | `true/true` | 是否输出 H 或 L。 |
4126
+ | `excludeBoundary` | `true` | 排除贴近边界或 NoData 空洞的候选。 |
4127
+ | `smoothingIterations` | `1` | 3×3 有效值平滑次数,范围 0~3。 |
4128
+
4129
+ 检测流程会先进行轻量平滑,再按显著度过滤、合并过近的同类中心并限制数量。超过 100 万格时只对候选搜索做块均值抽样,最终仍回到原网格取精确值和坐标。关闭检测后会清理上一次自动中心。
4130
+
4131
+ `RasterContourController` 也可直接传顶层 `centers`。手工数组(包括 `[]`)优先于自动检测;传 `centers: null` 可恢复使用当前 `centerDetection` 的结果。检测只产生中心数据,仍需 `centerAnnotation.visible: true` 才会显示。
4132
+
4133
+ ### 方法和状态
2687
4134
 
2688
4135
  | 方法 | 说明 |
2689
4136
  | --- | --- |
4137
+ | `mount(mapCore)` | 后挂载 `BaseGIS`;构造时已传入则无需调用。 |
2690
4138
  | `load(payload, options)` | 自动判断数据源并生成等值线。 |
2691
4139
  | `loadGeoTiff(payload, options)` | 从 GeoTIFF 生成。 |
2692
4140
  | `loadGrayImage(payload, options)` | 从灰度图生成。 |
2693
4141
  | `loadGrid(payload, options)` | 从数值网格生成。 |
2694
4142
  | `update(payload)` | 合并配置后重新计算;仅修改等值线或渲染参数时复用已解析网格,修改数据源参数时自动重新加载。 |
2695
- | `show/hide/toggle` | 控制等值线显隐。 |
4143
+ | `show/hide/toggle` | 统一控制等值线、线值标签和中心标注显隐。 |
2696
4144
  | `clear/destroy` | 清空或销毁控制器。 |
2697
- | `getState()` | 返回尺寸、值域、级别、线数、当前标注数、投影、耗时和底层 contour 状态。 |
4145
+ | `getState()` | 返回尺寸、值域、级别、线数、`centerCount`、标注数、投影、耗时和底层 contour 状态。 |
2698
4146
  | `getRasterData()` | 返回当前标准栅格,包含 `values/width/height/area`。 |
2699
4147
 
2700
- <a id="pressure-contour-layer"></a>
2701
-
2702
- ## PressureContourLayer
2703
-
2704
- 用于等压线、线值标签和高低压中心标注。`buildPressureRenderData()` 可把 Windy 风格的气压数据整理成 `{ isolines, centers }`,`createPressureDataResolver()` 在此基础上提供按数据版本缓存的解析函数。
2705
-
2706
4148
  ```js
2707
- import {
2708
- PressureContourLayer,
2709
- buildPressureRenderData,
2710
- createPressureDataResolver,
2711
- } from '@3clear/basegis/methods'
4149
+ // clear 只清空当前渲染结果,控制器仍可继续 update/load。
4150
+ rasterContour.clear()
4151
+
4152
+ // 页面卸载时彻底释放栅格、图层和监听。
4153
+ rasterContour.destroy()
4154
+ ```
4155
+ <a id="contour-center-annotation"></a>
2712
4156
 
2713
- // 单份原始数据可以直接转换。
2714
- const renderData = buildPressureRenderData(pressJson)
4157
+ ## 中心标注配置(两个控制器通用)
2715
4158
 
2716
- // 多个时次或版本可用解析器按 key 缓存转换结果。
2717
- const resolvePressureData = createPressureDataResolver({
2718
- default: pressJson,
2719
- next: pressJson2,
2720
- })
4159
+ `ContourLayerController` 通过顶层 `centers` 接收已经计算好的中心点,通过 `centerAnnotation` 控制显示和样式。该能力是通用标注,不把数据限制为气压;H/L 只是气压业务中的一种标签。
2721
4160
 
2722
- const pressureLayer = new PressureContourLayer({
4161
+ ```js
4162
+ const pressureContour = new ContourLayerController({
2723
4163
  mapCore,
2724
- layerId: 'pressure',
2725
- color: 'rgba(255,255,255,0.82)',
2726
- width: 1.15,
2727
- smoothFactor: 0.2,
2728
- // Cesium 用它判断标签碰撞和屏幕范围,Leaflet 可复用同一容器 id。
2729
- mapContainerId: 'map',
2730
- isAnnotationVisible: () => true,
4164
+ layerId: 'pressure-contour',
4165
+ showLabel: true,
4166
+ labelFormatter: (item) => String(item.value),
4167
+ centerAnnotation: {
4168
+ visible: true,
4169
+ collision: true,
4170
+ color: '#e3f8ff',
4171
+ fontSize: 20,
4172
+ valueColor: '#ffffff',
4173
+ valueFontSize: 12,
4174
+ },
2731
4175
  })
2732
4176
 
2733
- pressureLayer.mount()
2734
- await pressureLayer.load(renderData)
2735
- await pressureLayer.update(resolvePressureData('next'))
2736
- pressureLayer.hide()
2737
- pressureLayer.destroy()
4177
+ await pressureContour.load({
4178
+ isolines: [
4179
+ {
4180
+ value: 1012,
4181
+ lines: [[[31, 116.5], [31.4, 117.2], [31.7, 118]]],
4182
+ },
4183
+ ],
4184
+ centers: [
4185
+ { type: 'H', value: 1020, lat: 32.1, lon: 117.3 },
4186
+ { type: 'L', value: 1004, lat: 30.5, lon: 116.8 },
4187
+ ],
4188
+ })
2738
4189
  ```
2739
4190
 
2740
- 说明:
4191
+ `centers[]` 支持 `{ label, type, value, lat, lon }`,也支持 `position`。对象坐标字段固定按语义读取;`position` 数组遵循 `coordinateOrder`。主标签优先读取 `label`,其次读取 `type`。
4192
+
4193
+ | `centerAnnotation` 参数 | 默认值 | 说明 |
4194
+ | --- | --- | --- |
4195
+ | `visible` | `false` | 是否显示中心标注。数据与显示配置分开,传入 centers 不会自动开启。 |
4196
+ | `collision` | `true` | 是否避让其他中心;中心先占位,线值标签随后避让。 |
4197
+ | `formatter(raw, center)` | - | 返回文本,或返回 `{ label, value }`。 |
4198
+ | `styleCallback(raw, index, center)` | - | 按中心覆盖颜色、字号、字重或返回 `visible:false`。 |
4199
+ | `color/fontSize` | `#34464f / 18` | 主标签颜色与字号。 |
4200
+ | `valueColor/valueFontSize` | 跟随 `color` / `12` | 数值颜色与字号;`color` 也未传时颜色为 `#34464f`。 |
4201
+ | `collisionPadding` | `8` | 碰撞间距,单位 px。 |
4202
+ | `viewportPadding` | `24` | 屏幕安全边距。 |
2741
4203
 
2742
- - 等压线数据会被整理为 `isolines` 和 `centers`。
2743
- - Leaflet 使用 `divIcon` 绘制 H/L 中心和线值标签。
2744
- - Cesium 使用独立标注集合,地图变化时会调度刷新。
4204
+ 状态中的 `centerCount` 是成功解析的有效中心数,`lineLabelCount` 和 `centerLabelCount` 是当前视野实际显示数,`labelCount` 是两者合计。`show/hide/clear/destroy` 以及 `BaseGIS.setEngine()` 会和等值线一起管理这些中心标注。
2745
4205
 
2746
4206
  <a id="wind-field-methods"></a>
2747
4207
 
2748
- ## WindFieldMethods
4208
+ ## Canvas 风场(WindFieldMethods
2749
4209
 
2750
4210
  用于叠加 Canvas 风场粒子动画。它通过 `BaseGIS` 获取容器、投影、视野范围和视图变化事件,不直接依赖某个引擎。
2751
4211
 
4212
+ 与 `upsertWindLayer()` 不同,该控制器在两个引擎中都使用 Canvas,可叠加风速底图,但不参与 BaseGIS 托管恢复。切换引擎时先清理旧控制器,再在新地图初始化后重新挂载并加载数据。
4213
+
4214
+ Leaflet 的统一风场入口使用的是另一套 Canvas 渲染器。本节控制器使用 `particleCount / velocityScale` 等参数,不能直接套用统一入口的 `maxParticles / speedFactor`。
4215
+
2752
4216
  ```js
2753
4217
  import { WindFieldMethods } from '@3clear/basegis/methods'
2754
4218
 
@@ -2758,9 +4222,9 @@ wind.addWindField({
2758
4222
  id: 'wind-main',
2759
4223
  data: windData,
2760
4224
  particleCount: 3000,
2761
- velocityScale: 0.01,
2762
4225
  lineWidth: 1,
2763
- color: 'rgba(80, 180, 255, 0.8)',
4226
+ // 单色粒子用一档色标;不传则使用内置风速分级色标。
4227
+ particleColorScale: [{ value: 0, color: '#50b4ff' }],
2764
4228
  })
2765
4229
 
2766
4230
  wind.updateWindField({
@@ -2800,11 +4264,9 @@ const wind = new WindFieldMethods({ mapCore })
2800
4264
  ```js
2801
4265
  const windData = {
2802
4266
  // [经度最小值, 纬度最小值, 经向网格数, 纬向网格数, 经度跨度, 纬度跨度, 数值缩放]
2803
- Bound: [70, 15, 121, 81, 60, 40, 10],
4267
+ Bound: [100, 10, 2, 2, 10, 10, 10],
2804
4268
  // 从左下角开始,按行平铺;每个格点两个值:[u, v]
2805
- DataAry: [
2806
- // u0, v0, u1, v1, ...
2807
- ],
4269
+ DataAry: [30, 10, 40, 15, 20, 10, 35, 5],
2808
4270
  }
2809
4271
  ```
2810
4272
 
@@ -2845,12 +4307,12 @@ const windData = {
2845
4307
  | `particleCount` | `number` | `1400` | 粒子总数。数值越大越密,渲染开销越高。 |
2846
4308
  | `maxFrameParticles` | `number` | `1200` | 单帧最多推进和绘制的粒子数。 |
2847
4309
  | `frameParticleLimit` | `number` | `1200` | `maxFrameParticles` 的别名。 |
2848
- | `sceneModeMaxFrameParticles` | `number` | `0` | 场景模式相关帧粒子上限预留参数。当前未作为主路径使用。 |
4310
+ | `sceneModeMaxFrameParticles` | `number` | `0` | 大于 `0` 时进一步限制 2D / 2.5D 场景每帧推进的粒子数,且不超过 `maxFrameParticles`。 |
2849
4311
  | `maxAge` | `number` | `58` | 粒子最大生命周期,超过后重新随机出生。 |
2850
4312
  | `frameRate` | `number` | `30` | 粒子动画目标帧率,内部限制在 `1 ~ 60`。 |
2851
4313
  | `velocityScale` | `number` | `900` | 风速到粒子位移的缩放系数。数值越大粒子移动越快。 |
2852
4314
  | `lineWidth` | `number` | `0.85` | 粒子轨迹基础线宽。 |
2853
- | `color` | `string` | `rgba(230, 248, 255, 0.52)` | 粒子单色兜底颜色。传了 `particleColorScale` 时优先按风速分级着色。 |
4315
+ | `color` | `string` | `rgba(230, 248, 255, 0.52)` | 粒子兜底颜色;默认已有 `particleColorScale`,单色效果请使用一档粒子色标。 |
2854
4316
  | `particleOpacity` | `number` | `0.58` | 粒子轨迹透明度。 |
2855
4317
  | `fadeOpacity` | `number` | `0.86` | 拖尾淡出强度,越接近 `1` 轨迹残留越长。 |
2856
4318
  | `blendMode` | `string` | `lighter` | Canvas 合成模式,例如 `source-over`、`lighter`。 |
@@ -2909,70 +4371,6 @@ const windData = {
2909
4371
  | `bounds` | 风场数据范围 `{ west, south, east, north }`。 |
2910
4372
  | `center` | 风场中心 `{ lon, lat }`。 |
2911
4373
 
2912
- ## 在 Vue 中封装 Hook
2913
-
2914
- 推荐页面用 hook 管理生命周期,组件只处理 UI。
2915
-
2916
- ```js
2917
- import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
2918
- import { BaseGIS } from '@3clear/basegis'
2919
-
2920
- export function useBaseGIS(options = {}) {
2921
- const mapCore = shallowRef(null)
2922
-
2923
- onMounted(() => {
2924
- mapCore.value = new BaseGIS(options)
2925
- mapCore.value.init()
2926
- })
2927
-
2928
- onBeforeUnmount(() => {
2929
- mapCore.value?.destroy()
2930
- mapCore.value = null
2931
- })
2932
-
2933
- async function setEngine(engineType) {
2934
- if (!mapCore.value || mapCore.value.getEngineType() === engineType) {
2935
- return { success: true }
2936
- }
2937
-
2938
- return mapCore.value.setEngine(engineType, {
2939
- ...options,
2940
- })
2941
- }
2942
-
2943
- return {
2944
- mapCore,
2945
- setEngine,
2946
- }
2947
- }
2948
- ```
2949
-
2950
- 使用:
2951
-
2952
- ```vue
2953
- <template>
2954
- <div id="map" class="map"></div>
2955
- </template>
2956
-
2957
- <script setup>
2958
- import { useBaseGIS } from './useBaseGIS'
2959
-
2960
- const { mapCore, setEngine } = useBaseGIS({
2961
- engineType: 'cesium',
2962
- containerId: 'map',
2963
- })
2964
- </script>
2965
- ```
2966
-
2967
- BaseGIS 托管图层会自动恢复,页面不需要重新请求数据:
2968
-
2969
- ```js
2970
- const result = await setEngine('leaflet')
2971
- console.log(result.data?.restore)
2972
- ```
2973
-
2974
- 只有风场、剖面、一次性绘制对象或页面直接创建的底层引擎对象需要在切换成功后自行恢复。
2975
-
2976
4374
  ## 能力支持说明
2977
4375
 
2978
4376
  | 能力 | Cesium | Leaflet |
@@ -2980,18 +4378,29 @@ console.log(result.data?.restore)
2980
4378
  | 2D 地图 | 支持 | 支持 |
2981
4379
  | 2.5D / 3D 场景 | 支持 | 不支持 |
2982
4380
  | 放大、缩小、重置 | 支持 | 支持 |
4381
+ | 多地图视角联动 | 支持 | 支持 |
4382
+ | 地图截图与 PNG 导出 | 支持 | 支持 |
2983
4383
  | 底图切换 | 支持 | 支持 |
2984
4384
  | 点线面文字 | 支持 | 支持 |
4385
+ | Canvas 数据图标与扩散 Marker | 支持 | 支持 |
4386
+ | GeoJSON 图层 | 支持 | 支持 |
4387
+ | 独立线、虚线、渐变与沿线动画 | 支持 | 支持 |
4388
+ | 逐顶点色与路线时间裁剪 | 支持 | 支持 |
4389
+ | 台风路径与播放 | 支持 | 支持 |
2985
4390
  | 图片覆盖层 | 支持 | 支持 |
2986
4391
  | 网格 / TIF | 支持 | 支持 |
2987
4392
  | 海量点 | 支持 | 支持 |
4393
+ | 点位聚合 | 支持 | 支持 |
2988
4394
  | 点位密度抽稀 | 支持 | 支持 |
2989
4395
  | 等值线 | 支持 | 支持 |
2990
- | 等压线标注 | 支持 | 支持 |
2991
- | 风场 Canvas 动画 | 支持 | 支持 |
2992
- | 三维体渲染 | 支持 | 不支持 |
4396
+ | 等值线标签与 H/L 中心标注 | 支持 | 支持 |
4397
+ | 统一风场 `upsertWindLayer` | GPU | Canvas |
4398
+ | 独立 Canvas 风场 `WindFieldMethods` | 支持 | 支持 |
4399
+ | GPU 专用风场 `upsertGpuWindLayer` | 支持 | 不支持 |
4400
+ | 源解析弧线、灰色烟羽点云与目标汇聚体 | 支持 | 不支持 |
4401
+ | 三维体渲染、更新与取值 | 支持 | 不支持 |
2993
4402
  | 三维切片 / 剖面渲染 | 支持 | 不支持 |
2994
- | DEM 地形 | 支持 | 不支持 |
4403
+ | DEM 地形与夸张 | 支持 | 不支持 |
2995
4404
 
2996
4405
  ## 第三方许可
2997
4406
 
@@ -3023,14 +4432,14 @@ SOFTWARE.
3023
4432
 
3024
4433
  ## 本地开发与打包
3025
4434
 
3026
- 在仓库根目录执行:
4435
+ 包源码来自仓库 `src/gis`;`packages/basegis` 负责构建与发布配置。安装仓库依赖后,在仓库根目录执行:
3027
4436
 
3028
4437
  ```bash
3029
4438
  npm run build:basegis
3030
4439
  npm run pack:basegis
3031
4440
  ```
3032
4441
 
3033
- `build:basegis` 会生成:
4442
+ `build:basegis` 生成以下入口,以及它们依赖的共享、异步代码块:
3034
4443
 
3035
4444
  ```text
3036
4445
  packages/basegis/dist/basegis.js
@@ -3040,23 +4449,25 @@ packages/basegis/dist/assets.js
3040
4449
  packages/basegis/dist/style.css
3041
4450
  ```
3042
4451
 
3043
- `pack:basegis` 会在仓库根目录生成类似文件:
4452
+ 分发时必须保留完整 `packages/basegis/dist`,不能只复制上述入口文件。`pack:basegis` 不会自动构建,修改源码后应先执行 `build:basegis`。
4453
+
4454
+ 当前包版本为 `0.1.4`,`pack:basegis` 会在仓库根目录生成以下文件;后续以 `packages/basegis/package.json` 中的版本及实际输出为准:
3044
4455
 
3045
4456
  ```text
3046
- 3clear-basegis-0.1.0.tgz
4457
+ 3clear-basegis-0.1.4.tgz
3047
4458
  ```
3048
4459
 
3049
4460
  其他项目本地安装:
3050
4461
 
3051
4462
  ```bash
3052
- npm install ./3clear-basegis-0.1.0.tgz
4463
+ npm install ./3clear-basegis-0.1.4.tgz
3053
4464
  ```
3054
4465
 
3055
4466
  ## 发布到 npm
3056
4467
 
3057
4468
  只发布 `packages/basegis`,不要在项目根目录执行 `npm publish`,否则会把整个业务项目上传到 npm。
3058
4469
 
3059
- 发布前检查包内容:
4470
+ 更新包版本并重新构建后,先检查包内容:
3060
4471
 
3061
4472
  ```bash
3062
4473
  npm pack --dry-run ./packages/basegis
@@ -3068,38 +4479,13 @@ npm pack --dry-run ./packages/basegis
3068
4479
  npm publish ./packages/basegis --access public
3069
4480
  ```
3070
4481
 
3071
- 如果只希望私有发布,保留 `packages/basegis/package.json` 中的:
3072
-
3073
- ```json
3074
- {
3075
- "publishConfig": {
3076
- "access": "restricted"
3077
- }
3078
- }
3079
- ```
3080
-
3081
- 如果希望公开发布,需要改为:
3082
-
3083
- ```json
3084
- {
3085
- "publishConfig": {
3086
- "access": "public"
3087
- }
3088
- }
3089
- ```
4482
+ 包已配置 `publishConfig.access: 'public'`,发布内容为完整 `dist`、README 和 npm 自动附带的包元信息,不包含测试页面、mock 数据和 Cesium 静态资源。
3090
4483
 
3091
4484
  ## 常见问题
3092
4485
 
3093
4486
  ### Cesium 地图不显示
3094
4487
 
3095
- 检查宿主项目是否存在:
3096
-
3097
- ```text
3098
- public/lib/Cesium/Cesium.js
3099
- public/lib/Cesium/Widgets/widgets.css
3100
- ```
3101
-
3102
- 并确认页面初始化前能访问 `window.Cesium`。
4488
+ 按[安装](#安装)章节加载 Cesium 脚本、样式及完整静态资源,确认初始化前存在 `window.Cesium`,并确保地图容器有实际宽高。部署在子路径时还要检查 `CESIUM_BASE_URL`。
3103
4489
 
3104
4490
  ### Leaflet 样式异常
3105
4491
 
@@ -3120,18 +4506,19 @@ console.log(result.success, result.message)
3120
4506
 
3121
4507
  ### 切换配置后地图没有变化
3122
4508
 
3123
- `setConfig()` 只更新 `BaseGIS` 内部配置,不会自动重建地图。需要销毁后重新初始化:
4509
+ `setConfig()` 只更新内部配置,需要再次 `init()` 才会重建地图。不要先手动 `destroy()`,以免清空托管图层快照:
3124
4510
 
3125
4511
  ```js
3126
4512
  mapCore.setConfig(nextConfig)
3127
- mapCore.init()
4513
+ const result = mapCore.init()
4514
+ if (result.success) await mapCore.whenReady()
3128
4515
  ```
3129
4516
 
3130
4517
  `init()` 内部会先销毁旧适配器,再创建新实例。
3131
4518
 
3132
4519
  ### 切换引擎后图层没了
3133
4520
 
3134
- 直接使用 `setEngine()`。BaseGIS 会保留视野,并自动恢复图片、网格、海量点、点位抽稀、等值线和三维体等托管图层:
4521
+ 先确认通过 `setEngine()` 切换且没有提前 `destroy()`,再查看恢复结果:
3135
4522
 
3136
4523
  ```js
3137
4524
  const result = await mapCore.setEngine('leaflet')
@@ -3140,4 +4527,4 @@ console.log(result.data?.restore?.restored)
3140
4527
  console.log(result.data?.restore?.failed)
3141
4528
  ```
3142
4529
 
3143
- 如果丢失的是 `drawPoint/drawLine/drawPolygon/drawText/addMarker`、点击监听、风场、剖面图层或页面直接创建的 Cesium / Leaflet 对象,它们不属于托管图层,需要业务在切换成功后自行恢复。
4530
+ 自动恢复与手动恢复范围见[切换后的图层处理](#managed-layer-restore)。GeoJSON、基础绘制、独立 Canvas/GPU 风场、剖面和页面监听需重新加载或绑定;使用多地图联动时还需调用 `viewLinks.refreshMap(id)`。