@3clear/basegis 0.1.2 → 0.1.3

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,31 @@
4
4
  把leaflet cesium 常用方法做了二次封装统一了api 使同一套代码可以适配2套地图引擎
5
5
  当前包包含:
6
6
 
7
- - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、三维体渲染、三维切片/剖面渲染等基础能力。
7
+ - `BaseGIS`:地图初始化、引擎切换、视角控制、底图切换、绘制、图层显隐、DEM、点击事件、Cesium / Leaflet 统一粒子风场、GPU 粒子风场、三维体渲染、三维切片/剖面渲染等基础能力。
8
8
  - `methods`:图片图层、网格图层、海量点、点位抽稀、等值线、等压线、风场、图形组等高级控制器。
9
9
  - `layers`:天地图、GeoServer 金字塔瓦片、WMS、WMTS 图层配置快捷构造器。
10
10
  - `assets`:GIS 示例资源。
11
11
  - `style.css`:Leaflet、GIS 图层和标注相关基础样式。
12
12
 
13
+ ## 能力总览
14
+
15
+ 下表以当前 npm 包的真实出口和本项目源码为准。使用位置列展示原测试路由,点击后会在新标签页打开代码演练场,并通过 `path` 参数自动选中对应路由;没有独立页面的辅助函数与其所属控制器合并展示。
16
+
17
+ | <small>导出 / 能力</small> | <small>功能</small> | <small>在本项目中的使用位置或路由</small> |
18
+ | --- | --- | --- |
19
+ | <small>[`BaseGIS`](#base-gis)</small> | <small>Cesium / Leaflet 统一入口,负责地图初始化、引擎切换、视角控制、场景模式、底图、DEM、绘制、点击事件,以及 GeoJSON、风场、三维体渲染和剖面渲染等能力。</small> | <small>基础能力:<a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a><br>专项能力:<a href="http://61.50.111.214:31166/lgmap/test-page-14" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-15" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-20" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-21" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-23" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
20
+ | <small>[`GraphicGroupController`](#graphic-group-controller)</small> | <small>统一装载和清理一组点、线、面、文字及 Marker 图形。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
21
+ | <small>[`ImageLayerController` / `toImageLayerArea`](#image-layer-controller)</small> | <small>加载普通图片、GeoTIFF、灰度图着色和多时次栅格,并提供范围参数转换。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
22
+ | <small>[`GridLayerController`](#grid-layer-controller)</small> | <small>加载灰度图或 GeoTIFF 数值网格,支持着色、探针、网格注记和生命周期控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-4" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
23
+ | <small>[`PointLargeLayerController`](#point-large-layer-controller)</small> | <small>面向海量点位的异步构建、样式规则、点击、高亮、删除和显隐控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-5" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
24
+ | <small>[`PointDensityController`](#point-density-controller)</small> | <small>点位视野裁剪和屏幕网格抽稀,支持动态图标、点击回调、配置更新和状态统计。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-8" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-9" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-13" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
25
+ | <small>[`PressureContourLayer` / `createPressureDataResolver` / `buildPressureRenderData`](#pressure-contour-layer)</small> | <small>解析气压数据并生成等值线、数值标签及高低压中心标注。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-7" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
26
+ | <small>[`ContourLayerController`](#contour-layer-controller)</small> | <small>统一管理等值线图层的加载、更新、显隐、引擎切换和销毁。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
27
+ | <small>[`RasterContourController`](#raster-contour-controller)</small> | <small>从 GeoTIFF、灰度图或数值网格提取并渲染等值线。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-18" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
28
+ | <small>[`WindFieldMethods`](#wind-field-methods)</small> | <small>提供 Cesium / Leaflet 风场数据加载、粒子动画、速度底图、交互和生命周期控制。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
29
+ | <small>[`createTiandituLayer` / `createGeoserverPyramidLayer` / `createWmsLayer` / `createWmtsLayer`](#layer-config-builders)</small> | <small>构造天地图、GeoServer 金字塔瓦片、WMS 和 WMTS 图层配置。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
30
+ | <small>[`gisMarkerSample`](#gis-marker-sample)</small> | <small>可直接导入的 GIS Marker 示例资源。</small> | <small><a href="http://61.50.111.214:31166/lgmap/test-page-2" target="_blank" rel="noopener noreferrer">查看示例</a>、<a href="http://61.50.111.214:31166/lgmap/test-page-3" target="_blank" rel="noopener noreferrer">查看示例</a></small> |
31
+
13
32
  ## 安装
14
33
 
15
34
  ```bash
@@ -266,6 +285,70 @@ mapCore.setBasemap({
266
285
  | `parameters` | 选填 | WMS 附加参数。 |
267
286
  | `engineSupport` | 选填 | 支持的引擎列表,如 `['cesium', 'leaflet']`。 |
268
287
 
288
+ <a id="layer-config-builders"></a>
289
+
290
+ ## 图层配置构造器
291
+
292
+ `layers` 出口提供四个快捷构造器,只负责生成标准图层配置,不会直接操作地图。构造结果可以放入 `config.basemap.list`,也可以传给 `setBasemap()`。
293
+
294
+ ```js
295
+ import { BaseGIS } from '@3clear/basegis'
296
+ import {
297
+ createGeoserverPyramidLayer,
298
+ createTiandituLayer,
299
+ createWmsLayer,
300
+ createWmtsLayer,
301
+ } from '@3clear/basegis/layers'
302
+
303
+ const basemapList = [
304
+ createTiandituLayer({
305
+ id: 'tianditu-imagery',
306
+ name: '天地图影像',
307
+ category: 'basemap',
308
+ resourceKey: 'imagery',
309
+ annotationResourceKey: 'imageryLabel',
310
+ }),
311
+ createGeoserverPyramidLayer({
312
+ id: 'geoserver-pyramid',
313
+ name: 'GeoServer 金字塔瓦片',
314
+ category: 'basemap',
315
+ url: 'https://example.com/tiles/{z}/{x}/{y}.png',
316
+ }),
317
+ createWmsLayer({
318
+ id: 'weather-wms',
319
+ name: '气象 WMS',
320
+ category: 'basemap',
321
+ serviceUrl: 'https://example.com/geoserver/wms',
322
+ layers: 'workspace:weather',
323
+ parameters: {
324
+ transparent: true,
325
+ format: 'image/png',
326
+ },
327
+ }),
328
+ createWmtsLayer({
329
+ id: 'weather-wmts',
330
+ name: '气象 WMTS',
331
+ category: 'basemap',
332
+ url: 'https://example.com/wmts',
333
+ layer: 'weather',
334
+ tileMatrixSet: 'EPSG:3857',
335
+ }),
336
+ ]
337
+
338
+ const mapCore = new BaseGIS({
339
+ containerId: 'map',
340
+ config: {
341
+ basemap: {
342
+ list: basemapList,
343
+ defaultVisibleId: 'tianditu-imagery',
344
+ },
345
+ },
346
+ })
347
+ ```
348
+
349
+ 四个构造器都会保留额外传入字段,便于继续配置 `engineSupport`、缩放级别、注记资源或服务参数。
350
+
351
+ <a id="base-gis"></a>
269
352
 
270
353
  ## BaseGIS 基础能力
271
354
 
@@ -579,6 +662,25 @@ mapCore.addMarker({
579
662
  })
580
663
  ```
581
664
 
665
+ <a id="gis-marker-sample"></a>
666
+
667
+ #### 使用 gisMarkerSample 示例资源
668
+
669
+ `gisMarkerSample` 是可直接导入的 Marker 图片资源,适合快速验证 `addMarker()` 或控制器图标配置。
670
+
671
+ ```js
672
+ import { gisMarkerSample } from '@3clear/basegis/assets'
673
+
674
+ mapCore.addMarker({
675
+ id: 'marker-sample',
676
+ name: '示例站点',
677
+ position: [104, 35],
678
+ iconUrl: gisMarkerSample,
679
+ iconSize: [42, 50],
680
+ iconAnchor: [21, 50],
681
+ })
682
+ ```
683
+
582
684
  清理图形:
583
685
 
584
686
  ```js
@@ -668,6 +770,8 @@ Leaflet 当前返回坐标点击:
668
770
  - 海量点、点位抽稀等图层自己的点击事件,应使用对应 Controller 的 `onClick` 参数。
669
771
 
670
772
 
773
+ <a id="graphic-group-controller"></a>
774
+
671
775
  ## GraphicGroupController
672
776
 
673
777
  用于一次性加载一组点、线、面、文字、marker。它只调用 `BaseGIS.drawPoint / drawLine / drawPolygon / drawText / addMarker`。
@@ -705,10 +809,28 @@ graphics.clear()
705
809
  graphics.getState()
706
810
  ```
707
811
 
812
+ <a id="image-layer-controller"></a>
813
+
708
814
  ## ImageLayerController
709
815
 
710
816
  用于管理一个图片覆盖层。所有更新都走 `update(payload)`;页面有很多时次数据时,自己维护数据列表,然后把当前时次的数据传给 `update`。
711
817
 
818
+ `toImageLayerArea()` 可以把经纬度数组、Leaflet Bounds 或常见范围对象统一转换成 `{ startLon, startLat, endLon, endLat }`:
819
+
820
+ ```js
821
+ import {
822
+ ImageLayerController,
823
+ toImageLayerArea,
824
+ } from '@3clear/basegis/methods'
825
+
826
+ const area = toImageLayerArea({
827
+ west: 73,
828
+ south: 18,
829
+ east: 135,
830
+ north: 54,
831
+ })
832
+ ```
833
+
712
834
  先创建控制器。这里是控制器级默认参数,后续每次 `update(payload)` 都可以覆盖这些默认值:
713
835
 
714
836
  ```js
@@ -872,6 +994,8 @@ await imageLayer.update({
872
994
  })
873
995
  ```
874
996
 
997
+ Leaflet 会对经度跨度达到 360° 的全球图片按当前视野自动补充相邻世界副本,全球 TIF / 灰度格点的注记和鼠标探针也会使用同一套经度循环,因此业务页面不需要手动创建 `±360°` 图片层。原始图片按 `0° ~ 360°` 排列时传 `isSplit: true`,适配层会在交换左右半幅的同时把显示范围统一为 `-180° ~ 180°`,确保图片与格点值落在同一经度位置。
998
+
875
999
  `loadGrayImage` 的灰度值换算规则:
876
1000
 
877
1001
  ```js
@@ -1079,6 +1203,8 @@ imageLayer.destroy({ layerId: 'radar-image' })
1079
1203
  - `switchTo(idOrIndex)`:在已加载的 `items` 内切换到某一项。
1080
1204
  - `next() / prev()`:只适合本地小数组演示,不推荐作为业务时次更新主路径。
1081
1205
 
1206
+ <a id="grid-layer-controller"></a>
1207
+
1082
1208
  ## GridLayerController
1083
1209
 
1084
1210
  用于 TIF 网格、灰度图网格、外部数值网格。
@@ -1141,6 +1267,176 @@ gridLayer.show()
1141
1267
  gridLayer.destroy()
1142
1268
  ```
1143
1269
 
1270
+ ## 统一粒子风场
1271
+
1272
+ 同一组 API 会根据当前引擎自动选择渲染器:Cesium 使用 GPU 粒子风场,Leaflet 使用迁自 one-map 的 Canvas 粒子风场。
1273
+
1274
+ ```js
1275
+ const windData = await fetch('/mock/uv.json').then((response) => response.json())
1276
+
1277
+ await mapCore.upsertWindLayer({
1278
+ layerId: 'surface-wind',
1279
+ windData,
1280
+ maxParticles: 6400,
1281
+ speedFactor: 0.15,
1282
+ lineWidth: 2,
1283
+ fadeOpacity: 0.92,
1284
+ color: '#b7d9ff',
1285
+ particleOpacity: 0.72,
1286
+ })
1287
+
1288
+ await mapCore.updateWindLayer({
1289
+ layerId: 'surface-wind',
1290
+ speedFactor: 0.2,
1291
+ })
1292
+ mapCore.hideWindLayer({ layerId: 'surface-wind' })
1293
+ mapCore.showWindLayer({ layerId: 'surface-wind' })
1294
+ mapCore.flyToWindLayer({ layerId: 'surface-wind', duration: 0.8 })
1295
+ const stateResult = mapCore.getWindLayerState({ layerId: 'surface-wind' })
1296
+ mapCore.removeWindLayer({ layerId: 'surface-wind' })
1297
+ ```
1298
+
1299
+ | 方法 | 说明 |
1300
+ | --- | --- |
1301
+ | `upsertWindLayer(payload)` | 创建或更新当前引擎的风场 |
1302
+ | `updateWindLayer(payload)` | 更新数据或部分参数 |
1303
+ | `showWindLayer(payload)` / `hideWindLayer(payload)` | 显隐风场 |
1304
+ | `flyToWindLayer(payload)` | 定位到风场数据范围 |
1305
+ | `getWindLayerState(payload)` | 获取渲染器、粒子数量、范围和显隐状态 |
1306
+ | `removeWindLayer(payload)` | 删除风场并释放资源 |
1307
+
1308
+ 跨引擎统一使用 `Bound / DataAry` 数据格式。公共参数包括 `maxParticles`、`speedFactor`、`lineWidth`、`fadeOpacity`、`particleOpacity`、`color` 和 `visible`;适配层会统一速度、帧率拖尾、屏幕线宽、设备像素比和粒子视觉密度。`maxParticles` 表示跨引擎视觉预算:Cesium 的实际粒子数会按 GPU 纹理向上取整为整数平方,Leaflet 会按 Canvas 拖尾覆盖率换算实际粒子数,均可通过图层状态查看;算法和投影不同,因此不保证逐像素完全一致。Leaflet 还支持 `particleGap`、`maxAge`、`frameRate`、`minSpeed`。统一风场属于 BaseGIS 托管图层,`setEngine()` 切换引擎时会自动使用原数据和最新参数恢复。
1309
+
1310
+ ## GPU 粒子风场
1311
+
1312
+ GPU 粒子风场是 Cesium 专有能力,直接通过 `BaseGIS` 调用。它使用显卡纹理保存和更新粒子,相机平移或缩放期间清空轨迹,操作结束后按新视野重新生成粒子。Leaflet 调用同名方法时会返回 `UNSUPPORTED_CAPABILITY`,不会抛异常。
1313
+
1314
+ Cesium 由宿主通过 `window.Cesium` 提供。启用 `terrainEnabled` 后,图层会采样当前 `viewer.terrainProvider`,让轨迹高度随地形变化;如果当前使用椭球地形,采样高度为 0。
1315
+
1316
+ ### 基础用法
1317
+
1318
+ ```js
1319
+ const response = await fetch('/mock/uv.json')
1320
+ const windData = await response.json()
1321
+
1322
+ const result = await mapCore.upsertGpuWindLayer({
1323
+ layerId: 'surface-gpu-wind',
1324
+ windData,
1325
+ maxParticles: 10000,
1326
+ particleHeight: 0,
1327
+ terrainEnabled: false,
1328
+ fadeOpacity: 0.901,
1329
+ dropRate: 0.001,
1330
+ dropRateBump: 0.002,
1331
+ speedFactor: 0.15,
1332
+ lineWidth: 4.23,
1333
+ color: '#ffffff',
1334
+ particleOpacity: 1,
1335
+ })
1336
+
1337
+ if (!result.success) {
1338
+ console.warn(result.code, result.message)
1339
+ }
1340
+ ```
1341
+
1342
+ 创建和更新是异步方法,可能等待地形采样,建议始终使用 `await`。
1343
+
1344
+ ### 数据格式
1345
+
1346
+ 可直接使用 BaseGIS 现有 Canvas 风场的 `Bound / DataAry` 数据:
1347
+
1348
+ ```js
1349
+ const windData = {
1350
+ // 经度最小值、纬度最小值、经向格点数、纬向格点数、
1351
+ // 经度跨度、纬度跨度、数值缩放倍数。
1352
+ Bound: [100, 10, 181, 91, 80, 40, 10],
1353
+ // 每个格点按 U、V 交错;纬度行从南向北排列。
1354
+ DataAry: [u0, v0, u1, v1],
1355
+ }
1356
+ ```
1357
+
1358
+ 也可以传独立 U/V 分量:
1359
+
1360
+ ```js
1361
+ const windData = {
1362
+ width: 181,
1363
+ height: 91,
1364
+ bounds: {
1365
+ west: 100,
1366
+ east: 180,
1367
+ south: 10,
1368
+ north: 50,
1369
+ },
1370
+ u: new Float32Array(181 * 91),
1371
+ v: new Float32Array(181 * 91),
1372
+ // index-gpu.vue 解码结果从北向南排列,因此使用 north-to-south。
1373
+ rowOrder: 'north-to-south',
1374
+ }
1375
+ ```
1376
+
1377
+ 也可以不改 `windData`,在 `upsertGpuWindLayer` 参数中传 `flipY: true`。这与 `rowOrder: 'north-to-south'` 等价。
1378
+
1379
+ ### 更新、显隐和移除
1380
+
1381
+ ```js
1382
+ await mapCore.updateGpuWindLayer({
1383
+ layerId: 'surface-gpu-wind',
1384
+ maxParticles: 40000,
1385
+ speedFactor: 0.2,
1386
+ lineWidth: 3,
1387
+ terrainEnabled: true,
1388
+ particleHeight: 20,
1389
+ })
1390
+
1391
+ mapCore.hideGpuWindLayer({ layerId: 'surface-gpu-wind' })
1392
+ mapCore.showGpuWindLayer({ layerId: 'surface-gpu-wind' })
1393
+ mapCore.flyToGpuWindLayer({ layerId: 'surface-gpu-wind', duration: 0.8 })
1394
+
1395
+ const stateResult = mapCore.getGpuWindLayerState({
1396
+ layerId: 'surface-gpu-wind',
1397
+ })
1398
+ console.log(stateResult.data)
1399
+
1400
+ mapCore.removeGpuWindLayer({ layerId: 'surface-gpu-wind' })
1401
+ ```
1402
+
1403
+ `updateGpuWindLayer` 只传参数时复用现有数据;传入新的 `windData` 或 `data` 时会重建 GPU 图层。
1404
+
1405
+ ### 方法总表
1406
+
1407
+ | 方法 | 参数 | 说明 |
1408
+ | --- | --- | --- |
1409
+ | `upsertGpuWindLayer(payload)` | 数据和完整配置 | 创建或替换同 `layerId` 的 GPU 风场 |
1410
+ | `updateGpuWindLayer(payload)` | `{ layerId, ...partialOptions }` | 更新参数;传新数据时重建 |
1411
+ | `showGpuWindLayer(payload)` | `{ layerId }` | 显示图层 |
1412
+ | `hideGpuWindLayer(payload)` | `{ layerId }` | 隐藏图层 |
1413
+ | `removeGpuWindLayer(payload)` | `{ layerId }` | 删除图层并释放 GPU 资源 |
1414
+ | `flyToGpuWindLayer(payload)` | `{ layerId, duration? }` | 飞到风场数据范围 |
1415
+ | `getGpuWindLayerState(payload)` | `{ layerId }` | 获取粒子、范围、地形采样和显隐状态 |
1416
+
1417
+ ### 参数总表
1418
+
1419
+ | 参数 | 是否必填 | 默认值 | 说明 |
1420
+ | --- | --- | --- | --- |
1421
+ | `layerId` / `gpuWindLayerId` / `id` | 建议必填 | `gpu-wind-default` | 图层 id |
1422
+ | `windData` / `data` | 创建时必填 | - | 上述任一种风场数据 |
1423
+ | `maxParticles` | 选填 | `10000` | 请求粒子数;实际值向上取整为整数平方 |
1424
+ | `particleHeight` | 选填 | `0` | 离地高度,单位米 |
1425
+ | `terrainEnabled` | 选填 | `false` | 是否贴合 Cesium 地形 |
1426
+ | `fadeOpacity` | 选填 | `0.901` | 拖尾保留系数,范围 0~1 |
1427
+ | `dropRate` | 选填 | `0.001` | 基础粒子丢弃率 |
1428
+ | `dropRateBump` | 选填 | `0.002` | 随风速增加的附加丢弃率 |
1429
+ | `speedFactor` | 选填 | `0.15` | 移动速度倍率 |
1430
+ | `lineWidth` | 选填 | `4.23` | 轨迹线宽 |
1431
+ | `color` | 选填 | `#ffffff` | 粒子轨迹颜色 |
1432
+ | `particleOpacity` | 选填 | `1` | 粒子轨迹透明度,范围 0~1 |
1433
+ | `visible` | 选填 | `true` | 初始是否显示 |
1434
+ | `terrainMaxSamples` | 选填 | `4096` | 单次地形采样点上限,允许 4~16384 |
1435
+ | `terrainSampleLevel` | 选填 | 自动计算 | 固定地形采样层级,允许 0~15 |
1436
+ | `flipY` | 选填 | `false` | 独立 U/V 数据为北到南行序时传 `true` |
1437
+
1438
+ GPU 专用 API 不属于跨引擎自动恢复图层。切换 Cesium/Leaflet 后,业务应重新调用 `upsertGpuWindLayer`;需要自动恢复时改用 `upsertWindLayer`。核心渲染流程改编自 [RaymanNg/3D-Wind-Field](https://github.com/RaymanNg/3D-Wind-Field),遵循源码目录内 `LICENSE-RaymanNg.txt` 的 MIT License。
1439
+
1144
1440
  ## 三维体渲染
1145
1441
 
1146
1442
  三维体渲染是 Cesium 专有能力,直接通过 `BaseGIS` 调用。Leaflet 调用时会返回不支持结果,不会抛出破坏页面的异常。
@@ -1449,6 +1745,8 @@ if (result.success) {
1449
1745
  | `lon / lat / hpa` | hover 取值必填 | Cesium 拾取得到的经度、纬度、气压高度。 |
1450
1746
  | `showZLayer` | hover 取值选填 | Z 切片是否参与 hover 命中,默认参与。 |
1451
1747
 
1748
+ <a id="point-large-layer-controller"></a>
1749
+
1452
1750
  ## PointLargeLayerController
1453
1751
 
1454
1752
  用于海量点位渲染。它会尽量把传入的有效点位全部渲染出来,适合站点、设备、告警、监测点等需要保留全部点位并支持点击、高亮、删除、显隐的场景。
@@ -1811,6 +2109,8 @@ console.log(state)
1811
2109
  ```
1812
2110
 
1813
2111
 
2112
+ <a id="point-density-controller"></a>
2113
+
1814
2114
  ## PointDensityController
1815
2115
 
1816
2116
  用于点位密度抽稀。地图移动、缩放后会按当前视野和屏幕网格重新计算可见点,适合站点、设备、告警、空气质量监测点等高密度点位。
@@ -2170,6 +2470,8 @@ console.log(state)
2170
2470
  // }
2171
2471
  ```
2172
2472
 
2473
+ <a id="contour-layer-controller"></a>
2474
+
2173
2475
  ## ContourLayerController
2174
2476
 
2175
2477
  用于等值线图层,支持加载、更新、显隐、清空和销毁。
@@ -2216,7 +2518,7 @@ contour.clear()
2216
2518
  contour.destroy()
2217
2519
  ```
2218
2520
 
2219
- 数据别名支持 `contours / isolines / isoline / lines / data / items`。开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的位置;地图平移、缩放、容器尺寸变化或切换引擎时只重排标签,不重新生成等值线。
2521
+ 数据别名支持 `contours / isolines / isoline / lines / data / items`。开启 `showLabel` 后,Cesium 和 Leaflet 都会根据当前屏幕范围重新选择可见线段上的位置。Leaflet 传入经度跨度达到 360° 的 `worldCopyArea` 后,还会按当前视野动态更新等值线路径和标签的世界副本;这个过程只重建可见路径,不重新解析栅格或追踪等值线。`RasterContourController` 会自动把栅格范围作为 `worldCopyArea` 传入。
2220
2522
 
2221
2523
  Cesium 在默认椭球地表上使用 `PolylineCollection` 批量落图;真正启用 DEM 地形后才使用 `GroundPolylinePrimitive`。新批次就绪后再替换旧批次,不为每条线创建独立 Entity。标签定位可通过 `labelLineThinStep` 抽样投影点,不会改变实际绘制的等值线。
2222
2524
 
@@ -2244,6 +2546,8 @@ Leaflet 默认复用高精度 SVG renderer,并使用 `smoothFactor: 0.5` 做
2244
2546
 
2245
2547
  `getState()` 的 `labelCount` 是当前视野实际显示的标签数量。Cesium 状态额外包含 `lineRenderer/lineBuildElapsed/labelLayoutElapsed/primitiveReadyElapsed`,用于区分线构建、标签排版和异步就绪耗时。`onStateChange(state)` 会在视野重排后回传新状态。页面存在侧栏等遮挡时,可通过 `labelViewportPadding` 排除对应区域。
2246
2548
 
2549
+ <a id="raster-contour-controller"></a>
2550
+
2247
2551
  ## RasterContourController
2248
2552
 
2249
2553
  用于从 GeoTIFF、灰度图或数值网格直接计算并绘制等值线。内部统一使用 `d3-contour` 追踪等值线,输出仍交给 `ContourLayerController`,所以页面不需要区分 Cesium 和 Leaflet;调用 `BaseGIS.setEngine()` 时,已经生成的等值线会随托管图层自动恢复。
@@ -2393,69 +2697,45 @@ await rasterContour.loadGrid({
2393
2697
  | `getState()` | 返回尺寸、值域、级别、线数、当前标注数、投影、耗时和底层 contour 状态。 |
2394
2698
  | `getRasterData()` | 返回当前标准栅格,包含 `values/width/height/area`。 |
2395
2699
 
2396
- [//]: # ()
2397
- [//]: # (## PressureContourLayer)
2700
+ <a id="pressure-contour-layer"></a>
2398
2701
 
2399
- [//]: # ()
2400
- [//]: # (用于等压线和高低压中心标注。`buildPressureRenderData&#40;&#41;` 可把 Windy 风格的 `press.json` 数据整理成渲染结构,`createPressureDataResolver&#40;&#41;` 提供简单缓存。)
2702
+ ## PressureContourLayer
2401
2703
 
2402
- [//]: # ()
2403
- [//]: # (```js)
2704
+ 用于等压线、线值标签和高低压中心标注。`buildPressureRenderData()` 可把 Windy 风格的气压数据整理成 `{ isolines, centers }`,`createPressureDataResolver()` 在此基础上提供按数据版本缓存的解析函数。
2404
2705
 
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;)
2706
+ ```js
2707
+ import {
2708
+ PressureContourLayer,
2709
+ buildPressureRenderData,
2710
+ createPressureDataResolver,
2711
+ } from '@3clear/basegis/methods'
2451
2712
 
2452
- [//]: # (await pressureLayer.update&#40;resolvePressureData&#40;'next'&#41;&#41;)
2713
+ // 单份原始数据可以直接转换。
2714
+ const renderData = buildPressureRenderData(pressJson)
2453
2715
 
2454
- [//]: # (pressureLayer.hide&#40;&#41;)
2716
+ // 多个时次或版本可用解析器按 key 缓存转换结果。
2717
+ const resolvePressureData = createPressureDataResolver({
2718
+ default: pressJson,
2719
+ next: pressJson2,
2720
+ })
2455
2721
 
2456
- [//]: # (pressureLayer.destroy&#40;&#41;)
2722
+ const pressureLayer = new PressureContourLayer({
2723
+ mapCore,
2724
+ layerId: 'pressure',
2725
+ color: 'rgba(255,255,255,0.82)',
2726
+ width: 1.15,
2727
+ smoothFactor: 0.2,
2728
+ // Cesium 用它判断标签碰撞和屏幕范围,Leaflet 可复用同一容器 id。
2729
+ mapContainerId: 'map',
2730
+ isAnnotationVisible: () => true,
2731
+ })
2457
2732
 
2458
- [//]: # (```)
2733
+ pressureLayer.mount()
2734
+ await pressureLayer.load(renderData)
2735
+ await pressureLayer.update(resolvePressureData('next'))
2736
+ pressureLayer.hide()
2737
+ pressureLayer.destroy()
2738
+ ```
2459
2739
 
2460
2740
  说明:
2461
2741
 
@@ -2463,6 +2743,8 @@ await rasterContour.loadGrid({
2463
2743
  - Leaflet 使用 `divIcon` 绘制 H/L 中心和线值标签。
2464
2744
  - Cesium 使用独立标注集合,地图变化时会调度刷新。
2465
2745
 
2746
+ <a id="wind-field-methods"></a>
2747
+
2466
2748
  ## WindFieldMethods
2467
2749
 
2468
2750
  用于叠加 Canvas 风场粒子动画。它通过 `BaseGIS` 获取容器、投影、视野范围和视图变化事件,不直接依赖某个引擎。
@@ -2711,6 +2993,34 @@ console.log(result.data?.restore)
2711
2993
  | 三维切片 / 剖面渲染 | 支持 | 不支持 |
2712
2994
  | DEM 地形 | 支持 | 不支持 |
2713
2995
 
2996
+ ## 第三方许可
2997
+
2998
+ GPU 粒子风场核心改编自 RaymanNg/3D-Wind-Field:
2999
+
3000
+ ```text
3001
+ MIT License
3002
+
3003
+ Copyright (c) 2019 RaymanNg
3004
+
3005
+ Permission is hereby granted, free of charge, to any person obtaining a copy
3006
+ of this software and associated documentation files (the "Software"), to deal
3007
+ in the Software without restriction, including without limitation the rights
3008
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3009
+ copies of the Software, and to permit persons to whom the Software is
3010
+ furnished to do so, subject to the following conditions:
3011
+
3012
+ The above copyright notice and this permission notice shall be included in all
3013
+ copies or substantial portions of the Software.
3014
+
3015
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
3016
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
3017
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
3018
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
3019
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3020
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3021
+ SOFTWARE.
3022
+ ```
3023
+
2714
3024
  ## 本地开发与打包
2715
3025
 
2716
3026
  在仓库根目录执行:
@@ -0,0 +1,96 @@
1
+ const u = {
2
+ OK: "OK",
3
+ FAILED: "FAILED",
4
+ NOT_INITIALIZED: "NOT_INITIALIZED",
5
+ INVALID_CONTAINER: "INVALID_CONTAINER",
6
+ ENGINE_NOT_REGISTERED: "ENGINE_NOT_REGISTERED",
7
+ ENGINE_RESOURCE_MISSING: "ENGINE_RESOURCE_MISSING",
8
+ METHOD_NOT_FOUND: "METHOD_NOT_FOUND",
9
+ UNSUPPORTED_CAPABILITY: "UNSUPPORTED_CAPABILITY",
10
+ INVALID_DEM: "INVALID_DEM"
11
+ };
12
+ function _({
13
+ success: i = !0,
14
+ message: t = "",
15
+ data: s = null,
16
+ code: n = u.OK
17
+ } = {}) {
18
+ return { success: i, message: t, data: s, code: n };
19
+ }
20
+ function C(i = null, t = "OK", s = u.OK) {
21
+ return _({ success: !0, message: t, data: i, code: s });
22
+ }
23
+ function g(i = "Operation failed.", t = u.FAILED, s = null) {
24
+ return _({ success: !1, message: i, data: s, code: t });
25
+ }
26
+ function T(i, t, s = "") {
27
+ const n = `${t} adapter does not support "${i}". ${s}`.trim();
28
+ return console.warn(`[3clear-dt-base] ${n}`), g(n, u.UNSUPPORTED_CAPABILITY, {
29
+ methodName: i,
30
+ engineType: t,
31
+ detail: s
32
+ });
33
+ }
34
+ class x {
35
+ constructor(t = {}) {
36
+ this.rawData = t, this.bound = Array.isArray(t.Bound) ? t.Bound : [], this.data = Array.isArray(t.DataAry) ? t.DataAry : [], this.valid = !1, this.errorMessage = "", this.lonMin = 0, this.latMin = 0, this.lonMax = 0, this.latMax = 0, this.lonCount = 0, this.latCount = 0, this.lonSpan = 0, this.latSpan = 0, this.lonStep = 0, this.latStep = 0, this.valueScale = 1, this.parse();
37
+ }
38
+ parse() {
39
+ if (this.bound.length < 6) {
40
+ this.errorMessage = "Wind field Bound must contain at least 6 values.";
41
+ return;
42
+ }
43
+ const [t, s, n, o, e, a, l = 1] = this.bound, r = Number(n) * Number(o) * 2;
44
+ if (!Number.isFinite(Number(t)) || !Number.isFinite(Number(s)) || Number(n) < 2 || Number(o) < 2 || Number(e) <= 0 || Number(a) <= 0 || this.data.length !== r) {
45
+ this.errorMessage = `Invalid wind field data. Expected DataAry length ${r}, got ${this.data.length}.`;
46
+ return;
47
+ }
48
+ this.lonMin = Number(t), this.latMin = Number(s), this.lonCount = Number(n), this.latCount = Number(o), this.lonSpan = Number(e), this.latSpan = Number(a), this.valueScale = Number(l) || 1, this.lonMax = this.lonMin + this.lonSpan, this.latMax = this.latMin + this.latSpan, this.lonStep = this.lonSpan / (this.lonCount - 1), this.latStep = this.latSpan / (this.latCount - 1), this.valid = !0;
49
+ }
50
+ contains(t, s) {
51
+ return Number.isFinite(t) && Number.isFinite(s) && t >= this.lonMin && t <= this.lonMax && s >= this.latMin && s <= this.latMax;
52
+ }
53
+ getBounds() {
54
+ return {
55
+ west: this.lonMin,
56
+ south: this.latMin,
57
+ east: this.lonMax,
58
+ north: this.latMax
59
+ };
60
+ }
61
+ getCenter() {
62
+ return {
63
+ lon: this.lonMin + this.lonSpan / 2,
64
+ lat: this.latMin + this.latSpan / 2
65
+ };
66
+ }
67
+ getVector(t, s) {
68
+ const n = (t * this.lonCount + s) * 2;
69
+ return {
70
+ u: Number(this.data[n]) / this.valueScale,
71
+ v: Number(this.data[n + 1]) / this.valueScale
72
+ };
73
+ }
74
+ /**
75
+ * 双线性插值。
76
+ *
77
+ * 数据约定为从左下角开始平铺,因此 row 直接对应从南到北的纬度索引。
78
+ */
79
+ sample(t, s) {
80
+ if (!this.valid || !this.contains(t, s))
81
+ return null;
82
+ const n = (t - this.lonMin) / this.lonStep, o = (s - this.latMin) / this.latStep, e = Math.max(0, Math.min(Math.floor(n), this.lonCount - 2)), a = Math.max(0, Math.min(Math.floor(o), this.latCount - 2)), l = e + 1, r = a + 1, h = Math.max(0, Math.min(n - e, 1)), c = Math.max(0, Math.min(o - a, 1)), I = this.getVector(a, e), S = this.getVector(a, l), E = this.getVector(r, e), m = this.getVector(r, l), d = 1 - h, p = 1 - c, b = d * p, A = h * p, O = d * c, D = h * c, N = I.u * b + S.u * A + E.u * O + m.u * D, M = I.v * b + S.v * A + E.v * O + m.v * D;
83
+ return !Number.isFinite(N) || !Number.isFinite(M) ? null : {
84
+ u: N,
85
+ v: M,
86
+ speed: Math.hypot(N, M)
87
+ };
88
+ }
89
+ }
90
+ export {
91
+ u as R,
92
+ x as W,
93
+ g as f,
94
+ C as s,
95
+ T as u
96
+ };