@3clear/basegis 0.1.2 → 0.1.4

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
@@ -4,12 +4,196 @@
4
4
  把leaflet cesium 常用方法做了二次封装统一了api 使同一套代码可以适配2套地图引擎
5
5
  当前包包含:
6
6
 
7
- - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、三维体渲染、三维切片/剖面渲染等基础能力。
8
- - `methods`:图片图层、网格图层、海量点、点位抽稀、等值线、等压线、风场、图形组等高级控制器。
7
+ - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、Cesium / Leaflet 统一粒子风场、GPU 粒子风场、三维体渲染、三维切片/剖面渲染等基础能力。
8
+ - `methods`:独立线图层、图片图层、网格图层、海量点、点位聚合、点位抽稀、等值线、风场、图形组等高级控制器。
9
9
  - `layers`:天地图、GeoServer 金字塔瓦片、WMS、WMTS 图层配置快捷构造器。
10
10
  - `assets`:GIS 示例资源。
11
11
  - `style.css`:Leaflet、GIS 图层和标注相关基础样式。
12
12
 
13
+ ## 能力总览
14
+
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
+ ### Graphics 图形与点位(4)
31
+
32
+ <table>
33
+ <tbody>
34
+ <tr>
35
+ <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>
38
+ </td>
39
+ <td width="50%">
40
+ <a href="#point-large-layer-controller"><strong>PointLargeLayerController</strong></a><br>
41
+ <small>海量点 · <a href="http://61.50.111.214:31166/lgmap/test-page-5" target="_blank" rel="noopener noreferrer">查看示例</a></small>
42
+ </td>
43
+ </tr>
44
+ <tr>
45
+ <td width="50%">
46
+ <a href="#point-cluster-controller"><strong>PointClusterController</strong></a><br>
47
+ <small>点位聚合 · <a href="http://61.50.111.214:31166/lgmap/test-page-29" target="_blank" rel="noopener noreferrer">查看示例</a></small>
48
+ </td>
49
+ <td width="50%">
50
+ <a href="#point-density-controller"><strong>PointDensityController</strong></a><br>
51
+ <small>点位视野裁剪抽稀 · <a href="http://61.50.111.214:31166/lgmap/test-page-8" target="_blank" rel="noopener noreferrer">查看示例</a></small>
52
+ </td>
53
+ </tr>
54
+ </tbody>
55
+ </table>
56
+
57
+ ### Line 线图层(1)
58
+
59
+ <table>
60
+ <tbody>
61
+ <tr>
62
+ <td>
63
+ <a href="#line-layer-controller"><strong>LineLayerController</strong></a><br>
64
+ <small>独立线图层、实线/虚线、固定/流动渐变与沿线动画 · <a href="http://61.50.111.214:31166/lgmap/test-page-34" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
65
+ <small>支持:一条或多条经纬度折线,可选图标引领的逐步出线</small>
66
+ </td>
67
+ </tr>
68
+ </tbody>
69
+ </table>
70
+
71
+ ### Image 图片图层(1)
72
+
73
+ <table>
74
+ <tbody>
75
+ <tr>
76
+ <td>
77
+ <a href="#image-layer-controller"><strong>ImageLayerController</strong></a><br>
78
+ <small>图片覆盖层与时次切换 · <a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
79
+ <small>支持:PNG / JPG / WebP 等浏览器图片、灰度图;可配套 GeoTIFF 取值</small>
80
+ </td>
81
+ </tr>
82
+ </tbody>
83
+ </table>
84
+
85
+ ### Grid 数值网格(1)
86
+
87
+ <table>
88
+ <tbody>
89
+ <tr>
90
+ <td>
91
+ <a href="#grid-layer-controller"><strong>GridLayerController</strong></a><br>
92
+ <small>栅格数值注记与鼠标探针 · <a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small><br>
93
+ <small>支持:GeoTIFF、灰度图、直接数值网格</small>
94
+ </td>
95
+ </tr>
96
+ </tbody>
97
+ </table>
98
+
99
+ ### Contour 通用等值线与中心标注(2)
100
+
101
+ <table>
102
+ <tbody>
103
+ <tr>
104
+ <td width="50%">
105
+ <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>
107
+ </td>
108
+ <td width="50%">
109
+ <a href="#raster-contour-controller"><strong>RasterContourController</strong></a><br>
110
+ <small>从栅格追线,可选自动检测 H/L 中心 · <a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small>
111
+ </td>
112
+ </tr>
113
+ </tbody>
114
+ </table>
115
+
116
+ ### Volume 三维体渲染(1)
117
+
118
+ <table>
119
+ <tbody>
120
+ <tr>
121
+ <td>
122
+ <a href="#volume-rendering"><strong>BaseGIS.upsertVolumeLayer</strong></a><br>
123
+ <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>
125
+ </td>
126
+ </tr>
127
+ </tbody>
128
+ </table>
129
+
130
+ ### Section 三维切片 / 剖面(1)
131
+
132
+ <table>
133
+ <tbody>
134
+ <tr>
135
+ <td>
136
+ <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>
138
+ <small>支持:{ Bound, DataAry } 三维格点数据</small>
139
+ </td>
140
+ </tr>
141
+ </tbody>
142
+ </table>
143
+
144
+ ### Wind 风场(1)
145
+
146
+ <table>
147
+ <tbody>
148
+ <tr>
149
+ <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>
152
+ </td>
153
+ </tr>
154
+ </tbody>
155
+ </table>
156
+
157
+ ### Layers 图层构造器(4)
158
+
159
+ <table>
160
+ <tbody>
161
+ <tr>
162
+ <td width="50%">
163
+ <a href="#layer-config-builders"><strong>createTiandituLayer</strong></a><br>
164
+ <small>天地图图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
165
+ </td>
166
+ <td width="50%">
167
+ <a href="#layer-config-builders"><strong>createGeoserverPyramidLayer</strong></a><br>
168
+ <small>GeoServer 金字塔瓦片配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
169
+ </td>
170
+ </tr>
171
+ <tr>
172
+ <td width="50%">
173
+ <a href="#layer-config-builders"><strong>createWmsLayer</strong></a><br>
174
+ <small>WMS 图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
175
+ </td>
176
+ <td width="50%">
177
+ <a href="#layer-config-builders"><strong>createWmtsLayer</strong></a><br>
178
+ <small>WMTS 图层配置 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
179
+ </td>
180
+ </tr>
181
+ </tbody>
182
+ </table>
183
+
184
+ ### Assets 示例资源(1)
185
+
186
+ <table>
187
+ <tbody>
188
+ <tr>
189
+ <td>
190
+ <a href="#gis-marker-sample"><strong>gisMarkerSample</strong></a><br>
191
+ <small>GIS Marker 示例资源 · <a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a></small>
192
+ </td>
193
+ </tr>
194
+ </tbody>
195
+ </table>
196
+
13
197
  ## 安装
14
198
 
15
199
  ```bash
@@ -36,15 +220,15 @@ import { BaseGIS } from '@3clear/basegis'
36
220
  // 高级能力控制器
37
221
  import {
38
222
  GraphicGroupController,
223
+ LineLayerController,
39
224
  ImageLayerController,
225
+ toImageLayerArea,
40
226
  GridLayerController,
41
227
  PointLargeLayerController,
228
+ PointClusterController,
42
229
  PointDensityController,
43
230
  ContourLayerController,
44
231
  RasterContourController,
45
- PressureContourLayer,
46
- createPressureDataResolver,
47
- buildPressureRenderData,
48
232
  WindFieldMethods,
49
233
  } from '@3clear/basegis/methods'
50
234
 
@@ -266,10 +450,74 @@ mapCore.setBasemap({
266
450
  | `parameters` | 选填 | WMS 附加参数。 |
267
451
  | `engineSupport` | 选填 | 支持的引擎列表,如 `['cesium', 'leaflet']`。 |
268
452
 
453
+ <a id="layer-config-builders"></a>
454
+
455
+ ## 图层配置构造器
456
+
457
+ `layers` 出口提供四个快捷构造器,只负责生成标准图层配置,不会直接操作地图。构造结果可以放入 `config.basemap.list`,也可以传给 `setBasemap()`。
458
+
459
+ ```js
460
+ import { BaseGIS } from '@3clear/basegis'
461
+ import {
462
+ createGeoserverPyramidLayer,
463
+ createTiandituLayer,
464
+ createWmsLayer,
465
+ createWmtsLayer,
466
+ } from '@3clear/basegis/layers'
467
+
468
+ const basemapList = [
469
+ createTiandituLayer({
470
+ id: 'tianditu-imagery',
471
+ name: '天地图影像',
472
+ category: 'basemap',
473
+ resourceKey: 'imagery',
474
+ annotationResourceKey: 'imageryLabel',
475
+ }),
476
+ createGeoserverPyramidLayer({
477
+ id: 'geoserver-pyramid',
478
+ name: 'GeoServer 金字塔瓦片',
479
+ category: 'basemap',
480
+ url: 'https://example.com/tiles/{z}/{x}/{y}.png',
481
+ }),
482
+ createWmsLayer({
483
+ id: 'weather-wms',
484
+ name: '气象 WMS',
485
+ category: 'basemap',
486
+ serviceUrl: 'https://example.com/geoserver/wms',
487
+ layers: 'workspace:weather',
488
+ parameters: {
489
+ transparent: true,
490
+ format: 'image/png',
491
+ },
492
+ }),
493
+ createWmtsLayer({
494
+ id: 'weather-wmts',
495
+ name: '气象 WMTS',
496
+ category: 'basemap',
497
+ url: 'https://example.com/wmts',
498
+ layer: 'weather',
499
+ tileMatrixSet: 'EPSG:3857',
500
+ }),
501
+ ]
502
+
503
+ const mapCore = new BaseGIS({
504
+ containerId: 'map',
505
+ config: {
506
+ basemap: {
507
+ list: basemapList,
508
+ defaultVisibleId: 'tianditu-imagery',
509
+ },
510
+ },
511
+ })
512
+ ```
513
+
514
+ 四个构造器都会保留额外传入字段,便于继续配置 `engineSupport`、缩放级别、注记资源或服务参数。
515
+
516
+ <a id="base-gis"></a>
269
517
 
270
518
  ## BaseGIS 基础能力
271
519
 
272
- 这一节列的是 `BaseGIS` 主入口直接提供的基础能力。业务页面优先调用这些方法;图片图层、网格图层、海量点、点位抽稀、等值线、风场等更复杂能力,建议使用后文 `methods` 中对应的 Controller。
520
+ 这一节列的是 `BaseGIS` 主入口直接提供的基础能力。业务页面优先调用这些方法;图片图层、网格图层、海量点、点位聚合、点位抽稀、等值线、风场等更复杂能力,建议使用后文 `methods` 中对应的 Controller。
273
521
 
274
522
  ### 1. 生命周期、实例与引擎切换
275
523
 
@@ -370,7 +618,7 @@ if (result.success) {
370
618
 
371
619
  引擎切换不是把 Cesium 图层对象“搬到” Leaflet,也不是把 Leaflet 图层对象“搬到” Cesium。BaseGIS 会保存托管图层的业务参数,并在新 adapter 中重新创建图层。
372
620
 
373
- - 自动恢复范围包括图片、网格、海量点、点位抽稀、等值线和三维体图层,对应 `upsert*Layer` 方法及 Controller。
621
+ - 自动恢复范围包括图片、网格、海量点、点位聚合、点位抽稀、独立线、等值线、统一风场和三维体图层,对应 `upsert*Layer` 方法及 Controller。
374
622
  - 图层最新的数据参数、显隐、清空、删除、海量点删除和高亮状态会同步到 BaseGIS 快照。
375
623
  - 快照只保存业务参数引用,不复制大数组,不保存任何底层引擎对象。
376
624
  - 三维体图层切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
@@ -488,7 +736,7 @@ viewListener.data?.off?.()
488
736
 
489
737
  ### 4. 绘制点、线、面、文字和 Marker
490
738
 
491
- 这些方法用于轻量绘制和样例验证。大量点位或复杂业务图层请优先使用后文的 `PointLargeLayerController`、`PointDensityController`、`GraphicGroupController` 等控制器。
739
+ 这些方法用于轻量绘制和样例验证。大量点位或复杂业务图层请优先使用后文的 `PointLargeLayerController`、`PointClusterController`、`PointDensityController`、`GraphicGroupController` 等控制器。
492
740
 
493
741
  点:
494
742
 
@@ -579,6 +827,25 @@ mapCore.addMarker({
579
827
  })
580
828
  ```
581
829
 
830
+ <a id="gis-marker-sample"></a>
831
+
832
+ #### 使用 gisMarkerSample 示例资源
833
+
834
+ `gisMarkerSample` 是可直接导入的 Marker 图片资源,适合快速验证 `addMarker()` 或控制器图标配置。
835
+
836
+ ```js
837
+ import { gisMarkerSample } from '@3clear/basegis/assets'
838
+
839
+ mapCore.addMarker({
840
+ id: 'marker-sample',
841
+ name: '示例站点',
842
+ position: [104, 35],
843
+ iconUrl: gisMarkerSample,
844
+ iconSize: [42, 50],
845
+ iconAnchor: [21, 50],
846
+ })
847
+ ```
848
+
582
849
  清理图形:
583
850
 
584
851
  ```js
@@ -665,9 +932,11 @@ Leaflet 当前返回坐标点击:
665
932
  说明:
666
933
 
667
934
  - 基础 `onClick` 适合地图空白点击、简单 Entity 点击。
668
- - 海量点、点位抽稀等图层自己的点击事件,应使用对应 Controller 的 `onClick` 参数。
935
+ - 海量点、点位聚合、点位抽稀等图层自己的点击事件,应使用对应 Controller 的点击回调参数。
669
936
 
670
937
 
938
+ <a id="graphic-group-controller"></a>
939
+
671
940
  ## GraphicGroupController
672
941
 
673
942
  用于一次性加载一组点、线、面、文字、marker。它只调用 `BaseGIS.drawPoint / drawLine / drawPolygon / drawText / addMarker`。
@@ -705,10 +974,180 @@ graphics.clear()
705
974
  graphics.getState()
706
975
  ```
707
976
 
977
+ <a id="line-layer-controller"></a>
978
+
979
+ ## LineLayerController
980
+
981
+ 独立管理一组折线,支持整体更新、显隐、清空、销毁和引擎切换恢复。每条线必须提供唯一 `id` 和至少两个 `[longitude, latitude, height?]` 坐标点。第一版可以加载多条线,但同一控制器只播放一条线的动画。
982
+
983
+ ### 实线、虚线与纯色
984
+
985
+ ```js
986
+ import { LineLayerController } from '@3clear/basegis/methods'
987
+
988
+ const lineLayer = new LineLayerController({
989
+ mapCore,
990
+ layerId: 'flight-route',
991
+ style: {
992
+ color: '#00d8ff',
993
+ width: 4,
994
+ opacity: 0.9,
995
+ pattern: 'dashed', // solid | dashed
996
+ dashLength: 14,
997
+ gapLength: 8,
998
+ },
999
+ })
1000
+
1001
+ await lineLayer.load({
1002
+ lines: [{
1003
+ id: 'route-1',
1004
+ positions: [
1005
+ [116.4, 39.9],
1006
+ [117.8, 37.5],
1007
+ [121.5, 31.2],
1008
+ ],
1009
+ }],
1010
+ })
1011
+ ```
1012
+
1013
+ 单条线的 `style` 可以覆盖控制器的顶层默认样式。
1014
+
1015
+ ### 固定渐变与流动渐变
1016
+
1017
+ `style.color` 可以是 CSS 颜色字符串,也可以是渐变配置。`stops` 的 `offset` 按 `0~1` 升序排列;配置 `flow` 后渐变会沿线路循环流动。
1018
+
1019
+ ```js
1020
+ const gradientStyle = {
1021
+ width: 5,
1022
+ pattern: 'solid',
1023
+ color: {
1024
+ type: 'gradient',
1025
+ stops: [
1026
+ { offset: 0, color: '#00e5ff' },
1027
+ { offset: 0.5, color: '#2563eb' },
1028
+ { offset: 1, color: '#a855f7' },
1029
+ ],
1030
+ flow: {
1031
+ enabled: true,
1032
+ durationMs: 2400,
1033
+ direction: 'forward', // forward | reverse
1034
+ },
1035
+ },
1036
+ }
1037
+ ```
1038
+
1039
+ 渐变与虚线可以同时配置。Cesium 和 Leaflet 的底层绘制机制不同,虚线端点和颜色交界处可能有轻微视觉差异。
1040
+
1041
+ ### 飞机引领的逐步出线
1042
+
1043
+ 飞机不是单独的类,而是通用 `animation.icon` 配置。省略 `icon` 时只播放线路逐步出现。
1044
+
1045
+ ```js
1046
+ // BaseGIS 不内置飞机图片,替换成业务项目自己的资源地址。
1047
+ const planeIconUrl = '/your-app/plane.svg'
1048
+
1049
+ const flightLine = new LineLayerController({
1050
+ mapCore,
1051
+ layerId: 'flight-route',
1052
+ style: gradientStyle,
1053
+ animation: {
1054
+ enabled: true,
1055
+ lineId: 'route-1',
1056
+ mode: 'reveal',
1057
+ durationMs: 12000,
1058
+ autoplay: true,
1059
+ loop: true,
1060
+ icon: {
1061
+ url: planeIconUrl,
1062
+ size: [36, 36],
1063
+ rotateToPath: true,
1064
+ rotationOffsetDeg: 0,
1065
+ },
1066
+ },
1067
+ })
1068
+
1069
+ await flightLine.load({
1070
+ lines: [{
1071
+ id: 'route-1',
1072
+ positions: [
1073
+ [116.4, 39.9],
1074
+ [117.8, 37.5],
1075
+ [121.5, 31.2],
1076
+ ],
1077
+ }],
1078
+ })
1079
+
1080
+ flightLine.pauseAnimation()
1081
+ flightLine.playAnimation()
1082
+ flightLine.restartAnimation()
1083
+ ```
1084
+
1085
+ 动画按照各段实际地理距离插值。`hide()` 暂停帧更新并记录播放状态,`show()` 恢复隐藏前正在运行的动画;`clear()` 和 `destroy()` 会取消动画。切换引擎会恢复播放/暂停意图,但不会保存逐帧进度,动画从起点重新计算。
1086
+
1087
+ ### 参数
1088
+
1089
+ | 参数 | 默认值 | 说明 |
1090
+ | --- | --- | --- |
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` | - | 可选的飞机、车辆或船舶图标配置。 |
1102
+
1103
+ ### 方法
1104
+
1105
+ | 方法 | 说明 |
1106
+ | --- | --- |
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()` | 删除图层、动画和引擎恢复快照。 |
1114
+
1115
+ 页面卸载时先执行 `lineLayer.destroy()`,再执行 `mapCore.destroy()`。
1116
+
1117
+ 在线示例:[查看 LineLayerController 双引擎应用 Demo](http://61.50.111.214:31166/lgmap/test-page-34)。
1118
+
1119
+ <a id="image-layer-controller"></a>
1120
+
708
1121
  ## ImageLayerController
709
1122
 
710
1123
  用于管理一个图片覆盖层。所有更新都走 `update(payload)`;页面有很多时次数据时,自己维护数据列表,然后把当前时次的数据传给 `update`。
711
1124
 
1125
+ ### 支持的数据格式
1126
+
1127
+ | 数据模式 | 核心字段 | 用途 |
1128
+ | --- | --- | --- |
1129
+ | 普通图片 | `imageUrl + area` | 浏览器可解码的图片,常用 PNG、JPG/JPEG、WebP;用作雷达、云图或已填色格点图覆盖层。 |
1130
+ | 灰度图 | `imageUrl + area + imageSourceType: 'grayscale' + colorize` | 把像素灰度映射成业务值,再按色带生成可见图片。 |
1131
+ | 图片 + GeoTIFF | `imageUrl + tifUrl + area` | 图片负责显示,GeoTIFF 为网格注记和鼠标探针提供数值。 |
1132
+
1133
+ `ImageLayerController` 不会把单独的 GeoTIFF 渲染成彩色图片底图。只有 TIF 数值数据时,优先使用 [`GridLayerController`](#grid-layer-controller);需要由栅格生成等值线时使用 [`RasterContourController`](#raster-contour-controller)。
1134
+
1135
+ `toImageLayerArea()` 可以把经纬度数组、Leaflet Bounds 或常见范围对象统一转换成 `{ startLon, startLat, endLon, endLat }`:
1136
+
1137
+ ```js
1138
+ import {
1139
+ ImageLayerController,
1140
+ toImageLayerArea,
1141
+ } from '@3clear/basegis/methods'
1142
+
1143
+ const area = toImageLayerArea({
1144
+ west: 73,
1145
+ south: 18,
1146
+ east: 135,
1147
+ north: 54,
1148
+ })
1149
+ ```
1150
+
712
1151
  先创建控制器。这里是控制器级默认参数,后续每次 `update(payload)` 都可以覆盖这些默认值:
713
1152
 
714
1153
  ```js
@@ -872,57 +1311,28 @@ await imageLayer.update({
872
1311
  })
873
1312
  ```
874
1313
 
875
- `loadGrayImage` 的灰度值换算规则:
876
-
877
- ```js
878
- 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue) * (maxValue - minValue) + minValue
879
- ```
1314
+ Leaflet 会对经度跨度达到 360° 的全球图片按当前视野自动补充相邻世界副本,全球 TIF / 灰度格点的注记和鼠标探针也会使用同一套经度循环,因此业务页面不需要手动创建 `±360°` 图片层。原始图片按 `0° ~ 360°` 排列时传 `isSplit: true`,适配层会在交换左右半幅的同时把显示范围统一为 `-180° ~ 180°`,确保图片与格点值落在同一经度位置。
880
1315
 
881
- 所以灰度图要表达真实业务值时,建议显式传 `minValue / maxValue`。如果只写 `colorize: true`,会使用默认范围 `minValue: 0`、`maxValue: 1`、`grayMinValue: 1`、`grayMaxValue: 254`,只适合临时预览,不适合正式业务图层。
1316
+ ### 4. TIF 数值层
882
1317
 
883
- `loadGrayImage` 支持两种写法:
884
-
885
- ```js
886
- await gridLayer.loadGrayImage({
887
- imageUrl: '/data/grid/temp-gray.png',
888
- area,
889
- colorize: {
890
- minValue: -20,
891
- maxValue: 50,
892
- colors: ['#2639a7', '#2faaf3', '#8ee5b2', '#fd9919', '#fb422d'],
893
- },
894
- })
895
-
896
- await gridLayer.loadGrayImage({
897
- imageUrl: '/data/grid/temp-gray.png',
898
- area,
899
- minValue: -20,
900
- maxValue: 50,
901
- grayMinValue: 1,
902
- grayMaxValue: 254,
903
- colors: ['#2639a7', '#2faaf3', '#8ee5b2', '#fd9919', '#fb422d'],
904
- })
905
- ```
906
- ### 4. 纯 TIF
907
-
908
- 只传 TIF,不传图片。适合数据本身带地理范围、需要按 TIF 渲染的场景。
1318
+ 只传 TIF 时不会生成彩色图片底图,仅能使用 TIF 数值显示网格注记或鼠标探针。这种场景更推荐直接使用 `GridLayerController`。
909
1319
 
910
1320
  ```js
911
1321
  await imageLayer.update({
912
1322
  // 选填:当前数据 id。
913
1323
  id: 'rain-tif',
914
1324
 
915
- // 必填建议:纯 TIF 模式下显式清空 imageUrl,避免沿用上一次图片。
1325
+ // 必填建议:仅 TIF 模式下显式清空 imageUrl,避免沿用上一次图片。
916
1326
  imageUrl: '',
917
1327
 
918
1328
  // 必填:TIF 地址。
919
1329
  tifUrl: '/data/rain.tif',
920
1330
 
921
- // 选填:是否按默认色带着色。需要更精细控制时传 colorize 对象。
922
- colorize: true,
923
-
924
1331
  // 选填:是否开启鼠标探针。
925
1332
  showProbe: true,
1333
+
1334
+ // 选填:是否显示网格数值注记。
1335
+ showLabel: true,
926
1336
  })
927
1337
  ```
928
1338
 
@@ -1014,7 +1424,7 @@ imageLayer.destroy({ layerId: 'radar-image' })
1014
1424
  | 方法 | 参数 | 返回值 | 说明 |
1015
1425
  | --- | --- | --- | --- |
1016
1426
  | `mount(target)` | `BaseGIS` 实例 | `Result` | 绑定 `BaseGIS`。构造时已传 `mapCore/baseGIS` 时通常不需要手动调用。 |
1017
- | `update(payload)` | 图片图层数据 | `Promise<Result>` | 推荐主入口。创建或更新当前图片图层,支持普通图片、图片+TIF、灰度图+色带、纯 TIF |
1427
+ | `update(payload)` | 图片图层数据 | `Promise<Result>` | 推荐主入口。创建或更新普通图片、灰度图或图片 + TIF;仅 TIF 时只有数值注记/探针。 |
1018
1428
  | `show(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 显示图片图层。成功后控制器 `visible` 变为 `true`。 |
1019
1429
  | `hide(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 隐藏图片图层。成功后控制器 `visible` 变为 `false`。 |
1020
1430
  | `destroy(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 移除图片图层,并清空 `items / activeIndex / layerType / currentPayload`。如果地图已销毁,只清本地状态。 |
@@ -1055,19 +1465,19 @@ imageLayer.destroy({ layerId: 'radar-image' })
1055
1465
  | `defaultArea` | 选填 | 默认图片范围,`update` 未传 `area` 时使用。 |
1056
1466
  | `id` | 选填 | 当前数据 id,便于状态记录。 |
1057
1467
  | `name` | 选填 | 当前数据名称,便于页面展示。 |
1058
- | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。纯 TIF 模式建议传空字符串清掉旧图片。 |
1059
- | `tifUrl` | TIF、图片+TIF 必填 | GeoTIFF 地址,用于渲染、探针或网格注记。 |
1468
+ | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。仅 TIF 模式建议传空字符串清掉旧图片。 |
1469
+ | `tifUrl` | TIF、图片+TIF 必填 | GeoTIFF 数值地址,用于鼠标探针或网格注记,不会单独生成彩色图片底图。 |
1060
1470
  | `area` | 传 `imageUrl` 时必填 | 图片范围:`startLon / startLat / endLon / endLat`。 |
1061
1471
  | `imageSourceType` | 灰度图必填,其他选填 | `color` 表示已填色图片;`grayscale` 表示灰度图。 |
1062
- | `colorize` | 灰度图必填,TIF 着色选填 | 色带配置,或传 `true` 使用默认着色。 |
1472
+ | `colorize` | 灰度图必填 | 灰度图业务值和色带配置。 |
1063
1473
  | `colorize.minValue` | 灰度图必填 | 色带映射最小值。 |
1064
1474
  | `colorize.maxValue` | 灰度图必填 | 色带映射最大值。 |
1065
1475
  | `colorize.noDataValue` | 选填 | 无效值。 |
1066
1476
  | `colorize.colors` | 灰度图必填 | 色带颜色数组。 |
1067
1477
  | `opacity` | 选填 | 透明度,通常 0 - 1。 |
1068
1478
  | `visible` | 选填 | 是否显示图层。 |
1069
- | `showProbe` | 选填 | 是否开启鼠标探针,通常依赖 `tifUrl`。 |
1070
- | `showLabel` | 选填 | 是否显示网格注记,通常依赖 `tifUrl`。 |
1479
+ | `showProbe` | 选填 | 是否开启鼠标探针,需要 `tifUrl` 或灰度图数值。 |
1480
+ | `showLabel` | 选填 | 是否显示网格注记,需要 `tifUrl` 或灰度图数值。 |
1071
1481
  | `decimalPlaces` | 选填 | 探针或注记数值小数位。 |
1072
1482
  | `isSplit` | 选填 | 是否切片处理大图。 |
1073
1483
  | `gridTotal` | 选填 | 切片网格数量。 |
@@ -1079,9 +1489,19 @@ imageLayer.destroy({ layerId: 'radar-image' })
1079
1489
  - `switchTo(idOrIndex)`:在已加载的 `items` 内切换到某一项。
1080
1490
  - `next() / prev()`:只适合本地小数组演示,不推荐作为业务时次更新主路径。
1081
1491
 
1492
+ <a id="grid-layer-controller"></a>
1493
+
1082
1494
  ## GridLayerController
1083
1495
 
1084
- 用于 TIF 网格、灰度图网格、外部数值网格。
1496
+ 用于 GeoTIFF、灰度图和外部数值网格的采样、数值注记和鼠标探针。它不绘制彩色栅格底图;`showLabel` 和 `showProbe` 都为 `false` 时,数据会被读取,但地图上没有可见内容。
1497
+
1498
+ ### 支持的数据格式
1499
+
1500
+ | 数据模式 | 核心字段 | 数据契约 |
1501
+ | --- | --- | --- |
1502
+ | GeoTIFF | `tifUrl` | `.tif/.tiff` 地址;读取第一个栅格波段及文件地理范围。 |
1503
+ | 灰度图 | `imageUrl/grayImageUrl + area` | 浏览器可解码的灰度图;通过 `grayMinValue/grayMaxValue/minValue/maxValue/noDataValue` 换算业务值。 |
1504
+ | 直接数值网格 | `imageGridData` | `{ values, width, height, area }` 或 `{ values, width, height, bbox }`;`values` 必须是长度不小于 `width * height` 的一维数组或 TypedArray。 |
1085
1505
 
1086
1506
  ```js
1087
1507
  import { GridLayerController } from '@3clear/basegis/methods'
@@ -1096,7 +1516,6 @@ const gridLayer = new GridLayerController({
1096
1516
 
1097
1517
  await gridLayer.loadTif({
1098
1518
  tifUrl: '/data/grid/temp.tif',
1099
- colorize: true,
1100
1519
  })
1101
1520
 
1102
1521
  await gridLayer.loadGrayImage({
@@ -1107,7 +1526,7 @@ await gridLayer.loadGrayImage({
1107
1526
  endLon: 135,
1108
1527
  endLat: 54,
1109
1528
  },
1110
- // 灰度图需要把灰度值映射成业务值,再按业务值找色带。
1529
+ // 灰度图需要把灰度值映射成业务值。
1111
1530
  colorize: {
1112
1531
  // 必填建议:业务值最小值。
1113
1532
  minValue: -20,
@@ -1123,24 +1542,206 @@ await gridLayer.loadGrayImage({
1123
1542
  })
1124
1543
 
1125
1544
  await gridLayer.loadData({
1126
- imageGridData: [
1127
- [12.1, 13.4],
1128
- [14.2, 15.6],
1129
- ],
1130
- area: {
1131
- startLon: 100,
1132
- startLat: 30,
1133
- endLon: 110,
1134
- endLat: 40,
1545
+ imageGridData: {
1546
+ values: [12.1, 13.4, 14.2, 15.6],
1547
+ width: 2,
1548
+ height: 2,
1549
+ area: {
1550
+ startLon: 100,
1551
+ startLat: 30,
1552
+ endLon: 110,
1553
+ endLat: 40,
1554
+ },
1135
1555
  },
1136
1556
  })
1137
1557
 
1138
- gridLayer.update({ opacity: 0.7 })
1558
+ gridLayer.update({ decimalPlaces: 2 })
1139
1559
  gridLayer.hide()
1140
1560
  gridLayer.show()
1141
1561
  gridLayer.destroy()
1142
1562
  ```
1143
1563
 
1564
+ 灰度值换算规则:
1565
+
1566
+ ```text
1567
+ 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue)
1568
+ * (maxValue - minValue) + minValue
1569
+ ```
1570
+
1571
+ 灰度图要表达真实业务值时,应显式传入 `minValue / maxValue`。数值图层不使用色带绘制背景;如果需要同时显示彩色图片,请使用 `ImageLayerController`。
1572
+
1573
+ ## 统一粒子风场
1574
+
1575
+ 同一组 API 会根据当前引擎自动选择渲染器:Cesium 使用 GPU 粒子风场,Leaflet 使用迁自 one-map 的 Canvas 粒子风场。
1576
+
1577
+ ```js
1578
+ const windData = await fetch('/mock/uv.json').then((response) => response.json())
1579
+
1580
+ await mapCore.upsertWindLayer({
1581
+ layerId: 'surface-wind',
1582
+ windData,
1583
+ maxParticles: 6400,
1584
+ speedFactor: 0.15,
1585
+ lineWidth: 2,
1586
+ fadeOpacity: 0.92,
1587
+ color: '#b7d9ff',
1588
+ particleOpacity: 0.72,
1589
+ })
1590
+
1591
+ await mapCore.updateWindLayer({
1592
+ layerId: 'surface-wind',
1593
+ speedFactor: 0.2,
1594
+ })
1595
+ mapCore.hideWindLayer({ layerId: 'surface-wind' })
1596
+ mapCore.showWindLayer({ layerId: 'surface-wind' })
1597
+ mapCore.flyToWindLayer({ layerId: 'surface-wind', duration: 0.8 })
1598
+ const stateResult = mapCore.getWindLayerState({ layerId: 'surface-wind' })
1599
+ mapCore.removeWindLayer({ layerId: 'surface-wind' })
1600
+ ```
1601
+
1602
+ | 方法 | 说明 |
1603
+ | --- | --- |
1604
+ | `upsertWindLayer(payload)` | 创建或更新当前引擎的风场 |
1605
+ | `updateWindLayer(payload)` | 更新数据或部分参数 |
1606
+ | `showWindLayer(payload)` / `hideWindLayer(payload)` | 显隐风场 |
1607
+ | `flyToWindLayer(payload)` | 定位到风场数据范围 |
1608
+ | `getWindLayerState(payload)` | 获取渲染器、粒子数量、范围和显隐状态 |
1609
+ | `removeWindLayer(payload)` | 删除风场并释放资源 |
1610
+
1611
+ 跨引擎统一使用 `Bound / DataAry` 数据格式。公共参数包括 `maxParticles`、`speedFactor`、`lineWidth`、`fadeOpacity`、`particleOpacity`、`color` 和 `visible`;适配层会统一速度、帧率拖尾、屏幕线宽、设备像素比和粒子视觉密度。`maxParticles` 表示跨引擎视觉预算:Cesium 的实际粒子数会按 GPU 纹理向上取整为整数平方,Leaflet 会按 Canvas 拖尾覆盖率换算实际粒子数,均可通过图层状态查看;算法和投影不同,因此不保证逐像素完全一致。Leaflet 还支持 `particleGap`、`maxAge`、`frameRate`、`minSpeed`。统一风场属于 BaseGIS 托管图层,`setEngine()` 切换引擎时会自动使用原数据和最新参数恢复。
1612
+
1613
+ ## GPU 粒子风场
1614
+
1615
+ GPU 粒子风场是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用同名方法时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
1616
+
1617
+ Cesium 由宿主通过 `window.Cesium` 提供。启用 `terrainEnabled` 后,图层会采样当前 `viewer.terrainProvider`,让轨迹高度随地形变化;如果当前使用椭球地形,采样高度为 0。
1618
+
1619
+ ### 基础用法
1620
+
1621
+ ```js
1622
+ const response = await fetch('/mock/uv.json')
1623
+ const windData = await response.json()
1624
+
1625
+ const result = await mapCore.upsertGpuWindLayer({
1626
+ layerId: 'surface-gpu-wind',
1627
+ windData,
1628
+ maxParticles: 10000,
1629
+ particleHeight: 0,
1630
+ terrainEnabled: false,
1631
+ fadeOpacity: 0.901,
1632
+ dropRate: 0.001,
1633
+ dropRateBump: 0.002,
1634
+ speedFactor: 0.15,
1635
+ lineWidth: 4.23,
1636
+ color: '#ffffff',
1637
+ particleOpacity: 1,
1638
+ })
1639
+
1640
+ if (!result.success) {
1641
+ console.warn(result.code, result.message)
1642
+ }
1643
+ ```
1644
+
1645
+ 创建和更新是异步方法,可能等待地形采样,建议始终使用 `await`。
1646
+
1647
+ ### 数据格式
1648
+
1649
+ 可直接使用 BaseGIS 现有 Canvas 风场的 `Bound / DataAry` 数据:
1650
+
1651
+ ```js
1652
+ const windData = {
1653
+ // 经度最小值、纬度最小值、经向格点数、纬向格点数、
1654
+ // 经度跨度、纬度跨度、数值缩放倍数。
1655
+ Bound: [100, 10, 181, 91, 80, 40, 10],
1656
+ // 每个格点按 U、V 交错;纬度行从南向北排列。
1657
+ DataAry: [u0, v0, u1, v1],
1658
+ }
1659
+ ```
1660
+
1661
+ 也可以传独立 U/V 分量:
1662
+
1663
+ ```js
1664
+ const windData = {
1665
+ width: 181,
1666
+ height: 91,
1667
+ bounds: {
1668
+ west: 100,
1669
+ east: 180,
1670
+ south: 10,
1671
+ north: 50,
1672
+ },
1673
+ u: new Float32Array(181 * 91),
1674
+ v: new Float32Array(181 * 91),
1675
+ // index-gpu.vue 解码结果从北向南排列,因此使用 north-to-south。
1676
+ rowOrder: 'north-to-south',
1677
+ }
1678
+ ```
1679
+
1680
+ 也可以不改 `windData`,在 `upsertGpuWindLayer` 参数中传 `flipY: true`。这与 `rowOrder: 'north-to-south'` 等价。
1681
+
1682
+ ### 更新、显隐和移除
1683
+
1684
+ ```js
1685
+ await mapCore.updateGpuWindLayer({
1686
+ layerId: 'surface-gpu-wind',
1687
+ maxParticles: 40000,
1688
+ speedFactor: 0.2,
1689
+ lineWidth: 3,
1690
+ terrainEnabled: true,
1691
+ particleHeight: 20,
1692
+ })
1693
+
1694
+ mapCore.hideGpuWindLayer({ layerId: 'surface-gpu-wind' })
1695
+ mapCore.showGpuWindLayer({ layerId: 'surface-gpu-wind' })
1696
+ mapCore.flyToGpuWindLayer({ layerId: 'surface-gpu-wind', duration: 0.8 })
1697
+
1698
+ const stateResult = mapCore.getGpuWindLayerState({
1699
+ layerId: 'surface-gpu-wind',
1700
+ })
1701
+ console.log(stateResult.data)
1702
+
1703
+ mapCore.removeGpuWindLayer({ layerId: 'surface-gpu-wind' })
1704
+ ```
1705
+
1706
+ `updateGpuWindLayer` 只传参数时复用现有数据;传入新的 `windData` 或 `data` 时会重建 GPU 图层。
1707
+
1708
+ ### 方法总表
1709
+
1710
+ | 方法 | 参数 | 说明 |
1711
+ | --- | --- | --- |
1712
+ | `upsertGpuWindLayer(payload)` | 数据和完整配置 | 创建或替换同 `layerId` 的 GPU 风场 |
1713
+ | `updateGpuWindLayer(payload)` | `{ layerId, ...partialOptions }` | 更新参数;传新数据时重建 |
1714
+ | `showGpuWindLayer(payload)` | `{ layerId }` | 显示图层 |
1715
+ | `hideGpuWindLayer(payload)` | `{ layerId }` | 隐藏图层 |
1716
+ | `removeGpuWindLayer(payload)` | `{ layerId }` | 删除图层并释放 GPU 资源 |
1717
+ | `flyToGpuWindLayer(payload)` | `{ layerId, duration? }` | 飞到风场数据范围 |
1718
+ | `getGpuWindLayerState(payload)` | `{ layerId }` | 获取粒子、范围、地形采样和显隐状态 |
1719
+
1720
+ ### 参数总表
1721
+
1722
+ | 参数 | 是否必填 | 默认值 | 说明 |
1723
+ | --- | --- | --- | --- |
1724
+ | `layerId` / `gpuWindLayerId` / `id` | 建议必填 | `gpu-wind-default` | 图层 id |
1725
+ | `windData` / `data` | 创建时必填 | - | 上述任一种风场数据 |
1726
+ | `maxParticles` | 选填 | `10000` | 请求粒子数;实际值向上取整为整数平方 |
1727
+ | `particleHeight` | 选填 | `0` | 离地高度,单位米 |
1728
+ | `terrainEnabled` | 选填 | `false` | 是否贴合 Cesium 地形 |
1729
+ | `fadeOpacity` | 选填 | `0.901` | 拖尾保留系数,范围 0~1 |
1730
+ | `dropRate` | 选填 | `0.001` | 基础粒子丢弃率 |
1731
+ | `dropRateBump` | 选填 | `0.002` | 随风速增加的附加丢弃率 |
1732
+ | `speedFactor` | 选填 | `0.15` | 移动速度倍率 |
1733
+ | `lineWidth` | 选填 | `4.23` | 轨迹线宽 |
1734
+ | `color` | 选填 | `#ffffff` | 粒子轨迹颜色 |
1735
+ | `particleOpacity` | 选填 | `1` | 粒子轨迹透明度,范围 0~1 |
1736
+ | `visible` | 选填 | `true` | 初始是否显示 |
1737
+ | `terrainMaxSamples` | 选填 | `4096` | 单次地形采样点上限,允许 4~16384 |
1738
+ | `terrainSampleLevel` | 选填 | 自动计算 | 固定地形采样层级,允许 0~15 |
1739
+ | `flipY` | 选填 | `false` | 独立 U/V 数据为北到南行序时传 `true` |
1740
+
1741
+ GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后,业务应重新调用 `upsertGpuWindLayer`;需要自动恢复时改用 `upsertWindLayer`。核心渲染流程改编自 [RaymanNg/3D-Wind-Field](https://github.com/RaymanNg/3D-Wind-Field),遵循源码目录内 `LICENSE-RaymanNg.txt` 的 MIT License。
1742
+
1743
+ <a id="volume-rendering"></a>
1744
+
1144
1745
  ## 三维体渲染
1145
1746
 
1146
1747
  三维体渲染是 Cesium 专有能力,直接通过 `BaseGIS` 调用。Leaflet 调用时会返回不支持结果,不会抛出破坏页面的异常。
@@ -1267,6 +1868,8 @@ console.log(stateResult.data)
1267
1868
  | `geometry` / `dim` | 选填 | 自定义体渲染几何或维度,普通业务通常不需要传。 |
1268
1869
  | `visible` | 选填 | 初始是否显示,默认显示。 |
1269
1870
 
1871
+ <a id="section-rendering"></a>
1872
+
1270
1873
  ## 三维切片 / 剖面渲染
1271
1874
 
1272
1875
  三维切片/剖面渲染也是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它把三维网格数据按 X、Y、Z 三个方向切出剖面,适合气象温度、湿度、风场标量、污染物浓度等三维格点数据查看。
@@ -1449,6 +2052,8 @@ if (result.success) {
1449
2052
  | `lon / lat / hpa` | hover 取值必填 | Cesium 拾取得到的经度、纬度、气压高度。 |
1450
2053
  | `showZLayer` | hover 取值选填 | Z 切片是否参与 hover 命中,默认参与。 |
1451
2054
 
2055
+ <a id="point-large-layer-controller"></a>
2056
+
1452
2057
  ## PointLargeLayerController
1453
2058
 
1454
2059
  用于海量点位渲染。它会尽量把传入的有效点位全部渲染出来,适合站点、设备、告警、监测点等需要保留全部点位并支持点击、高亮、删除、显隐的场景。
@@ -1811,6 +2416,315 @@ console.log(state)
1811
2416
  ```
1812
2417
 
1813
2418
 
2419
+ <a id="point-cluster-controller"></a>
2420
+
2421
+ ## PointClusterController 点位聚合
2422
+
2423
+ ### 能力介绍
2424
+
2425
+ 用于点位聚合。地图移动、缩放或数据更新后,会把当前视野内落入同一屏幕网格的邻近点合成一个带数量的聚合圆;放大后,聚合圆会继续拆分为更小的聚合组或独立点。
2426
+
2427
+ Cesium / Leaflet 使用相同的点位解析、屏幕网格和状态字段。页面只创建 `BaseGIS` 与 `PointClusterController`,不需要判断当前引擎。
2428
+
2429
+ 一次聚合过程会:
2430
+
2431
+ 1. 根据当前地图视野筛选可见点位。
2432
+ 2. 把经纬度投影到屏幕坐标,并按 `pixelRange` 划分网格。
2433
+ 3. 网格内点数达到 `minimumClusterSize` 时渲染聚合圆,否则渲染独立点。
2434
+ 4. 在地图移动、缩放或数据更新后重新计算,并通过 `onStateChange` 返回最新统计。
2435
+
2436
+ 聚合只改变当前视野的渲染方式,不修改传入的原始点位数据。它适合站点、设备、告警等密集点位;如果需要显示全部点位或只保留代表点,应分别使用 `PointLargeLayerController` 或 `PointDensityController`。
2437
+
2438
+ 它和另外两种点位能力的区别:
2439
+
2440
+ - `PointLargeLayerController`:尽量显示全部有效点位。
2441
+ - `PointClusterController`:邻近点合成一个带数量的聚合圆,可点击继续展开。
2442
+ - `PointDensityController`:邻近点只保留一个代表点,不显示合计数量。
2443
+
2444
+ ### Demo 位置
2445
+
2446
+ - 路由:`/test-page-29`
2447
+ - 页面:`src/views/test-page-29/index.vue`
2448
+ - 数据量:默认生成 10,000 个点位。
2449
+ - 覆盖能力:Cesium / Leaflet 切换、聚合参数更新、数据更新、聚合点击放大、单点点击、显隐、清空、删除和状态读取。
2450
+
2451
+ ### Vue 页面完整用法
2452
+
2453
+ ```vue
2454
+ <template>
2455
+ <div id="station-cluster-map" class="station-cluster-map"></div>
2456
+ </template>
2457
+
2458
+ <script setup>
2459
+ import { onBeforeUnmount, onMounted } from 'vue'
2460
+ import { BaseGIS } from '@3clear/basegis'
2461
+ import { PointClusterController } from '@3clear/basegis/methods'
2462
+ import '@3clear/basegis/style.css'
2463
+
2464
+ let mapCore = null
2465
+ let stationCluster = null
2466
+
2467
+ const points = [
2468
+ { id: 'A001', name: '站点 A', position: [121.4700, 31.2300] },
2469
+ { id: 'A002', name: '站点 B', position: [121.4705, 31.2304] },
2470
+ { id: 'A003', name: '站点 C', position: [121.4710, 31.2308] },
2471
+ ]
2472
+
2473
+ onMounted(async () => {
2474
+ mapCore = new BaseGIS({
2475
+ engineType: 'cesium',
2476
+ containerId: 'station-cluster-map',
2477
+ config: {
2478
+ view: {
2479
+ initialView: {
2480
+ center: [121.4737, 31.2304],
2481
+ height: 420000,
2482
+ zoom: 8,
2483
+ pitch: -90,
2484
+ },
2485
+ },
2486
+ },
2487
+ })
2488
+
2489
+ const initResult = mapCore.init()
2490
+ if (!initResult.success) {
2491
+ console.warn(initResult.message)
2492
+ return
2493
+ }
2494
+
2495
+ stationCluster = new PointClusterController({
2496
+ mapCore,
2497
+ layerId: 'station-cluster',
2498
+
2499
+ // 聚合网格大小,单位屏幕像素。值越大,点越容易聚在一起。
2500
+ pixelRange: 72,
2501
+
2502
+ // 同一网格至少有 3 个点时才显示聚合圆。
2503
+ minimumClusterSize: 3,
2504
+
2505
+ pointColor: '#36cfc9',
2506
+ pointOutlineColor: '#e6fffb',
2507
+ pointSize: 14,
2508
+ clusterColor: '#1677ff',
2509
+ clusterTextColor: '#ffffff',
2510
+ clusterSize: 42,
2511
+
2512
+ // 默认 true。点击聚合圆后,当前引擎会自动向聚合中心放大。
2513
+ zoomOnClusterClick: true,
2514
+
2515
+ onClick(point) {
2516
+ console.log('点击单点:', point)
2517
+ },
2518
+
2519
+ onClusterClick(cluster) {
2520
+ console.log(`点击了包含 ${cluster.count} 个点的聚合圆`)
2521
+ console.log(cluster.center, cluster.ids, cluster.points)
2522
+ },
2523
+
2524
+ onStateChange(state) {
2525
+ console.log('源点、聚合组、渲染对象:', {
2526
+ sourceCount: state.sourceCount,
2527
+ clusterCount: state.clusterCount,
2528
+ renderedCount: state.renderedCount,
2529
+ })
2530
+ },
2531
+ })
2532
+
2533
+ const loadResult = await stationCluster.load({
2534
+ points,
2535
+ visible: true,
2536
+ })
2537
+ if (!loadResult.success) {
2538
+ console.warn(loadResult.message)
2539
+ }
2540
+ })
2541
+
2542
+ onBeforeUnmount(() => {
2543
+ stationCluster?.destroy()
2544
+ stationCluster = null
2545
+ mapCore?.destroy()
2546
+ mapCore = null
2547
+ })
2548
+ </script>
2549
+
2550
+ <style scoped lang="scss">
2551
+ .station-cluster-map {
2552
+ width: 100%;
2553
+ height: 100%;
2554
+ }
2555
+ </style>
2556
+ ```
2557
+
2558
+ `load` 也支持直接传数组或 GeoJSON FeatureCollection:
2559
+
2560
+ ```js
2561
+ await stationCluster.load(points)
2562
+ await stationCluster.load(featureCollection)
2563
+ ```
2564
+
2565
+ 点位既可以使用 `position: [经度, 纬度, 高度]`、`coordinates` 或直接坐标数组,也可以使用以下默认字段:
2566
+
2567
+ - 经度:`longitude / lon / lng / x`
2568
+ - 纬度:`latitude / lat / y`
2569
+ - 高度:`height / altitude / z`
2570
+ - 唯一值:默认 `id`,可通过 `idKey` 指定
2571
+
2572
+ ### 更新聚合参数与数据
2573
+
2574
+ ```js
2575
+ // 让点位更容易形成聚合。
2576
+ await stationCluster.setConfig({
2577
+ pixelRange: 96,
2578
+ minimumClusterSize: 2,
2579
+ })
2580
+
2581
+ // 替换数据,保留图层和其他配置。
2582
+ await stationCluster.update({
2583
+ points: nextPoints,
2584
+ })
2585
+ ```
2586
+
2587
+ `clusterRadius` 是 `pixelRange` 的别名。两者同时传入时,以 `pixelRange` 为准。
2588
+
2589
+ ### 切换 Cesium / Leaflet
2590
+
2591
+ 聚合图层由 `BaseGIS` 托管。切换引擎时直接调用 `setEngine()`,无需在页面里重建 Cesium / Leaflet 分支;切换成功后,现有聚合数据和配置会通过托管快照自动恢复。
2592
+
2593
+ ```js
2594
+ const result = await mapCore.setEngine('leaflet')
2595
+ if (!result.success) {
2596
+ console.warn(result.message)
2597
+ }
2598
+ ```
2599
+
2600
+ ### 点击回调
2601
+
2602
+ 单点点击的第一个参数是原始点位数据:
2603
+
2604
+ ```js
2605
+ onClick(point, engineObject, event) {
2606
+ console.log(point.id)
2607
+ }
2608
+ ```
2609
+
2610
+ 聚合点击的第一个参数结构如下:
2611
+
2612
+ ```js
2613
+ {
2614
+ layerId: 'station-cluster',
2615
+ key: 'cluster:12:8',
2616
+ count: 36,
2617
+ center: [121.48, 31.23, 0],
2618
+ ids: ['A001', 'A002'],
2619
+ points: [/* 原始点位数据 */],
2620
+ }
2621
+ ```
2622
+
2623
+ `engineObject` 和 `event` 是当前引擎的拾取对象与事件,仅在确实需要底层信息时使用。通用业务逻辑应优先只读取第一个参数。
2624
+
2625
+ ### 显隐、清空和销毁
2626
+
2627
+ ```js
2628
+ stationCluster.hide()
2629
+ stationCluster.show()
2630
+ stationCluster.toggle()
2631
+ stationCluster.toggle(true)
2632
+ stationCluster.refreshState()
2633
+
2634
+ // 清空数据但保留图层实例,后续仍可 update/load。
2635
+ stationCluster.clear()
2636
+
2637
+ // 删除图层并释放监听与渲染资源。
2638
+ stationCluster.destroy()
2639
+ ```
2640
+
2641
+ | 方法 | 说明 |
2642
+ | --- | --- |
2643
+ | `mount(mapCore)` | 挂载 `BaseGIS`。构造时已传 `mapCore/baseGIS` 时无需重复调用。 |
2644
+ | `load(payload, options)` | 创建或覆盖聚合点数据,支持数组、FeatureCollection 或 `{ points }`。 |
2645
+ | `upsert(payload, options)` | `load` 的别名。 |
2646
+ | `update(payload)` | 合并现有配置,更新数据、样式或聚合参数。 |
2647
+ | `setConfig(payload)` | `update` 的别名,适合只表达参数更新。 |
2648
+ | `show()` / `hide()` / `toggle()` | 控制图层显隐。 |
2649
+ | `clear()` | 清空点位,保留图层实例。 |
2650
+ | `refreshState()` | 从当前引擎重新读取状态。 |
2651
+ | `destroy()` / `removeLayer()` | 删除图层并释放资源。 |
2652
+ | `getState()` | 获取控制器与引擎侧聚合状态。 |
2653
+
2654
+ ### 参数总表
2655
+
2656
+ | 参数 | 是否必填 | 默认值 | 说明 |
2657
+ | --- | --- | --- | --- |
2658
+ | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。 |
2659
+ | `layerId` / `pointClusterLayerId` / `clusterLayerId` / `id` | 否 | `point-cluster-default` | 聚合图层 ID。 |
2660
+ | `points` / `data` / `items` | 首次加载必填其一 | - | 点位数组或 GeoJSON FeatureCollection。 |
2661
+ | `idKey` | 否 | `id` | 点位唯一值字段。 |
2662
+ | `longitudeKeys` / `latitudeKeys` / `heightKeys` | 否 | 内置常用字段 | 自定义坐标字段候选名。 |
2663
+ | `visible` | 否 | `true` | 初始是否显示。 |
2664
+ | `pixelRange` / `clusterRadius` | 否 | `64` | 屏幕聚合网格大小,单位像素。 |
2665
+ | `minimumClusterSize` | 否 | `2` | 形成聚合圆所需的最少点数,最小为 2。 |
2666
+ | `pointSize` | 否 | Cesium `16` / Leaflet `14` | 未传图片时的圆点尺寸。 |
2667
+ | `pointWidth` / `pointHeight` | 否 | `pointSize` | 单点图片显示尺寸。 |
2668
+ | `pointColor` | 否 | `#3aa7ff` | 默认单点填充色。 |
2669
+ | `pointOutlineColor` / `pointOutlineWidth` | 否 | 白色 / `2` | 默认单点描边。 |
2670
+ | `image` / `pointImage` / `icon` / `iconUrl` | 否 | - | 自定义单点图片。 |
2671
+ | `imageCallback` | 否 | - | 按点位返回图片地址或图标配置。 |
2672
+ | `clusterSize` | 否 | `42` | 聚合圆基础尺寸;数量增大时会轻微放大。 |
2673
+ | `clusterColor` | 否 | `#1677ff` | 聚合圆背景色。 |
2674
+ | `clusterTextColor` | 否 | `#ffffff` | 聚合数量文字颜色。 |
2675
+ | `clusterOutlineColor` / `clusterOutlineWidth` | 否 | 白色 / `3` | 聚合圆描边。 |
2676
+ | `clusterImageCallback` | 否 | - | 默认图标由双引擎共用 Canvas 生成;可返回自定义图片地址、Canvas 或图标配置。 |
2677
+ | `zoomOnClusterClick` | 否 | `true` | 点击聚合圆后是否自动放大。 |
2678
+ | `throttleTime` | 否 | `100` | 地图变化后重新聚合的节流时间,毫秒。 |
2679
+ | `onClick` | 否 | - | 单点点击回调。 |
2680
+ | `onClusterClick` | 否 | - | 聚合点击回调。 |
2681
+ | `onStateChange` | 否 | - | 聚合状态变化回调。 |
2682
+ | `viewportBuffer` | 否 | `80` | Cesium 专用:屏幕视野缓冲像素。 |
2683
+ | `cullByGlobe` | 否 | `true` | Cesium 专用:剔除地球背面点。 |
2684
+ | `clampToGround` | 否 | `false` | Cesium 专用:单点是否贴地。 |
2685
+ | `clusterZoomFactor` | 否 | `0.45` | Cesium 专用:每次点击后的目标高度比例。 |
2686
+ | `clusterZoomStep` | 否 | `2` | Leaflet 专用:每次点击增加的缩放级别。 |
2687
+ | `maxClusterZoom` | 否 | `Infinity` | Leaflet 专用:聚合点击最大缩放级别。 |
2688
+ | `zIndexOffset` | 否 | `0` | Leaflet 专用:Marker 层级偏移。 |
2689
+
2690
+ ### 状态返回
2691
+
2692
+ ```js
2693
+ const state = stationCluster.getState()
2694
+
2695
+ console.log(state)
2696
+ // {
2697
+ // engineType: 'leaflet',
2698
+ // layerType: 'leaflet-point-cluster-layer',
2699
+ // sourceCount: 10000,
2700
+ // inViewCount: 9800,
2701
+ // outOfViewCount: 200,
2702
+ // clusterCount: 185,
2703
+ // clusteredPointCount: 9600,
2704
+ // visiblePointCount: 200,
2705
+ // renderedCount: 385,
2706
+ // pixelRange: 72,
2707
+ // minimumClusterSize: 3,
2708
+ // visible: true,
2709
+ // destroyed: false,
2710
+ // }
2711
+ ```
2712
+
2713
+ 其中 `renderedCount = clusterCount + visiblePointCount`;被聚合的原始点数量单独记录在 `clusteredPointCount`,不会和聚合圆数量混淆。
2714
+
2715
+ 底层统一方法也可直接通过 `BaseGIS` 调用:
2716
+
2717
+ ```js
2718
+ mapCore.upsertPointClusterLayer(payload)
2719
+ mapCore.showPointClusterLayer({ layerId: 'station-cluster' })
2720
+ mapCore.hidePointClusterLayer({ layerId: 'station-cluster' })
2721
+ mapCore.clearPointClusterLayer({ layerId: 'station-cluster' })
2722
+ mapCore.getPointClusterLayerState({ layerId: 'station-cluster' })
2723
+ mapCore.removePointClusterLayer({ layerId: 'station-cluster' })
2724
+ ```
2725
+
2726
+ <a id="point-density-controller"></a>
2727
+
1814
2728
  ## PointDensityController
1815
2729
 
1816
2730
  用于点位密度抽稀。地图移动、缩放后会按当前视野和屏幕网格重新计算可见点,适合站点、设备、告警、空气质量监测点等高密度点位。
@@ -2170,9 +3084,35 @@ console.log(state)
2170
3084
  // }
2171
3085
  ```
2172
3086
 
2173
- ## ContourLayerController
3087
+ <a id="contour-guide"></a>
3088
+
3089
+ ## 等值线与中心标注能力选择
3090
+
3091
+ 对外只保留 2 个控制器:已有线坐标或中心坐标时用 `ContourLayerController`,手里是栅格时用 `RasterContourController`。线值标签与中心标注都属于通用 Contour 渲染配置,不再需要单独的气压组合层。
3092
+
3093
+ | 手里的数据 / 目标 | 使用入口 | 负责内容 |
3094
+ | --- | --- | --- |
3095
+ | 已经计算好的等值线,可选已有中心点 | [`ContourLayerController`](#contour-layer-controller) | 渲染线、线值标签和传入的 `centers`,并管理统一生命周期。 |
3096
+ | GeoTIFF、灰度图或数值网格 | [`RasterContourController`](#raster-contour-controller) | 读取栅格、追踪等值线;显式开启后可从网格检测局部 H/L 中心。 |
3097
+
3098
+ 两条数据链路:
3099
+
3100
+ ```text
3101
+ 已有 isolines + 可选 centers ──> ContourLayerController ──> 线 + 标签 + 中心
3102
+ GeoTIFF / 灰度图 / 数值网格 ──> RasterContourController ──> ContourLayerController ──> 地图
3103
+ ```
3104
+
3105
+ 特别注意:
3106
+
3107
+ - `ContourLayerController` 只渲染传入的 `centers`,不会从已有线坐标反推极值中心。
3108
+ - `RasterContourController.centerDetection` 默认关闭;只有气压等确实需要 H/L 语义的数值网格才应显式开启。
3109
+ - 两个控制器都不转换业务单位或 Windy 原始格式;传入值就是最终计算和展示值。
2174
3110
 
2175
- 用于等值线图层,支持加载、更新、显隐、清空和销毁。
3111
+ <a id="contour-layer-controller"></a>
3112
+
3113
+ ## ContourLayerController:渲染已有等值线与中心标注
3114
+
3115
+ 负责渲染已经计算好的线和业务传入的中心坐标,不读取栅格,也不执行插值或等值线追踪。Cesium / Leaflet 共用同一套数据、样式和生命周期 API。
2176
3116
 
2177
3117
  ```js
2178
3118
  import { ContourLayerController } from '@3clear/basegis/methods'
@@ -2181,6 +3121,10 @@ const contour = new ContourLayerController({
2181
3121
  mapCore,
2182
3122
  layerId: 'temperature-contour',
2183
3123
  visible: true,
3124
+ color: '#ff4d4f',
3125
+ width: 2,
3126
+ showLabel: true,
3127
+ labelFormatter: (item) => `${item.value}℃`,
2184
3128
  })
2185
3129
 
2186
3130
  await contour.load({
@@ -2188,6 +3132,7 @@ await contour.load({
2188
3132
  {
2189
3133
  value: 20,
2190
3134
  lines: [
3135
+ // 默认坐标顺序是 [纬度, 经度]。
2191
3136
  [
2192
3137
  [30, 104],
2193
3138
  [31, 105],
@@ -2196,18 +3141,11 @@ await contour.load({
2196
3141
  ],
2197
3142
  },
2198
3143
  ],
2199
- style: {
2200
- color: '#ff4d4f',
2201
- width: 2,
2202
- },
2203
- showLabel: true,
2204
- labelFormatter: (item) => String(item.value),
2205
3144
  })
2206
3145
 
2207
3146
  await contour.update({
2208
- style: {
2209
- color: '#1677ff',
2210
- },
3147
+ color: '#1677ff',
3148
+ width: 1.5,
2211
3149
  })
2212
3150
 
2213
3151
  contour.hide()
@@ -2216,13 +3154,63 @@ contour.clear()
2216
3154
  contour.destroy()
2217
3155
  ```
2218
3156
 
2219
- 数据别名支持 `contours / isolines / isoline / lines / data / items`。开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的位置;地图平移、缩放、容器尺寸变化或切换引擎时只重排标签,不重新生成等值线。
3157
+ 样式参数必须放在配置顶层,或通过 `styleCallback` 按线返回;`style: { color, width }` 不会生效。
3158
+
3159
+ ### 输入数据
3160
+
3161
+ 推荐使用 `{ value, lines }` 结构:
3162
+
3163
+ ```js
3164
+ const contours = [
3165
+ {
3166
+ value: 20,
3167
+ label: '20℃',
3168
+ lines: [
3169
+ // 一条等值线;默认每个坐标是 [纬度, 经度]。
3170
+ [[30, 104], [31, 105], [32, 106]],
3171
+ ],
3172
+ },
3173
+ ]
3174
+ ```
3175
+
3176
+ 还支持:
3177
+
3178
+ - `{ value, coordinates: [[lat, lon], ...] }`
3179
+ - `{ value, points: [lat, lon, lat, lon, ...] }`
3180
+ - GeoJSON `LineString` / `MultiLineString` Feature;GeoJSON 坐标按标准 `[经度, 纬度]` 解析
3181
+ - 原始线数组,以及顶层字段别名 `contours / isolines / isoline / lines / data / items`
3182
+
3183
+ 传 GeoJSON FeatureCollection 时,请传 `featureCollection.features` 或 `{ contours: featureCollection.features }`。
3184
+
3185
+ <a id="contour-layer-parameters"></a>
3186
+
3187
+ ### 核心参数
3188
+
3189
+ | 参数 | 类型 | 默认值 | 说明 |
3190
+ | --- | --- | --- | --- |
3191
+ | `mapCore/baseGIS` | `BaseGIS` | - | 当前 `BaseGIS` 实例;构造时不传则后续调用 `mount(mapCore)`。 |
3192
+ | `layerId/contourLayerId` | `string` | `contour-default` | 图层唯一 ID。 |
3193
+ | `coordinateOrder` | `latlng/lnglat` | `latlng` | `lines/coordinates` 的嵌套坐标顺序。 |
3194
+ | `flatOrder` | `latlng/lnglat` | `latlng` | `points` 平铺数组的坐标顺序。 |
3195
+ | `color` | `string` | `rgba(255,255,255,0.82)` | 默认线色。 |
3196
+ | `width/weight` | `number` | `1.15` | 线宽;两种写法用于兼容双引擎。 |
3197
+ | `opacity` | `number` | `1` | 线透明度。 |
3198
+ | `styleCallback(rawItem, line, lineIndex, record)` | `Function` | - | 按原始记录和线段返回动态样式。 |
3199
+ | `splitWrappedLine` | `boolean` | `true` | 是否拆分跨日期变更线的异常跳线。 |
3200
+ | `worldCopyOffsets` | `number[]` | Cesium `[0]`;Leaflet `[-360,0,360]` | 世界副本经度偏移。 |
3201
+ | `visible` | `boolean` | `true` | 初始是否显示。 |
3202
+
3203
+ Cesium 专用参数包括 `height / clampToGround / granularity / lineThinStep`;Leaflet 专用参数包括 `smoothFactor / lineCap / lineJoin / interactive / pane`。
3204
+
3205
+ ### 双引擎渲染说明
3206
+
3207
+ 开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的标签位置。Leaflet 传入经度跨度达到 360° 的 `worldCopyArea` 后,还会按当前视野动态更新等值线路径和标签的世界副本;这个过程只重建可见路径,不重新解析栅格或追踪等值线。`RasterContourController` 会自动把栅格范围作为 `worldCopyArea` 传入。
2220
3208
 
2221
3209
  Cesium 在默认椭球地表上使用 `PolylineCollection` 批量落图;真正启用 DEM 地形后才使用 `GroundPolylinePrimitive`。新批次就绪后再替换旧批次,不为每条线创建独立 Entity。标签定位可通过 `labelLineThinStep` 抽样投影点,不会改变实际绘制的等值线。
2222
3210
 
2223
3211
  Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做屏幕空间简化:缩小时清理挤在同一像素内的折点,放大后自动保留曲线细节,避免全局 `preferCanvas` 放大细线锯齿。线位于 `dt-contour-pane`(层级 `450`),稳定显示在图片图层上方,并保持在 `markerPane` 数值标签下方;显式传入 `pane` 时仍以业务配置为准。
2224
3212
 
2225
- 屏幕标注参数:
3213
+ ### 通用线值标签参数
2226
3214
 
2227
3215
  | 参数 | 类型 | 默认值 | 说明 |
2228
3216
  | --- | --- | --- | --- |
@@ -2244,11 +3232,28 @@ Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做
2244
3232
 
2245
3233
  `getState()` 的 `labelCount` 是当前视野实际显示的标签数量。Cesium 状态额外包含 `lineRenderer/lineBuildElapsed/labelLayoutElapsed/primitiveReadyElapsed`,用于区分线构建、标签排版和异步就绪耗时。`onStateChange(state)` 会在视野重排后回传新状态。页面存在侧栏等遮挡时,可通过 `labelViewportPadding` 排除对应区域。
2246
3234
 
2247
- ## RasterContourController
3235
+ ### 生命周期与状态
3236
+
3237
+ | 方法 | 说明 |
3238
+ | --- | --- |
3239
+ | `mount(mapCore)` | 后挂载 `BaseGIS`;构造时已传入则无需调用。 |
3240
+ | `load(payload, options)` / `upsert(...)` | 首次创建或覆盖图层,必须包含等值线数据或 `centers`。 |
3241
+ | `update(payload)` / `setConfig(...)` | 合并数据或渲染参数并更新已经加载的图层。 |
3242
+ | `show()` / `hide()` / `toggle(visible)` | 控制图层显隐。 |
3243
+ | `clear()` | 清空线、线值标签和中心标注,但保留图层实例,可再次加载。 |
3244
+ | `refreshState()` | 从当前引擎重新读取状态。 |
3245
+ | `getState()` | 返回普通状态对象,包括 `levelCount / lineCount / centerCount / lineLabelCount / centerLabelCount / labelCount / visible`。 |
3246
+ | `destroy()` / `removeLayer()` | 删除图层、释放资源,并移除 `BaseGIS` 托管快照。 |
3247
+
3248
+ 推荐顺序:`BaseGIS.init()` 成功后创建控制器并 `load()`;页面卸载时先 `contour.destroy()`,再 `mapCore.destroy()`。
3249
+
3250
+ <a id="raster-contour-controller"></a>
2248
3251
 
2249
- 用于从 GeoTIFF、灰度图或数值网格直接计算并绘制等值线。内部统一使用 `d3-contour` 追踪等值线,输出仍交给 `ContourLayerController`,所以页面不需要区分 Cesium 和 Leaflet;调用 `BaseGIS.setEngine()` 时,已经生成的等值线会随托管图层自动恢复。
3252
+ ## RasterContourController:从栅格生成等值线
2250
3253
 
2251
- ### 1. GeoTIFF 生成等值线
3254
+ 负责完整的“读取数据源 → 标准化数值网格 → 追踪等值线 → 双引擎渲染”链路。内部使用 `d3-contour` 生成线,再交给 `ContourLayerController` 渲染;页面不需要区分 Cesium 和 Leaflet。调用 `BaseGIS.setEngine()` 时,已经生成的线会随托管图层自动恢复。
3255
+
3256
+ ### GeoTIFF
2252
3257
 
2253
3258
  ```js
2254
3259
  import { RasterContourController } from '@3clear/basegis/methods'
@@ -2294,7 +3299,7 @@ await rasterContour.update({ interval: 2 })
2294
3299
 
2295
3300
  GeoTIFF 依赖宿主页面提供 `window.GeoTIFF`。控制器会读取波段、尺寸、坐标系和范围;当前原生支持 EPSG:4326、EPSG:3857。其他投影可先转换为 WGS84,或传入 WGS84 `area` 覆盖原始范围。
2296
3301
 
2297
- ### 2. 灰度图生成等值线
3302
+ ### 灰度图
2298
3303
 
2299
3304
  ```js
2300
3305
  await rasterContour.loadGrayImage({
@@ -2318,7 +3323,9 @@ await rasterContour.loadGrayImage({
2318
3323
 
2319
3324
  灰度图片本身没有地理范围和业务值含义,因此 `area` 必填;要表达真实数据,还应传正确的灰度范围与业务值范围。已经着色的彩色 PNG 不能反推出原始业务值,应使用对应 TIF 或直接传数值网格。
2320
3325
 
2321
- ### 3. 数值网格生成等值线
3326
+ 上例虽然使用了气压值域,但 `centerDetection` 默认关闭,因此只生成普通等压线和线值标签。需要 H/L 中心时,按下方“自动检测 H/L 中心”配置显式开启。
3327
+
3328
+ ### 数值网格
2322
3329
 
2323
3330
  ```js
2324
3331
  await rasterContour.loadGrid({
@@ -2340,13 +3347,13 @@ await rasterContour.loadGrid({
2340
3347
  })
2341
3348
  ```
2342
3349
 
2343
- ### 4. 参数
3350
+ ### 参数
2344
3351
 
2345
3352
  数据源参数:
2346
3353
 
2347
3354
  | 参数 | 类型 | 必填 | 默认值 | 说明 |
2348
3355
  | --- | --- | --- | --- | --- |
2349
- | `mapCore/baseGIS` | `BaseGIS` | 构造时必填 | `null` | 当前 BaseGIS 实例。 |
3356
+ | `mapCore/baseGIS` | `BaseGIS` | | `null` | 当前 BaseGIS 实例;构造时不传则后续调用 `mount(mapCore)`。 |
2350
3357
  | `layerId/contourLayerId` | `string` | 否 | `raster-contour-default` | 等值线图层 id。 |
2351
3358
  | `sourceType` | `geotiff/grayscale/grid` | 否 | 自动判断 | `loadGeoTiff/loadGrayImage/loadGrid` 会自动补齐。 |
2352
3359
  | `tifUrl/tiffUrl` | `string` | GeoTIFF 必填 | `''` | GeoTIFF 地址。 |
@@ -2377,91 +3384,127 @@ await rasterContour.loadGrid({
2377
3384
  | `minimumLinePoints` | `number` | 否 | `3` | 最短线点数,用于过滤栅格四角的两点补边。 |
2378
3385
  | `coordinatePrecision` | `number` | 否 | `6` | 输出经纬度小数位数。 |
2379
3386
  | `visible` | `boolean` | 否 | `true` | 初始显隐。 |
2380
- | 其他渲染参数 | - | 否 | - | `ContourLayerController` 相同,如 `color/width/weight/styleCallback/showLabel/labelViewportPadding`。 |
3387
+ | 其他渲染参数 | - | 否 | - | 复用 [`ContourLayerController` 核心参数](#contour-layer-parameters),如 `color/width/weight/styleCallback/showLabel/labelViewportPadding`。 |
3388
+
3389
+ 级别选择优先级为 `thresholds` → `interval` → `thresholdCount`。显式传入 `thresholds` 时,只保留实际值域内的级别。
3390
+
3391
+ ### 自动检测 H/L 中心
3392
+
3393
+ 该能力默认关闭。它使用标准化后的数值网格查找局部极大值(H)和局部极小值(L),而不是根据已经生成的线反推。因此只应用于确实存在 H/L 语义的栅格数据。
3394
+
3395
+ ```js
3396
+ await rasterContour.loadGrid({
3397
+ values: pressureValues,
3398
+ width,
3399
+ height,
3400
+ area: [70, 10, 140, 60],
3401
+ interval: 4,
3402
+ showLabel: true,
3403
+ centerDetection: {
3404
+ enabled: true,
3405
+ radius: 6,
3406
+ minProminence: 2,
3407
+ minGridDistance: 20,
3408
+ maxCount: 12,
3409
+ },
3410
+ centerAnnotation: {
3411
+ visible: true,
3412
+ collision: true,
3413
+ color: '#e3f8ff',
3414
+ valueColor: '#ffffff',
3415
+ },
3416
+ })
3417
+ ```
2381
3418
 
2382
- ### 5. 方法和状态
3419
+ | `centerDetection` 参数 | 默认值 | 说明 |
3420
+ | --- | --- | --- |
3421
+ | `enabled` | `false` | 开启 H/L 自动检测。 |
3422
+ | `radius` | `6` | 计算局部显著度的网格半径。 |
3423
+ | `minProminence` | `2` | 中心值与邻域平均值的最小差异,单位与输入网格一致。 |
3424
+ | `minGridDistance` | `20` | 同类中心的最小网格距离,距离过近时合并。 |
3425
+ | `maxCount` | `12` | H/L 合计最大输出数。 |
3426
+ | `includeHigh/includeLow` | `true/true` | 是否输出 H 或 L。 |
3427
+ | `excludeBoundary` | `true` | 排除贴近边界或 NoData 空洞的候选。 |
3428
+ | `smoothingIterations` | `1` | 3×3 有效值平滑次数,范围 0~3。 |
3429
+
3430
+ 检测流程会先进行轻量平滑,再按显著度过滤、合并过近的同类中心并限制数量。超过 100 万格时只对候选搜索做块均值抽样,最终仍回到原网格取精确值和坐标。关闭检测后会清理上一次自动中心。
3431
+
3432
+ `RasterContourController` 也可直接传顶层 `centers`。手工数组(包括 `[]`)优先于自动检测;传 `centers: null` 可恢复使用当前 `centerDetection` 的结果。检测只产生中心数据,仍需 `centerAnnotation.visible: true` 才会显示。
3433
+
3434
+ ### 方法和状态
2383
3435
 
2384
3436
  | 方法 | 说明 |
2385
3437
  | --- | --- |
3438
+ | `mount(mapCore)` | 后挂载 `BaseGIS`;构造时已传入则无需调用。 |
2386
3439
  | `load(payload, options)` | 自动判断数据源并生成等值线。 |
2387
3440
  | `loadGeoTiff(payload, options)` | 从 GeoTIFF 生成。 |
2388
3441
  | `loadGrayImage(payload, options)` | 从灰度图生成。 |
2389
3442
  | `loadGrid(payload, options)` | 从数值网格生成。 |
2390
3443
  | `update(payload)` | 合并配置后重新计算;仅修改等值线或渲染参数时复用已解析网格,修改数据源参数时自动重新加载。 |
2391
- | `show/hide/toggle` | 控制等值线显隐。 |
3444
+ | `show/hide/toggle` | 统一控制等值线、线值标签和中心标注显隐。 |
2392
3445
  | `clear/destroy` | 清空或销毁控制器。 |
2393
- | `getState()` | 返回尺寸、值域、级别、线数、当前标注数、投影、耗时和底层 contour 状态。 |
3446
+ | `getState()` | 返回尺寸、值域、级别、线数、`centerCount`、标注数、投影、耗时和底层 contour 状态。 |
2394
3447
  | `getRasterData()` | 返回当前标准栅格,包含 `values/width/height/area`。 |
2395
3448
 
2396
- [//]: # ()
2397
- [//]: # (## PressureContourLayer)
2398
-
2399
- [//]: # ()
2400
- [//]: # (用于等压线和高低压中心标注。`buildPressureRenderData&#40;&#41;` 可把 Windy 风格的 `press.json` 数据整理成渲染结构,`createPressureDataResolver&#40;&#41;` 提供简单缓存。)
2401
-
2402
- [//]: # ()
2403
- [//]: # (```js)
2404
-
2405
- [//]: # (import {)
2406
-
2407
- [//]: # ( PressureContourLayer,)
2408
-
2409
- [//]: # ( createPressureDataResolver,)
2410
-
2411
- [//]: # (} from '@3clear/basegis/methods')
2412
-
2413
- [//]: # ()
2414
- [//]: # (const resolvePressureData = createPressureDataResolver&#40;{)
2415
-
2416
- [//]: # ( default: pressJson,)
2417
-
2418
- [//]: # ( next: pressJson2,)
2419
-
2420
- [//]: # (}&#41;)
2421
-
2422
- [//]: # ()
2423
- [//]: # (const pressureLayer = new PressureContourLayer&#40;{)
2424
-
2425
- [//]: # ( mapCore,)
2426
-
2427
- [//]: # ( layerId: 'pressure',)
2428
-
2429
- [//]: # ( color: 'rgba&#40;255,255,255,0.82&#41;',)
2430
-
2431
- [//]: # ( width: 1.15,)
2432
-
2433
- [//]: # ( smoothFactor: 0.2,)
2434
-
2435
- [//]: # ( // Cesium 场景下用于定位容器尺寸,Leaflet 场景也可以传同一个地图容器 id。)
2436
-
2437
- [//]: # ( mapContainerId: 'map',)
2438
-
2439
- [//]: # ( // 标注层通过该函数读取当前数据,便于地图缩放后重建标签。)
2440
-
2441
- [//]: # ( getData: &#40;&#41; => resolvePressureData&#40;'default'&#41;,)
2442
-
2443
- [//]: # ( isAnnotationVisible: &#40;&#41; => true,)
3449
+ ```js
3450
+ // clear 只清空当前渲染结果,控制器仍可继续 update/load。
3451
+ rasterContour.clear()
2444
3452
 
2445
- [//]: # (}&#41;)
3453
+ // 页面卸载时彻底释放栅格、图层和监听。
3454
+ rasterContour.destroy()
3455
+ ```
3456
+ <a id="contour-center-annotation"></a>
2446
3457
 
2447
- [//]: # ()
2448
- [//]: # (pressureLayer.mount&#40;&#41;)
3458
+ ## 中心标注配置(两个控制器通用)
2449
3459
 
2450
- [//]: # (await pressureLayer.load&#40;resolvePressureData&#40;'default'&#41;&#41;)
3460
+ `ContourLayerController` 通过顶层 `centers` 接收已经计算好的中心点,通过 `centerAnnotation` 控制显示和样式。该能力是通用标注,不把数据限制为气压;H/L 只是气压业务中的一种标签。
2451
3461
 
2452
- [//]: # (await pressureLayer.update&#40;resolvePressureData&#40;'next'&#41;&#41;)
3462
+ ```js
3463
+ const pressureContour = new ContourLayerController({
3464
+ mapCore,
3465
+ layerId: 'pressure-contour',
3466
+ showLabel: true,
3467
+ labelFormatter: (item) => String(item.value),
3468
+ centerAnnotation: {
3469
+ visible: true,
3470
+ collision: true,
3471
+ color: '#e3f8ff',
3472
+ fontSize: 20,
3473
+ valueColor: '#ffffff',
3474
+ valueFontSize: 12,
3475
+ },
3476
+ })
2453
3477
 
2454
- [//]: # (pressureLayer.hide&#40;&#41;)
3478
+ await pressureContour.load({
3479
+ isolines: [
3480
+ {
3481
+ value: 1012,
3482
+ lines: [[[31, 116.5], [31.4, 117.2], [31.7, 118]]],
3483
+ },
3484
+ ],
3485
+ centers: [
3486
+ { type: 'H', value: 1020, lat: 32.1, lon: 117.3 },
3487
+ { type: 'L', value: 1004, lat: 30.5, lon: 116.8 },
3488
+ ],
3489
+ })
3490
+ ```
2455
3491
 
2456
- [//]: # (pressureLayer.destroy&#40;&#41;)
3492
+ `centers[]` 支持 `{ label, type, value, lat, lon }`,也支持 `position`。对象坐标字段固定按语义读取;`position` 数组遵循 `coordinateOrder`。主标签优先读取 `label`,其次读取 `type`。
2457
3493
 
2458
- [//]: # (```)
3494
+ | `centerAnnotation` 参数 | 默认值 | 说明 |
3495
+ | --- | --- | --- |
3496
+ | `visible` | `false` | 是否显示中心标注。数据与显示配置分开,传入 centers 不会自动开启。 |
3497
+ | `collision` | `true` | 是否避让其他中心;中心先占位,线值标签随后避让。 |
3498
+ | `formatter(raw, center)` | - | 返回文本,或返回 `{ label, value }`。 |
3499
+ | `styleCallback(raw, index, center)` | - | 按中心覆盖颜色、字号、字重或返回 `visible:false`。 |
3500
+ | `color/fontSize` | `#34464f / 18` | 主标签颜色与字号。 |
3501
+ | `valueColor/valueFontSize` | 跟随 `color` / `12` | 数值颜色与字号;`color` 也未传时颜色为 `#34464f`。 |
3502
+ | `collisionPadding` | `8` | 碰撞间距,单位 px。 |
3503
+ | `viewportPadding` | `24` | 屏幕安全边距。 |
2459
3504
 
2460
- 说明:
3505
+ 状态中的 `centerCount` 是成功解析的有效中心数,`lineLabelCount` 和 `centerLabelCount` 是当前视野实际显示数,`labelCount` 是两者合计。`show/hide/clear/destroy` 以及 `BaseGIS.setEngine()` 会和等值线一起管理这些中心标注。
2461
3506
 
2462
- - 等压线数据会被整理为 `isolines` 和 `centers`。
2463
- - Leaflet 使用 `divIcon` 绘制 H/L 中心和线值标签。
2464
- - Cesium 使用独立标注集合,地图变化时会调度刷新。
3507
+ <a id="wind-field-methods"></a>
2465
3508
 
2466
3509
  ## WindFieldMethods
2467
3510
 
@@ -2689,7 +3732,7 @@ const result = await setEngine('leaflet')
2689
3732
  console.log(result.data?.restore)
2690
3733
  ```
2691
3734
 
2692
- 只有风场、剖面、一次性绘制对象或页面直接创建的底层引擎对象需要在切换成功后自行恢复。
3735
+ 只有 GPU 专用风场、剖面、一次性绘制对象或页面直接创建的底层引擎对象需要在切换成功后自行恢复。
2693
3736
 
2694
3737
  ## 能力支持说明
2695
3738
 
@@ -2700,9 +3743,11 @@ console.log(result.data?.restore)
2700
3743
  | 放大、缩小、重置 | 支持 | 支持 |
2701
3744
  | 底图切换 | 支持 | 支持 |
2702
3745
  | 点线面文字 | 支持 | 支持 |
3746
+ | 独立线、虚线、渐变与沿线动画 | 支持 | 支持 |
2703
3747
  | 图片覆盖层 | 支持 | 支持 |
2704
3748
  | 网格 / TIF | 支持 | 支持 |
2705
3749
  | 海量点 | 支持 | 支持 |
3750
+ | 点位聚合 | 支持 | 支持 |
2706
3751
  | 点位密度抽稀 | 支持 | 支持 |
2707
3752
  | 等值线 | 支持 | 支持 |
2708
3753
  | 等压线标注 | 支持 | 支持 |
@@ -2711,6 +3756,34 @@ console.log(result.data?.restore)
2711
3756
  | 三维切片 / 剖面渲染 | 支持 | 不支持 |
2712
3757
  | DEM 地形 | 支持 | 不支持 |
2713
3758
 
3759
+ ## 第三方许可
3760
+
3761
+ GPU 粒子风场核心改编自 RaymanNg/3D-Wind-Field:
3762
+
3763
+ ```text
3764
+ MIT License
3765
+
3766
+ Copyright (c) 2019 RaymanNg
3767
+
3768
+ Permission is hereby granted, free of charge, to any person obtaining a copy
3769
+ of this software and associated documentation files (the "Software"), to deal
3770
+ in the Software without restriction, including without limitation the rights
3771
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3772
+ copies of the Software, and to permit persons to whom the Software is
3773
+ furnished to do so, subject to the following conditions:
3774
+
3775
+ The above copyright notice and this permission notice shall be included in all
3776
+ copies or substantial portions of the Software.
3777
+
3778
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3779
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3780
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3781
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3782
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3783
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3784
+ SOFTWARE.
3785
+ ```
3786
+
2714
3787
  ## 本地开发与打包
2715
3788
 
2716
3789
  在仓库根目录执行:
@@ -2821,7 +3894,7 @@ mapCore.init()
2821
3894
 
2822
3895
  ### 切换引擎后图层没了
2823
3896
 
2824
- 直接使用 `setEngine()`。BaseGIS 会保留视野,并自动恢复图片、网格、海量点、点位抽稀、等值线和三维体等托管图层:
3897
+ 直接使用 `setEngine()`。BaseGIS 会保留视野,并自动恢复图片、网格、海量点、点位聚合、点位抽稀、独立线、等值线、统一风场和三维体等托管图层:
2825
3898
 
2826
3899
  ```js
2827
3900
  const result = await mapCore.setEngine('leaflet')
@@ -2830,4 +3903,4 @@ console.log(result.data?.restore?.restored)
2830
3903
  console.log(result.data?.restore?.failed)
2831
3904
  ```
2832
3905
 
2833
- 如果丢失的是 `drawPoint/drawLine/drawPolygon/drawText/addMarker`、点击监听、风场、剖面图层或页面直接创建的 Cesium / Leaflet 对象,它们不属于托管图层,需要业务在切换成功后自行恢复。
3906
+ 如果丢失的是 `drawPoint/drawLine/drawPolygon/drawText/addMarker`、点击监听、GPU 专用风场、剖面图层或页面直接创建的 Cesium / Leaflet 对象,它们不属于托管图层,需要业务在切换成功后自行恢复。