@3clear/basegis 0.1.4 → 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,13 +1,13 @@
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
  ## 能力总览
@@ -27,14 +27,53 @@
27
27
  </tbody>
28
28
  </table>
29
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
+
30
69
  ### Graphics 图形与点位(4)
31
70
 
32
71
  <table>
33
72
  <tbody>
34
73
  <tr>
35
74
  <td width="50%">
36
- <a href="#graphic-group-controller"><strong>GraphicGroupController</strong></a><br>
37
- <small>点、线、面、文字与 Marker 图形组 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
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>
38
77
  </td>
39
78
  <td width="50%">
40
79
  <a href="#point-large-layer-controller"><strong>PointLargeLayerController</strong></a><br>
@@ -54,6 +93,32 @@
54
93
  </tbody>
55
94
  </table>
56
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
+
57
122
  ### Line 线图层(1)
58
123
 
59
124
  <table>
@@ -62,7 +127,21 @@
62
127
  <td>
63
128
  <a href="#line-layer-controller"><strong>LineLayerController</strong></a><br>
64
129
  <small>独立线图层、实线/虚线、固定/流动渐变与沿线动画 · <a href="http://61.50.111.214:31166/lgmap/test-page-34" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
65
- <small>支持:一条或多条经纬度折线,可选图标引领的逐步出线</small>
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>
66
145
  </td>
67
146
  </tr>
68
147
  </tbody>
@@ -103,7 +182,7 @@
103
182
  <tr>
104
183
  <td width="50%">
105
184
  <a href="#contour-layer-controller"><strong>ContourLayerController</strong></a><br>
106
- <small>已有等值线、线值标签与中心标注 · <a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small>
185
+ <small>已有等值线、线值标签与中心标注 · <a href="http://61.50.111.214:31166/lgmap/test-page-19" target="_blank" rel="noopener noreferrer">查看示例</a></small>
107
186
  </td>
108
187
  <td width="50%">
109
188
  <a href="#raster-contour-controller"><strong>RasterContourController</strong></a><br>
@@ -113,6 +192,20 @@
113
192
  </tbody>
114
193
  </table>
115
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
+
116
209
  ### Volume 三维体渲染(1)
117
210
 
118
211
  <table>
@@ -121,7 +214,7 @@
121
214
  <td>
122
215
  <a href="#volume-rendering"><strong>BaseGIS.upsertVolumeLayer</strong></a><br>
123
216
  <small>Cesium 专有;三维标量场体积采样与裁切 · <a href="http://61.50.111.214:31166/lgmap/test-page-14" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
124
- <small>支持:一维体数据 + rows / cols / heights 网格维度</small>
217
+ <small>支持:一维体数据 + rows / cols / heights 网格维度、原位更新体数据与屏幕位置取值</small>
125
218
  </td>
126
219
  </tr>
127
220
  </tbody>
@@ -134,21 +227,29 @@
134
227
  <tr>
135
228
  <td>
136
229
  <a href="#section-rendering"><strong>BaseGIS.upsertSectionLayer</strong></a><br>
137
- <small>Cesium 专有;按经度、纬度、高度/气压层切片 · <a href="http://61.50.111.214:31166/lgmap/test-page-15" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
230
+ <small>Cesium 专有;按经度、纬度、气压层切片 · <a href="http://61.50.111.214:31166/lgmap/test-page-15" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
138
231
  <small>支持:{ Bound, DataAry } 三维格点数据</small>
139
232
  </td>
140
233
  </tr>
141
234
  </tbody>
142
235
  </table>
143
236
 
144
- ### Wind 风场(1
237
+ ### Wind 风场(2
238
+
239
+ 风场分为 GPU 和 Canvas 两种渲染方式。推荐使用 [BaseGIS.upsertWindLayer](#unified-wind-layer) 统一入口:Cesium 自动使用 GPU,Leaflet 自动使用 Canvas,切换引擎时自动恢复风场。
145
240
 
146
241
  <table>
147
242
  <tbody>
148
243
  <tr>
149
244
  <td>
150
- <a href="#wind-field-methods"><strong>WindFieldMethods</strong></a><br>
151
- <small>Cesium / Leaflet 统一粒子风场 · <a href="http://61.50.111.214:31166/lgmap/test-page-21" target="_blank" rel="noopener noreferrer">查看示例</a></small>
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>
152
253
  </td>
153
254
  </tr>
154
255
  </tbody>
@@ -181,15 +282,29 @@
181
282
  </tbody>
182
283
  </table>
183
284
 
184
- ### Assets 示例资源(1
285
+ ### Assets 示例资源(4
185
286
 
186
287
  <table>
187
288
  <tbody>
188
289
  <tr>
189
- <td>
290
+ <td width="50%">
190
291
  <a href="#gis-marker-sample"><strong>gisMarkerSample</strong></a><br>
191
292
  <small>GIS Marker 示例资源 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
192
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>
193
308
  </tr>
194
309
  </tbody>
195
310
  </table>
@@ -200,7 +315,7 @@
200
315
  npm install @3clear/basegis leaflet axios
201
316
  ```
202
317
 
203
- `d3-contour`、`pixi.js` 和 `leaflet-pixi-overlay` 已随 BaseGIS 构建产物发布,业务项目不需要单独安装。其中 Pixi 相关代码只在首次使用 Leaflet 海量点能力时按需加载。
318
+ `d3-contour`、`pixi.js`、`leaflet-pixi-overlay` 和 `html2canvas` 已随 BaseGIS 构建产物发布,业务项目不需要单独安装。其中 Pixi 相关代码只在首次使用 Leaflet 海量点能力时按需加载,`html2canvas` 只在首次调用 Leaflet 截图时按需加载。
204
319
 
205
320
  使用样式:
206
321
 
@@ -208,8 +323,19 @@ npm install @3clear/basegis leaflet axios
208
323
  import '@3clear/basegis/style.css'
209
324
  ```
210
325
 
211
- Cesium 当前不随 npm 包发布,项目需要按原项目方式把 Cesium 静态资源放到 `public/lib/Cesium`,并保证初始化前能访问 `window.Cesium`。
212
- 如果需要加载 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。
213
339
 
214
340
  ## 出口
215
341
 
@@ -219,8 +345,9 @@ import { BaseGIS } from '@3clear/basegis'
219
345
 
220
346
  // 高级能力控制器
221
347
  import {
222
- GraphicGroupController,
223
348
  LineLayerController,
349
+ MapViewLinkController,
350
+ TyphoonPathController,
224
351
  ImageLayerController,
225
352
  toImageLayerArea,
226
353
  GridLayerController,
@@ -235,13 +362,19 @@ import {
235
362
  // 图层配置构造器
236
363
  import {
237
364
  createTiandituLayer,
365
+ createTiandituTileSource,
238
366
  createGeoserverPyramidLayer,
239
367
  createWmsLayer,
240
368
  createWmtsLayer,
241
369
  } from '@3clear/basegis/layers'
242
370
 
243
371
  // 示例资源
244
- import { gisMarkerSample } from '@3clear/basegis/assets'
372
+ import {
373
+ gisMarkerSample,
374
+ gisFactoryMarker,
375
+ gisInfoMarker,
376
+ typhoonPathIcon,
377
+ } from '@3clear/basegis/assets'
245
378
  ```
246
379
 
247
380
  ## 快速开始
@@ -297,17 +430,19 @@ onBeforeUnmount(() => {
297
430
  })
298
431
  </script>
299
432
 
300
- <style scoped>
433
+ <style scoped lang="scss">
301
434
  .map {
302
435
  width: 100%;
303
- height: 100%;
436
+ height: 100vh;
304
437
  }
305
438
  </style>
306
439
  ```
307
440
 
441
+ 以下 API 示例默认 `mapCore` 已初始化成功;示例中的业务数据、图片和服务地址需由页面准备。组件卸载时先销毁控制器、移除页面监听,再调用 `mapCore.destroy()`。
442
+
308
443
  ## 返回值约定
309
444
 
310
- 多数方法返回统一结果对象:
445
+ 操作方法通常返回统一结果对象;图片、GeoJSON、风场等异步加载方法应使用 `await`:
311
446
 
312
447
  ```js
313
448
  {
@@ -325,9 +460,12 @@ onBeforeUnmount(() => {
325
460
  message: 'error message',
326
461
  code: 'NOT_INITIALIZED'
327
462
  }
328
-
329
463
  ```
330
464
 
465
+ `getEngineType()`、`getConfig()`、`getMapInstance()` 等读取方法直接返回值;`createMarkerIcon()` 直接返回图标参数或 `null`。控制器的 `getState()` 也返回状态对象,不能一律按 `result.data` 读取。
466
+
467
+ ## 配置与底图
468
+
331
469
  说明:
332
470
 
333
471
  - `containerId` 和 `container` 二选一即可;`init()` 时也可以再次传入。
@@ -357,17 +495,6 @@ basemap: {
357
495
  | `tianditu-vector-label` | 天地图矢量注记 | `annotation` | `true` | `wmts` | `tianditu` | `vectorLabel` | 内置 `defaultAnnotationId` | Cesium / Leaflet |
358
496
  | `tianditu-terrain-label` | 天地图地形注记 | `annotation` | `true` | `wmts` | `tianditu` | `terrainLabel` | 可作为 `defaultAnnotationId` | Cesium / Leaflet |
359
497
 
360
- 可用于 `defaultVisibleId` 的内置底图 id:
361
-
362
- - `tianditu-vector`
363
- - `tianditu-imagery`
364
- - `tianditu-terrain`
365
-
366
- 可用于 `defaultAnnotationId` 的内置注记 id:
367
-
368
- - `tianditu-vector-label`
369
- - `tianditu-terrain-label`
370
-
371
498
  说明:
372
499
 
373
500
  - `defaultVisibleId` 应指向 `category: 'basemap'` 的底图。
@@ -376,7 +503,7 @@ basemap: {
376
503
  - `geoserver-wmts-sample` 和 `geoserver-wms-sample` 默认 `enabled: false`,只是配置格式示例;如果要作为默认底图,需要替换真实服务地址并改为 `enabled: true`。
377
504
  - 天地图 provider 内置资源还包括 `imageryLabel`;默认 `basemap.list` 没有单独注册影像注记 id,但 `resourceKey: 'imagery'` 会自动推断使用 `imageryLabel` 注记。
378
505
 
379
- ### 底图切换
506
+ ### 底图切换
380
507
 
381
508
  底图可以来自 `config.basemap.list`,也可以直接传配置对象。当前适配层支持:
382
509
 
@@ -494,9 +621,11 @@ const basemapList = [
494
621
  id: 'weather-wmts',
495
622
  name: '气象 WMTS',
496
623
  category: 'basemap',
497
- url: 'https://example.com/wmts',
498
- layer: 'weather',
499
- 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}',
500
629
  }),
501
630
  ]
502
631
 
@@ -513,6 +642,12 @@ const mapCore = new BaseGIS({
513
642
 
514
643
  四个构造器都会保留额外传入字段,便于继续配置 `engineSupport`、缩放级别、注记资源或服务参数。
515
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
+
516
651
  <a id="base-gis"></a>
517
652
 
518
653
  ## BaseGIS 基础能力
@@ -579,6 +714,8 @@ const mapCore = new BaseGIS({
579
714
  | `getEngineType()` | 无 | 返回当前引擎类型。 |
580
715
  | `getConfig()` | 无 | 返回当前运行时配置。 |
581
716
  | `getMapInstance()` | 无 | 返回底层地图实例:Cesium `viewer` 或 Leaflet `map`。 |
717
+ | `getMapContainer()` | 无 | 返回统一结果,`data.container` 为地图容器 DOM。 |
718
+ | `resize()` | 无 | 在容器尺寸改变、隐藏面板重新显示后刷新地图尺寸。 |
582
719
 
583
720
  #### 推荐的引擎切换写法
584
721
 
@@ -614,25 +751,28 @@ if (result.success) {
614
751
 
615
752
  `switchEngine()` 作为兼容别名保留,行为与 `setEngine()` 一致。
616
753
 
754
+ <a id="managed-layer-restore"></a>
755
+
617
756
  #### 切换后的图层处理
618
757
 
619
758
  引擎切换不是把 Cesium 图层对象“搬到” Leaflet,也不是把 Leaflet 图层对象“搬到” Cesium。BaseGIS 会保存托管图层的业务参数,并在新 adapter 中重新创建图层。
620
759
 
621
- - 自动恢复范围包括图片、网格、海量点、点位聚合、点位抽稀、独立线、等值线、统一风场和三维体图层,对应 `upsert*Layer` 方法及 Controller。
760
+ - 自动恢复范围包括图片、网格、海量点、点位聚合、点位抽稀、独立线、源解析传输、台风路径、等值线、统一风场和三维体图层。
622
761
  - 图层最新的数据参数、显隐、清空、删除、海量点删除和高亮状态会同步到 BaseGIS 快照。
623
762
  - 快照只保存业务参数引用,不复制大数组,不保存任何底层引擎对象。
624
- - 三维体图层切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
763
+ - 三维体、源解析传输切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
625
764
  - Cesium 专有能力在 Leaflet 下不可用,例如 DEM、三维体渲染、三维切片/剖面渲染。
626
- - 一次性绘制对象、点击监听、风场、剖面图层和页面直接操作底层引擎创建的对象不在托管范围内,需要业务自行恢复。
765
+ - 基础绘制对象、GeoJSON、点击/视图监听、GPU 专用风场、独立 `WindFieldMethods`、剖面图层和页面直接创建的底层引擎对象不在托管范围内,需要业务重新加载或绑定。
627
766
 
628
767
  切换结果中可以查看恢复明细:
629
768
 
630
769
  ```js
631
770
  const result = await mapCore.setEngine('leaflet')
632
- console.log(result.data.restore.restored)
633
- console.log(result.data.restore.failed)
771
+ console.log(result.data?.restore?.restored)
772
+ console.log(result.data?.restore?.failed)
634
773
  ```
635
774
 
775
+ `result.success` 表示引擎初始化/切换是否成功,不保证每个图层都恢复成功;应同时检查 `data.restore.failed`。不要在切换前先调用 `destroy()`,它会清空托管快照。
636
776
 
637
777
  ### 2. 视角控制与场景模式
638
778
 
@@ -688,6 +828,13 @@ const viewStateResult = mapCore.getViewState()
688
828
  // Cesium 通常包含 center / bounds / height / heading / pitch / roll / engineType。
689
829
  // Leaflet 通常包含 center / bounds / zoom / engineType。
690
830
 
831
+ // 让地图适配指定经纬度范围,可用于多地图首次统一范围。
832
+ mapCore.fitViewBounds({
833
+ bounds: { west: 73, south: 18, east: 135, north: 54 },
834
+ animate: false,
835
+ padding: 0,
836
+ })
837
+
691
838
  // 经纬度转地图容器像素坐标,常用于自定义 HTML 浮层定位。
692
839
  const pointResult = mapCore.projectToContainerPoint({
693
840
  longitude: 104,
@@ -708,6 +855,10 @@ const viewListener = mapCore.onViewChange({
708
855
  // 缩放过程中要实时刷新点位样式时,优先用 onChange。
709
856
  onChange() {},
710
857
 
858
+ // Cesium 交互期间逐渲染帧检测相机变化;适合多地图实时联动。
859
+ // 默认 false,普通业务监听无需开启。
860
+ continuous: true,
861
+
711
862
  // Cesium: camera.moveEnd / morphComplete;Leaflet: moveend / zoomend / resize。
712
863
  onEnd() {},
713
864
 
@@ -730,13 +881,98 @@ viewListener.data?.off?.()
730
881
  | `setSceneMode(payload)` | `'2d'/'2.5d'/'3d'` 或 `{ mode, duration, preserveView }` | 切换场景模式。 |
731
882
  | `getSceneMode()` | 无 | 获取当前场景模式。 |
732
883
  | `getViewBounds()` | 无 | 获取当前视图经纬度边界。 |
733
- | `getViewState()` | | 获取当前视图状态。 |
734
- | `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` 后交互期间逐帧检测。 |
735
887
  | `projectToContainerPoint(payload)` | `{ longitude, latitude, height }` | 经纬度投影到地图容器像素坐标。 |
736
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
+
737
973
  ### 4. 绘制点、线、面、文字和 Marker
738
974
 
739
- 这些方法用于轻量绘制和样例验证。大量点位或复杂业务图层请优先使用后文的 `PointLargeLayerController`、`PointClusterController`、`PointDensityController`、`GraphicGroupController` 等控制器。
975
+ 这些方法用于轻量绘制和样例验证。大量点位请优先使用后文的 `PointLargeLayerController`、`PointClusterController`、`PointDensityController` 等控制器。
740
976
 
741
977
  点:
742
978
 
@@ -827,11 +1063,188 @@ mapCore.addMarker({
827
1063
  })
828
1064
  ```
829
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
+
830
1236
  <a id="gis-marker-sample"></a>
831
1237
 
832
- #### 使用 gisMarkerSample 示例资源
1238
+ #### 使用 SVG 资源
833
1239
 
834
- `gisMarkerSample` 是可直接导入的 Marker 图片资源,适合快速验证 `addMarker()` 或控制器图标配置。
1240
+ `assets` 导出的资源均为可直接用于 `iconUrl` SVG URL;传给 Canvas 图标的 `image` 时则需要先加载为图片对象。
1241
+
1242
+ | 导出 | 用途 |
1243
+ | --- | --- |
1244
+ | `gisMarkerSample` | 基础 Marker 示例。 |
1245
+ | `gisFactoryMarker` | 工厂标记,适合企业排放数据。 |
1246
+ | `gisInfoMarker` | 信息标记,适合站点名称与数值。 |
1247
+ | `typhoonPathIcon` | 台风路径默认中心图标。 |
835
1248
 
836
1249
  ```js
837
1250
  import { gisMarkerSample } from '@3clear/basegis/assets'
@@ -862,13 +1275,105 @@ mapCore.clearGraphics()
862
1275
  | `drawLine(payload)` | 线配置 | 绘制线。 |
863
1276
  | `drawPolygon(payload)` | 面配置 | 绘制面。 |
864
1277
  | `drawText(payload)` | 文字配置 | 绘制文字。 |
865
- | `addMarker(payload)` | Marker 配置 | 绘制图片 Marker;未传 `iconUrl` 时降级为点。 |
1278
+ | `createMarkerIcon(options, data)` | 图标配置、原始数据 | 直接返回 `iconUrl/iconSize/iconAnchor`;未知 `type` 返回 `null`。 |
1279
+ | `addMarker(payload)` | Marker 配置 | 绘制图片或 `pulse` 扩散 Marker;未传图片或扩散参数时,Cesium 使用点、Leaflet 使用默认 Marker。 |
866
1280
  | `removeGraphic(payload)` | 图形 id 或 `{ id }` | 删除指定图形。 |
867
1281
  | `clearGraphics()` | 无 | 清空通过基础绘制方法创建的图形。 |
868
1282
 
869
1283
 
870
1284
 
871
- ### 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. 点击事件
872
1377
 
873
1378
  `onClick` 注册地图点击事件;`offClick` 移除当前点击监听。当前每个 adapter 只保留一个基础点击监听,重复调用 `onClick` 会先移除旧监听。
874
1379
 
@@ -935,50 +1440,149 @@ Leaflet 当前返回坐标点击:
935
1440
  - 海量点、点位聚合、点位抽稀等图层自己的点击事件,应使用对应 Controller 的点击回调参数。
936
1441
 
937
1442
 
938
- <a id="graphic-group-controller"></a>
1443
+ <a id="map-view-link-controller"></a>
1444
+
1445
+ ## 多地图联动(双屏联动)
939
1446
 
940
- ## GraphicGroupController
1447
+ `MapViewLinkController` 注册多个已经初始化的 BaseGIS,并实时同步拖动、缩放和视角。每个地图仍可独立加载不同模式、时次和图层;控制器只负责视角,不创建地图,也不会在 `unregisterMap()`、`clear()` 或 `destroy()` 时销毁 BaseGIS。
941
1448
 
942
- 用于一次性加载一组点、线、面、文字、marker。它只调用 `BaseGIS.drawPoint / drawLine / drawPolygon / drawText / addMarker`。
1449
+ 在线示例:[查看分屏布局中的页面地图联动](http://61.50.111.214:31166/lgmap/test-page-36)。布局可混合地图、图表和表格,地图实例与联动由页面管理。
943
1450
 
944
1451
  ```js
945
- import { GraphicGroupController } from '@3clear/basegis/methods'
1452
+ import { BaseGIS } from '@3clear/basegis'
1453
+ import { MapViewLinkController } from '@3clear/basegis/methods'
946
1454
 
947
- 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
+ })
948
1474
 
949
- graphics.load([
950
- {
951
- type: 'point',
952
- id: 'p1',
953
- longitude: 104,
954
- latitude: 35,
955
- },
956
- {
957
- type: 'line',
958
- id: 'l1',
959
- positions: [
960
- [103, 34],
961
- [105, 36],
962
- ],
963
- },
964
- {
965
- type: 'text',
966
- id: 't1',
967
- longitude: 104,
968
- latitude: 35,
969
- text: '站点',
970
- },
971
- ])
1475
+ viewLinks.registerMap('map-a', mapA, { group: 'forecast' })
1476
+ viewLinks.registerMap('map-b', mapB, { group: 'forecast' })
1477
+
1478
+ // 获取 BaseGIS、底层地图实例和全部 id -> BaseGIS 映射。
1479
+ const registeredMapA = viewLinks.getMap('map-a')
1480
+ const leafletMapA = viewLinks.getNativeMap('map-a')
1481
+ const allMaps = viewLinks.getMaps()
972
1482
 
973
- graphics.clear()
974
- graphics.getState()
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()
975
1494
  ```
976
1495
 
1496
+ 构造参数:
1497
+
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
+ 统一视角操作:
1533
+
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。 |
1544
+
1545
+ `setView / fitBounds / resetView / resizeAll` 始终作用于所有注册地图,不受联动分组或暂停状态限制。
1546
+
1547
+ 同为 Leaflet 时会同步 `center + zoom`;同为 Cesium 时会直接同步相机经纬度、`height + heading/pitch/roll`;两种视图模型无法直接对应时,`auto` 才会计算并使用 `bounds`。Cesium 交互期间会逐帧检测相机变化,再按 `syncInterval` 合并为最新状态写入目标地图,避免目标地图追赶稀疏跳点,也避免四五个 Cesium 实例每帧重复计算完整视域。一次拖动期间会锁定唯一交互源,目标地图的程序化相机事件不会反向接管并形成反馈循环。
1548
+
1549
+ > BaseGIS 切换引擎会重建 adapter,原视图监听随旧 adapter 销毁。切换完成后调用 `viewLinks.refreshMap(id)`;控制器不会劫持或改写 `BaseGIS.setEngine()`。
1550
+
1551
+
977
1552
  <a id="line-layer-controller"></a>
978
1553
 
979
1554
  ## LineLayerController
980
1555
 
981
- 独立管理一组折线,支持整体更新、显隐、清空、销毁和引擎切换恢复。每条线必须提供唯一 `id` 和至少两个 `[longitude, latitude, height?]` 坐标点。第一版可以加载多条线,但同一控制器只播放一条线的动画。
1556
+ 独立管理一组折线,支持整体更新、显隐、清空、销毁和引擎切换恢复。每条线必须提供唯一 `id` 和至少两个 `[longitude, latitude, height?]` 坐标点;同一控制器同一时刻只播放一条线的逐步出线动画。
1557
+
1558
+ ### 逐顶点色专题线与时间裁剪
1559
+
1560
+ `lines[].colors` 是六位 HEX 数组,长度必须与 `positions` 完全一致,颜色沿每两个相邻顶点连续插值,优先于 `style.color`:
1561
+
1562
+ ```js
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
+ })
1578
+
1579
+ lineLayer.setTime(startTime + 30000) // 显示前 30 秒已走过的路线。
1580
+ lineLayer.setTime(null) // 恢复完整路线。
1581
+ ```
1582
+
1583
+ 需要行驶回放时,为各条线附加 `times: [起点毫秒时间戳, 终点毫秒时间戳, ...]`,长度与坐标相同且严格递增。`load` 可设置 `currentTime`,加载后调用 `lineLayer.setTime(time)` 裁掉未来部分;`setTime(null)` 恢复完整路线。不带 `times` 的线不参与裁剪。成功返回 `{ success: true, data: { layerId, currentTime } }`,非法时间返回友好失败结果。
1584
+
1585
+ Cesium 使用一个批量 Primitive 做顶点色插值,时间用相对秒数存入顶点,回放每帧仅改材质参数。Leaflet 使用共享 Canvas 重绘已走过的线段,不重新投影或创建图层。这是实线专题叠加,不能与虚线、流动、单线 reveal 或贴地混用;Cesium 关闭深度测试,带时间的采样按直线连接,不用于地形遮挡/贴合,位置必须不同。数据/配色更新仍为全量替换,显隐和引擎切换保留时间。浓度映射、分车/断线、时钟和小车属于业务层;参考项目 `/test-page-43`。
982
1586
 
983
1587
  ### 实线、虚线与纯色
984
1588
 
@@ -1082,39 +1686,192 @@ flightLine.playAnimation()
1082
1686
  flightLine.restartAnimation()
1083
1687
  ```
1084
1688
 
1085
- 动画按照各段实际地理距离插值。`hide()` 暂停帧更新并记录播放状态,`show()` 恢复隐藏前正在运行的动画;`clear()` 和 `destroy()` 会取消动画。切换引擎会恢复播放/暂停意图,但不会保存逐帧进度,动画从起点重新计算。
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
+ ### 路径点格式
1086
1824
 
1087
- ### 参数
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
+ ### 样式与播放配置
1088
1837
 
1089
1838
  | 参数 | 默认值 | 说明 |
1090
1839
  | --- | --- | --- |
1091
- | `layerId` | `line-default` | 独立线图层 id。 |
1092
- | `visible` | `true` | 初始是否可见。 |
1093
- | `style.color` | `#2f80ff` | 纯色字符串或渐变对象。 |
1094
- | `style.width` / `style.opacity` | `3` / `1` | 线宽和透明度。 |
1095
- | `style.pattern` | `solid` | `solid` 或 `dashed`。 |
1096
- | `style.dashLength` / `gapLength` | `12` / `8` | 虚线实段和间隔长度。 |
1097
- | `animation.enabled` | `false` | 是否启用逐步出线。 |
1098
- | `animation.lineId` | 单线时自动选择 | 多条线启用动画时必填。 |
1099
- | `animation.durationMs` | `10000` | 从起点到终点的时长。 |
1100
- | `animation.autoplay` / `loop` | `true` / `false` | 自动播放与循环。 |
1101
- | `animation.icon` | - | 可选的飞机、车辆或船舶图标配置。 |
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 }`,弹窗由业务页面实现。
1102
1857
 
1103
1858
  ### 方法
1104
1859
 
1105
1860
  | 方法 | 说明 |
1106
1861
  | --- | --- |
1107
- | `mount(mapCore)` | 后挂载 BaseGIS。 |
1108
- | `load(payload, options?)` / `update(payload)` | 加载或更新线、样式和动画。 |
1109
- | `show()` / `hide()` / `toggle()` | 控制显隐。 |
1110
- | `playAnimation()` / `pauseAnimation()` / `restartAnimation()` | 控制沿线动画。 |
1111
- | `clear()` | 清空线和动画,保留控制器。 |
1112
- | `refreshState()` / `getState()` | 获取线数量、点数和动画状态。 |
1113
- | `destroy()` | 删除图层、动画和引擎恢复快照。 |
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()` | 删除图层、动画、点击监听和引擎恢复快照。 |
1114
1871
 
1115
- 页面卸载时先执行 `lineLayer.destroy()`,再执行 `mapCore.destroy()`。
1872
+ 切换引擎会恢复数据、样式、显隐、播放/暂停意图和最后一次显式 `seek` 的位置;不会保存正在播放的逐帧进度。页面卸载时先执行 `typhoonLayer.destroy()`,再执行 `mapCore.destroy()`。
1116
1873
 
1117
- 在线示例:[查看 LineLayerController 双引擎应用 Demo](http://61.50.111.214:31166/lgmap/test-page-34)。
1874
+ 在线示例:[查看 TyphoonPathController 双引擎应用 Demo](http://61.50.111.214:31166/lgmap/test-page-10)。
1118
1875
 
1119
1876
  <a id="image-layer-controller"></a>
1120
1877
 
@@ -1468,6 +2225,9 @@ imageLayer.destroy({ layerId: 'radar-image' })
1468
2225
  | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。仅 TIF 模式建议传空字符串清掉旧图片。 |
1469
2226
  | `tifUrl` | 仅 TIF、图片+TIF 必填 | GeoTIFF 数值地址,用于鼠标探针或网格注记,不会单独生成彩色图片底图。 |
1470
2227
  | `area` | 传 `imageUrl` 时必填 | 图片范围:`startLon / startLat / endLon / endLat`。 |
2228
+ | `sourceProjection` | 选填 | 源图投影;当前支持将 `EPSG:3857` 图片重采样为经纬度图片,不是任意投影转换器。 |
2229
+ | `sourceArea` | 选填 | 源图片范围,默认使用 `area`;支持墨卡托米坐标范围或经纬度范围。 |
2230
+ | `targetProjection` | 选填 | 默认从地图 CRS 推断;源投影与目标相同则不重采样。 |
1471
2231
  | `imageSourceType` | 灰度图必填,其他选填 | `color` 表示已填色图片;`grayscale` 表示灰度图。 |
1472
2232
  | `colorize` | 灰度图必填 | 灰度图业务值和色带配置。 |
1473
2233
  | `colorize.minValue` | 灰度图必填 | 色带映射最小值。 |
@@ -1475,6 +2235,7 @@ imageLayer.destroy({ layerId: 'radar-image' })
1475
2235
  | `colorize.noDataValue` | 选填 | 无效值。 |
1476
2236
  | `colorize.colors` | 灰度图必填 | 色带颜色数组。 |
1477
2237
  | `opacity` | 选填 | 透明度,通常 0 - 1。 |
2238
+ | `zIndex` | 选填 | Leaflet 图片叠放层级,默认 `650`。 |
1478
2239
  | `visible` | 选填 | 是否显示图层。 |
1479
2240
  | `showProbe` | 选填 | 是否开启鼠标探针,需要 `tifUrl` 或灰度图数值。 |
1480
2241
  | `showLabel` | 选填 | 是否显示网格注记,需要 `tifUrl` 或灰度图数值。 |
@@ -1483,11 +2244,7 @@ imageLayer.destroy({ layerId: 'radar-image' })
1483
2244
  | `gridTotal` | 选填 | 切片网格数量。 |
1484
2245
  | `tileSize` | 选填 | 切片尺寸。 |
1485
2246
 
1486
- 兼容方法:
1487
-
1488
- - `load(items, { index, visible })`:兼容旧的图片集合加载方式,会把 `items[index]` 写入当前图层。
1489
- - `switchTo(idOrIndex)`:在已加载的 `items` 内切换到某一项。
1490
- - `next() / prev()`:只适合本地小数组演示,不推荐作为业务时次更新主路径。
2247
+ 图片重采样依赖 Canvas 读取像素,跨域图片需要服务端允许 CORS。
1491
2248
 
1492
2249
  <a id="grid-layer-controller"></a>
1493
2250
 
@@ -1555,7 +2312,7 @@ await gridLayer.loadData({
1555
2312
  },
1556
2313
  })
1557
2314
 
1558
- gridLayer.update({ decimalPlaces: 2 })
2315
+ await gridLayer.update({ decimalPlaces: 2 })
1559
2316
  gridLayer.hide()
1560
2317
  gridLayer.show()
1561
2318
  gridLayer.destroy()
@@ -1570,9 +2327,31 @@ gridLayer.destroy()
1570
2327
 
1571
2328
  灰度图要表达真实业务值时,应显式传入 `minValue / maxValue`。数值图层不使用色带绘制背景;如果需要同时显示彩色图片,请使用 `ImageLayerController`。
1572
2329
 
1573
- ## 统一粒子风场
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
+ ## 风场统一入口(推荐)
1574
2345
 
1575
- 同一组 API 会根据当前引擎自动选择渲染器:Cesium 使用 GPU 粒子风场,Leaflet 使用迁自 one-map 的 Canvas 粒子风场。
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`。
1576
2355
 
1577
2356
  ```js
1578
2357
  const windData = await fetch('/mock/uv.json').then((response) => response.json())
@@ -1610,9 +2389,13 @@ mapCore.removeWindLayer({ layerId: 'surface-wind' })
1610
2389
 
1611
2390
  跨引擎统一使用 `Bound / DataAry` 数据格式。公共参数包括 `maxParticles`、`speedFactor`、`lineWidth`、`fadeOpacity`、`particleOpacity`、`color` 和 `visible`;适配层会统一速度、帧率拖尾、屏幕线宽、设备像素比和粒子视觉密度。`maxParticles` 表示跨引擎视觉预算:Cesium 的实际粒子数会按 GPU 纹理向上取整为整数平方,Leaflet 会按 Canvas 拖尾覆盖率换算实际粒子数,均可通过图层状态查看;算法和投影不同,因此不保证逐像素完全一致。Leaflet 还支持 `particleGap`、`maxAge`、`frameRate`、`minSpeed`。统一风场属于 BaseGIS 托管图层,`setEngine()` 切换引擎时会自动使用原数据和最新参数恢复。
1612
2391
 
1613
- ## GPU 粒子风场
2392
+ <a id="gpu-wind-layer"></a>
2393
+
2394
+ ## GPU 风场
1614
2395
 
1615
- GPU 粒子风场是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用同名方法时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
2396
+ `upsertGpuWindLayer()` 与统一入口在 Cesium 下使用同一套 GPU 实现。本节接口直接使用 GPU 参数,不参与 BaseGIS 的引擎切换恢复;统一入口还会换算线宽等参数,因此两组接口的参数值不宜直接照搬。
2397
+
2398
+ GPU 风场仅支持 Cesium。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用本节接口时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
1616
2399
 
1617
2400
  Cesium 由宿主通过 `window.Cesium` 提供。启用 `terrainEnabled` 后,图层会采样当前 `viewer.terrainProvider`,让轨迹高度随地形变化;如果当前使用椭球地形,采样高度为 0。
1618
2401
 
@@ -1652,9 +2435,9 @@ if (!result.success) {
1652
2435
  const windData = {
1653
2436
  // 经度最小值、纬度最小值、经向格点数、纬向格点数、
1654
2437
  // 经度跨度、纬度跨度、数值缩放倍数。
1655
- Bound: [100, 10, 181, 91, 80, 40, 10],
2438
+ Bound: [100, 10, 2, 2, 10, 10, 10],
1656
2439
  // 每个格点按 U、V 交错;纬度行从南向北排列。
1657
- DataAry: [u0, v0, u1, v1],
2440
+ DataAry: [30, 10, 40, 15, 20, 10, 35, 5],
1658
2441
  }
1659
2442
  ```
1660
2443
 
@@ -1672,7 +2455,7 @@ const windData = {
1672
2455
  },
1673
2456
  u: new Float32Array(181 * 91),
1674
2457
  v: new Float32Array(181 * 91),
1675
- // index-gpu.vue 解码结果从北向南排列,因此使用 north-to-south。
2458
+ // 仅当数据行从北向南排列时设置;默认按南到北排列。
1676
2459
  rowOrder: 'north-to-south',
1677
2460
  }
1678
2461
  ```
@@ -1740,6 +2523,76 @@ mapCore.removeGpuWindLayer({ layerId: 'surface-gpu-wind' })
1740
2523
 
1741
2524
  GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后,业务应重新调用 `upsertGpuWindLayer`;需要自动恢复时改用 `upsertWindLayer`。核心渲染流程改编自 [RaymanNg/3D-Wind-Field](https://github.com/RaymanNg/3D-Wind-Field),遵循源码目录内 `LICENSE-RaymanNg.txt` 的 MIT License。
1742
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
+
1743
2596
  <a id="volume-rendering"></a>
1744
2597
 
1745
2598
  ## 三维体渲染
@@ -1751,11 +2604,17 @@ GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后
1751
2604
  ### 基础用法
1752
2605
 
1753
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
+
1754
2613
  const result = mapCore.upsertVolumeLayer({
1755
2614
  // 建议必填:图层唯一 id。再次使用同一个 layerId 调用会覆盖旧体渲染层。
1756
2615
  layerId: 'volume-demo',
1757
2616
 
1758
- // 必填:体数据一维数组,长度通常为 rows * cols * heights。
2617
+ // 必填:0~255 编码的一维数组,长度必须等于 rows * cols * heights。
1759
2618
  data: volumeValues,
1760
2619
 
1761
2620
  // 必填:体数据范围和网格尺寸。
@@ -1766,19 +2625,20 @@ const result = mapCore.upsertVolumeLayer({
1766
2625
  // 纬度范围。
1767
2626
  ymin: 30,
1768
2627
  ymax: 40,
1769
- // 高度范围,单位按业务数据约定。
2628
+ // 高度范围,单位米。
1770
2629
  zmin: 0,
1771
2630
  zmax: 10000,
1772
- // 纬向、经向、高度向网格数量。
1773
- rows: 100,
1774
- cols: 100,
1775
- heights: 30,
2631
+ // 经度 X、纬度 Y、高度 Z 的网格数量,均为不小于 2 的整数。
2632
+ rows: 3,
2633
+ cols: 2,
2634
+ heights: 2,
1776
2635
  },
1777
2636
 
1778
2637
  // 选填:体渲染 shader 参数,可后续单独更新。
1779
2638
  parameters: {
1780
- // 采样阈值。值越小,通常显示范围越少;具体效果和数据归一化有关。
1781
- threshold: 0.3,
2639
+ // 使用 0~255 编码直接定位色带,不再采用旧的 0~15 映射。
2640
+ normalizedColorRamp: 1,
2641
+ opacity: 0.72,
1782
2642
  // 光线步进采样次数。越大越细腻,也越耗性能。
1783
2643
  steps: 100,
1784
2644
  // x/y/z 三个方向裁切位置,默认 -0.5 表示不裁切。
@@ -1787,6 +2647,8 @@ const result = mapCore.upsertVolumeLayer({
1787
2647
  zCut: -0.5,
1788
2648
  },
1789
2649
 
2650
+ colorRamp: ['#2f80ed', '#22c55e', '#facc15', '#ef4444'],
2651
+
1790
2652
  // 选填:初始是否显示。
1791
2653
  visible: true,
1792
2654
  })
@@ -1798,13 +2660,13 @@ if (!result.success) {
1798
2660
 
1799
2661
  ### 更新参数
1800
2662
 
1801
- 更新透明阈值、步进数、裁切面时,不需要重新加载体数据,直接更新参数即可。
2663
+ 更新透明度、密度、步进数或裁切面时,不需要重新加载体数据,直接更新参数即可。
1802
2664
 
1803
2665
  ```js
1804
2666
  mapCore.updateVolumeLayerParameters({
1805
2667
  layerId: 'volume-demo',
1806
2668
  parameters: {
1807
- threshold: 0.5,
2669
+ opacity: 0.5,
1808
2670
  steps: 180,
1809
2671
  zCut: 0.1,
1810
2672
  },
@@ -1813,27 +2675,46 @@ mapCore.updateVolumeLayerParameters({
1813
2675
 
1814
2676
  ### 更新数据
1815
2677
 
1816
- 更新数据或空间范围时,继续使用同一个 `layerId` 调用 `upsertVolumeLayer`。适配层会先移除旧 Primitive,再创建新的体渲染层。
2678
+ 空间范围和网格尺寸不变时,使用 `updateVolumeLayerData()` 更新同一 Primitive 的体纹理:
2679
+
2680
+ ```js
2681
+ mapCore.updateVolumeLayerData({
2682
+ layerId: 'volume-demo',
2683
+ data: Uint8Array.from(volumeValues, (value) => Math.min(255, value + 10)),
2684
+ })
2685
+ ```
2686
+
2687
+ 新数据长度必须与原网格一致。此方法会恢复显示;如果需要保持隐藏,更新后再调用 `hideVolumeLayer()`。空间范围或网格尺寸改变时,使用同一 `layerId` 重新调用 `upsertVolumeLayer({ data, option, ... })`。新 Primitive 创建并加入场景后才释放旧图层,创建失败时保留旧图层。
2688
+
2689
+ ### 按屏幕位置取值
2690
+
2691
+ `sampleVolumeLayerValue()` 沿屏幕位置的相机射线采样,选取对可见体云贡献最大的点,并对数据做三线性插值。`position` 为相对地图画布左上角的 CSS 像素坐标,不是经纬度:
1817
2692
 
1818
2693
  ```js
1819
- mapCore.upsertVolumeLayer({
2694
+ const sampleResult = mapCore.sampleVolumeLayerValue({
1820
2695
  layerId: 'volume-demo',
1821
- data: nextVolumeValues,
1822
- option: nextVolumeOption,
1823
- parameters: currentParameters,
2696
+ position: { x: 320, y: 180 },
2697
+ steps: 120,
2698
+ // 可选 data:与纹理同长度、同顺序的原始业务值数组。
2699
+ // 不传则返回渲染数据值;命中位置始终由可见体云决定。
1824
2700
  })
2701
+ if (sampleResult.success) {
2702
+ console.log(sampleResult.data) // { value, lon, lat, height },height 单位米。
2703
+ }
1825
2704
  ```
1826
2705
 
2706
+ 图层隐藏、未命中或被地表遮挡时返回失败结果。取值采样步数限制为 `32~400`,与渲染步数上限不同。
2707
+
1827
2708
  ### 显隐、定位和移除
1828
2709
 
1829
2710
  ```js
1830
2711
  mapCore.hideVolumeLayer({ layerId: 'volume-demo' })
1831
2712
  mapCore.showVolumeLayer({ layerId: 'volume-demo' })
1832
2713
  mapCore.flyToVolumeLayer({ layerId: 'volume-demo', duration: 0.8 })
1833
- mapCore.removeVolumeLayer({ layerId: 'volume-demo' })
1834
2714
 
1835
2715
  const stateResult = mapCore.getVolumeLayerState({ layerId: 'volume-demo' })
1836
2716
  console.log(stateResult.data)
2717
+ mapCore.removeVolumeLayer({ layerId: 'volume-demo' })
1837
2718
  ```
1838
2719
 
1839
2720
  ### 方法总表
@@ -1841,30 +2722,37 @@ console.log(stateResult.data)
1841
2722
  | 方法 | 参数 | 说明 |
1842
2723
  | --- | --- | --- |
1843
2724
  | `upsertVolumeLayer(payload)` | 体渲染配置 | 创建或更新体渲染层。同 `layerId` 会覆盖旧图层。 |
2725
+ | `updateVolumeLayerData(payload)` | `{ layerId, data }` | 同范围、同网格尺寸时更新体纹理,不重建 Primitive。 |
1844
2726
  | `updateVolumeLayerParameters(payload)` | `{ layerId, parameters }` | 更新体渲染 shader 参数,不重新加载体数据。 |
1845
2727
  | `showVolumeLayer(payload)` | `{ layerId }` | 显示体渲染层。 |
1846
2728
  | `hideVolumeLayer(payload)` | `{ layerId }` | 隐藏体渲染层。 |
1847
2729
  | `removeVolumeLayer(payload)` | `{ layerId }` | 移除体渲染层并释放 Primitive。 |
1848
2730
  | `flyToVolumeLayer(payload)` | `{ layerId, duration }` | 飞到体渲染层经纬度范围。 |
1849
2731
  | `getVolumeLayerState(payload)` | `{ layerId }` | 获取图层状态。 |
2732
+ | `sampleVolumeLayerValue(payload)` | `{ layerId, position, data?, steps? }` | 按屏幕位置取值;可用 `windowPosition`、`values` 作为别名。 |
1850
2733
 
1851
2734
  ### 参数总表
1852
2735
 
1853
2736
  | 参数 | 是否必填 | 说明 |
1854
2737
  | --- | --- | --- |
1855
2738
  | `layerId` / `volumeLayerId` / `id` | 建议必填 | 体渲染图层 id,不传默认 `volume-default`。 |
1856
- | `data` / `value` / `values` | 必填 | 体数据一维数组,通常按层、行、列展开。 |
2739
+ | `data` / `value` / `values` | 必填 | `0~255` 字节编码,长度为 `rows * cols * heights`;索引为 `z * rows * cols + y * rows + x`。业务浮点值需先编码,不会自动归一化。 |
1857
2740
  | `option` | 必填 | 体数据空间范围和网格尺寸。 |
1858
2741
  | `option.xmin / xmax` | 必填 | 经度最小值和最大值。 |
1859
2742
  | `option.ymin / ymax` | 必填 | 纬度最小值和最大值。 |
1860
- | `option.zmin / zmax` | 必填 | 高度最小值和最大值。 |
1861
- | `option.rows` | 必填 | 纬向网格数量。 |
1862
- | `option.cols` | 必填 | 经向网格数量。 |
1863
- | `option.heights` | 必填 | 高度层数量。 |
1864
- | `parameters.threshold` | 选填 | 采样阈值。 |
1865
- | `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` 定位色带。 |
1866
2754
  | `parameters.xCut / yCut / zCut` | 选填 | 三个方向的裁切位置,默认 `-0.5`。 |
1867
- | `colorRamp` / `colors` / `colorKeys` | 选填 | 自定义色带配置。 |
2755
+ | `colorRamp` | 选填 | CSS 颜色数组、RGBA 数组列表或展平的 RGBA 字节数组。 |
1868
2756
  | `geometry` / `dim` | 选填 | 自定义体渲染几何或维度,普通业务通常不需要传。 |
1869
2757
  | `visible` | 选填 | 初始是否显示,默认显示。 |
1870
2758
 
@@ -1874,27 +2762,28 @@ console.log(stateResult.data)
1874
2762
 
1875
2763
  三维切片/剖面渲染也是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它把三维网格数据按 X、Y、Z 三个方向切出剖面,适合气象温度、湿度、风场标量、污染物浓度等三维格点数据查看。
1876
2764
 
1877
- 这里的“切片”指三维数据剖面,不是 `ImageLayerController` 里的大图切片。
2765
+ 这里的“切片”指三维数据剖面,不是 `ImageLayerController` 里的大图切片。当前 Z 轴按 hPa 气压处理,内部将气压换算为高度后放大 10 倍显示,不接受任意米制高度层。
1878
2766
 
1879
2767
  ### 数据格式
1880
2768
 
1881
2769
  ```js
1882
2770
  const sectionData = {
1883
2771
  Bound: [
1884
- 1000, // 0: LayerMax,最高气压层或起始层值。
2772
+ 1000, // 0: LayerMax,起始气压值(hPa),较大气压在下方。
1885
2773
  108.68, // 1: LonMin,经度最小值。
1886
2774
  28.59, // 2: LatMin,纬度最小值。
1887
2775
  10, // 3: LayerNums,层数。
1888
2776
  147, // 4: LonNums,经向格点数。
1889
2777
  198, // 5: LatNums,纬向格点数。
1890
- 900, // 6: DLayer LayerMin 相关值,按数据生成规则提供。
2778
+ 900, // 6: DLayer,气压跨度;LayerMin = LayerMax - DLayer。
1891
2779
  16.74, // 7: DLon,经度跨度。
1892
2780
  16.71, // 8: DLat,纬度跨度。
1893
- 1, // 9: ValueScale,数值缩放。
1894
- 1, // 10: 预留/数据标记。
2781
+ 1, // 9: ValueScale,业务值 = 原始值 / ValueScale。
2782
+ 1, // 10: Stride,每个格点的分量数;标量为 1。
1895
2783
  [1000, 925, 850, 700, 600, 500, 400, 300, 200, 100], // 11: LayerList,气压层列表。
1896
2784
  ],
1897
- // 必填:三维格点值一维数组,长度通常为 LayerNums * LatNums * LonNums。
2785
+ // 长度必须等于 LayerNums * LatNums * LonNums * Stride
2786
+ // 每层从西南角开始,经度最快变化,再纬度,最后气压层。
1898
2787
  DataAry: valueList,
1899
2788
  }
1900
2789
 
@@ -1913,6 +2802,8 @@ const optionData = {
1913
2802
  }
1914
2803
  ```
1915
2804
 
2805
+ `Bound[11]` 可传 `null` 表示等间距气压层;传列表时长度必须等于 `LayerNums`,首项必须等于 `Bound[0]`。`Stride > 1` 时当前实现计算各分量的模长。
2806
+
1916
2807
  ### 加载或更新数据
1917
2808
 
1918
2809
  同一个 `layerId` 重复调用 `upsertSectionLayer` 就是更新数据。适配层会清理旧切片,再加载新数据。
@@ -1936,15 +2827,16 @@ const result = mapCore.upsertSectionLayer({
1936
2827
 
1937
2828
  // 选填:是否显示坐标轴和刻度。
1938
2829
  showAxis: false,
2830
+ // 选填:有 DEM 时避免近地切片被地形裁断,默认 false。
2831
+ ignoreTerrainDepth: true,
1939
2832
  })
1940
2833
 
1941
- if (!result.success) {
2834
+ if (result.success) {
2835
+ const state = result.data.state
2836
+ console.log(state.XRange, state.YRange, state.ZRange)
2837
+ } else {
1942
2838
  console.warn(result.message)
1943
- return
1944
2839
  }
1945
-
1946
- const state = result.data.state
1947
- console.log(state.XRange, state.YRange, state.ZRange)
1948
2840
  ```
1949
2841
 
1950
2842
  ### 渲染 X / Y / Z 切片
@@ -1964,7 +2856,7 @@ mapCore.renderSectionLayer({
1964
2856
  value: 39.9,
1965
2857
  })
1966
2858
 
1967
- // Z 高度/气压方向切片。
2859
+ // Z 气压方向切片,单位 hPa。
1968
2860
  mapCore.renderSectionLayer({
1969
2861
  layerId: 'section-demo',
1970
2862
  sectionType: 2,
@@ -1978,7 +2870,7 @@ mapCore.renderSectionLayer({
1978
2870
  | --- | --- | --- |
1979
2871
  | `0` | 经度方向剖面 | 经度值。 |
1980
2872
  | `1` | 纬度方向剖面 | 纬度值。 |
1981
- | `2` | 高度/气压方向剖面 | 气压层或高度层值。 |
2873
+ | `2` | 气压方向剖面 | 气压值,单位 hPa。 |
1982
2874
  | `3` | 全量剖面 | 可不传 `value`。 |
1983
2875
 
1984
2876
  ### 显隐、移除和定位
@@ -1994,16 +2886,16 @@ mapCore.removeSectionLayer({
1994
2886
  sectionType: 2,
1995
2887
  })
1996
2888
 
1997
- // 移除整个切片图层。
1998
- mapCore.removeSectionLayer({ layerId: 'section-demo' })
1999
-
2000
2889
  // 飞到切片数据范围。
2001
2890
  mapCore.flyToSectionLayer({ layerId: 'section-demo', duration: 0.8 })
2891
+
2892
+ // 移除整个切片图层。
2893
+ mapCore.removeSectionLayer({ layerId: 'section-demo' })
2002
2894
  ```
2003
2895
 
2004
2896
  ### hover 取值
2005
2897
 
2006
- 页面先用 Cesium 拾取得到鼠标所在三维点,再交给 `sampleSectionLayerValue` 计算当前切片上的数值。
2898
+ 页面先用 Cesium 拾取得到鼠标所在三维点,再将经纬度与对应气压交给 `sampleSectionLayerValue`。`hpa` 不是 Cesium 的 `cartographic.height`;应先还原 10 倍高度显示比例,再按剖面使用的气压高度关系换算。
2007
2899
 
2008
2900
  ```js
2009
2901
  const result = mapCore.sampleSectionLayerValue({
@@ -2047,9 +2939,10 @@ if (result.success) {
2047
2939
  | `colorInfo.rgbAry` | 建议必填 | 色标 RGB 数组。 |
2048
2940
  | `boxInfo` | 选填 | 自定义剖面盒子范围。不传时根据 `dataInfo` 自动生成。 |
2049
2941
  | `showAxis` | 选填 | 是否显示坐标轴和刻度。 |
2050
- | `sectionType` | 渲染、显隐、移除单个切片时必填 | `0` 经度,`1` 纬度,`2` 高度/气压,`3` 全量。 |
2942
+ | `ignoreTerrainDepth` | 选填 | 默认 `false`;设为 `true` 时,可见剖面暂时关闭 globe 地形深度检测,不改变切片自身深度写入。 |
2943
+ | `sectionType` | 渲染、显隐、移除单个切片时必填 | `0` 经度,`1` 纬度,`2` 气压,`3` 全量。 |
2051
2944
  | `value` / `param` | `sectionType` 为 `0/1/2` 时必填 | 切片位置值。 |
2052
- | `lon / lat / hpa` | hover 取值必填 | Cesium 拾取得到的经度、纬度、气压高度。 |
2945
+ | `lon / lat / hpa` | hover 取值必填 | 经度、纬度及换算后的气压(hPa),不是米制高度。 |
2053
2946
  | `showZLayer` | hover 取值选填 | Z 切片是否参与 hover 命中,默认参与。 |
2054
2947
 
2055
2948
  <a id="point-large-layer-controller"></a>
@@ -2062,160 +2955,54 @@ Leaflet 模式使用 BaseGIS 内置的 `pixi.js` 和 `leaflet-pixi-overlay` 异
2062
2955
 
2063
2956
  如果业务目标是“地图缩小时按屏幕网格抽稀,只显示代表点”,应使用后文的 `PointDensityController`。
2064
2957
 
2065
- ### Demo 位置
2066
-
2067
- 仓库内已有对应 demo:
2068
-
2069
- - 路由:`/test-page-5`
2070
- - 页面:`src/views/test-page-5/index.vue`
2071
- - 当前 demo 覆盖能力:加载、更新、显隐、高亮、取消高亮、删除点位、清空、销毁、状态读取、视角复位。
2072
-
2073
- demo 源码内部使用:
2074
-
2075
- ```js
2076
- import { BaseGIS } from '@/gis'
2077
- import { PointLargeLayerController } from '@/gis/methods'
2078
- ```
2958
+ ### 基础用法
2079
2959
 
2080
- npm 包外部项目使用:
2960
+ 地图初始化成功后创建控制器,再调用 `load()` 加载点位。以下使用内置圆点,无需先准备图片资源;实际项目可通过 `icon / highlightIcon` 替换普通与高亮图标。
2081
2961
 
2082
2962
  ```js
2083
- import { BaseGIS } from '@3clear/basegis'
2084
2963
  import { PointLargeLayerController } from '@3clear/basegis/methods'
2085
- ```
2086
-
2087
- ### 使用方式一:构造时传入 mapCore
2088
-
2089
- 这是业务页面里最常用的写法。地图初始化完成后创建控制器,再调用 `load` 加载点位。
2090
-
2091
- ```js
2092
- const mapCore = new BaseGIS({
2093
- engineType: 'leaflet',
2094
- containerId: 'map',
2095
- })
2096
-
2097
- const initResult = mapCore.init()
2098
- if (!initResult.success) {
2099
- console.warn(initResult.message)
2100
- }
2101
2964
 
2102
2965
  const pointLayer = new PointLargeLayerController({
2103
- // 必填:BaseGIS 实例。
2104
- mapCore,
2105
- // 建议必填:图层唯一 ID,后续显隐、高亮、删除都基于这个图层。
2106
- layerId: 'station-large',
2107
- // 选填:点位唯一值字段,不传默认使用 id。
2108
- idKey: 'stationCode',
2109
-
2110
- // 点位数据。也可以使用 data/items 字段;推荐统一使用 points。
2111
- points,
2112
-
2113
- // 初始是否显示图层。不传时默认 true。
2114
- visible: true,
2115
-
2116
- // 默认普通状态图标。支持图片 URL、base64、data URL。
2117
- icon: normalPointIcon,
2118
-
2119
- // 默认高亮状态图标。调用 setHighlight(id) 后使用。
2120
- highlightIcon: highlightPointIcon,
2121
-
2122
- // 样式规则读取的字段名。下面 styleRules 里的 gt/default 会基于 value 判断。
2123
- styleField: 'value',
2124
-
2125
- // 点位样式规则。从上到下匹配,命中后使用该规则里的 icon/highlightIcon/style 等配置。
2126
- styleRules: [
2127
- {
2128
- // value < 300 时命中
2129
- lt: 300,
2130
- icon: lowPointIcon,
2131
- highlightIcon: highlightPointIcon,
2132
- },
2133
- {
2134
- // value > 780 时命中
2135
- gt: 780,
2136
- icon: warningPointIcon,
2137
- highlightIcon: highlightPointIcon,
2138
- },
2139
- {
2140
- // 300 <= value < 780
2141
- gte: 300,
2142
- lt: 780,
2143
- icon: normalPointIcon,
2144
- highlightIcon: highlightPointIcon,
2145
- },
2146
- {
2147
- // 兜底规则。没有命中前面规则的点位使用普通图标。
2148
- default: true,
2149
- icon: normalPointIcon,
2150
- highlightIcon: highlightPointIcon,
2151
- },
2152
- ],
2153
-
2154
- // 普通图标宽度,单位像素。
2155
- width: 18,
2156
-
2157
- // 普通图标高度,单位像素。
2158
- height: 18,
2159
-
2160
- // 普通图标缩放比例。
2161
- scale: 1,
2162
-
2163
- // 未传 icon 时,可以使用内置圆点图标。下面这些配置用于控制圆点样式。
2164
- // color: '#19d3a2',
2165
- // outlineColor: '#ffffff',
2166
- // outlineWidth: 2,
2167
-
2168
- // 未传 highlightIcon 时,可以使用内置高亮圆点图标。下面这些配置用于控制高亮圆点样式。
2169
- // highlightColor: '#ffcf33',
2170
- // highlightOutlineColor: '#ffffff',
2171
- // highlightWidth: 20,
2172
- // highlightHeight: 20,
2173
- // highlightScale: 1.18,
2174
-
2175
- // 分帧构建批量大小。点位很多时可以调大或调小,默认 5000。
2176
- // chunkSize: 10000,
2177
-
2178
- // Cesium 专用:禁用深度检测距离。需要点位不被地形或模型遮挡时可使用。
2179
- // disableDepthTestDistance: Number.POSITIVE_INFINITY,
2180
-
2181
- // Cesium 专用:按相机距离缩放图标。
2182
- // 数组含义:[近距离, 近距离缩放, 远距离, 远距离缩放]。
2183
- // scaleByDistance: [50000, 1, 9000000, 0.34],
2184
-
2185
- // 点击点位回调。event.data 是原始点位数据。
2186
- onClick: ({ id, data }) => {`点击点位:${data || id}。`},
2187
-
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
+ },
2188
2982
  })
2189
2983
 
2190
2984
  await pointLayer.load({
2191
2985
  points: [
2192
- {
2193
- stationCode: 'A001',
2194
- name: '站点 A001',
2195
- longitude: 104,
2196
- latitude: 35,
2197
- value: 86,
2198
- },
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 },
2199
2989
  ],
2200
2990
  })
2201
2991
  ```
2202
2992
 
2203
2993
  `styleRules` 常用匹配方式:
2204
2994
 
2205
- | 写法 | 说明 |
2206
- |---------------------------------------------------| --- |
2207
- | `{ field: 'aqi', min: 51, max: 100 }` | 指定字段并按区间匹配。 |
2208
- | `{ gt: 780 }` | 使用 `styleField` 指定的字段做大于判断。 |
2209
- | `{ gte: 51, lt: 101 }` | 使用 `styleField` 指定的字段做区间判断。 |
2210
- | `{ values: ['优', '良'] }` | 命中指定值集合。 |
2211
- | `{ operator: '>', value: 100 }` | 使用操作符匹配,支持 `> / >= / < / <= / == / === / != / !== / in`。 |
2212
- | `{ when:(point, index, record) { return true } }` | 完全自定义匹配函数。 |
2213
- | `{ default: true }` | 兜底规则。 |
2214
-
2215
-
2216
-
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 }` | 兜底规则。 |
2217
3004
 
2218
- ### 使用方式:更新数据或样式
3005
+ ### 更新数据或样式
2219
3006
 
2220
3007
  时间轴、实时刷新等场景中,建议复用同一个控制器实例,不要每次刷新都重新 `new PointLargeLayerController()`。
2221
3008
 
@@ -2550,7 +3337,7 @@ onBeforeUnmount(() => {
2550
3337
  <style scoped lang="scss">
2551
3338
  .station-cluster-map {
2552
3339
  width: 100%;
2553
- height: 100%;
3340
+ height: 100vh;
2554
3341
  }
2555
3342
  </style>
2556
3343
  ```
@@ -2602,9 +3389,11 @@ if (!result.success) {
2602
3389
  单点点击的第一个参数是原始点位数据:
2603
3390
 
2604
3391
  ```js
2605
- onClick(point, engineObject, event) {
2606
- console.log(point.id)
2607
- }
3392
+ await stationCluster.setConfig({
3393
+ onClick(point, engineObject, event) {
3394
+ console.log(point.id)
3395
+ },
3396
+ })
2608
3397
  ```
2609
3398
 
2610
3399
  聚合点击的第一个参数结构如下:
@@ -2735,9 +3524,9 @@ mapCore.removePointClusterLayer({ layerId: 'station-cluster' })
2735
3524
 
2736
3525
  仓库内已有多个点位抽稀 demo:
2737
3526
 
2738
- - `/test-page-6`:站点 ICON 抽稀,使用 `imageCallback` 按数据绘制站点图标。
2739
3527
  - `/test-page-8`:空气质量站点抽稀,使用固定 `image` 图标。
2740
3528
  - `/test-page-13`:空气质量业务页里的城市点位抽稀。
3529
+ - `/test-page-41`:`imageCallback` 同步返回 Canvas 图标和 `pulse` 告警扩散参数。
2741
3530
 
2742
3531
  ### 基础用法
2743
3532
 
@@ -2763,12 +3552,31 @@ const density = new PointDensityController({
2763
3552
  // 同一网格内保留哪个点。字段值越大越优先显示。
2764
3553
  priorityKey: 'level',
2765
3554
 
2766
- // 默认图标。
2767
- image: '/icons/station.png',
2768
-
2769
- // 图标尺寸。
2770
- width: 28,
2771
- 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
+ },
2772
3580
 
2773
3581
  // 点击点位回调。第一个参数是原始点位数据。
2774
3582
  onClick(point) {
@@ -2783,122 +3591,13 @@ const density = new PointDensityController({
2783
3591
 
2784
3592
  await density.load({
2785
3593
  points: [
2786
- { staNum: 'A001', longitude: 104, latitude: 35, level: 5 },
2787
- { 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' },
2788
3596
  ],
2789
3597
  })
2790
3598
  ```
2791
3599
 
2792
- ### 完整配置模板
2793
-
2794
- 下面的配置接近 `test-page-6` 的写法,字段已加注释。业务中按需保留即可。
2795
-
2796
- ```js
2797
- const density = new PointDensityController({
2798
- // BaseGIS 实例。控制器只通过 BaseGIS 调用当前引擎能力,不直接依赖 Cesium / Leaflet。
2799
- mapCore,
2800
-
2801
- // 图层唯一标识。后续 update/show/hide/clear/destroy 都按该 id 定位图层。
2802
- layerId: 'station-density',
2803
-
2804
- // 点位唯一值字段。默认 id;如果点位唯一值在 properties 中,也会尝试读取 properties[idKey]。
2805
- idKey: 'staNum',
2806
-
2807
- // 初始是否显示图层。不传默认 true。
2808
- visible: true,
2809
-
2810
- // 原始点位数据。也可以不在构造时传,后续 density.load({ points }) 传入。
2811
- points: stationList,
2812
-
2813
- // 经度字段候选列表。默认已支持 longitude/lon/lng/x。
2814
- longitudeKeys: ['longitude', 'lon', 'lng', 'x'],
2815
-
2816
- // 纬度字段候选列表。默认已支持 latitude/lat/y。
2817
- latitudeKeys: ['latitude', 'lat', 'y'],
2818
-
2819
- // 高度字段候选列表。默认已支持 height/altitude/z。
2820
- heightKeys: ['height', 'altitude', 'z'],
2821
-
2822
- // 是否开启抽稀。true 时会按屏幕网格去重;false 时只做视野过滤,不做网格碰撞去重。
2823
- enableThinning: true,
2824
-
2825
- // 抽稀网格大小,单位屏幕像素。值越大,保留点越少;值越小,保留点越多。
2826
- gridSize: 150,
2827
-
2828
- // gridSize 的别名。如果同时传 gridSize 和 pixelRange,以 gridSize 为准。
2829
- // pixelRange: 150,
2830
-
2831
- // 最多显示点数量。超过后会按优先级截断。不传默认 Infinity。
2832
- maxCount: 500,
2833
-
2834
- // 同一网格内点位优先级字段。字段值越大越优先保留。
2835
- priorityKey: 'level',
2836
-
2837
- // 自定义优先级函数。返回值越大越优先保留;传了它后优先级逻辑可完全由业务决定。
2838
- // priorityCallback(point, markerOrBillboard, index) {
2839
- // return point.level * 100 + point.value
2840
- // },
2841
-
2842
- // 默认图标地址。支持图片 URL、base64、data URL。
2843
- image: '/icons/station.png',
2844
-
2845
- // 图标地址别名,和 image 作用一致。
2846
- // icon: '/icons/station.png',
2847
- // iconUrl: '/icons/station.png',
2848
-
2849
- // 按点位动态生成图标。适合空气质量、告警等级等需要每个点图标不同的场景。
2850
- // 返回值可以是图片地址、base64、data URL。
2851
- // imageCallback(point, index) {
2852
- // return createStationIcon(point)
2853
- // },
2854
-
2855
- // 图标宽度,单位像素。
2856
- width: 28,
2857
-
2858
- // 图标高度,单位像素。
2859
- height: 28,
2860
-
2861
- // 地图移动、缩放后重新计算抽稀的节流时间,单位毫秒。
2862
- throttleTime: 120,
2863
-
2864
- // Cesium 专用:视野过滤缓冲,单位屏幕像素。
2865
- // 值越大,视野边缘附近的点越不容易在移动时频繁出现/消失。
2866
- viewportBuffer: 100,
2867
-
2868
- // Cesium 专用:3D 场景下是否剔除地球背面的点。
2869
- cullByGlobe: true,
2870
-
2871
- // Cesium 专用:Billboard 是否贴地。
2872
- clampToGround: false,
2873
-
2874
- // Cesium 专用:Billboard 缩放比例。
2875
- scale: 1,
2876
-
2877
- // Cesium 专用:禁用深度检测距离。
2878
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
2879
-
2880
- // Cesium 专用:平滑更新,减少刷新抽稀结果时的突兀感。
2881
- smoothUpdate: true,
2882
- smoothUpdateFrames: 2,
2883
-
2884
- // Leaflet 专用:视野 bounds 扩展比例。
2885
- // 例如 0.1 表示在当前视野基础上向外扩展 10% 后再过滤点位。
2886
- boundsBufferRatio: 0,
2887
-
2888
- // Leaflet 专用:Marker 层级偏移。
2889
- zIndexOffset: 0,
2890
-
2891
- // 点位点击回调。第一个参数是原始点位数据。
2892
- onClick(point) {
2893
- console.log(point)
2894
- },
2895
-
2896
- // 状态变化回调。每次抽稀刷新、显隐、清空后会尽量触发。
2897
- onStateChange(state) {
2898
- console.log(state.visibleCount, state.hiddenCount)
2899
- },
2900
- })
2901
- ```
3600
+ 上例中的 `factoryImage` 是创建抽稀层前已加载完成的 `Image`。`pulse` 使用与普通 Marker 相同的参数,只为抽稀后实际保留的点创建;点被替换、移出视野、隐藏、清空或删除时,扩散圈会随图标一起回收。
2902
3601
 
2903
3602
  ### 点位数据格式
2904
3603
 
@@ -3044,7 +3743,7 @@ density.destroy()
3044
3743
  | `priorityKey` | 否 | - | 同一网格内点位优先级字段,值越大越优先显示。 |
3045
3744
  | `priorityCallback` | 否 | - | 自定义优先级函数,返回值越大越优先显示。 |
3046
3745
  | `image` / `icon` / `iconUrl` | 否 | - | 默认图标地址。 |
3047
- | `imageCallback` | 否 | - | 图标生成函数,适合每个点图标不同的场景。 |
3746
+ | `imageCallback` | 否 | - | 图标生成函数;配置对象可附带 `pulse` 告警扩散参数。 |
3048
3747
  | `width` | 否 | `32` | 图标宽度,单位像素。 |
3049
3748
  | `height` | 否 | `32` | 图标高度,单位像素。 |
3050
3749
  | `throttleTime` | 否 | `120` | 地图移动、缩放后刷新抽稀的节流时间,单位毫秒。 |
@@ -3088,7 +3787,7 @@ console.log(state)
3088
3787
 
3089
3788
  ## 等值线与中心标注能力选择
3090
3789
 
3091
- 对外只保留 2 个控制器:已有线坐标或中心坐标时用 `ContourLayerController`,手里是栅格时用 `RasterContourController`。线值标签与中心标注都属于通用 Contour 渲染配置,不再需要单独的气压组合层。
3790
+ 已有线坐标或中心坐标时用 `ContourLayerController`,输入栅格时用 `RasterContourController`。线值标签与中心标注均使用通用 Contour 渲染配置。
3092
3791
 
3093
3792
  | 手里的数据 / 目标 | 使用入口 | 负责内容 |
3094
3793
  | --- | --- | --- |
@@ -3384,7 +4083,7 @@ await rasterContour.loadGrid({
3384
4083
  | `minimumLinePoints` | `number` | 否 | `3` | 最短线点数,用于过滤栅格四角的两点补边。 |
3385
4084
  | `coordinatePrecision` | `number` | 否 | `6` | 输出经纬度小数位数。 |
3386
4085
  | `visible` | `boolean` | 否 | `true` | 初始显隐。 |
3387
- | 其他渲染参数 | - | 否 | - | 复用 [`ContourLayerController` 核心参数](#contour-layer-parameters),如 `color/width/weight/styleCallback/showLabel/labelViewportPadding`。 |
4086
+ | 其他渲染参数 | - | 否 | - | 复用 [`ContourLayerController` 核心参数](#contour-layer-parameters),如 `color/weight/styleCallback/showLabel/labelViewportPadding`;顶层 `width/height` 为栅格尺寸,线宽请用 `weight` 或 `styleCallback` 返回的 `width/weight`。 |
3388
4087
 
3389
4088
  级别选择优先级为 `thresholds` → `interval` → `thresholdCount`。显式传入 `thresholds` 时,只保留实际值域内的级别。
3390
4089
 
@@ -3506,10 +4205,14 @@ await pressureContour.load({
3506
4205
 
3507
4206
  <a id="wind-field-methods"></a>
3508
4207
 
3509
- ## WindFieldMethods
4208
+ ## Canvas 风场(WindFieldMethods
3510
4209
 
3511
4210
  用于叠加 Canvas 风场粒子动画。它通过 `BaseGIS` 获取容器、投影、视野范围和视图变化事件,不直接依赖某个引擎。
3512
4211
 
4212
+ 与 `upsertWindLayer()` 不同,该控制器在两个引擎中都使用 Canvas,可叠加风速底图,但不参与 BaseGIS 托管恢复。切换引擎时先清理旧控制器,再在新地图初始化后重新挂载并加载数据。
4213
+
4214
+ Leaflet 的统一风场入口使用的是另一套 Canvas 渲染器。本节控制器使用 `particleCount / velocityScale` 等参数,不能直接套用统一入口的 `maxParticles / speedFactor`。
4215
+
3513
4216
  ```js
3514
4217
  import { WindFieldMethods } from '@3clear/basegis/methods'
3515
4218
 
@@ -3519,9 +4222,9 @@ wind.addWindField({
3519
4222
  id: 'wind-main',
3520
4223
  data: windData,
3521
4224
  particleCount: 3000,
3522
- velocityScale: 0.01,
3523
4225
  lineWidth: 1,
3524
- color: 'rgba(80, 180, 255, 0.8)',
4226
+ // 单色粒子用一档色标;不传则使用内置风速分级色标。
4227
+ particleColorScale: [{ value: 0, color: '#50b4ff' }],
3525
4228
  })
3526
4229
 
3527
4230
  wind.updateWindField({
@@ -3561,11 +4264,9 @@ const wind = new WindFieldMethods({ mapCore })
3561
4264
  ```js
3562
4265
  const windData = {
3563
4266
  // [经度最小值, 纬度最小值, 经向网格数, 纬向网格数, 经度跨度, 纬度跨度, 数值缩放]
3564
- Bound: [70, 15, 121, 81, 60, 40, 10],
4267
+ Bound: [100, 10, 2, 2, 10, 10, 10],
3565
4268
  // 从左下角开始,按行平铺;每个格点两个值:[u, v]
3566
- DataAry: [
3567
- // u0, v0, u1, v1, ...
3568
- ],
4269
+ DataAry: [30, 10, 40, 15, 20, 10, 35, 5],
3569
4270
  }
3570
4271
  ```
3571
4272
 
@@ -3606,12 +4307,12 @@ const windData = {
3606
4307
  | `particleCount` | `number` | `1400` | 粒子总数。数值越大越密,渲染开销越高。 |
3607
4308
  | `maxFrameParticles` | `number` | `1200` | 单帧最多推进和绘制的粒子数。 |
3608
4309
  | `frameParticleLimit` | `number` | `1200` | `maxFrameParticles` 的别名。 |
3609
- | `sceneModeMaxFrameParticles` | `number` | `0` | 场景模式相关帧粒子上限预留参数。当前未作为主路径使用。 |
4310
+ | `sceneModeMaxFrameParticles` | `number` | `0` | 大于 `0` 时进一步限制 2D / 2.5D 场景每帧推进的粒子数,且不超过 `maxFrameParticles`。 |
3610
4311
  | `maxAge` | `number` | `58` | 粒子最大生命周期,超过后重新随机出生。 |
3611
4312
  | `frameRate` | `number` | `30` | 粒子动画目标帧率,内部限制在 `1 ~ 60`。 |
3612
4313
  | `velocityScale` | `number` | `900` | 风速到粒子位移的缩放系数。数值越大粒子移动越快。 |
3613
4314
  | `lineWidth` | `number` | `0.85` | 粒子轨迹基础线宽。 |
3614
- | `color` | `string` | `rgba(230, 248, 255, 0.52)` | 粒子单色兜底颜色。传了 `particleColorScale` 时优先按风速分级着色。 |
4315
+ | `color` | `string` | `rgba(230, 248, 255, 0.52)` | 粒子兜底颜色;默认已有 `particleColorScale`,单色效果请使用一档粒子色标。 |
3615
4316
  | `particleOpacity` | `number` | `0.58` | 粒子轨迹透明度。 |
3616
4317
  | `fadeOpacity` | `number` | `0.86` | 拖尾淡出强度,越接近 `1` 轨迹残留越长。 |
3617
4318
  | `blendMode` | `string` | `lighter` | Canvas 合成模式,例如 `source-over`、`lighter`。 |
@@ -3670,70 +4371,6 @@ const windData = {
3670
4371
  | `bounds` | 风场数据范围 `{ west, south, east, north }`。 |
3671
4372
  | `center` | 风场中心 `{ lon, lat }`。 |
3672
4373
 
3673
- ## 在 Vue 中封装 Hook
3674
-
3675
- 推荐页面用 hook 管理生命周期,组件只处理 UI。
3676
-
3677
- ```js
3678
- import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
3679
- import { BaseGIS } from '@3clear/basegis'
3680
-
3681
- export function useBaseGIS(options = {}) {
3682
- const mapCore = shallowRef(null)
3683
-
3684
- onMounted(() => {
3685
- mapCore.value = new BaseGIS(options)
3686
- mapCore.value.init()
3687
- })
3688
-
3689
- onBeforeUnmount(() => {
3690
- mapCore.value?.destroy()
3691
- mapCore.value = null
3692
- })
3693
-
3694
- async function setEngine(engineType) {
3695
- if (!mapCore.value || mapCore.value.getEngineType() === engineType) {
3696
- return { success: true }
3697
- }
3698
-
3699
- return mapCore.value.setEngine(engineType, {
3700
- ...options,
3701
- })
3702
- }
3703
-
3704
- return {
3705
- mapCore,
3706
- setEngine,
3707
- }
3708
- }
3709
- ```
3710
-
3711
- 使用:
3712
-
3713
- ```vue
3714
- <template>
3715
- <div id="map" class="map"></div>
3716
- </template>
3717
-
3718
- <script setup>
3719
- import { useBaseGIS } from './useBaseGIS'
3720
-
3721
- const { mapCore, setEngine } = useBaseGIS({
3722
- engineType: 'cesium',
3723
- containerId: 'map',
3724
- })
3725
- </script>
3726
- ```
3727
-
3728
- BaseGIS 托管图层会自动恢复,页面不需要重新请求数据:
3729
-
3730
- ```js
3731
- const result = await setEngine('leaflet')
3732
- console.log(result.data?.restore)
3733
- ```
3734
-
3735
- 只有 GPU 专用风场、剖面、一次性绘制对象或页面直接创建的底层引擎对象需要在切换成功后自行恢复。
3736
-
3737
4374
  ## 能力支持说明
3738
4375
 
3739
4376
  | 能力 | Cesium | Leaflet |
@@ -3741,20 +4378,29 @@ console.log(result.data?.restore)
3741
4378
  | 2D 地图 | 支持 | 支持 |
3742
4379
  | 2.5D / 3D 场景 | 支持 | 不支持 |
3743
4380
  | 放大、缩小、重置 | 支持 | 支持 |
4381
+ | 多地图视角联动 | 支持 | 支持 |
4382
+ | 地图截图与 PNG 导出 | 支持 | 支持 |
3744
4383
  | 底图切换 | 支持 | 支持 |
3745
4384
  | 点线面文字 | 支持 | 支持 |
4385
+ | Canvas 数据图标与扩散 Marker | 支持 | 支持 |
4386
+ | GeoJSON 图层 | 支持 | 支持 |
3746
4387
  | 独立线、虚线、渐变与沿线动画 | 支持 | 支持 |
4388
+ | 逐顶点色与路线时间裁剪 | 支持 | 支持 |
4389
+ | 台风路径与播放 | 支持 | 支持 |
3747
4390
  | 图片覆盖层 | 支持 | 支持 |
3748
4391
  | 网格 / TIF | 支持 | 支持 |
3749
4392
  | 海量点 | 支持 | 支持 |
3750
4393
  | 点位聚合 | 支持 | 支持 |
3751
4394
  | 点位密度抽稀 | 支持 | 支持 |
3752
4395
  | 等值线 | 支持 | 支持 |
3753
- | 等压线标注 | 支持 | 支持 |
3754
- | 风场 Canvas 动画 | 支持 | 支持 |
3755
- | 三维体渲染 | 支持 | 不支持 |
4396
+ | 等值线标签与 H/L 中心标注 | 支持 | 支持 |
4397
+ | 统一风场 `upsertWindLayer` | GPU | Canvas |
4398
+ | 独立 Canvas 风场 `WindFieldMethods` | 支持 | 支持 |
4399
+ | GPU 专用风场 `upsertGpuWindLayer` | 支持 | 不支持 |
4400
+ | 源解析弧线、灰色烟羽点云与目标汇聚体 | 支持 | 不支持 |
4401
+ | 三维体渲染、更新与取值 | 支持 | 不支持 |
3756
4402
  | 三维切片 / 剖面渲染 | 支持 | 不支持 |
3757
- | DEM 地形 | 支持 | 不支持 |
4403
+ | DEM 地形与夸张 | 支持 | 不支持 |
3758
4404
 
3759
4405
  ## 第三方许可
3760
4406
 
@@ -3786,14 +4432,14 @@ SOFTWARE.
3786
4432
 
3787
4433
  ## 本地开发与打包
3788
4434
 
3789
- 在仓库根目录执行:
4435
+ 包源码来自仓库 `src/gis`;`packages/basegis` 负责构建与发布配置。安装仓库依赖后,在仓库根目录执行:
3790
4436
 
3791
4437
  ```bash
3792
4438
  npm run build:basegis
3793
4439
  npm run pack:basegis
3794
4440
  ```
3795
4441
 
3796
- `build:basegis` 会生成:
4442
+ `build:basegis` 生成以下入口,以及它们依赖的共享、异步代码块:
3797
4443
 
3798
4444
  ```text
3799
4445
  packages/basegis/dist/basegis.js
@@ -3803,23 +4449,25 @@ packages/basegis/dist/assets.js
3803
4449
  packages/basegis/dist/style.css
3804
4450
  ```
3805
4451
 
3806
- `pack:basegis` 会在仓库根目录生成类似文件:
4452
+ 分发时必须保留完整 `packages/basegis/dist`,不能只复制上述入口文件。`pack:basegis` 不会自动构建,修改源码后应先执行 `build:basegis`。
4453
+
4454
+ 当前包版本为 `0.1.4`,`pack:basegis` 会在仓库根目录生成以下文件;后续以 `packages/basegis/package.json` 中的版本及实际输出为准:
3807
4455
 
3808
4456
  ```text
3809
- 3clear-basegis-0.1.0.tgz
4457
+ 3clear-basegis-0.1.4.tgz
3810
4458
  ```
3811
4459
 
3812
4460
  其他项目本地安装:
3813
4461
 
3814
4462
  ```bash
3815
- npm install ./3clear-basegis-0.1.0.tgz
4463
+ npm install ./3clear-basegis-0.1.4.tgz
3816
4464
  ```
3817
4465
 
3818
4466
  ## 发布到 npm
3819
4467
 
3820
4468
  只发布 `packages/basegis`,不要在项目根目录执行 `npm publish`,否则会把整个业务项目上传到 npm。
3821
4469
 
3822
- 发布前检查包内容:
4470
+ 更新包版本并重新构建后,先检查包内容:
3823
4471
 
3824
4472
  ```bash
3825
4473
  npm pack --dry-run ./packages/basegis
@@ -3831,38 +4479,13 @@ npm pack --dry-run ./packages/basegis
3831
4479
  npm publish ./packages/basegis --access public
3832
4480
  ```
3833
4481
 
3834
- 如果只希望私有发布,保留 `packages/basegis/package.json` 中的:
3835
-
3836
- ```json
3837
- {
3838
- "publishConfig": {
3839
- "access": "restricted"
3840
- }
3841
- }
3842
- ```
3843
-
3844
- 如果希望公开发布,需要改为:
3845
-
3846
- ```json
3847
- {
3848
- "publishConfig": {
3849
- "access": "public"
3850
- }
3851
- }
3852
- ```
4482
+ 包已配置 `publishConfig.access: 'public'`,发布内容为完整 `dist`、README 和 npm 自动附带的包元信息,不包含测试页面、mock 数据和 Cesium 静态资源。
3853
4483
 
3854
4484
  ## 常见问题
3855
4485
 
3856
4486
  ### Cesium 地图不显示
3857
4487
 
3858
- 检查宿主项目是否存在:
3859
-
3860
- ```text
3861
- public/lib/Cesium/Cesium.js
3862
- public/lib/Cesium/Widgets/widgets.css
3863
- ```
3864
-
3865
- 并确认页面初始化前能访问 `window.Cesium`。
4488
+ 按[安装](#安装)章节加载 Cesium 脚本、样式及完整静态资源,确认初始化前存在 `window.Cesium`,并确保地图容器有实际宽高。部署在子路径时还要检查 `CESIUM_BASE_URL`。
3866
4489
 
3867
4490
  ### Leaflet 样式异常
3868
4491
 
@@ -3883,18 +4506,19 @@ console.log(result.success, result.message)
3883
4506
 
3884
4507
  ### 切换配置后地图没有变化
3885
4508
 
3886
- `setConfig()` 只更新 `BaseGIS` 内部配置,不会自动重建地图。需要销毁后重新初始化:
4509
+ `setConfig()` 只更新内部配置,需要再次 `init()` 才会重建地图。不要先手动 `destroy()`,以免清空托管图层快照:
3887
4510
 
3888
4511
  ```js
3889
4512
  mapCore.setConfig(nextConfig)
3890
- mapCore.init()
4513
+ const result = mapCore.init()
4514
+ if (result.success) await mapCore.whenReady()
3891
4515
  ```
3892
4516
 
3893
4517
  `init()` 内部会先销毁旧适配器,再创建新实例。
3894
4518
 
3895
4519
  ### 切换引擎后图层没了
3896
4520
 
3897
- 直接使用 `setEngine()`。BaseGIS 会保留视野,并自动恢复图片、网格、海量点、点位聚合、点位抽稀、独立线、等值线、统一风场和三维体等托管图层:
4521
+ 先确认通过 `setEngine()` 切换且没有提前 `destroy()`,再查看恢复结果:
3898
4522
 
3899
4523
  ```js
3900
4524
  const result = await mapCore.setEngine('leaflet')
@@ -3903,4 +4527,4 @@ console.log(result.data?.restore?.restored)
3903
4527
  console.log(result.data?.restore?.failed)
3904
4528
  ```
3905
4529
 
3906
- 如果丢失的是 `drawPoint/drawLine/drawPolygon/drawText/addMarker`、点击监听、GPU 专用风场、剖面图层或页面直接创建的 Cesium / Leaflet 对象,它们不属于托管图层,需要业务在切换成功后自行恢复。
4530
+ 自动恢复与手动恢复范围见[切换后的图层处理](#managed-layer-restore)。GeoJSON、基础绘制、独立 Canvas/GPU 风场、剖面和页面监听需重新加载或绑定;使用多地图联动时还需调用 `viewLinks.refreshMap(id)`。