@3clear/basegis 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @3clear/basegis
2
2
 
3
3
  `@3clear/basegis` 是 3clear 一张图项目抽出的 GIS 能力包。它提供一个统一入口 `BaseGIS`,把 Cesium / Leaflet 的差异收敛到适配层里;业务页面优先只操作 `BaseGIS`,复杂图层能力通过 `methods` 控制器组合 `BaseGIS` 公共方法完成。
4
-
4
+ 把leaflet cesium 常用方法做了二次封装统一了api 使同一套代码可以适配2套地图引擎
5
5
  当前包包含:
6
6
 
7
- - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件等基础能力。
7
+ - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、三维体渲染、三维切片/剖面渲染等基础能力。
8
8
  - `methods`:图片图层、网格图层、海量点、点位抽稀、等值线、等压线、风场、图形组等高级控制器。
9
9
  - `layers`:天地图、GeoServer 金字塔瓦片、WMS、WMTS 图层配置快捷构造器。
10
10
  - `assets`:GIS 示例资源。
@@ -13,18 +13,19 @@
13
13
  ## 安装
14
14
 
15
15
  ```bash
16
- npm install @3clear/basegis leaflet axios pixi.js leaflet-pixi-overlay
16
+ npm install @3clear/basegis leaflet axios
17
17
  ```
18
18
 
19
+ `d3-contour`、`pixi.js` 和 `leaflet-pixi-overlay` 已随 BaseGIS 构建产物发布,业务项目不需要单独安装。其中 Pixi 相关代码只在首次使用 Leaflet 海量点能力时按需加载。
20
+
19
21
  使用样式:
20
22
 
21
23
  ```js
22
24
  import '@3clear/basegis/style.css'
23
25
  ```
24
26
 
25
- Cesium 当前不随 npm 包发布,宿主项目需要按原项目方式把 Cesium 静态资源放到 `public/lib/Cesium`,并保证初始化前能访问 `window.Cesium`。默认配置会从 `${BASE_URL}lib/Cesium/Cesium.js` 和 `${BASE_URL}lib/Cesium/Widgets/widgets.css` 加载。
26
-
27
- 如果需要加载 GeoTIFF 网格,需要宿主项目提前提供 `window.GeoTIFF`。
27
+ Cesium 当前不随 npm 包发布,项目需要按原项目方式把 Cesium 静态资源放到 `public/lib/Cesium`,并保证初始化前能访问 `window.Cesium`。
28
+ 如果需要加载 GeoTIFF 网格,需要项目提前提供 `window.GeoTIFF`。
28
29
 
29
30
  ## 出口
30
31
 
@@ -40,6 +41,7 @@ import {
40
41
  PointLargeLayerController,
41
42
  PointDensityController,
42
43
  ContourLayerController,
44
+ RasterContourController,
43
45
  PressureContourLayer,
44
46
  createPressureDataResolver,
45
47
  buildPressureRenderData,
@@ -91,6 +93,12 @@ onMounted(() => {
91
93
  pitch: -90,
92
94
  },
93
95
  },
96
+ basemap: {
97
+ // 默认内置值:tianditu-imagery。
98
+ defaultVisibleId: 'tianditu-imagery',
99
+ // 默认内置值:tianditu-vector-label。
100
+ defaultAnnotationId: 'tianditu-vector-label',
101
+ },
94
102
  },
95
103
  })
96
104
 
@@ -133,263 +141,532 @@ onBeforeUnmount(() => {
133
141
  message: 'error message',
134
142
  code: 'NOT_INITIALIZED'
135
143
  }
144
+
145
+ ```
146
+
147
+ 说明:
148
+
149
+ - `containerId` 和 `container` 二选一即可;`init()` 时也可以再次传入。
150
+ - 页面传入的 `config` 会覆盖内置默认配置。
151
+ - `basemap.list` 和 `dem.list` 是数组,外部传入时会整体覆盖内置列表;如果要使用内置默认底图,不要传空数组覆盖。
152
+ - Cesium 默认视角 `pitch: -90`,表示垂直俯视。
153
+ - `BaseGIS` 只保存运行时配置,调用 `setConfig()` 不会自动重建地图,需要重新 `init()` 才会创建新地图实例。
154
+
155
+ ### 默认底图配置
156
+
157
+ 不传 `config.basemap` 时,内置默认配置如下:
158
+
159
+ ```js
160
+ basemap: {
161
+ defaultVisibleId: 'tianditu-imagery',
162
+ defaultAnnotationId: 'tianditu-vector-label',
163
+ }
164
+ ```
165
+
166
+ 默认底图列表:
167
+
168
+ | id | 名称 | category | enabled | type | provider | resourceKey | 默认用途 | 引擎支持 |
169
+ | --- | --- | --- | --- | --- | --- | --- | --- | --- |
170
+ | `tianditu-vector` | 天地图矢量底图 | `basemap` | `true` | `wmts` | `tianditu` | `vector` | 可作为 `defaultVisibleId` | Cesium / Leaflet |
171
+ | `tianditu-imagery` | 天地图影像底图 | `basemap` | `true` | `wmts` | `tianditu` | `imagery` | 内置 `defaultVisibleId` | Cesium / Leaflet |
172
+ | `tianditu-terrain` | 天地图地形底图 | `basemap` | `true` | `wmts` | `tianditu` | `terrain` | 可作为 `defaultVisibleId` | Cesium / Leaflet |
173
+ | `tianditu-vector-label` | 天地图矢量注记 | `annotation` | `true` | `wmts` | `tianditu` | `vectorLabel` | 内置 `defaultAnnotationId` | Cesium / Leaflet |
174
+ | `tianditu-terrain-label` | 天地图地形注记 | `annotation` | `true` | `wmts` | `tianditu` | `terrainLabel` | 可作为 `defaultAnnotationId` | Cesium / Leaflet |
175
+
176
+ 可用于 `defaultVisibleId` 的内置底图 id:
177
+
178
+ - `tianditu-vector`
179
+ - `tianditu-imagery`
180
+ - `tianditu-terrain`
181
+
182
+ 可用于 `defaultAnnotationId` 的内置注记 id:
183
+
184
+ - `tianditu-vector-label`
185
+ - `tianditu-terrain-label`
186
+
187
+ 说明:
188
+
189
+ - `defaultVisibleId` 应指向 `category: 'basemap'` 的底图。
190
+ - `defaultAnnotationId` 应指向 `category: 'annotation'` 的注记层。
191
+ - `tianditu-terrain` 是天地图地形底图瓦片,不是 Cesium 的 DEM 高程地形;如果要控制 Cesium terrainProvider,请看后文 DEM。
192
+ - `geoserver-wmts-sample` 和 `geoserver-wms-sample` 默认 `enabled: false`,只是配置格式示例;如果要作为默认底图,需要替换真实服务地址并改为 `enabled: true`。
193
+ - 天地图 provider 内置资源还包括 `imageryLabel`;默认 `basemap.list` 没有单独注册影像注记 id,但 `resourceKey: 'imagery'` 会自动推断使用 `imageryLabel` 注记。
194
+
195
+ ### 底图切换
196
+
197
+ 底图可以来自 `config.basemap.list`,也可以直接传配置对象。当前适配层支持:
198
+
199
+ - 天地图:`provider: 'tianditu'` 或传 `resourceKey`
200
+ - URL 模板瓦片:`type: 'wmts' / 'xyz' / 'tile'`
201
+ - WMS:`type: 'wms'`
202
+
203
+ 按配置 id 切换:
204
+
205
+ ```js
206
+ const result = mapCore.setBasemapById('tianditu-imagery')
207
+ if (!result.success) {
208
+ console.warn(result.message)
209
+ }
210
+ ```
211
+
212
+ 直接传底图配置:
213
+
214
+ ```js
215
+
216
+ mapCore.setBasemap({
217
+ id: 'custom-xyz',
218
+ name: '自定义 XYZ',
219
+ category: 'basemap',
220
+ type: 'xyz',
221
+ provider: 'custom',
222
+ url: 'https://example.com/tiles/{z}/{x}/{y}.png',
223
+ minZoom: 0,
224
+ maxZoom: 18,
225
+ subdomains: ['a', 'b', 'c'],
226
+ })
227
+
228
+ mapCore.setBasemap({
229
+ id: 'custom-wms',
230
+ name: '自定义 WMS',
231
+ category: 'basemap',
232
+ type: 'wms',
233
+ provider: 'custom',
234
+ serviceUrl: 'https://example.com/geoserver/wms',
235
+ layers: 'workspace:layer',
236
+ parameters: {
237
+ transparent: true,
238
+ format: 'image/png',
239
+ version: '1.1.1',
240
+ },
241
+ })
136
242
  ```
137
243
 
138
- Leaflet 不支持 3D、DEM 等能力时,会返回失败结果或控制台提示,不应直接打断页面。
139
244
 
140
- ## BaseGIS 配置
245
+ 方法说明:
246
+
247
+ | 方法 | 参数 | 说明 |
248
+ | --- | --- | --- |
249
+ | `setBasemapById(id)` | 底图 id | 从 `config.basemap.list` 查找底图并切换。会校验 `category / engineSupport / enabled`。 |
250
+ | `setBasemap(payload)` | 底图配置对象 | 直接切换到底图配置。 |
251
+
252
+ 常用底图参数:
253
+
254
+ | 参数 | 是否必填 | 说明 |
255
+ | --- | --- | --- |
256
+ | `id` | 建议必填 | 底图唯一 id。 |
257
+ | `name` | 选填 | 底图名称。 |
258
+ | `category` | 建议传 `basemap` | `setBasemapById` 会拒绝非 `basemap` 分类。 |
259
+ | `type` | 自定义服务必填 | `wmts`、`xyz`、`tile`、`wms`。 |
260
+ | `provider` | 选填 | 天地图传 `tianditu`,自定义服务传 `custom`。 |
261
+ | `resourceKey` | 天地图必填 | `vector`、`imagery`、`terrain` 等。 |
262
+ | `annotationResourceKey` | 选填 | 天地图注记资源,如 `vectorLabel`、`imageryLabel`。 |
263
+ | `url` | URL 模板必填 | `wmts/xyz/tile` 使用,支持 `{z}/{x}/{y}` 模板。 |
264
+ | `serviceUrl` | WMS 必填 | WMS 服务地址;也可用 `url`。 |
265
+ | `layers` | WMS 必填 | WMS 图层名。 |
266
+ | `parameters` | 选填 | WMS 附加参数。 |
267
+ | `engineSupport` | 选填 | 支持的引擎列表,如 `['cesium', 'leaflet']`。 |
268
+
269
+
270
+ ## BaseGIS 基础能力
271
+
272
+ 这一节列的是 `BaseGIS` 主入口直接提供的基础能力。业务页面优先调用这些方法;图片图层、网格图层、海量点、点位抽稀、等值线、风场等更复杂能力,建议使用后文 `methods` 中对应的 Controller。
273
+
274
+ ### 1. 生命周期、实例与引擎切换
275
+
276
+ `BaseGIS` 支持 `cesium` 和 `leaflet` 两种引擎。引擎可以在构造时指定,也可以在 `init()` 时指定。
141
277
 
142
278
  ```js
143
279
  const mapCore = new BaseGIS({
144
280
  engineType: 'cesium',
145
281
  containerId: 'map',
282
+ })
283
+
284
+ // 创建地图实例。
285
+ mapCore.init()
286
+
287
+ // 只传目标引擎即可完成切换、视野保留和托管图层恢复。
288
+ await mapCore.setEngine('leaflet')
289
+
290
+ // 覆盖运行时配置,不会自动重建地图。
291
+ mapCore.setConfig({ view: { defaultSceneMode: '2d' } })
292
+
293
+ // 读取当前地图引擎
294
+ mapCore.getEngineType()
295
+ //获取当前地图配置
296
+ mapCore.getConfig()
297
+ //获取地图实例
298
+ mapCore.getMapInstance()
299
+ // 销毁当前地图实例。
300
+ mapCore.destroy()
301
+ ```
302
+
303
+ Cesium 默认使用最高 2 倍设备像素比、FXAA 和 4 倍 MSAA,避免高分屏上的细线出现明显像素阶梯。可按设备性能覆盖:
304
+
305
+ ```js
306
+ const mapCore = new BaseGIS({
146
307
  config: {
147
308
  engine: {
148
- active: 'cesium',
149
309
  cesium: {
150
- // 本地 Cesium 静态资源地址。
151
- scriptUrl: '/lib/Cesium/Cesium.js',
152
- cssUrl: '/lib/Cesium/Widgets/widgets.css',
153
- },
154
- leaflet: {
155
- sourceType: 'npm',
156
- },
157
- },
158
- view: {
159
- defaultSceneMode: '3d',
160
- initialView: {
161
- center: [121.4737, 31.2304],
162
- height: 1800000,
163
- zoom: 7,
164
- heading: 0,
165
- pitch: -90,
166
- roll: 0,
310
+ renderQuality: {
311
+ maximumDevicePixelRatio: 1.5,
312
+ fxaa: true,
313
+ msaaSamples: 4,
314
+ },
167
315
  },
168
316
  },
169
- basemap: {
170
- defaultVisibleId: 'tianditu-imagery',
171
- defaultAnnotationId: 'tianditu-vector-label',
172
- list: [],
173
- },
174
- dem: {
175
- defaultEnabled: true,
176
- defaultVisibleId: 'ellipsoid-flat',
177
- list: [],
178
- },
179
317
  },
180
318
  })
181
319
  ```
182
320
 
183
- 说明:
321
+ 方法说明:
184
322
 
185
- - `containerId` `container` 二选一即可;`init()` 时也可以再次传入。
186
- - 页面传入的 `config` 会覆盖内置默认配置。
187
- - Cesium 默认视角 `pitch: -90`,表示垂直俯视。
188
- - `BaseGIS` 只保存运行时配置,调用 `setConfig()` 不会自动重建地图,需要重新 `init()` 才会创建新地图实例。
323
+ | 方法 | 参数 | 说明 |
324
+ | --- | --- | --- |
325
+ | `init(options)` | `{ container, containerId, engineType }` | 初始化地图。重复调用时会自动恢复 BaseGIS 托管图层。 |
326
+ | `setEngine(engineType, options)` | 引擎类型、初始化参数 | 未初始化时记录默认引擎;已初始化时自动切换、保留视野并恢复托管图层。 |
327
+ | `switchEngine(engineType, options)` | 引擎类型、初始化参数 | `setEngine()` 的兼容别名。 |
328
+ | `whenReady()` | 无 | 等待最近一次 `init()` 触发的托管图层恢复完成。 |
329
+ | `destroy()` | 无 | 销毁当前地图实例和 adapter,并清空托管图层快照。 |
330
+ | `setConfig(config)` | `Object` | 合并运行时配置;不自动重建地图。 |
331
+ | `getEngineType()` | 无 | 返回当前引擎类型。 |
332
+ | `getConfig()` | 无 | 返回当前运行时配置。 |
333
+ | `getMapInstance()` | 无 | 返回底层地图实例:Cesium `viewer` 或 Leaflet `map`。 |
189
334
 
190
- ## BaseGIS 基础能力
335
+ #### 推荐的引擎切换写法
191
336
 
192
- ### 生命周期与实例
337
+ 页面上做 Cesium / Leaflet 切换时,直接调用 `setEngine()`。它会保留当前视野、重建目标引擎,并等待托管图层自动恢复。
193
338
 
194
339
  ```js
195
- mapCore.init({ containerId: 'map', engineType: 'leaflet' })
196
- mapCore.destroy()
340
+ async function changeEngine(engineType) {
341
+ if (!mapCore || mapCore.getEngineType() === engineType) {
342
+ return
343
+ }
197
344
 
198
- mapCore.setEngine('cesium')
199
- mapCore.setConfig({ view: { defaultSceneMode: '2d' } })
345
+ const result = await mapCore.setEngine(engineType)
200
346
 
201
- mapCore.getEngineType()
202
- mapCore.getConfig()
203
- mapCore.getMapInstance()
347
+ if (!result.success) {
348
+ console.warn(result.message)
349
+ return
350
+ }
351
+ }
352
+ ```
353
+
354
+ 直接重复调用 `init({ engineType })` 也会自动开始恢复;如果后续逻辑依赖恢复完成,需要再等待 `whenReady()`:
355
+
356
+ ```js
357
+ const result = mapCore.init({
358
+ engineType: 'leaflet',
359
+ containerId: 'map',
360
+ })
361
+
362
+ if (result.success) {
363
+ await mapCore.whenReady()
364
+ }
365
+ ```
366
+
367
+ `switchEngine()` 作为兼容别名保留,行为与 `setEngine()` 一致。
368
+
369
+ #### 切换后的图层处理
370
+
371
+ 引擎切换不是把 Cesium 图层对象“搬到” Leaflet,也不是把 Leaflet 图层对象“搬到” Cesium。BaseGIS 会保存托管图层的业务参数,并在新 adapter 中重新创建图层。
372
+
373
+ - 自动恢复范围包括图片、网格、海量点、点位抽稀、等值线和三维体图层,对应 `upsert*Layer` 方法及 Controller。
374
+ - 图层最新的数据参数、显隐、清空、删除、海量点删除和高亮状态会同步到 BaseGIS 快照。
375
+ - 快照只保存业务参数引用,不复制大数组,不保存任何底层引擎对象。
376
+ - 三维体图层切到 Leaflet 时会返回不支持结果,但快照仍保留,切回 Cesium 后会继续恢复。
377
+ - Cesium 专有能力在 Leaflet 下不可用,例如 DEM、三维体渲染、三维切片/剖面渲染。
378
+ - 一次性绘制对象、点击监听、风场、剖面图层和页面直接操作底层引擎创建的对象不在托管范围内,需要业务自行恢复。
379
+
380
+ 切换结果中可以查看恢复明细:
381
+
382
+ ```js
383
+ const result = await mapCore.setEngine('leaflet')
384
+ console.log(result.data.restore.restored)
385
+ console.log(result.data.restore.failed)
204
386
  ```
205
387
 
206
- ### 视角与场景
388
+
389
+ ### 2. 视角控制与场景模式
390
+
391
+ 视角控制分为缩放、重置视角、设置初始视角、场景模式切换、视图状态读取和视图变化监听。
207
392
 
208
393
  ```js
394
+ // 放大 / 缩小。
395
+ // Cesium 可传 { distance },Leaflet 可传 { step }。
209
396
  mapCore.zoomIn()
210
397
  mapCore.zoomOut()
211
- mapCore.resetView()
398
+ mapCore.zoomIn({ distance: 300000 })
399
+ mapCore.zoomOut({ step: 1 })
212
400
 
213
- mapCore.setSceneMode('2d')
214
- mapCore.setSceneMode('2.5d')
215
- mapCore.setSceneMode('3d')
216
- mapCore.getSceneMode()
401
+ // 回到配置中的 initialView,也可以传入目标视角覆盖。
402
+ mapCore.resetView()
403
+ mapCore.resetView({
404
+ center: [104, 35],
405
+ height: 3000000,
406
+ zoom: 5,
407
+ pitch: -90,
408
+ })
217
409
 
410
+ // 设置视角。center / position 均为 [经度, 纬度]。
218
411
  mapCore.setInitialView({
219
412
  center: [104, 35],
413
+ // Cesium 使用 height。
220
414
  height: 3000000,
415
+ // Leaflet 使用 zoom。
221
416
  zoom: 5,
417
+ // Cesium 使用 heading / pitch / roll,单位是度。
418
+ heading: 0,
222
419
  pitch: -90,
420
+ roll: 0,
421
+ })
422
+
423
+ // 场景模式。Cesium 支持 2d / 2.5d / 3d;Leaflet 只支持 2d。
424
+ mapCore.setSceneMode('3d')
425
+ mapCore.setSceneMode({
426
+ mode: '2d',
427
+ // Cesium morph 动画时长,单位秒。
428
+ duration: 0.4,
429
+ // Cesium 场景切换后是否尽量恢复原视野,默认 true。
430
+ preserveView: true,
431
+ })
432
+ mapCore.getSceneMode()
433
+
434
+ // 获取当前视图边界。
435
+ const boundsResult = mapCore.getViewBounds()
436
+ // boundsResult.data: { west, south, east, north }
437
+
438
+ // 获取当前视图状态。
439
+ const viewStateResult = mapCore.getViewState()
440
+ // Cesium 通常包含 center / bounds / height / heading / pitch / roll / engineType。
441
+ // Leaflet 通常包含 center / bounds / zoom / engineType。
442
+
443
+ // 经纬度转地图容器像素坐标,常用于自定义 HTML 浮层定位。
444
+ const pointResult = mapCore.projectToContainerPoint({
445
+ longitude: 104,
446
+ latitude: 35,
447
+ height: 0,
223
448
  })
449
+ // pointResult.data: { x, y }
224
450
  ```
225
451
 
226
- ### 底图与普通图层
452
+ 视图变化监听:
227
453
 
228
454
  ```js
229
- mapCore.setBasemapById('tianditu-imagery')
455
+ const viewListener = mapCore.onViewChange({
456
+ // Cesium: camera.moveStart / morphStart;Leaflet: movestart / zoomstart。
457
+ onStart() {},
230
458
 
231
- mapCore.setBasemap({
232
- id: 'custom-wmts',
233
- name: '自定义 WMTS',
234
- type: 'wmts',
235
- provider: 'custom',
236
- url: 'https://example.com/wmts?...',
459
+ // Cesium: camera.changed;Leaflet: move / zoom / resize。
460
+ // 缩放过程中要实时刷新点位样式时,优先用 onChange。
461
+ onChange() {},
462
+
463
+ // Cesium: camera.moveEnd / morphComplete;Leaflet: moveend / zoomend / resize。
464
+ onEnd() {},
465
+
466
+ // Cesium onEnd 延迟,默认 120ms。
467
+ endDelay: 120,
237
468
  })
238
469
 
239
- mapCore.addLayer(layerConfig)
240
- mapCore.showLayer({ layerId: 'custom-wmts' })
241
- mapCore.hideLayer({ layerId: 'custom-wmts' })
242
- mapCore.removeLayer({ layerId: 'custom-wmts' })
470
+ // 组件卸载时移除监听。
471
+ viewListener.data?.off?.()
243
472
  ```
244
473
 
245
- ### 点线面文字
474
+ 方法说明:
475
+
476
+ | 方法 | 参数 | 说明 |
477
+ | --- | --- | --- |
478
+ | `zoomIn(payload)` | Cesium `{ distance }`;Leaflet `{ step }` | 放大地图。 |
479
+ | `zoomOut(payload)` | Cesium `{ distance }`;Leaflet `{ step }` | 缩小地图。 |
480
+ | `resetView(payload)` | 视角对象,可选 | 回到初始视角或传入的目标视角。 |
481
+ | `setInitialView(payload)` | `{ center, height, zoom, heading, pitch, roll }` | 设置当前视角。 |
482
+ | `setSceneMode(payload)` | `'2d'/'2.5d'/'3d'` 或 `{ mode, duration, preserveView }` | 切换场景模式。 |
483
+ | `getSceneMode()` | 无 | 获取当前场景模式。 |
484
+ | `getViewBounds()` | 无 | 获取当前视图经纬度边界。 |
485
+ | `getViewState()` | 无 | 获取当前视图状态。 |
486
+ | `onViewChange(payload)` | `{ onStart, onChange, onEnd, endDelay }` | 监听视图变化,返回 `{ off }`。 |
487
+ | `projectToContainerPoint(payload)` | `{ longitude, latitude, height }` | 经纬度投影到地图容器像素坐标。 |
488
+
489
+ ### 4. 绘制点、线、面、文字和 Marker
490
+
491
+ 这些方法用于轻量绘制和样例验证。大量点位或复杂业务图层请优先使用后文的 `PointLargeLayerController`、`PointDensityController`、`GraphicGroupController` 等控制器。
492
+
493
+ 点:
246
494
 
247
495
  ```js
248
496
  mapCore.drawPoint({
249
497
  id: 'point-1',
250
- longitude: 104,
251
- latitude: 35,
252
- style: {
253
- color: '#ff4d4f',
254
- pixelSize: 10,
255
- },
498
+ name: '点位',
499
+ // 必填建议:[经度, 纬度]。
500
+ position: [104, 35],
501
+ height: 0,
502
+ // Cesium 使用 pixelSize;Leaflet 使用 radius。
503
+ pixelSize: 10,
504
+ radius: 7,
505
+ color: '#ff4d4f',
256
506
  })
507
+ ```
257
508
 
509
+ 线:
510
+
511
+ ```js
258
512
  mapCore.drawLine({
259
513
  id: 'line-1',
514
+ name: '连线',
260
515
  positions: [
261
516
  [103, 34],
262
517
  [105, 36],
263
518
  ],
264
- style: {
265
- color: '#1677ff',
266
- width: 3,
267
- },
519
+ width: 3,
520
+ color: '#1677ff',
268
521
  })
522
+ ```
269
523
 
524
+ 面:
525
+
526
+ ```js
270
527
  mapCore.drawPolygon({
271
528
  id: 'polygon-1',
529
+ name: '区域',
272
530
  positions: [
273
531
  [102, 33],
274
532
  [106, 33],
275
533
  [106, 36],
276
534
  [102, 36],
277
535
  ],
278
- style: {
279
- color: 'rgba(22, 119, 255, 0.25)',
280
- outlineColor: '#1677ff',
281
- },
536
+ // Cesium 使用 fillColor;Leaflet 主要使用 color / fillOpacity。
537
+ fillColor: 'rgba(22, 119, 255, 0.25)',
538
+ color: '#1677ff',
539
+ outlineWidth: 2,
540
+ fillOpacity: 0.35,
541
+ // Cesium 默认贴地;显式传 height 时按非贴地面绘制。
542
+ height: 0,
282
543
  })
544
+ ```
283
545
 
546
+ 文字:
547
+
548
+ ```js
284
549
  mapCore.drawText({
285
550
  id: 'text-1',
286
- longitude: 104,
287
- latitude: 35,
551
+ name: '文字',
552
+ position: [104, 35],
288
553
  text: '示例文字',
289
- style: {
290
- color: '#ffffff',
291
- font: '14px sans-serif',
554
+ font: '16px Microsoft YaHei',
555
+ color: '#0f2d4d',
556
+ backgroundColor: 'rgba(255,255,255,0.7)',
557
+ // 默认 center/bottom,也可以传 left/top/right/bottom。
558
+ textAnchor: {
559
+ horizontal: 'center',
560
+ vertical: 'bottom',
292
561
  },
562
+ textOffset: [0, 0],
293
563
  })
564
+ ```
294
565
 
566
+ 图片 Marker:
567
+
568
+ ```js
295
569
  mapCore.addMarker({
296
570
  id: 'marker-1',
297
- longitude: 104,
298
- latitude: 35,
299
- image: '/marker.png',
300
- width: 32,
301
- height: 32,
571
+ name: '站点',
572
+ position: [104, 35],
573
+ iconUrl: '/marker.png',
574
+ iconSize: [32, 32],
575
+ // 也支持 iconWidth / iconHeight。
576
+ iconAnchor: [16, 32],
577
+ iconOffset: [0, 0],
578
+ label: '站点名称',
302
579
  })
580
+ ```
581
+
582
+ 清理图形:
303
583
 
584
+ ```js
304
585
  mapCore.removeGraphic({ id: 'marker-1' })
586
+ mapCore.removeGraphic('marker-1')
305
587
  mapCore.clearGraphics()
306
588
  ```
307
589
 
308
- ### 点击事件与投影
590
+ 方法说明:
591
+
592
+ | 方法 | 参数 | 说明 |
593
+ | --- | --- | --- |
594
+ | `drawPoint(payload)` | 点配置 | 绘制点。 |
595
+ | `drawLine(payload)` | 线配置 | 绘制线。 |
596
+ | `drawPolygon(payload)` | 面配置 | 绘制面。 |
597
+ | `drawText(payload)` | 文字配置 | 绘制文字。 |
598
+ | `addMarker(payload)` | Marker 配置 | 绘制图片 Marker;未传 `iconUrl` 时降级为点。 |
599
+ | `removeGraphic(payload)` | 图形 id 或 `{ id }` | 删除指定图形。 |
600
+ | `clearGraphics()` | 无 | 清空通过基础绘制方法创建的图形。 |
601
+
602
+
603
+
604
+ ### 6. 点击事件
605
+
606
+ `onClick` 注册地图点击事件;`offClick` 移除当前点击监听。当前每个 adapter 只保留一个基础点击监听,重复调用 `onClick` 会先移除旧监听。
309
607
 
310
608
  ```js
311
- const clickResult = mapCore.onClick({
312
- id: 'station-click',
609
+ mapCore.onClick({
313
610
  callback(event) {
314
- // event 中通常包含 longitude / latitude / pickedObject 等信息,具体字段与引擎有关。
315
611
  console.log(event)
316
612
  },
317
613
  })
318
614
 
319
- mapCore.offClick({ id: 'station-click' })
320
-
321
- const point = mapCore.projectToContainerPoint({
322
- longitude: 104,
323
- latitude: 35,
324
- height: 0,
325
- })
326
-
327
- const bounds = mapCore.getViewBounds()
328
- const viewListener = mapCore.onViewChange({
329
- onStart() {},
330
- onEnd() {},
331
- })
332
- viewListener.data?.off?.()
615
+ mapCore.offClick()
333
616
  ```
334
617
 
335
- ### DEM
618
+ Cesium 点击空地时的事件结构:
336
619
 
337
620
  ```js
338
- mapCore.loadDefaultDEM()
339
- mapCore.loadDEMById('ellipsoid-flat')
340
- mapCore.loadDEM({
341
- id: 'custom-dem',
342
- sourceType: 'url',
343
- url: 'https://example.com/terrain',
344
- options: {},
345
- })
621
+ {
622
+ clickType: 'coordinate',
623
+ engineType: 'cesium',
624
+ coordinates: {
625
+ longitude: 104,
626
+ latitude: 35,
627
+ height: 0,
628
+ },
629
+ }
346
630
  ```
347
631
 
348
- DEM 当前主要面向 Cesium;Leaflet 调用会返回不支持结果。
632
+ Cesium 点击 Entity 时的事件结构:
349
633
 
350
- ## 图层构造器
634
+ ```js
635
+ {
636
+ clickType: 'entity',
637
+ engineType: 'cesium',
638
+ coordinates: {
639
+ longitude: 104,
640
+ latitude: 35,
641
+ height: 0,
642
+ },
643
+ target: {
644
+ id: 'point-1',
645
+ name: '点位',
646
+ entityType: 'entity',
647
+ },
648
+ }
649
+ ```
351
650
 
352
- 构造器只返回标准配置对象,通常传给 `config.basemap.list` 或 `mapCore.addLayer()`。
651
+ Leaflet 当前返回坐标点击:
353
652
 
354
653
  ```js
355
- import {
356
- createTiandituLayer,
357
- createGeoserverPyramidLayer,
358
- createWmsLayer,
359
- createWmtsLayer,
360
- } from '@3clear/basegis/layers'
361
-
362
- const tianditu = createTiandituLayer({
363
- id: 'tdt-img',
364
- name: '天地图影像',
365
- resourceKey: 'imagery',
366
- category: 'basemap',
367
- engineSupport: ['cesium', 'leaflet'],
368
- })
654
+ {
655
+ clickType: 'coordinate',
656
+ engineType: 'leaflet',
657
+ coordinates: {
658
+ longitude: 104,
659
+ latitude: 35,
660
+ height: 0,
661
+ },
662
+ }
663
+ ```
369
664
 
370
- const geoserverTile = createGeoserverPyramidLayer({
371
- id: 'geoserver-tile',
372
- name: 'GeoServer 瓦片',
373
- url: 'https://example.com/tiles/{z}/{x}/{y}.png',
374
- })
665
+ 说明:
375
666
 
376
- const wms = createWmsLayer({
377
- id: 'wms-layer',
378
- serviceUrl: 'https://example.com/geoserver/wms',
379
- layers: 'workspace:layer',
380
- parameters: {
381
- transparent: true,
382
- format: 'image/png',
383
- },
384
- })
667
+ - 基础 `onClick` 适合地图空白点击、简单 Entity 点击。
668
+ - 海量点、点位抽稀等图层自己的点击事件,应使用对应 Controller 的 `onClick` 参数。
385
669
 
386
- const wmts = createWmtsLayer({
387
- id: 'wmts-layer',
388
- url: 'https://example.com/wmts?...TILEMATRIX={z}&TILEROW={y}&TILECOL={x}',
389
- layer: 'workspace:layer',
390
- tileMatrixSet: 'EPSG:3857',
391
- })
392
- ```
393
670
 
394
671
  ## GraphicGroupController
395
672
 
@@ -430,60 +707,377 @@ graphics.getState()
430
707
 
431
708
  ## ImageLayerController
432
709
 
433
- 用于加载图片覆盖层或 TIF 图片集合,并提供上一张、下一张、显隐和销毁能力。
710
+ 用于管理一个图片覆盖层。所有更新都走 `update(payload)`;页面有很多时次数据时,自己维护数据列表,然后把当前时次的数据传给 `update`。
711
+
712
+ 先创建控制器。这里是控制器级默认参数,后续每次 `update(payload)` 都可以覆盖这些默认值:
434
713
 
435
714
  ```js
436
715
  import { ImageLayerController } from '@3clear/basegis/methods'
437
716
 
438
717
  const imageLayer = new ImageLayerController({
718
+ // 必填:BaseGIS 实例。也可以写 baseGIS: mapCore。
439
719
  mapCore,
720
+
721
+ // 选填:图层 id。不传时默认 image-layer-default。
722
+ // 后续 show / hide / destroy 会按这个 id 找图层。
440
723
  layerId: 'radar-image',
724
+
725
+ // 选填:默认透明度,范围通常是 0 - 1。不传时默认 0.85。
441
726
  opacity: 0.85,
727
+
728
+ // 选填:默认图片四至范围。
729
+ // 如果 update(payload) 里传 imageUrl,但没有传 area,就会使用这里的 defaultArea。
442
730
  defaultArea: {
443
731
  startLon: 73,
444
732
  startLat: 18,
445
733
  endLon: 135,
446
734
  endLat: 54,
447
735
  },
736
+
737
+ // 选填:默认图片类型。color 表示图片已经填色;grayscale 表示灰度图需要运行时着色。
738
+ imageSourceType: 'color',
739
+
740
+ // 选填:默认小数位,网格标注或探针显示数值时使用。
741
+ decimalPlaces: 2,
448
742
  })
743
+ ```
449
744
 
450
- await imageLayer.load([
745
+ ### 1. 普通图片
746
+
747
+ 只显示一张已经处理好的 PNG/JPG。适合雷达图、云图、已填色格点图。
748
+
749
+ ```js
750
+ await imageLayer.update({
751
+ // 选填:当前数据 id,用于控制器内部记录 activeItem。
752
+ id: 'radar-202607010800',
753
+
754
+ // 选填:当前数据名称,用于状态展示。
755
+ name: '08:00 雷达',
756
+
757
+ // 必填:图片地址。普通图片模式必须传 imageUrl。
758
+ imageUrl: '/data/radar/202607010800.png',
759
+
760
+ // 必填:图片四至范围。只要传 imageUrl,就必须能确定 area。
761
+ area: {
762
+ // 必填:左边界经度。
763
+ startLon: 73,
764
+ // 必填:下边界纬度。
765
+ startLat: 18,
766
+ // 必填:右边界经度。
767
+ endLon: 135,
768
+ // 必填:上边界纬度。
769
+ endLat: 54,
770
+ },
771
+
772
+ // 选填:color 表示图片本身已经填色,不需要再按色带处理。
773
+ imageSourceType: 'color',
774
+
775
+ // 选填:透明度,覆盖控制器默认 opacity。
776
+ opacity: 0.8,
777
+
778
+ // 选填:是否显示图层。不传时使用控制器当前 visible 状态。
779
+ visible: true,
780
+ })
781
+ ```
782
+
783
+ ### 2. 图片 + TIF
784
+
785
+ 图片负责显示,TIF 负责鼠标探针、网格取值、网格注记。
786
+
787
+ ```js
788
+ await imageLayer.update({
789
+ // 选填:当前数据 id。
790
+ id: 'temper-202607010900',
791
+
792
+ // 选填:当前数据名称。
793
+ name: '09:00 温度图',
794
+
795
+ // 必填:显示用图片地址。
796
+ imageUrl: '/data/temper/202607010900.png',
797
+
798
+ // 必填:取值用 TIF 地址。开启探针或注记时必须有可取值数据。
799
+ tifUrl: '/data/temper/202607010900.tif',
800
+
801
+ // 必填:图片四至范围。
802
+ area: {
803
+ startLon: 73,
804
+ startLat: 18,
805
+ endLon: 135,
806
+ endLat: 54,
807
+ },
808
+
809
+ // 选填:显示图是已经填色的图片。
810
+ imageSourceType: 'color',
811
+
812
+ // 选填:是否开启鼠标探针。依赖 tifUrl。
813
+ showProbe: true,
814
+
815
+ // 选填:是否显示网格注记。依赖 tifUrl。
816
+ showLabel: true,
817
+
818
+ // 选填:探针或注记数值的小数位。
819
+ decimalPlaces: 1,
820
+
821
+ // 选填:透明度。
822
+ opacity: 0.85,
823
+ })
824
+ ```
825
+
826
+ ### 3. 灰度图 + 色带
827
+
828
+ 灰度图不是已经填好颜色的图,需要传 `imageSourceType: 'grayscale'` 和 `colorize`。
829
+
830
+ ```js
831
+ await imageLayer.update({
832
+ // 选填:当前数据 id。
833
+ id: 'pressure-gray',
834
+
835
+ // 选填:当前数据名称。
836
+ name: '气压灰度图',
837
+
838
+ // 必填:灰度图地址。
839
+ imageUrl: '/data/pressure-gray.png',
840
+
841
+ // 必填:灰度图四至范围。
842
+ area: {
843
+ startLon: -180,
844
+ startLat: -90,
845
+ endLon: 180,
846
+ endLat: 90,
847
+ },
848
+
849
+ // 必填:灰度图必须声明为 grayscale,适配层才会按 colorize 着色。
850
+ imageSourceType: 'grayscale',
851
+
852
+ // 选填:是否按切片方式处理大图。大范围灰度图建议开启。
853
+ isSplit: true,
854
+
855
+ // 选填:切分网格数量,isSplit 为 true 时使用。
856
+ gridTotal: 4,
857
+
858
+ // 选填:探针或注记数值的小数位。
859
+ decimalPlaces: 1,
860
+
861
+ // 必填:灰度图着色配置。
862
+ colorize: {
863
+ // 必填:灰度值或数据值最小值。
864
+ minValue: 960,
865
+ // 必填:灰度值或数据值最大值。
866
+ maxValue: 1040,
867
+ // 选填:无效值,命中该值时不参与着色。
868
+ noDataValue: 255,
869
+ // 必填:色带颜色。支持 16 进制字符串、rgb 字符串或 RGB 数组。
870
+ colors: ['#2639a7', '#2faaf3', '#8ee5b2', '#fd9919', '#fb422d'],
871
+ },
872
+ })
873
+ ```
874
+
875
+ `loadGrayImage` 的灰度值换算规则:
876
+
877
+ ```js
878
+ 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue) * (maxValue - minValue) + minValue
879
+ ```
880
+
881
+ 所以灰度图要表达真实业务值时,建议显式传 `minValue / maxValue`。如果只写 `colorize: true`,会使用默认范围 `minValue: 0`、`maxValue: 1`、`grayMinValue: 1`、`grayMaxValue: 254`,只适合临时预览,不适合正式业务图层。
882
+
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 渲染的场景。
909
+
910
+ ```js
911
+ await imageLayer.update({
912
+ // 选填:当前数据 id。
913
+ id: 'rain-tif',
914
+
915
+ // 必填建议:纯 TIF 模式下显式清空 imageUrl,避免沿用上一次图片。
916
+ imageUrl: '',
917
+
918
+ // 必填:TIF 地址。
919
+ tifUrl: '/data/rain.tif',
920
+
921
+ // 选填:是否按默认色带着色。需要更精细控制时传 colorize 对象。
922
+ colorize: true,
923
+
924
+ // 选填:是否开启鼠标探针。
925
+ showProbe: true,
926
+ })
927
+ ```
928
+
929
+ ### 5. 很多时次数据怎么更新
930
+
931
+ 很多数据不要用 `next()`。页面维护数组,点击或时间轴变化时,把当前项传给 `update()`。
932
+
933
+ ```js
934
+ const imageDataSources = [
935
+ {
936
+ // 必填:业务自己的时次 key。
937
+ key: '202607010800',
938
+ // 必填:当前时次图片地址。
939
+ imageUrl: '/data/radar/202607010800.png',
940
+ },
451
941
  {
452
- id: 'radar-001',
453
- name: '雷达 001',
454
- imageUrl: '/data/radar/001.png',
942
+ key: '202607010900',
943
+ imageUrl: '/data/radar/202607010900.png',
944
+ },
945
+ ]
946
+
947
+ async function updateImageByTime(timeKey) {
948
+ const source = imageDataSources.find((item) => item.key === timeKey)
949
+ if (!source) return
950
+
951
+ await imageLayer.update({
952
+ // 选填:用时次 key 作为当前数据 id。
953
+ id: source.key,
954
+
955
+ // 必填:当前时次图片地址。
956
+ imageUrl: source.imageUrl,
957
+
958
+ // 必填:图片四至范围。
455
959
  area: {
456
960
  startLon: 73,
457
961
  startLat: 18,
458
962
  endLon: 135,
459
963
  endLat: 54,
460
964
  },
461
- opacity: 0.8,
462
- },
463
- {
464
- id: 'tif-001',
465
- name: 'TIF 001',
466
- tifUrl: '/data/grid/001.tif',
467
- colorize: true,
965
+
966
+ // 选填:普通已填色图片使用 color。
967
+ imageSourceType: 'color',
968
+
969
+ // 选填:更新时间后直接显示图层。
970
+ visible: true,
971
+ })
972
+ }
973
+ ```
974
+
975
+ ### 6. 控制方法
976
+
977
+ `ImageLayerController` 除了 `update(payload)`,还提供显隐、销毁、状态读取和兼容旧集合模式的方法。
978
+
979
+ ```js
980
+ // 创建或更新当前图片图层。
981
+ await imageLayer.update({
982
+ imageUrl: '/data/radar/202607010800.png',
983
+ area: {
984
+ startLon: 73,
985
+ startLat: 18,
986
+ endLon: 135,
987
+ endLat: 54,
468
988
  },
469
- ])
989
+ })
470
990
 
471
- await imageLayer.switchTo('radar-001')
472
- await imageLayer.next()
473
- await imageLayer.prev()
991
+ // 隐藏当前控制器对应的图片图层。
474
992
  imageLayer.hide()
993
+
994
+ // 重新显示当前控制器对应的图片图层。
475
995
  imageLayer.show()
996
+
997
+ // 读取当前控制器状态。
998
+ const state = imageLayer.getState()
999
+
1000
+ // 移除地图上的图片图层,并清空控制器本地状态。
476
1001
  imageLayer.destroy()
477
1002
  ```
478
1003
 
479
- 常用字段:
1004
+ 如果一个控制器需要临时操作其他图片图层,可以传入 `layerId`;也可以直接传字符串:
1005
+
1006
+ ```js
1007
+ imageLayer.show({ layerId: 'radar-image' })
1008
+ imageLayer.hide('radar-image')
1009
+ imageLayer.destroy({ layerId: 'radar-image' })
1010
+ ```
1011
+
1012
+ 方法总表:
1013
+
1014
+ | 方法 | 参数 | 返回值 | 说明 |
1015
+ | --- | --- | --- | --- |
1016
+ | `mount(target)` | `BaseGIS` 实例 | `Result` | 绑定 `BaseGIS`。构造时已传 `mapCore/baseGIS` 时通常不需要手动调用。 |
1017
+ | `update(payload)` | 图片图层数据 | `Promise<Result>` | 推荐主入口。创建或更新当前图片图层,支持普通图片、图片+TIF、灰度图+色带、纯 TIF。 |
1018
+ | `show(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 显示图片图层。成功后控制器 `visible` 变为 `true`。 |
1019
+ | `hide(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 隐藏图片图层。成功后控制器 `visible` 变为 `false`。 |
1020
+ | `destroy(payload)` | 可选 `layerId` 或 `{ layerId }` | `Result` | 移除图片图层,并清空 `items / activeIndex / layerType / currentPayload`。如果地图已销毁,只清本地状态。 |
1021
+ | `getState()` | 无 | `Object` | 获取控制器状态。 |
1022
+ | `load(items, options)` | 图片数组、可选 `{ index, visible }` | `Promise<Result>` | 兼容旧的图片集合加载方式,会加载 `items[index]`。 |
1023
+ | `switchTo(target)` | 图片索引或图片 id | `Promise<Result>` | 在 `load` 后的本地图片集合中切换当前图片。 |
1024
+ | `next()` | 无 | `Promise<Result>` | 切到下一张本地图片。只适合本地小数组演示,不推荐作为业务时次更新主路径。 |
1025
+ | `prev()` | 无 | `Promise<Result>` | 切到上一张本地图片。只适合本地小数组演示,不推荐作为业务时次更新主路径。 |
480
1026
 
481
- - `imageUrl`:普通图片地址。
482
- - `tifUrl`:GeoTIFF 地址。
483
- - `area`:图片四至范围,字段为 `startLon / startLat / endLon / endLat`。
484
- - `opacity`:透明度。
485
- - `showLabel / showProbe`:显示网格标注或探针。
486
- - `colorize / colorStops / colors`:色带渲染配置。
1027
+ `getState()` 返回结构:
1028
+
1029
+ ```js
1030
+ {
1031
+ mounted: true,
1032
+ engineType: 'cesium',
1033
+ layerType: 'image',
1034
+ visible: true,
1035
+ total: 1,
1036
+ activeIndex: 0,
1037
+ activeItem: {
1038
+ id: 'radar-202607010800',
1039
+ imageUrl: '/data/radar/202607010800.png',
1040
+ },
1041
+ payload: {
1042
+ layerId: 'radar-image',
1043
+ imageUrl: '/data/radar/202607010800.png',
1044
+ opacity: 0.85,
1045
+ },
1046
+ }
1047
+ ```
1048
+
1049
+ ### 参数总表
1050
+
1051
+ | 参数 | 是否必填 | 说明 |
1052
+ | --- | --- | --- |
1053
+ | `mapCore` / `baseGIS` | 创建控制器必填 | `BaseGIS` 实例。 |
1054
+ | `layerId` | 选填 | 图层 id,不传默认 `image-layer-default`。 |
1055
+ | `defaultArea` | 选填 | 默认图片范围,`update` 未传 `area` 时使用。 |
1056
+ | `id` | 选填 | 当前数据 id,便于状态记录。 |
1057
+ | `name` | 选填 | 当前数据名称,便于页面展示。 |
1058
+ | `imageUrl` | 普通图片、灰度图、图片+TIF 必填 | 图片地址。纯 TIF 模式建议传空字符串清掉旧图片。 |
1059
+ | `tifUrl` | 纯 TIF、图片+TIF 必填 | GeoTIFF 地址,用于渲染、探针或网格注记。 |
1060
+ | `area` | 传 `imageUrl` 时必填 | 图片范围:`startLon / startLat / endLon / endLat`。 |
1061
+ | `imageSourceType` | 灰度图必填,其他选填 | `color` 表示已填色图片;`grayscale` 表示灰度图。 |
1062
+ | `colorize` | 灰度图必填,TIF 着色选填 | 色带配置,或传 `true` 使用默认着色。 |
1063
+ | `colorize.minValue` | 灰度图必填 | 色带映射最小值。 |
1064
+ | `colorize.maxValue` | 灰度图必填 | 色带映射最大值。 |
1065
+ | `colorize.noDataValue` | 选填 | 无效值。 |
1066
+ | `colorize.colors` | 灰度图必填 | 色带颜色数组。 |
1067
+ | `opacity` | 选填 | 透明度,通常 0 - 1。 |
1068
+ | `visible` | 选填 | 是否显示图层。 |
1069
+ | `showProbe` | 选填 | 是否开启鼠标探针,通常依赖 `tifUrl`。 |
1070
+ | `showLabel` | 选填 | 是否显示网格注记,通常依赖 `tifUrl`。 |
1071
+ | `decimalPlaces` | 选填 | 探针或注记数值小数位。 |
1072
+ | `isSplit` | 选填 | 是否切片处理大图。 |
1073
+ | `gridTotal` | 选填 | 切片网格数量。 |
1074
+ | `tileSize` | 选填 | 切片尺寸。 |
1075
+
1076
+ 兼容方法:
1077
+
1078
+ - `load(items, { index, visible })`:兼容旧的图片集合加载方式,会把 `items[index]` 写入当前图层。
1079
+ - `switchTo(idOrIndex)`:在已加载的 `items` 内切换到某一项。
1080
+ - `next() / prev()`:只适合本地小数组演示,不推荐作为业务时次更新主路径。
487
1081
 
488
1082
  ## GridLayerController
489
1083
 
@@ -513,7 +1107,19 @@ await gridLayer.loadGrayImage({
513
1107
  endLon: 135,
514
1108
  endLat: 54,
515
1109
  },
516
- colorize: true,
1110
+ // 灰度图需要把灰度值映射成业务值,再按业务值找色带。
1111
+ colorize: {
1112
+ // 必填建议:业务值最小值。
1113
+ minValue: -20,
1114
+ // 必填建议:业务值最大值。
1115
+ maxValue: 50,
1116
+ // 选填:灰度有效最小值,不传默认 1。
1117
+ grayMinValue: 1,
1118
+ // 选填:灰度有效最大值,不传默认 254。
1119
+ grayMaxValue: 254,
1120
+ // 选填:无效灰度值,不传默认 255。
1121
+ noDataValue: 255,
1122
+ },
517
1123
  })
518
1124
 
519
1125
  await gridLayer.loadData({
@@ -529,106 +1135,1040 @@ await gridLayer.loadData({
529
1135
  },
530
1136
  })
531
1137
 
532
- gridLayer.update({ opacity: 0.7 })
533
- gridLayer.hide()
534
- gridLayer.show()
535
- gridLayer.destroy()
1138
+ gridLayer.update({ opacity: 0.7 })
1139
+ gridLayer.hide()
1140
+ gridLayer.show()
1141
+ gridLayer.destroy()
1142
+ ```
1143
+
1144
+ ## 三维体渲染
1145
+
1146
+ 三维体渲染是 Cesium 专有能力,直接通过 `BaseGIS` 调用。Leaflet 调用时会返回不支持结果,不会抛出破坏页面的异常。
1147
+
1148
+ 适合体数据云图、污染物三维浓度、气象三维场等需要在一个三维盒子内做体积采样的场景。
1149
+
1150
+ ### 基础用法
1151
+
1152
+ ```js
1153
+ const result = mapCore.upsertVolumeLayer({
1154
+ // 建议必填:图层唯一 id。再次使用同一个 layerId 调用会覆盖旧体渲染层。
1155
+ layerId: 'volume-demo',
1156
+
1157
+ // 必填:体数据一维数组,长度通常为 rows * cols * heights。
1158
+ data: volumeValues,
1159
+
1160
+ // 必填:体数据范围和网格尺寸。
1161
+ option: {
1162
+ // 经度范围。
1163
+ xmin: 110,
1164
+ xmax: 120,
1165
+ // 纬度范围。
1166
+ ymin: 30,
1167
+ ymax: 40,
1168
+ // 高度范围,单位按业务数据约定。
1169
+ zmin: 0,
1170
+ zmax: 10000,
1171
+ // 纬向、经向、高度向网格数量。
1172
+ rows: 100,
1173
+ cols: 100,
1174
+ heights: 30,
1175
+ },
1176
+
1177
+ // 选填:体渲染 shader 参数,可后续单独更新。
1178
+ parameters: {
1179
+ // 采样阈值。值越小,通常显示范围越少;具体效果和数据归一化有关。
1180
+ threshold: 0.3,
1181
+ // 光线步进采样次数。越大越细腻,也越耗性能。
1182
+ steps: 100,
1183
+ // x/y/z 三个方向裁切位置,默认 -0.5 表示不裁切。
1184
+ xCut: -0.5,
1185
+ yCut: -0.5,
1186
+ zCut: -0.5,
1187
+ },
1188
+
1189
+ // 选填:初始是否显示。
1190
+ visible: true,
1191
+ })
1192
+
1193
+ if (!result.success) {
1194
+ console.warn(result.message)
1195
+ }
1196
+ ```
1197
+
1198
+ ### 更新参数
1199
+
1200
+ 更新透明阈值、步进数、裁切面时,不需要重新加载体数据,直接更新参数即可。
1201
+
1202
+ ```js
1203
+ mapCore.updateVolumeLayerParameters({
1204
+ layerId: 'volume-demo',
1205
+ parameters: {
1206
+ threshold: 0.5,
1207
+ steps: 180,
1208
+ zCut: 0.1,
1209
+ },
1210
+ })
1211
+ ```
1212
+
1213
+ ### 更新数据
1214
+
1215
+ 更新数据或空间范围时,继续使用同一个 `layerId` 调用 `upsertVolumeLayer`。适配层会先移除旧 Primitive,再创建新的体渲染层。
1216
+
1217
+ ```js
1218
+ mapCore.upsertVolumeLayer({
1219
+ layerId: 'volume-demo',
1220
+ data: nextVolumeValues,
1221
+ option: nextVolumeOption,
1222
+ parameters: currentParameters,
1223
+ })
1224
+ ```
1225
+
1226
+ ### 显隐、定位和移除
1227
+
1228
+ ```js
1229
+ mapCore.hideVolumeLayer({ layerId: 'volume-demo' })
1230
+ mapCore.showVolumeLayer({ layerId: 'volume-demo' })
1231
+ mapCore.flyToVolumeLayer({ layerId: 'volume-demo', duration: 0.8 })
1232
+ mapCore.removeVolumeLayer({ layerId: 'volume-demo' })
1233
+
1234
+ const stateResult = mapCore.getVolumeLayerState({ layerId: 'volume-demo' })
1235
+ console.log(stateResult.data)
1236
+ ```
1237
+
1238
+ ### 方法总表
1239
+
1240
+ | 方法 | 参数 | 说明 |
1241
+ | --- | --- | --- |
1242
+ | `upsertVolumeLayer(payload)` | 体渲染配置 | 创建或更新体渲染层。同 `layerId` 会覆盖旧图层。 |
1243
+ | `updateVolumeLayerParameters(payload)` | `{ layerId, parameters }` | 更新体渲染 shader 参数,不重新加载体数据。 |
1244
+ | `showVolumeLayer(payload)` | `{ layerId }` | 显示体渲染层。 |
1245
+ | `hideVolumeLayer(payload)` | `{ layerId }` | 隐藏体渲染层。 |
1246
+ | `removeVolumeLayer(payload)` | `{ layerId }` | 移除体渲染层并释放 Primitive。 |
1247
+ | `flyToVolumeLayer(payload)` | `{ layerId, duration }` | 飞到体渲染层经纬度范围。 |
1248
+ | `getVolumeLayerState(payload)` | `{ layerId }` | 获取图层状态。 |
1249
+
1250
+ ### 参数总表
1251
+
1252
+ | 参数 | 是否必填 | 说明 |
1253
+ | --- | --- | --- |
1254
+ | `layerId` / `volumeLayerId` / `id` | 建议必填 | 体渲染图层 id,不传默认 `volume-default`。 |
1255
+ | `data` / `value` / `values` | 必填 | 体数据一维数组,通常按层、行、列展开。 |
1256
+ | `option` | 必填 | 体数据空间范围和网格尺寸。 |
1257
+ | `option.xmin / xmax` | 必填 | 经度最小值和最大值。 |
1258
+ | `option.ymin / ymax` | 必填 | 纬度最小值和最大值。 |
1259
+ | `option.zmin / zmax` | 必填 | 高度最小值和最大值。 |
1260
+ | `option.rows` | 必填 | 纬向网格数量。 |
1261
+ | `option.cols` | 必填 | 经向网格数量。 |
1262
+ | `option.heights` | 必填 | 高度层数量。 |
1263
+ | `parameters.threshold` | 选填 | 采样阈值。 |
1264
+ | `parameters.steps` | 选填 | 光线步进采样次数。 |
1265
+ | `parameters.xCut / yCut / zCut` | 选填 | 三个方向的裁切位置,默认 `-0.5`。 |
1266
+ | `colorRamp` / `colors` / `colorKeys` | 选填 | 自定义色带配置。 |
1267
+ | `geometry` / `dim` | 选填 | 自定义体渲染几何或维度,普通业务通常不需要传。 |
1268
+ | `visible` | 选填 | 初始是否显示,默认显示。 |
1269
+
1270
+ ## 三维切片 / 剖面渲染
1271
+
1272
+ 三维切片/剖面渲染也是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它把三维网格数据按 X、Y、Z 三个方向切出剖面,适合气象温度、湿度、风场标量、污染物浓度等三维格点数据查看。
1273
+
1274
+ 这里的“切片”指三维数据剖面,不是 `ImageLayerController` 里的大图切片。
1275
+
1276
+ ### 数据格式
1277
+
1278
+ ```js
1279
+ const sectionData = {
1280
+ Bound: [
1281
+ 1000, // 0: LayerMax,最高气压层或起始层值。
1282
+ 108.68, // 1: LonMin,经度最小值。
1283
+ 28.59, // 2: LatMin,纬度最小值。
1284
+ 10, // 3: LayerNums,层数。
1285
+ 147, // 4: LonNums,经向格点数。
1286
+ 198, // 5: LatNums,纬向格点数。
1287
+ 900, // 6: DLayer 或 LayerMin 相关值,按数据生成规则提供。
1288
+ 16.74, // 7: DLon,经度跨度。
1289
+ 16.71, // 8: DLat,纬度跨度。
1290
+ 1, // 9: ValueScale,数值缩放。
1291
+ 1, // 10: 预留/数据标记。
1292
+ [1000, 925, 850, 700, 600, 500, 400, 300, 200, 100], // 11: LayerList,气压层列表。
1293
+ ],
1294
+ // 必填:三维格点值一维数组,长度通常为 LayerNums * LatNums * LonNums。
1295
+ DataAry: valueList,
1296
+ }
1297
+
1298
+ const optionData = {
1299
+ Item: 'TEMP',
1300
+ Values: [-30, -20, -10, 0, 10, 20, 30],
1301
+ Colors: [
1302
+ [30, 80, 180],
1303
+ [50, 160, 220],
1304
+ [120, 220, 180],
1305
+ [250, 230, 120],
1306
+ [240, 150, 80],
1307
+ [220, 80, 60],
1308
+ [160, 30, 30],
1309
+ ],
1310
+ }
1311
+ ```
1312
+
1313
+ ### 加载或更新数据
1314
+
1315
+ 同一个 `layerId` 重复调用 `upsertSectionLayer` 就是更新数据。适配层会清理旧切片,再加载新数据。
1316
+
1317
+ ```js
1318
+ const result = mapCore.upsertSectionLayer({
1319
+ // 建议必填:切片图层唯一 id。
1320
+ layerId: 'section-demo',
1321
+
1322
+ // 必填:包含 Bound / DataAry 的三维剖面数据。
1323
+ dataInfo: sectionData,
1324
+
1325
+ // 选填:数据类型,传给色标取色逻辑。
1326
+ itemType: optionData.Item,
1327
+
1328
+ // 必填建议:色标配置。
1329
+ colorInfo: {
1330
+ valueAry: optionData.Values,
1331
+ rgbAry: optionData.Colors,
1332
+ },
1333
+
1334
+ // 选填:是否显示坐标轴和刻度。
1335
+ showAxis: false,
1336
+ })
1337
+
1338
+ if (!result.success) {
1339
+ console.warn(result.message)
1340
+ return
1341
+ }
1342
+
1343
+ const state = result.data.state
1344
+ console.log(state.XRange, state.YRange, state.ZRange)
1345
+ ```
1346
+
1347
+ ### 渲染 X / Y / Z 切片
1348
+
1349
+ ```js
1350
+ // X 经度方向切片。
1351
+ mapCore.renderSectionLayer({
1352
+ layerId: 'section-demo',
1353
+ sectionType: 0,
1354
+ value: 116.4,
1355
+ })
1356
+
1357
+ // Y 纬度方向切片。
1358
+ mapCore.renderSectionLayer({
1359
+ layerId: 'section-demo',
1360
+ sectionType: 1,
1361
+ value: 39.9,
1362
+ })
1363
+
1364
+ // Z 高度/气压方向切片。
1365
+ mapCore.renderSectionLayer({
1366
+ layerId: 'section-demo',
1367
+ sectionType: 2,
1368
+ value: 850,
1369
+ })
1370
+ ```
1371
+
1372
+ `sectionType` 含义:
1373
+
1374
+ | 值 | 含义 | `value` |
1375
+ | --- | --- | --- |
1376
+ | `0` | 经度方向剖面 | 经度值。 |
1377
+ | `1` | 纬度方向剖面 | 纬度值。 |
1378
+ | `2` | 高度/气压方向剖面 | 气压层或高度层值。 |
1379
+ | `3` | 全量剖面 | 可不传 `value`。 |
1380
+
1381
+ ### 显隐、移除和定位
1382
+
1383
+ ```js
1384
+ // 隐藏或显示全部切片。
1385
+ mapCore.hideSectionLayer({ layerId: 'section-demo' })
1386
+ mapCore.showSectionLayer({ layerId: 'section-demo' })
1387
+
1388
+ // 只移除 Z 切片,X/Y 不受影响。
1389
+ mapCore.removeSectionLayer({
1390
+ layerId: 'section-demo',
1391
+ sectionType: 2,
1392
+ })
1393
+
1394
+ // 移除整个切片图层。
1395
+ mapCore.removeSectionLayer({ layerId: 'section-demo' })
1396
+
1397
+ // 飞到切片数据范围。
1398
+ mapCore.flyToSectionLayer({ layerId: 'section-demo', duration: 0.8 })
1399
+ ```
1400
+
1401
+ ### hover 取值
1402
+
1403
+ 页面先用 Cesium 拾取得到鼠标所在三维点,再交给 `sampleSectionLayerValue` 计算当前切片上的数值。
1404
+
1405
+ ```js
1406
+ const result = mapCore.sampleSectionLayerValue({
1407
+ layerId: 'section-demo',
1408
+ lon: 116.4,
1409
+ lat: 39.9,
1410
+ hpa: 850,
1411
+ // 选填:Z 切片关闭时传 false,避免 hover 命中已隐藏的 Z 切片。
1412
+ showZLayer: true,
1413
+ })
1414
+
1415
+ if (result.success) {
1416
+ console.log(result.data)
1417
+ // { lon, lat, hpa, sectionType, value }
1418
+ }
1419
+ ```
1420
+
1421
+ ### 方法总表
1422
+
1423
+ | 方法 | 参数 | 说明 |
1424
+ | --- | --- | --- |
1425
+ | `upsertSectionLayer(payload)` | 切片数据与色标配置 | 创建或更新切片图层。同 `layerId` 会覆盖旧图层。 |
1426
+ | `renderSectionLayer(payload)` | `{ layerId, sectionType, value }` | 渲染指定方向切片。 |
1427
+ | `showSectionLayer(payload)` | `{ layerId, sectionType }` | 显示切片。不传 `sectionType` 时显示全部。 |
1428
+ | `hideSectionLayer(payload)` | `{ layerId, sectionType }` | 隐藏切片。不传 `sectionType` 时隐藏全部。 |
1429
+ | `removeSectionLayer(payload)` | `{ layerId, sectionType }` | 移除切片。不传 `sectionType` 时移除整个图层。 |
1430
+ | `flyToSectionLayer(payload)` | `{ layerId, duration }` | 飞到切片数据范围。 |
1431
+ | `getSectionLayerState(payload)` | `{ layerId }` | 获取切片图层状态。 |
1432
+ | `sampleSectionLayerValue(payload)` | `{ layerId, lon, lat, hpa, showZLayer }` | 根据拾取位置计算当前切片数值。 |
1433
+
1434
+ ### 参数总表
1435
+
1436
+ | 参数 | 是否必填 | 说明 |
1437
+ | --- | --- | --- |
1438
+ | `layerId` / `sectionLayerId` / `id` | 建议必填 | 切片图层 id,不传默认 `section-default`。 |
1439
+ | `dataInfo` / `data` | 必填 | 三维切片数据,必须包含 `Bound / DataAry`。 |
1440
+ | `dataInfo.Bound` | 必填 | 数据范围、网格数量、层级列表等元信息。 |
1441
+ | `dataInfo.DataAry` | 必填 | 三维格点值一维数组。 |
1442
+ | `itemType` | 选填 | 数据类型,传给色标取色逻辑。 |
1443
+ | `colorInfo.valueAry` | 建议必填 | 色标分级值数组。 |
1444
+ | `colorInfo.rgbAry` | 建议必填 | 色标 RGB 数组。 |
1445
+ | `boxInfo` | 选填 | 自定义剖面盒子范围。不传时根据 `dataInfo` 自动生成。 |
1446
+ | `showAxis` | 选填 | 是否显示坐标轴和刻度。 |
1447
+ | `sectionType` | 渲染、显隐、移除单个切片时必填 | `0` 经度,`1` 纬度,`2` 高度/气压,`3` 全量。 |
1448
+ | `value` / `param` | `sectionType` 为 `0/1/2` 时必填 | 切片位置值。 |
1449
+ | `lon / lat / hpa` | hover 取值必填 | Cesium 拾取得到的经度、纬度、气压高度。 |
1450
+ | `showZLayer` | hover 取值选填 | Z 切片是否参与 hover 命中,默认参与。 |
1451
+
1452
+ ## PointLargeLayerController
1453
+
1454
+ 用于海量点位渲染。它会尽量把传入的有效点位全部渲染出来,适合站点、设备、告警、监测点等需要保留全部点位并支持点击、高亮、删除、显隐的场景。
1455
+
1456
+ Leaflet 模式使用 BaseGIS 内置的 `pixi.js` 和 `leaflet-pixi-overlay` 异步渲染,业务项目无需安装这两个依赖;Cesium 模式不加载 Pixi 相关代码。
1457
+
1458
+ 如果业务目标是“地图缩小时按屏幕网格抽稀,只显示代表点”,应使用后文的 `PointDensityController`。
1459
+
1460
+ ### Demo 位置
1461
+
1462
+ 仓库内已有对应 demo:
1463
+
1464
+ - 路由:`/test-page-5`
1465
+ - 页面:`src/views/test-page-5/index.vue`
1466
+ - 当前 demo 覆盖能力:加载、更新、显隐、高亮、取消高亮、删除点位、清空、销毁、状态读取、视角复位。
1467
+
1468
+ demo 源码内部使用:
1469
+
1470
+ ```js
1471
+ import { BaseGIS } from '@/gis'
1472
+ import { PointLargeLayerController } from '@/gis/methods'
1473
+ ```
1474
+
1475
+ npm 包外部项目使用:
1476
+
1477
+ ```js
1478
+ import { BaseGIS } from '@3clear/basegis'
1479
+ import { PointLargeLayerController } from '@3clear/basegis/methods'
1480
+ ```
1481
+
1482
+ ### 使用方式一:构造时传入 mapCore
1483
+
1484
+ 这是业务页面里最常用的写法。地图初始化完成后创建控制器,再调用 `load` 加载点位。
1485
+
1486
+ ```js
1487
+ const mapCore = new BaseGIS({
1488
+ engineType: 'leaflet',
1489
+ containerId: 'map',
1490
+ })
1491
+
1492
+ const initResult = mapCore.init()
1493
+ if (!initResult.success) {
1494
+ console.warn(initResult.message)
1495
+ }
1496
+
1497
+ const pointLayer = new PointLargeLayerController({
1498
+ // 必填:BaseGIS 实例。
1499
+ mapCore,
1500
+ // 建议必填:图层唯一 ID,后续显隐、高亮、删除都基于这个图层。
1501
+ layerId: 'station-large',
1502
+ // 选填:点位唯一值字段,不传默认使用 id。
1503
+ idKey: 'stationCode',
1504
+
1505
+ // 点位数据。也可以使用 data/items 字段;推荐统一使用 points。
1506
+ points,
1507
+
1508
+ // 初始是否显示图层。不传时默认 true。
1509
+ visible: true,
1510
+
1511
+ // 默认普通状态图标。支持图片 URL、base64、data URL。
1512
+ icon: normalPointIcon,
1513
+
1514
+ // 默认高亮状态图标。调用 setHighlight(id) 后使用。
1515
+ highlightIcon: highlightPointIcon,
1516
+
1517
+ // 样式规则读取的字段名。下面 styleRules 里的 gt/default 会基于 value 判断。
1518
+ styleField: 'value',
1519
+
1520
+ // 点位样式规则。从上到下匹配,命中后使用该规则里的 icon/highlightIcon/style 等配置。
1521
+ styleRules: [
1522
+ {
1523
+ // value < 300 时命中
1524
+ lt: 300,
1525
+ icon: lowPointIcon,
1526
+ highlightIcon: highlightPointIcon,
1527
+ },
1528
+ {
1529
+ // value > 780 时命中
1530
+ gt: 780,
1531
+ icon: warningPointIcon,
1532
+ highlightIcon: highlightPointIcon,
1533
+ },
1534
+ {
1535
+ // 300 <= value < 780
1536
+ gte: 300,
1537
+ lt: 780,
1538
+ icon: normalPointIcon,
1539
+ highlightIcon: highlightPointIcon,
1540
+ },
1541
+ {
1542
+ // 兜底规则。没有命中前面规则的点位使用普通图标。
1543
+ default: true,
1544
+ icon: normalPointIcon,
1545
+ highlightIcon: highlightPointIcon,
1546
+ },
1547
+ ],
1548
+
1549
+ // 普通图标宽度,单位像素。
1550
+ width: 18,
1551
+
1552
+ // 普通图标高度,单位像素。
1553
+ height: 18,
1554
+
1555
+ // 普通图标缩放比例。
1556
+ scale: 1,
1557
+
1558
+ // 未传 icon 时,可以使用内置圆点图标。下面这些配置用于控制圆点样式。
1559
+ // color: '#19d3a2',
1560
+ // outlineColor: '#ffffff',
1561
+ // outlineWidth: 2,
1562
+
1563
+ // 未传 highlightIcon 时,可以使用内置高亮圆点图标。下面这些配置用于控制高亮圆点样式。
1564
+ // highlightColor: '#ffcf33',
1565
+ // highlightOutlineColor: '#ffffff',
1566
+ // highlightWidth: 20,
1567
+ // highlightHeight: 20,
1568
+ // highlightScale: 1.18,
1569
+
1570
+ // 分帧构建批量大小。点位很多时可以调大或调小,默认 5000。
1571
+ // chunkSize: 10000,
1572
+
1573
+ // Cesium 专用:禁用深度检测距离。需要点位不被地形或模型遮挡时可使用。
1574
+ // disableDepthTestDistance: Number.POSITIVE_INFINITY,
1575
+
1576
+ // Cesium 专用:按相机距离缩放图标。
1577
+ // 数组含义:[近距离, 近距离缩放, 远距离, 远距离缩放]。
1578
+ // scaleByDistance: [50000, 1, 9000000, 0.34],
1579
+
1580
+ // 点击点位回调。event.data 是原始点位数据。
1581
+ onClick: ({ id, data }) => {`点击点位:${data || id}。`},
1582
+
1583
+ })
1584
+
1585
+ await pointLayer.load({
1586
+ points: [
1587
+ {
1588
+ stationCode: 'A001',
1589
+ name: '站点 A001',
1590
+ longitude: 104,
1591
+ latitude: 35,
1592
+ value: 86,
1593
+ },
1594
+ ],
1595
+ })
1596
+ ```
1597
+
1598
+ `styleRules` 常用匹配方式:
1599
+
1600
+ | 写法 | 说明 |
1601
+ |---------------------------------------------------| --- |
1602
+ | `{ field: 'aqi', min: 51, max: 100 }` | 指定字段并按区间匹配。 |
1603
+ | `{ gt: 780 }` | 使用 `styleField` 指定的字段做大于判断。 |
1604
+ | `{ gte: 51, lt: 101 }` | 使用 `styleField` 指定的字段做区间判断。 |
1605
+ | `{ values: ['优', '良'] }` | 命中指定值集合。 |
1606
+ | `{ operator: '>', value: 100 }` | 使用操作符匹配,支持 `> / >= / < / <= / == / === / != / !== / in`。 |
1607
+ | `{ when:(point, index, record) { return true } }` | 完全自定义匹配函数。 |
1608
+ | `{ default: true }` | 兜底规则。 |
1609
+
1610
+
1611
+
1612
+
1613
+ ### 使用方式:更新数据或样式
1614
+
1615
+ 时间轴、实时刷新等场景中,建议复用同一个控制器实例,不要每次刷新都重新 `new PointLargeLayerController()`。
1616
+
1617
+ ```js
1618
+ await pointLayer.load({
1619
+ points: firstPoints,
1620
+ icon: normalIcon,
1621
+ })
1622
+
1623
+ // 替换下一批点位。
1624
+ await pointLayer.update({
1625
+ points: nextPoints,
1626
+ })
1627
+
1628
+ // 只更新样式规则。
1629
+ await pointLayer.update({
1630
+ styleField: 'value',
1631
+ styleRules: nextStyleRules,
1632
+ })
1633
+ ```
1634
+
1635
+
1636
+ ### 点位数据格式
1637
+
1638
+ 默认支持以下坐标字段:
1639
+
1640
+ - 经度:`longitude / lon / lng / x`
1641
+ - 纬度:`latitude / lat / y`
1642
+ - 高度:`height / altitude / z`
1643
+ - 唯一值:默认 `id`,可通过 `idKey` 指定
1644
+
1645
+ ```js
1646
+ const points = [
1647
+ {
1648
+ id: 'large-point-1',
1649
+ name: '点位 1',
1650
+ longitude: 104.1234,
1651
+ latitude: 35.1234,
1652
+ height: 0,
1653
+ value: 820,
1654
+ level: '高',
1655
+ },
1656
+ ]
1657
+ ```
1658
+
1659
+ ### 事件回调
1660
+
1661
+ ```js
1662
+ await pointLayer.load({
1663
+ points,
1664
+ onClick(event) {
1665
+ console.log(event.id)
1666
+ console.log(event.data)
1667
+ },
1668
+ onHover(event) {
1669
+ console.log(event.id)
1670
+ },
1671
+ onHoverIn(event) {
1672
+ console.log('进入点位', event.id)
1673
+ },
1674
+ onHoverOut(event) {
1675
+ console.log('离开点位', event.id)
1676
+ },
1677
+ tooltipFormatter(event) {
1678
+ return `${event.data.name}:${event.data.value}`
1679
+ },
1680
+ })
1681
+ ```
1682
+
1683
+ 事件对象常用字段:
1684
+
1685
+ | 字段 | 说明 |
1686
+ | --- | --- |
1687
+ | `layerId` | 图层 ID。 |
1688
+ | `id` | 点位唯一值。 |
1689
+ | `key` | 内部点位 key,通常与 `id` 一致。 |
1690
+ | `index` | 点位在当前数据中的索引。 |
1691
+ | `data` | 原始点位数据。 |
1692
+ | `record` | 标准化后的点位记录。 |
1693
+ | `layer` | 引擎侧海量点图层实例。 |
1694
+ | `movement` | Cesium 鼠标事件对象。 |
1695
+ | `mapEvent` | Leaflet 鼠标事件对象。 |
1696
+ | `picked` / `billboard` / `sprite` | 引擎拾取结果,按当前引擎返回。 |
1697
+
1698
+ ### 常用方法
1699
+
1700
+ ```js
1701
+ // 显示 / 隐藏 / 切换显隐。
1702
+ pointLayer.show()
1703
+ pointLayer.hide()
1704
+ pointLayer.toggle()
1705
+ pointLayer.toggle(true)
1706
+
1707
+ // 高亮 / 取消高亮。
1708
+ pointLayer.setHighlight('A001')
1709
+ pointLayer.highlight('A001')
1710
+ pointLayer.clearHighlight()
1711
+
1712
+ // 删除单点 / 批量删除。
1713
+ pointLayer.removePoint('A001')
1714
+ pointLayer.deletePoint('A001')
1715
+ await pointLayer.removePoints(['A001', 'A002'])
1716
+
1717
+ // 清空点位,但保留图层实例。
1718
+ pointLayer.clear()
1719
+
1720
+ // 从引擎侧重新读取状态。
1721
+ pointLayer.refreshState()
1722
+
1723
+ // 销毁图层。
1724
+ pointLayer.destroy()
1725
+
1726
+ // 读取当前状态。
1727
+ pointLayer.getState()
536
1728
  ```
537
1729
 
538
- ## PointLargeLayerController
1730
+ 方法说明:
1731
+
1732
+ | 方法 | 说明 |
1733
+ | --- | --- |
1734
+ | `mount(mapCore)` | 挂载 `BaseGIS` 实例。构造时已传 `mapCore/baseGIS` 时通常不需要手动调用。 |
1735
+ | `load(payload, options)` | 加载或覆盖海量点数据。支持 `load(points, options)` 和 `load({ points, ...options })`。 |
1736
+ | `upsert(payload, options)` | `load` 的别名。 |
1737
+ | `update(payload)` | 基于当前配置合并更新。可替换点位,也可只更新样式、图标、显隐参数。 |
1738
+ | `show(payload)` | 显示图层。 |
1739
+ | `hide(payload)` | 隐藏图层。 |
1740
+ | `toggle(visible)` | 切换显隐。不传 `visible` 时按当前状态取反。 |
1741
+ | `setHighlight(id, payload)` | 高亮指定点位。别名:`highlight`、`setHighlighted`。 |
1742
+ | `clearHighlight(payload)` | 清除当前高亮。别名:`clearHighlighted`。 |
1743
+ | `removePoint(id, payload)` | 删除单个点位。别名:`deletePoint`、`removeById`。 |
1744
+ | `removePoints(ids, payload)` | 批量删除点位。别名:`deletePoints`、`removeByIds`。 |
1745
+ | `clear(payload)` | 清空图层点位数据,但保留图层实例。 |
1746
+ | `refreshState(payload)` | 从引擎侧重新读取图层状态。 |
1747
+ | `destroy(payload)` | 销毁图层并释放资源。别名:`removeLayer`。 |
1748
+ | `getState()` | 读取控制器状态。 |
1749
+
1750
+ ### 构造和加载参数
1751
+
1752
+ | 参数 | 是否必填 | 默认值 | 说明 |
1753
+ | --- | --- | --- | --- |
1754
+ | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。也可以后续通过 `mount(mapCore)` 挂载。 |
1755
+ | `layerId` / `pointLargeLayerId` / `largeLayerId` / `id` | 否 | `point-large-default` | 海量点图层 ID。 |
1756
+ | `visible` | 否 | `true` | 初始是否显示。 |
1757
+ | `idKey` | 否 | `id` | 点位唯一值字段。高亮、删除单点依赖该值。 |
1758
+ | `points` / `data` / `items` | 加载时必填其一 | - | 点位数组。 |
1759
+ | `longitudeKeys` | 否 | `['longitude', 'lon', 'lng', 'x']` | 经度字段候选列表。 |
1760
+ | `latitudeKeys` | 否 | `['latitude', 'lat', 'y']` | 纬度字段候选列表。 |
1761
+ | `heightKeys` | 否 | `['height', 'altitude', 'z']` | 高度字段候选列表。 |
1762
+ | `image` / `icon` / `iconUrl` / `imageUrl` | 否 | - | 普通状态图标。未传时使用内置圆点图标。 |
1763
+ | `highlightImage` / `highlightIcon` / `highlightIconUrl` / `highlightImageUrl` | 否 | - | 高亮状态图标。未传时使用高亮圆点图标。 |
1764
+ | `width` / `height` | 否 | `32` | 普通图标尺寸。 |
1765
+ | `scale` | 否 | `1` | 普通图标缩放比例。 |
1766
+ | `color` / `outlineColor` / `outlineWidth` | 否 | `#1e88e5` / `#fff` / `2` | 未传图标时的内置圆点样式。 |
1767
+ | `highlightColor` / `highlightOutlineColor` / `highlightOutlineWidth` | 否 | `#ffcc00` / `#fff` / `3` | 未传高亮图标时的高亮圆点样式。 |
1768
+ | `highlightWidth` / `highlightHeight` / `highlightScale` | 否 | `36` / `36` / `scale * 1.35` | 高亮图标尺寸和缩放。 |
1769
+ | `styleField` / `valueKey` | 否 | `value` | `styleRules` 默认读取的业务值字段。 |
1770
+ | `styleRules` | 否 | `[]` | 点位分级样式规则。 |
1771
+ | `imageCallback` | 否 | - | 自定义返回点位图标。 |
1772
+ | `styleCallback` | 否 | - | 自定义返回普通状态样式。 |
1773
+ | `highlightStyleCallback` | 否 | - | 自定义返回高亮状态样式。 |
1774
+ | `showTooltip` | 否 | `true` | 是否显示内置悬浮提示。 |
1775
+ | `tooltipFormatter` | 否 | - | 自定义悬浮提示内容。 |
1776
+ | `tooltipOffset` | 否 | `[14, 14]` | 悬浮提示偏移。 |
1777
+ | `tooltipClassName` | 否 | - | 悬浮提示 DOM class。 |
1778
+ | `onClick` / `onHover` / `onHoverIn` / `onHoverOut` | 否 | - | 点位事件回调。 |
1779
+ | `chunkSize` | 否 | `5000` | 分帧构建时每批处理数量。 |
1780
+ | `chunkFrameBudget` | 否 | `0` | 分帧构建每帧预算,单位毫秒。 |
1781
+ | `pickRadius` | 否 | `10` | Leaflet / Pixi 模式下的拾取半径。 |
1782
+ | `keepSize` | 否 | `true` | Cesium 下是否尽量保持屏幕像素尺寸。 |
1783
+ | `clampToGround` | 否 | `false` | Cesium 点位是否贴地。 |
1784
+ | `disableDepthTestDistance` | 否 | - | Cesium 深度检测距离。 |
1785
+ | `pixelOffset` / `eyeOffset` | 否 | - | Cesium 图标偏移。 |
1786
+ | `scaleByDistance` | 否 | - | Cesium 按距离缩放。 |
1787
+ | `translucencyByDistance` | 否 | - | Cesium 按距离透明。 |
1788
+ | `distanceDisplayCondition` | 否 | - | Cesium 按距离显示隐藏。 |
1789
+ | `useParticleContainer` | 否 | `false` | Leaflet 下是否使用 Pixi 粒子容器能力。 |
1790
+ | `pixiOptions` | 否 | - | Leaflet / Pixi 渲染参数。 |
1791
+
1792
+ ### 状态返回
539
1793
 
540
- 用于海量点位渲染,支持显隐、高亮、删除单点、批量删除、清空和状态读取。
1794
+ ```js
1795
+ const state = pointLayer.getState()
1796
+
1797
+ console.log(state)
1798
+ // {
1799
+ // mounted: true,
1800
+ // engineType: 'leaflet',
1801
+ // layerType: 'leaflet-point-large-layer',
1802
+ // visible: true,
1803
+ // layerId: 'station-large',
1804
+ // total: 20000,
1805
+ // renderedCount: 20000,
1806
+ // highlightedId: 'A001',
1807
+ // building: false,
1808
+ // destroyed: false,
1809
+ // payload: {}
1810
+ // }
1811
+ ```
1812
+
1813
+
1814
+ ## PointDensityController
1815
+
1816
+ 用于点位密度抽稀。地图移动、缩放后会按当前视野和屏幕网格重新计算可见点,适合站点、设备、告警、空气质量监测点等高密度点位。
1817
+
1818
+ 它和 `PointLargeLayerController` 的区别是:`PointLargeLayerController` 尽量渲染全部有效点;`PointDensityController` 会根据视野、屏幕网格、优先级和最大显示数量筛选一部分点显示。
1819
+
1820
+ ### Demo 位置
1821
+
1822
+ 仓库内已有多个点位抽稀 demo:
1823
+
1824
+ - `/test-page-6`:站点 ICON 抽稀,使用 `imageCallback` 按数据绘制站点图标。
1825
+ - `/test-page-8`:空气质量站点抽稀,使用固定 `image` 图标。
1826
+ - `/test-page-13`:空气质量业务页里的城市点位抽稀。
1827
+
1828
+ ### 基础用法
541
1829
 
542
1830
  ```js
543
- import { PointLargeLayerController } from '@3clear/basegis/methods'
1831
+ import { PointDensityController } from '@3clear/basegis/methods'
544
1832
 
545
- const largePoints = new PointLargeLayerController({
1833
+ const density = new PointDensityController({
1834
+ // 必填:BaseGIS 实例。
546
1835
  mapCore,
547
- layerId: 'station-large',
1836
+
1837
+ // 建议必填:图层唯一 ID。
1838
+ layerId: 'station-density',
1839
+
1840
+ // 点位唯一值字段。不传默认 id。
548
1841
  idKey: 'staNum',
1842
+
1843
+ // 抽稀网格大小,单位屏幕像素。同一网格内只保留一个点。
1844
+ gridSize: 150,
1845
+
1846
+ // 最多显示点数。不传默认不限制。
1847
+ maxCount: 500,
1848
+
1849
+ // 同一网格内保留哪个点。字段值越大越优先显示。
1850
+ priorityKey: 'level',
1851
+
1852
+ // 默认图标。
549
1853
  image: '/icons/station.png',
550
- width: 24,
551
- height: 24,
1854
+
1855
+ // 图标尺寸。
1856
+ width: 28,
1857
+ height: 28,
1858
+
1859
+ // 点击点位回调。第一个参数是原始点位数据。
552
1860
  onClick(point) {
553
- console.log('点击站点', point)
1861
+ console.log(point)
1862
+ },
1863
+
1864
+ // 状态变化回调。地图移动、缩放或数据更新后会触发。
1865
+ onStateChange(state) {
1866
+ console.log(state.visibleCount, state.hiddenCount)
554
1867
  },
555
1868
  })
556
1869
 
557
- await largePoints.load({
1870
+ await density.load({
558
1871
  points: [
559
- { staNum: 'A001', longitude: 104, latitude: 35, name: 'A001' },
560
- { staNum: 'A002', longitude: 105, latitude: 36, name: 'A002' },
1872
+ { staNum: 'A001', longitude: 104, latitude: 35, level: 5 },
1873
+ { staNum: 'A002', longitude: 104.01, latitude: 35.01, level: 3 },
561
1874
  ],
562
1875
  })
563
-
564
- largePoints.setHighlight('A001')
565
- largePoints.clearHighlight()
566
- largePoints.removePoint('A002')
567
- largePoints.removePoints(['A001', 'A003'])
568
- largePoints.hide()
569
- largePoints.show()
570
- largePoints.clear()
571
- largePoints.destroy()
572
- largePoints.getState()
573
1876
  ```
574
1877
 
575
- 点位字段默认支持:
576
-
577
- - 经度:`longitude / lon / lng / x`
578
- - 纬度:`latitude / lat / y`
579
- - 高度:`height / altitude / z`
580
- - 唯一值:默认 `id`,可通过 `idKey` 指定
581
-
582
- ## PointDensityController
1878
+ ### 完整配置模板
583
1879
 
584
- 用于点位密度抽稀。地图移动、缩放后按屏幕网格重新计算可见点,适合站点、设备、告警等高密度点位。
1880
+ 下面的配置接近 `test-page-6` 的写法,字段已加注释。业务中按需保留即可。
585
1881
 
586
1882
  ```js
587
- import { PointDensityController } from '@3clear/basegis/methods'
588
-
589
1883
  const density = new PointDensityController({
1884
+ // BaseGIS 实例。控制器只通过 BaseGIS 调用当前引擎能力,不直接依赖 Cesium / Leaflet。
590
1885
  mapCore,
1886
+
1887
+ // 图层唯一标识。后续 update/show/hide/clear/destroy 都按该 id 定位图层。
591
1888
  layerId: 'station-density',
1889
+
1890
+ // 点位唯一值字段。默认 id;如果点位唯一值在 properties 中,也会尝试读取 properties[idKey]。
592
1891
  idKey: 'staNum',
1892
+
1893
+ // 初始是否显示图层。不传默认 true。
1894
+ visible: true,
1895
+
1896
+ // 原始点位数据。也可以不在构造时传,后续 density.load({ points }) 传入。
1897
+ points: stationList,
1898
+
1899
+ // 经度字段候选列表。默认已支持 longitude/lon/lng/x。
1900
+ longitudeKeys: ['longitude', 'lon', 'lng', 'x'],
1901
+
1902
+ // 纬度字段候选列表。默认已支持 latitude/lat/y。
1903
+ latitudeKeys: ['latitude', 'lat', 'y'],
1904
+
1905
+ // 高度字段候选列表。默认已支持 height/altitude/z。
1906
+ heightKeys: ['height', 'altitude', 'z'],
1907
+
1908
+ // 是否开启抽稀。true 时会按屏幕网格去重;false 时只做视野过滤,不做网格碰撞去重。
1909
+ enableThinning: true,
1910
+
1911
+ // 抽稀网格大小,单位屏幕像素。值越大,保留点越少;值越小,保留点越多。
593
1912
  gridSize: 150,
1913
+
1914
+ // gridSize 的别名。如果同时传 gridSize 和 pixelRange,以 gridSize 为准。
1915
+ // pixelRange: 150,
1916
+
1917
+ // 最多显示点数量。超过后会按优先级截断。不传默认 Infinity。
594
1918
  maxCount: 500,
1919
+
1920
+ // 同一网格内点位优先级字段。字段值越大越优先保留。
595
1921
  priorityKey: 'level',
1922
+
1923
+ // 自定义优先级函数。返回值越大越优先保留;传了它后优先级逻辑可完全由业务决定。
1924
+ // priorityCallback(point, markerOrBillboard, index) {
1925
+ // return point.level * 100 + point.value
1926
+ // },
1927
+
1928
+ // 默认图标地址。支持图片 URL、base64、data URL。
596
1929
  image: '/icons/station.png',
1930
+
1931
+ // 图标地址别名,和 image 作用一致。
1932
+ // icon: '/icons/station.png',
1933
+ // iconUrl: '/icons/station.png',
1934
+
1935
+ // 按点位动态生成图标。适合空气质量、告警等级等需要每个点图标不同的场景。
1936
+ // 返回值可以是图片地址、base64、data URL。
1937
+ // imageCallback(point, index) {
1938
+ // return createStationIcon(point)
1939
+ // },
1940
+
1941
+ // 图标宽度,单位像素。
597
1942
  width: 28,
1943
+
1944
+ // 图标高度,单位像素。
598
1945
  height: 28,
1946
+
1947
+ // 地图移动、缩放后重新计算抽稀的节流时间,单位毫秒。
1948
+ throttleTime: 120,
1949
+
1950
+ // Cesium 专用:视野过滤缓冲,单位屏幕像素。
1951
+ // 值越大,视野边缘附近的点越不容易在移动时频繁出现/消失。
1952
+ viewportBuffer: 100,
1953
+
1954
+ // Cesium 专用:3D 场景下是否剔除地球背面的点。
1955
+ cullByGlobe: true,
1956
+
1957
+ // Cesium 专用:Billboard 是否贴地。
1958
+ clampToGround: false,
1959
+
1960
+ // Cesium 专用:Billboard 缩放比例。
1961
+ scale: 1,
1962
+
1963
+ // Cesium 专用:禁用深度检测距离。
1964
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
1965
+
1966
+ // Cesium 专用:平滑更新,减少刷新抽稀结果时的突兀感。
1967
+ smoothUpdate: true,
1968
+ smoothUpdateFrames: 2,
1969
+
1970
+ // Leaflet 专用:视野 bounds 扩展比例。
1971
+ // 例如 0.1 表示在当前视野基础上向外扩展 10% 后再过滤点位。
1972
+ boundsBufferRatio: 0,
1973
+
1974
+ // Leaflet 专用:Marker 层级偏移。
1975
+ zIndexOffset: 0,
1976
+
1977
+ // 点位点击回调。第一个参数是原始点位数据。
599
1978
  onClick(point) {
600
1979
  console.log(point)
601
1980
  },
1981
+
1982
+ // 状态变化回调。每次抽稀刷新、显隐、清空后会尽量触发。
602
1983
  onStateChange(state) {
603
1984
  console.log(state.visibleCount, state.hiddenCount)
604
1985
  },
605
1986
  })
1987
+ ```
1988
+
1989
+ ### 点位数据格式
606
1990
 
1991
+ `load` 支持直接传数组,也支持传对象。对象中点位字段支持 `points / data / items`,推荐统一使用 `points`。
1992
+
1993
+ ```js
607
1994
  await density.load({
608
1995
  points: [
609
1996
  { staNum: 'A001', longitude: 104, latitude: 35, level: 5 },
610
1997
  { staNum: 'A002', longitude: 104.01, latitude: 35.01, level: 3 },
611
1998
  ],
612
1999
  })
2000
+ ```
2001
+
2002
+ 也可以写成:
2003
+
2004
+ ```js
2005
+ await density.load([
2006
+ { id: 'A001', lon: 104, lat: 35, value: 86 },
2007
+ { id: 'A002', lon: 104.01, lat: 35.01, value: 120 },
2008
+ ])
2009
+
2010
+ await density.load({
2011
+ data: [
2012
+ { id: 'A001', x: 104, y: 35 },
2013
+ ],
2014
+ })
2015
+ ```
2016
+
2017
+ 默认坐标字段:
2018
+
2019
+ - 经度:`longitude / lon / lng / x`
2020
+ - 纬度:`latitude / lat / y`
2021
+ - 高度:`height / altitude / z`
2022
+ - 唯一值:默认 `id`,可通过 `idKey` 指定。
2023
+
2024
+ ### 抽稀规则
2025
+
2026
+ 核心规则:
2027
+
2028
+ 1. 先按当前地图视野过滤点位。
2029
+ 2. `enableThinning: true` 时,再按屏幕像素网格抽稀。
2030
+ 3. 同一网格内默认保留优先级更高的点。
2031
+ 4. 如果设置了 `maxCount`,最终显示数量不会超过该值。
2032
+
2033
+ 优先级规则:
2034
+
2035
+ ```js
2036
+ const density = new PointDensityController({
2037
+ mapCore,
2038
+ layerId: 'station-density',
2039
+ points,
2040
+ gridSize: 120,
2041
+
2042
+ // 简单写法:按字段值排序,值越大越优先显示。
2043
+ priorityKey: 'value',
2044
+ })
2045
+ ```
2046
+
2047
+ 复杂优先级可以用 `priorityCallback`:
2048
+
2049
+ ```js
2050
+ const density = new PointDensityController({
2051
+ mapCore,
2052
+ layerId: 'station-density',
2053
+ points,
2054
+ gridSize: 120,
2055
+ priorityCallback(point, markerOrBillboard, index) {
2056
+ const alarmWeight = point.alarm ? 10000 : 0
2057
+ return alarmWeight + Number(point.value || 0) - index * 0.001
2058
+ },
2059
+ })
2060
+ ```
613
2061
 
2062
+ ### 更新配置
2063
+
2064
+ 地图上已经有图层后,可以通过 `update` 或 `setConfig` 更新数据或抽稀参数。
2065
+
2066
+ ```js
2067
+ // 更新抽稀网格。
614
2068
  density.update({ gridSize: 120 })
2069
+
2070
+ // 关闭抽稀,只做视野过滤。
2071
+ density.update({ enableThinning: false })
2072
+
2073
+ // 替换点位数据。
2074
+ density.update({
2075
+ points: nextPoints,
2076
+ })
2077
+
2078
+ // setConfig 是 update 的别名,更适合只表达“改配置”的场景。
2079
+ density.setConfig({
2080
+ gridSize: 180,
2081
+ maxCount: 300,
2082
+ })
2083
+ ```
2084
+
2085
+ ### 显隐、清空和销毁
2086
+
2087
+ ```js
615
2088
  density.hide()
616
2089
  density.show()
617
2090
  density.toggle()
2091
+ density.toggle(true)
618
2092
  density.refreshState()
619
2093
  density.clear()
620
2094
  density.destroy()
621
2095
  ```
622
2096
 
623
- 常用参数:
2097
+ 方法说明:
2098
+
2099
+ | 方法 | 说明 |
2100
+ | --- | --- |
2101
+ | `mount(mapCore)` | 挂载 `BaseGIS` 实例。构造时已传 `mapCore/baseGIS` 时通常不需要手动调用。 |
2102
+ | `load(payload, options)` | 加载或覆盖抽稀点位数据。支持 `load(points, options)` 和 `load({ points, ...options })`。 |
2103
+ | `upsert(payload, options)` | `load` 的别名。 |
2104
+ | `update(payload)` | 合并当前配置后重新加载。可更新点位、抽稀参数、图标、事件等。 |
2105
+ | `setConfig(payload)` | `update` 的别名。适合只更新抽稀参数。 |
2106
+ | `show(payload)` | 显示图层。 |
2107
+ | `hide(payload)` | 隐藏图层。 |
2108
+ | `toggle(visible)` | 切换显隐。不传 `visible` 时按当前状态取反。 |
2109
+ | `clear(payload)` | 清空图层点位数据,但保留图层实例。 |
2110
+ | `refreshState(payload)` | 从引擎侧重新读取图层状态。 |
2111
+ | `destroy(payload)` | 销毁图层并释放资源。别名:`removeLayer`。 |
2112
+ | `getState()` | 读取控制器状态。 |
2113
+
2114
+ ### 参数总表
2115
+
2116
+ | 参数 | 是否必填 | 默认值 | 说明 |
2117
+ | --- | --- | --- | --- |
2118
+ | `mapCore` / `baseGIS` | 构造时建议必填 | - | `BaseGIS` 实例。也可以后续调用 `mount(mapCore)`。 |
2119
+ | `layerId` / `pointDensityLayerId` / `densityLayerId` / `id` | 否 | `point-density-default` | 点位抽稀图层 ID。 |
2120
+ | `points` / `data` / `items` | 加载时必填其一 | - | 点位数据数组,也支持 GeoJSON FeatureCollection。 |
2121
+ | `idKey` | 否 | `id` | 点位唯一标识字段。 |
2122
+ | `longitudeKeys` | 否 | `['longitude', 'lon', 'lng', 'x']` | 经度字段候选名。 |
2123
+ | `latitudeKeys` | 否 | `['latitude', 'lat', 'y']` | 纬度字段候选名。 |
2124
+ | `heightKeys` | 否 | `['height', 'altitude', 'z']` | 高度字段候选名。 |
2125
+ | `visible` | 否 | `true` | 初始是否显示图层。 |
2126
+ | `enableThinning` | 否 | `true` | 是否开启抽稀;关闭后只做视野过滤。 |
2127
+ | `gridSize` | 否 | `150` | 抽稀网格大小,单位屏幕像素。 |
2128
+ | `pixelRange` | 否 | `150` | `gridSize` 的别名。 |
2129
+ | `maxCount` | 否 | `Infinity` | 单次最多显示的点数量。 |
2130
+ | `priorityKey` | 否 | - | 同一网格内点位优先级字段,值越大越优先显示。 |
2131
+ | `priorityCallback` | 否 | - | 自定义优先级函数,返回值越大越优先显示。 |
2132
+ | `image` / `icon` / `iconUrl` | 否 | - | 默认图标地址。 |
2133
+ | `imageCallback` | 否 | - | 图标生成函数,适合每个点图标不同的场景。 |
2134
+ | `width` | 否 | `32` | 图标宽度,单位像素。 |
2135
+ | `height` | 否 | `32` | 图标高度,单位像素。 |
2136
+ | `throttleTime` | 否 | `120` | 地图移动、缩放后刷新抽稀的节流时间,单位毫秒。 |
2137
+ | `onClick` | 否 | - | 点位点击回调。第一个参数为原始点位数据。 |
2138
+ | `onStateChange` | 否 | - | 图层状态变化回调。 |
2139
+ | `viewportBuffer` | 否 | `100` | Cesium 专用:视野过滤缓冲,单位屏幕像素。 |
2140
+ | `cullByGlobe` | 否 | `true` | Cesium 专用:3D 场景下是否剔除地球背面点。 |
2141
+ | `clampToGround` | 否 | `false` | Cesium 专用:Billboard 是否贴地。 |
2142
+ | `scale` | 否 | `1` | Cesium 专用:Billboard 缩放比例。 |
2143
+ | `disableDepthTestDistance` | 否 | `Infinity` | Cesium 专用:禁用深度检测距离。 |
2144
+ | `smoothUpdate` | 否 | `true` | Cesium 专用:是否平滑更新抽稀结果。 |
2145
+ | `smoothUpdateFrames` | 否 | `2` | Cesium 专用:平滑更新保留旧集合的帧数。 |
2146
+ | `boundsBufferRatio` | 否 | `0` | Leaflet 专用:视野 bounds 扩展比例。 |
2147
+ | `zIndexOffset` | 否 | `0` | Leaflet 专用:Marker 层级偏移。 |
2148
+
2149
+ ### 状态返回
2150
+
2151
+ `getState()` 和 `onStateChange(state)` 返回的状态结构基本一致:
624
2152
 
625
- - `enableThinning`:是否开启抽稀,默认开启。
626
- - `gridSize / pixelRange`:屏幕网格大小,单位像素。
627
- - `maxCount`:最多显示点数。
628
- - `priorityKey / priorityCallback`:同网格内优先显示哪个点。
629
- - `viewportBuffer`:Cesium 屏幕视野缓冲。
630
- - `cullByGlobe`:Cesium 3D 下是否剔除地球背面点。
631
- - `boundsBufferRatio`:Leaflet bounds 扩展比例。
2153
+ ```js
2154
+ const state = density.getState()
2155
+
2156
+ console.log(state)
2157
+ // {
2158
+ // mounted: true,
2159
+ // engineType: 'cesium',
2160
+ // layerType: 'cesium-point-density-layer',
2161
+ // visible: true,
2162
+ // layerId: 'station-density',
2163
+ // sourceCount: 10000,
2164
+ // visibleCount: 420,
2165
+ // hiddenCount: 9580,
2166
+ // gridCount: 420,
2167
+ // gridSize: 150,
2168
+ // destroyed: false,
2169
+ // payload: {}
2170
+ // }
2171
+ ```
632
2172
 
633
2173
  ## ContourLayerController
634
2174
 
@@ -660,6 +2200,8 @@ await contour.load({
660
2200
  color: '#ff4d4f',
661
2201
  width: 2,
662
2202
  },
2203
+ showLabel: true,
2204
+ labelFormatter: (item) => String(item.value),
663
2205
  })
664
2206
 
665
2207
  await contour.update({
@@ -674,43 +2216,247 @@ contour.clear()
674
2216
  contour.destroy()
675
2217
  ```
676
2218
 
677
- 数据别名支持 `contours / isolines / isoline / lines / data / items`。
2219
+ 数据别名支持 `contours / isolines / isoline / lines / data / items`。开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的位置;地图平移、缩放、容器尺寸变化或切换引擎时只重排标签,不重新生成等值线。
2220
+
2221
+ Cesium 在默认椭球地表上使用 `PolylineCollection` 批量落图;真正启用 DEM 地形后才使用 `GroundPolylinePrimitive`。新批次就绪后再替换旧批次,不为每条线创建独立 Entity。标签定位可通过 `labelLineThinStep` 抽样投影点,不会改变实际绘制的等值线。
2222
+
2223
+ Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做屏幕空间简化:缩小时清理挤在同一像素内的折点,放大后自动保留曲线细节,避免全局 `preferCanvas` 放大细线锯齿。线位于 `dt-contour-pane`(层级 `450`),稳定显示在图片图层上方,并保持在 `markerPane` 数值标签下方;显式传入 `pane` 时仍以业务配置为准。
2224
+
2225
+ 屏幕标注参数:
2226
+
2227
+ | 参数 | 类型 | 默认值 | 说明 |
2228
+ | --- | --- | --- | --- |
2229
+ | `showLabel/showLabels` | `boolean` | `false` | 是否显示等值线数值。 |
2230
+ | `labelFormatter(item, record)` | `Function` | 等值级别 | 格式化标签文本。 |
2231
+ | `labelColor` | `string` | `#34464f` | 文字颜色。 |
2232
+ | `labelBackgroundColor` | `string` | `rgba(255,255,255,0.78)` | 背景色。 |
2233
+ | `labelBorderColor/labelBorderWidth` | `string/number` | `rgba(93,112,116,0.35)` / `0.5` | 边框颜色和宽度。 |
2234
+ | `labelBorderRadius` | `number` | `8` | 圆角,单位 px。 |
2235
+ | `labelFontSize/labelFontWeight` | `number/string` | `10/600` | 字号和字重。 |
2236
+ | `labelPaddingX/labelPaddingY` | `number` | `4/0` | 标签内部留白。 |
2237
+ | `labelMinWidth` | `number` | `32` | 标签最小宽度,短数值保持横向胶囊形状。 |
2238
+ | `labelViewportPadding` | `number/object/Function` | `24` | 屏幕安全边距;对象为 `top/right/bottom/left`,函数接收 `{width,height}`。 |
2239
+ | `labelMinScreenLength` | `number` | `72` | 可见线段达到该像素长度后才允许放标签。 |
2240
+ | `labelLineThinStep` | `number` | Cesium `3`,Leaflet `1` | 标签定位时的投影抽样步长,不影响实际线几何。 |
2241
+ | `labelCollisionPadding` | `number` | `7` | 标签碰撞间距。 |
2242
+ | `labelMaxPerLevel` | `number` | `Infinity` | 每个等值级别在当前视野中的标签上限;默认按可见线数量自动决定。 |
2243
+ | `labelDisableDepthTestDistance` | `number` | `Infinity` | Cesium 标签关闭深度检测的距离。 |
2244
+
2245
+ `getState()` 的 `labelCount` 是当前视野实际显示的标签数量。Cesium 状态额外包含 `lineRenderer/lineBuildElapsed/labelLayoutElapsed/primitiveReadyElapsed`,用于区分线构建、标签排版和异步就绪耗时。`onStateChange(state)` 会在视野重排后回传新状态。页面存在侧栏等遮挡时,可通过 `labelViewportPadding` 排除对应区域。
678
2246
 
679
- ## PressureContourLayer
2247
+ ## RasterContourController
680
2248
 
681
- 用于等压线和高低压中心标注。`buildPressureRenderData()` 可把 Windy 风格的 `press.json` 数据整理成渲染结构,`createPressureDataResolver()` 提供简单缓存。
2249
+ 用于从 GeoTIFF、灰度图或数值网格直接计算并绘制等值线。内部统一使用 `d3-contour` 追踪等值线,输出仍交给 `ContourLayerController`,所以页面不需要区分 Cesium 和 Leaflet;调用 `BaseGIS.setEngine()` 时,已经生成的等值线会随托管图层自动恢复。
2250
+
2251
+ ### 1. GeoTIFF 生成等值线
682
2252
 
683
2253
  ```js
684
- import {
685
- PressureContourLayer,
686
- createPressureDataResolver,
687
- } from '@3clear/basegis/methods'
2254
+ import { RasterContourController } from '@3clear/basegis/methods'
688
2255
 
689
- const resolvePressureData = createPressureDataResolver({
690
- default: pressJson,
691
- next: pressJson2,
2256
+ const rasterContour = new RasterContourController({
2257
+ mapCore,
2258
+ layerId: 'temperature-raster-contour',
2259
+ // thresholds / interval / thresholdCount 三选一。
2260
+ interval: 5,
2261
+ smooth: true,
2262
+ visible: true,
692
2263
  })
693
2264
 
694
- const pressureLayer = new PressureContourLayer({
695
- mapCore,
696
- layerId: 'pressure',
697
- color: 'rgba(255,255,255,0.82)',
698
- width: 1.15,
699
- smoothFactor: 0.2,
700
- // Cesium 场景下用于定位容器尺寸,Leaflet 场景也可以传同一个地图容器 id。
701
- mapContainerId: 'map',
702
- // 标注层通过该函数读取当前数据,便于地图缩放后重建标签。
703
- getData: () => resolvePressureData('default'),
704
- isAnnotationVisible: () => true,
2265
+ const result = await rasterContour.loadGeoTiff({
2266
+ tifUrl: '/data/temperature.tif',
2267
+ // TIF 范围不准确时可用 WGS84 四至覆盖文件元数据。
2268
+ area: {
2269
+ startLon: -180,
2270
+ startLat: -90,
2271
+ endLon: 180,
2272
+ endLat: 90,
2273
+ },
2274
+ band: 0,
2275
+ interval: 5,
2276
+ clampToGround: true,
2277
+ showLabel: true,
2278
+ labelFormatter: (item) => String(Math.round(item.value)),
2279
+ labelViewportPadding: { top: 32, right: 32, bottom: 32, left: 360 },
2280
+ styleCallback: (item) => ({
2281
+ color: item.value >= 30 ? '#ff6b6b' : '#d5f4ff',
2282
+ width: item.value % 10 === 0 ? 2 : 1,
2283
+ weight: item.value % 10 === 0 ? 2 : 1,
2284
+ }),
2285
+ })
2286
+
2287
+ if (!result.success) {
2288
+ console.warn(result.message)
2289
+ }
2290
+
2291
+ // 仅调整等值距时复用已解析网格,不再请求和解码 TIF。
2292
+ await rasterContour.update({ interval: 2 })
2293
+ ```
2294
+
2295
+ GeoTIFF 依赖宿主页面提供 `window.GeoTIFF`。控制器会读取波段、尺寸、坐标系和范围;当前原生支持 EPSG:4326、EPSG:3857。其他投影可先转换为 WGS84,或传入 WGS84 `area` 覆盖原始范围。
2296
+
2297
+ ### 2. 灰度图生成等值线
2298
+
2299
+ ```js
2300
+ await rasterContour.loadGrayImage({
2301
+ grayImageUrl: '/data/pressure-gray.png',
2302
+ area: [-180, -90, 180, 90],
2303
+ grayMinValue: 0,
2304
+ grayMaxValue: 255,
2305
+ minValue: 960,
2306
+ maxValue: 1040,
2307
+ noDataValue: 255,
2308
+ interval: 4,
2309
+ })
2310
+ ```
2311
+
2312
+ 灰度换算规则:
2313
+
2314
+ ```text
2315
+ 业务值 = (灰度值 - grayMinValue) / (grayMaxValue - grayMinValue)
2316
+ * (maxValue - minValue) + minValue
2317
+ ```
2318
+
2319
+ 灰度图片本身没有地理范围和业务值含义,因此 `area` 必填;要表达真实数据,还应传正确的灰度范围与业务值范围。已经着色的彩色 PNG 不能反推出原始业务值,应使用对应 TIF 或直接传数值网格。
2320
+
2321
+ ### 3. 数值网格生成等值线
2322
+
2323
+ ```js
2324
+ await rasterContour.loadGrid({
2325
+ values: [12, 14, 16, 18, 20, 22],
2326
+ width: 3,
2327
+ height: 2,
2328
+ area: [100, 20, 103, 22],
2329
+ thresholds: [15, 20],
705
2330
  })
706
2331
 
707
- pressureLayer.mount()
708
- await pressureLayer.load(resolvePressureData('default'))
709
- await pressureLayer.update(resolvePressureData('next'))
710
- pressureLayer.hide()
711
- pressureLayer.destroy()
2332
+ // values 也可以直接传二维数组,此时不需要 width / height。
2333
+ await rasterContour.loadGrid({
2334
+ values: [
2335
+ [12, 14, 16],
2336
+ [18, 20, 22],
2337
+ ],
2338
+ area: [100, 20, 103, 22],
2339
+ thresholdCount: 6,
2340
+ })
712
2341
  ```
713
2342
 
2343
+ ### 4. 参数
2344
+
2345
+ 数据源参数:
2346
+
2347
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
2348
+ | --- | --- | --- | --- | --- |
2349
+ | `mapCore/baseGIS` | `BaseGIS` | 构造时必填 | `null` | 当前 BaseGIS 实例。 |
2350
+ | `layerId/contourLayerId` | `string` | 否 | `raster-contour-default` | 等值线图层 id。 |
2351
+ | `sourceType` | `geotiff/grayscale/grid` | 否 | 自动判断 | `loadGeoTiff/loadGrayImage/loadGrid` 会自动补齐。 |
2352
+ | `tifUrl/tiffUrl` | `string` | GeoTIFF 必填 | `''` | GeoTIFF 地址。 |
2353
+ | `arrayBuffer` | `ArrayBuffer` | 否 | `null` | 可代替 `tifUrl` 直接传 TIF 内容。 |
2354
+ | `band` | `number` | 否 | `0` | GeoTIFF 波段下标,从 0 开始。 |
2355
+ | `imageIndex` | `number` | 否 | `0` | 多图像 GeoTIFF 的图像下标。 |
2356
+ | `grayImageUrl/imageUrl` | `string` | 灰度图必填 | `''` | 灰度图地址,服务端必须允许 Canvas 跨域读取。 |
2357
+ | `values/data/grid` | `Array/TypedArray` | 数值网格必填 | - | 一维或二维数值。 |
2358
+ | `width/height` | `number` | 一维网格必填 | `0` | 一维数组的网格尺寸。 |
2359
+ | `area` | `object/number[]` | 图片和普通网格必填 | `null` | WGS84 范围;数组顺序为 `[west,south,east,north]`。 |
2360
+ | `sourceProjection` | `string` | 否 | 自动读取 | 支持 `EPSG:4326`、`EPSG:3857`。 |
2361
+ | `noDataValue` | `number` | 否 | TIF 自动读取 | 指定无效值;灰度图不传时仅透明像素无效。 |
2362
+ | `validMin/validMax` | `number` | 否 | - | 过滤值域外数据。 |
2363
+ | `scale/offset` | `number` | 否 | `1/0` | 数值换算为 `value * scale + offset`。 |
2364
+ | `isSplit` | `boolean` | 否 | `false` | 把左右半幅交换,处理以 0° 经线为边界的全球数据。 |
2365
+
2366
+ 等值线参数:
2367
+
2368
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
2369
+ | --- | --- | --- | --- | --- |
2370
+ | `thresholds/levels` | `number[]` | 三选一 | - | 显式等值线级别,仅保留实际值域内的值。 |
2371
+ | `interval/contourInterval` | `number` | 三选一 | - | 固定等值距。 |
2372
+ | `thresholdCount/levelCount` | `number` | 三选一 | `12` | 自动均分级别数量,范围 1~100。 |
2373
+ | `smooth` | `boolean` | 否 | `true` | 是否平滑等值线。 |
2374
+ | `smoothingIterations` | `number` | 否 | `0` | Chaikin 补点平滑次数,范围 0~3;气象格点通常取 1~2,次数越高,点数越多。 |
2375
+ | `excludeBoundary` | `boolean` | 否 | `true` | 去掉 d3 面边界中贴着栅格四边的伪矩形线。 |
2376
+ | `maxCells` | `number` | 否 | `1000000` | 超过该像元数时先等比例抽样,避免大图阻塞页面。 |
2377
+ | `minimumLinePoints` | `number` | 否 | `3` | 最短线点数,用于过滤栅格四角的两点补边。 |
2378
+ | `coordinatePrecision` | `number` | 否 | `6` | 输出经纬度小数位数。 |
2379
+ | `visible` | `boolean` | 否 | `true` | 初始显隐。 |
2380
+ | 其他渲染参数 | - | 否 | - | 与 `ContourLayerController` 相同,如 `color/width/weight/styleCallback/showLabel/labelViewportPadding`。 |
2381
+
2382
+ ### 5. 方法和状态
2383
+
2384
+ | 方法 | 说明 |
2385
+ | --- | --- |
2386
+ | `load(payload, options)` | 自动判断数据源并生成等值线。 |
2387
+ | `loadGeoTiff(payload, options)` | 从 GeoTIFF 生成。 |
2388
+ | `loadGrayImage(payload, options)` | 从灰度图生成。 |
2389
+ | `loadGrid(payload, options)` | 从数值网格生成。 |
2390
+ | `update(payload)` | 合并配置后重新计算;仅修改等值线或渲染参数时复用已解析网格,修改数据源参数时自动重新加载。 |
2391
+ | `show/hide/toggle` | 控制等值线显隐。 |
2392
+ | `clear/destroy` | 清空或销毁控制器。 |
2393
+ | `getState()` | 返回尺寸、值域、级别、线数、当前标注数、投影、耗时和底层 contour 状态。 |
2394
+ | `getRasterData()` | 返回当前标准栅格,包含 `values/width/height/area`。 |
2395
+
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,)
2444
+
2445
+ [//]: # (}&#41;)
2446
+
2447
+ [//]: # ()
2448
+ [//]: # (pressureLayer.mount&#40;&#41;)
2449
+
2450
+ [//]: # (await pressureLayer.load&#40;resolvePressureData&#40;'default'&#41;&#41;)
2451
+
2452
+ [//]: # (await pressureLayer.update&#40;resolvePressureData&#40;'next'&#41;&#41;)
2453
+
2454
+ [//]: # (pressureLayer.hide&#40;&#41;)
2455
+
2456
+ [//]: # (pressureLayer.destroy&#40;&#41;)
2457
+
2458
+ [//]: # (```)
2459
+
714
2460
  说明:
715
2461
 
716
2462
  - 等压线数据会被整理为 `isolines` 和 `centers`。
@@ -749,58 +2495,138 @@ wind.clearWindFields()
749
2495
  wind.getState()
750
2496
  ```
751
2497
 
752
- ## 直接使用 BaseGIS 图层方法
753
-
754
- 控制器适合页面长期维护状态。如果只是一次调用,也可以直接用 `BaseGIS` 方法。
2498
+ ### 构造参数
755
2499
 
756
2500
  ```js
757
- await mapCore.upsertImageLayer({
758
- layerId: 'image-layer',
759
- imageUrl: '/data/image.png',
760
- area: {
761
- startLon: 73,
762
- startLat: 18,
763
- endLon: 135,
764
- endLat: 54,
765
- },
766
- })
767
- mapCore.hideImageLayer({ layerId: 'image-layer' })
768
- mapCore.showImageLayer({ layerId: 'image-layer' })
769
- mapCore.removeImageLayer({ layerId: 'image-layer' })
2501
+ const wind = new WindFieldMethods({ mapCore })
2502
+ // 等价写法:
2503
+ // const wind = new WindFieldMethods({ baseGIS: mapCore })
2504
+ // const wind = new WindFieldMethods(mapCore)
2505
+ ```
770
2506
 
771
- await mapCore.upsertGridLayer({
772
- layerId: 'grid-layer',
773
- tifUrl: '/data/grid.tif',
774
- })
775
- mapCore.removeGridLayer({ layerId: 'grid-layer' })
2507
+ | 参数 | 类型 | 是否必填 | 说明 |
2508
+ | --- | --- | --- | --- |
2509
+ | `mapCore` | `BaseGIS` | 是 | `BaseGIS` 实例。构造时传入会自动 `mount`。 |
2510
+ | `baseGIS` | `BaseGIS` | 是 | `mapCore` 的别名。 |
776
2511
 
777
- await mapCore.upsertPointLargeLayer({
778
- layerId: 'large-points',
779
- points: [{ id: '1', longitude: 104, latitude: 35 }],
780
- })
781
- mapCore.setPointLargeHighlight({ layerId: 'large-points', pointId: '1' })
782
- mapCore.clearPointLargeHighlight({ layerId: 'large-points' })
783
- mapCore.removePointLargePoint({ layerId: 'large-points', pointId: '1' })
784
- mapCore.clearPointLargeLayer({ layerId: 'large-points' })
785
- mapCore.removePointLargeLayer({ layerId: 'large-points' })
2512
+ 如果构造时没有传 `mapCore / baseGIS`,可以后续调用 `wind.mount(mapCore)`。目标实例必须提供 `getMapInstance / getMapContainer / projectToContainerPoint / getViewBounds / onViewChange` 等 `BaseGIS` 公共方法。
786
2513
 
787
- await mapCore.upsertPointDensityLayer({
788
- layerId: 'density-points',
789
- points: [{ id: '1', longitude: 104, latitude: 35 }],
790
- gridSize: 150,
791
- })
792
- mapCore.getPointDensityLayerState({ layerId: 'density-points' })
793
- mapCore.removePointDensityLayer({ layerId: 'density-points' })
2514
+ ### 风场数据格式
794
2515
 
795
- await mapCore.upsertContourLayer({
796
- layerId: 'contour',
797
- contours: [],
798
- })
799
- await mapCore.updateContourLayer({ layerId: 'contour', contours: [] })
800
- mapCore.getContourLayerState({ layerId: 'contour' })
801
- mapCore.removeContourLayer({ layerId: 'contour' })
2516
+ `addWindField` 的 `data` 和 `updateWindField` 的 `data` 使用项目约定的 `Bound / DataAry` 结构:
2517
+
2518
+ ```js
2519
+ const windData = {
2520
+ // [经度最小值, 纬度最小值, 经向网格数, 纬向网格数, 经度跨度, 纬度跨度, 数值缩放]
2521
+ Bound: [70, 15, 121, 81, 60, 40, 10],
2522
+ // 从左下角开始,按行平铺;每个格点两个值:[u, v]
2523
+ DataAry: [
2524
+ // u0, v0, u1, v1, ...
2525
+ ],
2526
+ }
802
2527
  ```
803
2528
 
2529
+ | 字段 | 类型 | 是否必填 | 说明 |
2530
+ | --- | --- | --- | --- |
2531
+ | `Bound[0]` | `number` | 是 | `lonMin`,风场网格西边界经度。 |
2532
+ | `Bound[1]` | `number` | 是 | `latMin`,风场网格南边界纬度。 |
2533
+ | `Bound[2]` | `number` | 是 | `lonCount`,经向格点数量,必须大于等于 `2`。 |
2534
+ | `Bound[3]` | `number` | 是 | `latCount`,纬向格点数量,必须大于等于 `2`。 |
2535
+ | `Bound[4]` | `number` | 是 | `lonSpan`,经度跨度,东边界为 `lonMin + lonSpan`。 |
2536
+ | `Bound[5]` | `number` | 是 | `latSpan`,纬度跨度,北边界为 `latMin + latSpan`。 |
2537
+ | `Bound[6]` | `number` | 否 | `valueScale`,数值缩放系数,默认 `1`;实际风速分量为 `DataAry / valueScale`。 |
2538
+ | `DataAry` | `number[]` | 是 | 风矢量数组,长度必须等于 `lonCount * latCount * 2`。每个格点依次存 `u / v` 两个分量。 |
2539
+
2540
+ 数据按“左下角开始、从南到北逐行、每行从西到东”的顺序平铺。渲染时会对 `u / v` 做双线性插值,粒子速度使用 `Math.hypot(u, v)`。
2541
+
2542
+ ### addWindField / updateWindField 参数
2543
+
2544
+ `addWindField(payload)` 用于新增风场;如果同 id 已存在,会先移除旧风场再创建。`updateWindField(payload)` 用于更新已有风场的参数或数据。
2545
+
2546
+ | 参数 | 类型 | 默认值 | 说明 |
2547
+ | --- | --- | --- | --- |
2548
+ | `id` | `string` | `wind-field-default` | 风场 id。后续 start/stop/show/hide/remove 都按该 id 查找。 |
2549
+ | `layerId` | `string` | `wind-field-default` | `id` 的别名。`id` 优先级更高。 |
2550
+ | `data` | `object` | - | 风场数据。`addWindField` 必填;`updateWindField` 可选,传入时会替换旧数据。 |
2551
+ | `windData` | `object` | - | `data` 的别名。 |
2552
+ | `options` | `object` | `{}` | 渲染参数集合。会和 payload 顶层其它参数合并,顶层参数优先级低于 `options` 内同名参数。 |
2553
+ | `visible` | `boolean` | `true` | 初始是否显示 Canvas。隐藏后仍保留图层实例。 |
2554
+ | `running` | `boolean` | `true` | 初始是否播放粒子动画。 |
2555
+ | `zIndex` | `number` | `30` | 风场 Canvas 层级。速度底图使用该值,粒子层使用 `zIndex + 1`。 |
2556
+ | `height` | `number` | `0` | 投影时使用的高度,主要影响 Cesium 场景。 |
2557
+ | `devicePixelRatio` | `number` | `window.devicePixelRatio` | Canvas 渲染倍率,内部会限制在 `1 ~ 2`。 |
2558
+
2559
+ ### 粒子动画参数
2560
+
2561
+ | 参数 | 类型 | 默认值 | 说明 |
2562
+ | --- | --- | --- | --- |
2563
+ | `particleCount` | `number` | `1400` | 粒子总数。数值越大越密,渲染开销越高。 |
2564
+ | `maxFrameParticles` | `number` | `1200` | 单帧最多推进和绘制的粒子数。 |
2565
+ | `frameParticleLimit` | `number` | `1200` | `maxFrameParticles` 的别名。 |
2566
+ | `sceneModeMaxFrameParticles` | `number` | `0` | 场景模式相关帧粒子上限预留参数。当前未作为主路径使用。 |
2567
+ | `maxAge` | `number` | `58` | 粒子最大生命周期,超过后重新随机出生。 |
2568
+ | `frameRate` | `number` | `30` | 粒子动画目标帧率,内部限制在 `1 ~ 60`。 |
2569
+ | `velocityScale` | `number` | `900` | 风速到粒子位移的缩放系数。数值越大粒子移动越快。 |
2570
+ | `lineWidth` | `number` | `0.85` | 粒子轨迹基础线宽。 |
2571
+ | `color` | `string` | `rgba(230, 248, 255, 0.52)` | 粒子单色兜底颜色。传了 `particleColorScale` 时优先按风速分级着色。 |
2572
+ | `particleOpacity` | `number` | `0.58` | 粒子轨迹透明度。 |
2573
+ | `fadeOpacity` | `number` | `0.86` | 拖尾淡出强度,越接近 `1` 轨迹残留越长。 |
2574
+ | `blendMode` | `string` | `lighter` | Canvas 合成模式,例如 `source-over`、`lighter`。 |
2575
+ | `particleColorScale` | `Array` | 内置色标 | 粒子颜色分级。支持 `{ value, color }`、`{ speed, color }`、`[value, color]`。 |
2576
+
2577
+ ### 速度底图参数
2578
+
2579
+ 风场默认会额外绘制一层速度底图,用于表达风速强弱;只想显示粒子时可以关闭。
2580
+
2581
+ | 参数 | 类型 | 默认值 | 说明 |
2582
+ | --- | --- | --- | --- |
2583
+ | `velocityOverlay` | `boolean` | `true` | 是否绘制速度底图。 |
2584
+ | `velocityOverlayOpacity` | `number` | `0.22` | 速度底图透明度。 |
2585
+ | `velocityOverlayCellSize` | `number` | `54` | 速度底图采样网格像素大小。 |
2586
+ | `velocityOverlayEdgeFade` | `number` | `0.12` | 速度底图边缘淡出比例。 |
2587
+ | `colorScale` | `Array` | 内置色标 | 速度底图颜色分级。支持 `{ value, color }`、`{ speed, color }`、`[value, color]`。 |
2588
+
2589
+ `color` 可以写成 `#4ab3ff`、`rgb(80, 180, 255)`、`rgba(80, 180, 255, 0.8)` 或 `[80, 180, 255]`。
2590
+
2591
+ ### 交互与投影参数
2592
+
2593
+ | 参数 | 类型 | 默认值 | 说明 |
2594
+ | --- | --- | --- | --- |
2595
+ | `interactionResumeDelay` | `number` | `80` | 地图交互结束后恢复动画的延迟,单位毫秒。交互过程中会暂停粒子绘制。 |
2596
+ | `projectionGrid` | `boolean` | `true` | 2D / 2.5D 场景是否启用投影网格缓存。 |
2597
+ | `projectionGridCols` | `number` | `32` | 投影网格列数,内部限制在 `8 ~ 96`。 |
2598
+ | `projectionGridRows` | `number` | `20` | 投影网格行数,内部限制在 `6 ~ 64`。 |
2599
+ | `reset` | `boolean` | `false` | `updateWindField` 时传 `true` 会强制重置粒子。 |
2600
+
2601
+ `container / project / getViewBounds / getSceneMode` 由 `WindFieldMethods` 根据 `BaseGIS` 自动注入,业务侧不要传这些字段作为主路径。
2602
+
2603
+ ### 控制方法
2604
+
2605
+ | 方法 | 参数 | 说明 |
2606
+ | --- | --- | --- |
2607
+ | `mount(mapCore)` | `BaseGIS` 实例 | 后挂载 `BaseGIS`。 |
2608
+ | `addWindField(payload)` | 风场数据与渲染参数 | 新增风场。`data / windData` 必填。 |
2609
+ | `updateWindField(payload)` | `{ id, ...options }` | 更新已有风场。可只更新样式、粒子参数,也可传 `data / windData` 替换数据。 |
2610
+ | `startWindField(idOrPayload)` | `string` 或 `{ id / layerId }` | 启动粒子动画。 |
2611
+ | `stopWindField(idOrPayload)` | `string` 或 `{ id / layerId }` | 停止粒子动画,图层仍可见。 |
2612
+ | `showWindField(idOrPayload)` | `string` 或 `{ id / layerId }` | 显示风场 Canvas。 |
2613
+ | `hideWindField(idOrPayload)` | `string` 或 `{ id / layerId }` | 隐藏风场 Canvas。 |
2614
+ | `removeWindField(idOrPayload)` | `string` 或 `{ id / layerId }` | 移除指定风场并解绑视图监听。 |
2615
+ | `clearWindFields()` | 无 | 移除全部风场。 |
2616
+ | `getState()` | 无 | 返回 `{ mounted, count, layers }`。 |
2617
+
2618
+ 单个风场状态包含:
2619
+
2620
+ | 字段 | 说明 |
2621
+ | --- | --- |
2622
+ | `id` | 风场 id。 |
2623
+ | `valid` | 数据网格是否合法。 |
2624
+ | `visible` | 当前是否显示。 |
2625
+ | `running` | 当前是否播放动画。 |
2626
+ | `particleCount` | 当前粒子数量。 |
2627
+ | `bounds` | 风场数据范围 `{ west, south, east, north }`。 |
2628
+ | `center` | 风场中心 `{ lon, lat }`。 |
2629
+
804
2630
  ## 在 Vue 中封装 Hook
805
2631
 
806
2632
  推荐页面用 hook 管理生命周期,组件只处理 UI。
@@ -822,8 +2648,19 @@ export function useBaseGIS(options = {}) {
822
2648
  mapCore.value = null
823
2649
  })
824
2650
 
2651
+ async function setEngine(engineType) {
2652
+ if (!mapCore.value || mapCore.value.getEngineType() === engineType) {
2653
+ return { success: true }
2654
+ }
2655
+
2656
+ return mapCore.value.setEngine(engineType, {
2657
+ ...options,
2658
+ })
2659
+ }
2660
+
825
2661
  return {
826
2662
  mapCore,
2663
+ setEngine,
827
2664
  }
828
2665
  }
829
2666
  ```
@@ -838,13 +2675,22 @@ export function useBaseGIS(options = {}) {
838
2675
  <script setup>
839
2676
  import { useBaseGIS } from './useBaseGIS'
840
2677
 
841
- const { mapCore } = useBaseGIS({
2678
+ const { mapCore, setEngine } = useBaseGIS({
842
2679
  engineType: 'cesium',
843
2680
  containerId: 'map',
844
2681
  })
845
2682
  </script>
846
2683
  ```
847
2684
 
2685
+ BaseGIS 托管图层会自动恢复,页面不需要重新请求数据:
2686
+
2687
+ ```js
2688
+ const result = await setEngine('leaflet')
2689
+ console.log(result.data?.restore)
2690
+ ```
2691
+
2692
+ 只有风场、剖面、一次性绘制对象或页面直接创建的底层引擎对象需要在切换成功后自行恢复。
2693
+
848
2694
  ## 能力支持说明
849
2695
 
850
2696
  | 能力 | Cesium | Leaflet |
@@ -861,6 +2707,8 @@ const { mapCore } = useBaseGIS({
861
2707
  | 等值线 | 支持 | 支持 |
862
2708
  | 等压线标注 | 支持 | 支持 |
863
2709
  | 风场 Canvas 动画 | 支持 | 支持 |
2710
+ | 三维体渲染 | 支持 | 不支持 |
2711
+ | 三维切片 / 剖面渲染 | 支持 | 不支持 |
864
2712
  | DEM 地形 | 支持 | 不支持 |
865
2713
 
866
2714
  ## 本地开发与打包
@@ -970,3 +2818,16 @@ mapCore.init()
970
2818
  ```
971
2819
 
972
2820
  `init()` 内部会先销毁旧适配器,再创建新实例。
2821
+
2822
+ ### 切换引擎后图层没了
2823
+
2824
+ 直接使用 `setEngine()`。BaseGIS 会保留视野,并自动恢复图片、网格、海量点、点位抽稀、等值线和三维体等托管图层:
2825
+
2826
+ ```js
2827
+ const result = await mapCore.setEngine('leaflet')
2828
+
2829
+ console.log(result.data?.restore?.restored)
2830
+ console.log(result.data?.restore?.failed)
2831
+ ```
2832
+
2833
+ 如果丢失的是 `drawPoint/drawLine/drawPolygon/drawText/addMarker`、点击监听、风场、剖面图层或页面直接创建的 Cesium / Leaflet 对象,它们不属于托管图层,需要业务在切换成功后自行恢复。