cmap-core 0.0.8 → 0.2.0

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 ADDED
@@ -0,0 +1,568 @@
1
+ # cmap-core
2
+
3
+ 基于 [Mapbox GL JS v3](https://docs.mapbox.com/mapbox-gl-js/) 封装的 WebGIS SDK,提供天地图底图、船舶渲染、轨迹绘制、标绘工具等核心能力。
4
+
5
+ ---
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pnpm add cmap-core
11
+ ```
12
+
13
+ 引入样式(必须):
14
+
15
+ ```ts
16
+ import 'cmap-core/dist/cmap-core.css'
17
+ ```
18
+
19
+ ---
20
+
21
+ ## CMap — 地图主模块
22
+
23
+ ### 初始化
24
+
25
+ ```ts
26
+ import { CMap } from 'cmap-core'
27
+
28
+ const cmap = new CMap({
29
+ container: 'map', // DOM 元素 id 或 HTMLElement
30
+ center: [120.38, 36.07], // [经度, 纬度]
31
+ zoom: 10,
32
+ type: CMap.LAND, // 底图类型,默认 LAND
33
+ TDTToken: 'your-token', // 天地图 Token,不传使用内置默认值
34
+ http2: true, // 是否使用 HTTPS,默认 true
35
+ })
36
+
37
+ await cmap.mapLoaded()
38
+ ```
39
+
40
+ > `ICMapOptions` 继承自 Mapbox GL 的 `MapOptions`,所有原生选项均可透传。如果传入自定义 `style`,则 `type`/`TDTToken`/`http2` 不会生效。
41
+
42
+ ### 静态常量
43
+
44
+ ```ts
45
+ CMap.LAND // MapType.LAND = 'land'
46
+ CMap.SATELLITE // MapType.SATELLITE = 'satellite'
47
+ ```
48
+
49
+ ### 方法
50
+
51
+ | 方法 | 说明 |
52
+ |------|------|
53
+ | `mapLoaded(): Promise<Map>` | 等待地图加载完成,resolve 后可安全操作地图 |
54
+ | `getMap(): Map` | 获取原生 Mapbox `Map` 实例 |
55
+ | `change(type: MapType): void` | 切换底图(陆地 / 卫星),**自动保留**已有自定义 source 和 layer |
56
+ | `zoomIn(): void` | 放大一级(正在缩放时跳过) |
57
+ | `zoomOut(): void` | 缩小一级(正在缩放时跳过) |
58
+
59
+ ### 事件
60
+
61
+ ```ts
62
+ // 地图加载完成
63
+ cmap.on('loaded', (map: Map) => { })
64
+
65
+ // 地图销毁前(可拦截)
66
+ cmap.getMap().on('beforeRemove', ({ cancel, next }) => {
67
+ cancel() // 阻止销毁
68
+ next() // 放行销毁
69
+ })
70
+ ```
71
+
72
+ ### 切换底图
73
+
74
+ `change` 方法会在切换底图前备份所有自定义 source / layer(包括 GeoJSON 运行时数据),在新底图加载完成后自动还原。
75
+
76
+ ```ts
77
+ cmap.change(CMap.SATELLITE)
78
+ cmap.change(CMap.LAND)
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Ship — 船舶模块
84
+
85
+ ### 初始化
86
+
87
+ ```ts
88
+ import { Ship, AisShip } from 'cmap-core'
89
+
90
+ const ship = new Ship(cmap.getMap(), {
91
+ plugins: [AisShip], // 注册船舶类型插件
92
+ })
93
+ ```
94
+
95
+ ### 方法
96
+
97
+ | 方法 | 说明 |
98
+ |------|------|
99
+ | `add(data)` | 添加单条船舶,返回 `BaseShip` 实例 |
100
+ | `load(list)` | 批量加载,**会清空已有数据**,返回 `BaseShip[]` |
101
+ | `get(id)` | 获取指定船舶实例 |
102
+ | `remove(id)` | 移除指定船舶 |
103
+ | `removeAll()` | 移除全部船舶 |
104
+ | `focus(id)` | 聚焦高亮指定船舶 |
105
+ | `unfocus(id)` | 取消聚焦 |
106
+ | `select(id)` | 选中指定船舶 |
107
+ | `unselect(id)` | 取消选中 |
108
+ | `render()` | 手动触发渲染 |
109
+
110
+ ### 数据结构
111
+
112
+ ```ts
113
+ interface IBaseShipOptions {
114
+ id: string | number // 唯一标识(如 MMSI)
115
+ name: string // 船名
116
+ position: LngLat // [经度, 纬度]
117
+ direction: number // 方向角(度)
118
+ speed: number // 速度(节)
119
+ hdg: number // 船首向
120
+ cog: number // 对地航向
121
+ rot: number // 转向速率
122
+ type: string // 插件名称(对应 static NAME)
123
+ time: Date // 数据时间戳
124
+ tooltip?: boolean // 是否显示提示框,默认 true
125
+ width?: number // 船宽(米)
126
+ height?: number // 船长(米)
127
+ icon?: string // 自定义图标名称
128
+ props?: Record<string, any>
129
+ }
130
+ ```
131
+
132
+ ### AisShip 插件
133
+
134
+ 内置插件,根据最后更新时间自动判断状态:
135
+
136
+ | 状态 | 说明 |
137
+ |------|------|
138
+ | `ONLINE` | 在线 |
139
+ | `DELAY` | 延迟 |
140
+ | `OFFLINE` | 离线 |
141
+
142
+ ### 自定义船舶插件
143
+
144
+ ```ts
145
+ import { BaseShip } from 'cmap-core'
146
+
147
+ class MyShip extends BaseShip<IMyShipOptions> {
148
+ static NAME = 'MyShip'
149
+
150
+ getIconName() {
151
+ return 'my-icon-name'
152
+ }
153
+ }
154
+
155
+ const ship = new Ship(map, { plugins: [MyShip] })
156
+ ship.add({ type: 'MyShip', ... })
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Track — 轨迹模块
162
+
163
+ ### 初始化
164
+
165
+ ```ts
166
+ import { Track } from 'cmap-core'
167
+
168
+ const track = new Track(cmap.getMap(), {
169
+ startLabel: '起', // 起点标签文本,可选
170
+ endLabel: '终', // 终点标签文本,可选
171
+ })
172
+ ```
173
+
174
+ ### 方法
175
+
176
+ | 方法 | 说明 |
177
+ |------|------|
178
+ | `load(items)` | 加载轨迹数据 |
179
+ | `remove()` | 清空轨迹 |
180
+ | `render()` | 渲染 |
181
+ | `getFeature()` | 获取所有 GeoJSON Feature |
182
+
183
+ ### 数据结构
184
+
185
+ ```ts
186
+ interface TrackItem {
187
+ id: string // 轨迹�� ID
188
+ pId: string // 所属轨迹 ID(通常为船舶 ID)
189
+ index: number // 点索引(决定顺序)
190
+ position: LngLat // [经度, 纬度]
191
+ cog?: number // 航向角
192
+ sog?: number // 地速(节)
193
+ time: Date // 时间戳
194
+ props?: Record<string, any>
195
+ }
196
+ ```
197
+
198
+ ### 自动特征标记
199
+
200
+ | 类型 | 触发条件 |
201
+ |------|---------|
202
+ | 起终点 | 第一个和最后一个点 |
203
+ | 急转弯 | 航向变化 > 25° |
204
+ | 启停变化 | 速度在 0 与非 0 之间切换 |
205
+ | 时间锚点 | 相邻两点间隔 > 30 分钟 |
206
+
207
+ ---
208
+
209
+ ## Plot — 标绘模块
210
+
211
+ 所有标绘类型继承自 `Poi`,具备统一的生命周期接口。
212
+
213
+ ### 通用方法
214
+
215
+ | 方法 | 说明 |
216
+ |------|------|
217
+ | `render()` | 渲染到地图 |
218
+ | `start()` | 开始绘制 |
219
+ | `stop()` | 停止绘制 |
220
+ | `edit()` | 进入编辑模式 |
221
+ | `unedit()` | 退出编辑模式 |
222
+ | `show()` | 显示 |
223
+ | `hide()` | 隐藏 |
224
+ | `focus()` | 聚焦高亮 |
225
+ | `unfocus()` | 取消高亮 |
226
+ | `move(position)` | 移动 |
227
+ | `update(options)` | 更新配置 |
228
+ | `remove()` | 移除 |
229
+ | `getFeature()` | 获取 GeoJSON Feature |
230
+ | `setState(states)` | 设置内部状态 |
231
+ | `getState()` | 获取内部状态 |
232
+
233
+ ### 通用事件
234
+
235
+ ```ts
236
+ poi.on('create', (feature) => { }) // 创建完成
237
+ poi.on('update.done', (feature) => { }) // 更新完成
238
+ poi.on('click', (e) => { }) // 点击
239
+ poi.on('hover', (e) => { }) // 悬停
240
+ poi.on('unhover', (e) => { }) // 离开
241
+ ```
242
+
243
+ ---
244
+
245
+ ### Point — 圆点
246
+
247
+ ```ts
248
+ import { Point } from 'cmap-core'
249
+
250
+ const point = new Point(cmap.getMap(), {
251
+ id: 'point-1',
252
+ name: '标记点',
253
+ visibility: 'visible',
254
+ position: [120.38, 36.07],
255
+ isName: true,
256
+ style: {
257
+ 'circle-radius': 8,
258
+ 'circle-color': '#3B82F6',
259
+ 'circle-stroke-color': '#ffffff',
260
+ 'circle-stroke-width': 2,
261
+ },
262
+ })
263
+
264
+ point.render()
265
+ ```
266
+
267
+ **样式属性**
268
+
269
+ | 属性 | 类型 | 说明 |
270
+ |------|------|------|
271
+ | `circle-radius` | `number` | 半径(像素) |
272
+ | `circle-color` | `string` | 填充颜色 |
273
+ | `circle-stroke-color` | `string` | 描边颜色 |
274
+ | `circle-stroke-width` | `number` | 描边宽度 |
275
+
276
+ ---
277
+
278
+ ### Line — 折线
279
+
280
+ ```ts
281
+ import { Line } from 'cmap-core'
282
+
283
+ const line = new Line(cmap.getMap(), {
284
+ id: 'line-1',
285
+ name: '航线',
286
+ visibility: 'visible',
287
+ position: [[120.38, 36.07], [120.45, 36.12], [120.52, 36.08]],
288
+ style: {
289
+ 'line-color': '#EF4444',
290
+ 'line-width': 2,
291
+ },
292
+ vertexStyle: { // 编辑时顶点样式
293
+ 'circle-radius': 5,
294
+ 'circle-color': '#fff',
295
+ },
296
+ midStyle: { // 编辑时中点样式
297
+ 'circle-radius': 3,
298
+ 'circle-color': '#3B82F6',
299
+ },
300
+ })
301
+
302
+ line.render()
303
+ ```
304
+
305
+ **特有方法**
306
+
307
+ | 方法 | 说明 |
308
+ |------|------|
309
+ | `insertPoint(index, position)` | 在指定索引处插入顶点 |
310
+ | `updatePoint(index, position)` | 更新指定顶点坐标 |
311
+ | `getPoint(index)` | 获取指定顶点 |
312
+ | `getMidPoint(index)` | 获取指定中点 |
313
+
314
+ ---
315
+
316
+ ### Fill — 多边形
317
+
318
+ ```ts
319
+ import { Fill } from 'cmap-core'
320
+
321
+ const fill = new Fill(cmap.getMap(), {
322
+ id: 'fill-1',
323
+ name: '区域',
324
+ visibility: 'visible',
325
+ position: [[[120.38, 36.07], [120.45, 36.07], [120.45, 36.12], [120.38, 36.07]]],
326
+ style: {
327
+ 'fill-color': '#3B82F6',
328
+ 'fill-opacity': 0.3,
329
+ },
330
+ outLineStyle: {
331
+ 'line-color': '#3B82F6',
332
+ 'line-width': 2,
333
+ },
334
+ })
335
+
336
+ fill.render()
337
+ ```
338
+
339
+ ---
340
+
341
+ ### Circle — 圆形
342
+
343
+ ```ts
344
+ import { Circle } from 'cmap-core'
345
+
346
+ const circle = new Circle(cmap.getMap(), {
347
+ id: 'circle-1',
348
+ name: '范围圈',
349
+ visibility: 'visible',
350
+ position: [120.38, 36.07],
351
+ radius: 1000,
352
+ units: 'meters', // 'meters' | 'kilometers' | 'miles'
353
+ style: {
354
+ 'fill-color': '#F59E0B',
355
+ 'fill-opacity': 0.2,
356
+ },
357
+ })
358
+
359
+ circle.render()
360
+ ```
361
+
362
+ ---
363
+
364
+ ### ArrowLine — 箭头线
365
+
366
+ 继承自 `Line`,顶点以箭头图标表示,箭头方向自动计算。
367
+
368
+ ```ts
369
+ import { ArrowLine } from 'cmap-core'
370
+
371
+ const arrowLine = new ArrowLine(cmap.getMap(), {
372
+ id: 'arrow-1',
373
+ name: '流向',
374
+ visibility: 'visible',
375
+ position: [[120.38, 36.07], [120.45, 36.12]],
376
+ style: { 'line-color': '#10B981', 'line-width': 2 },
377
+ })
378
+
379
+ arrowLine.render()
380
+ ```
381
+
382
+ ---
383
+
384
+ ### IconPoint — 图标点
385
+
386
+ ```ts
387
+ import { IconPoint } from 'cmap-core'
388
+
389
+ const iconPoint = new IconPoint(cmap.getMap(), {
390
+ id: 'icon-1',
391
+ name: '港口',
392
+ visibility: 'visible',
393
+ position: [120.38, 36.07],
394
+ style: {
395
+ 'icon-size': 1,
396
+ 'icon-rotate': 0,
397
+ 'text-color': '#1F2937',
398
+ 'text-size': 12,
399
+ 'text-offset': [0, 1.5],
400
+ },
401
+ })
402
+
403
+ iconPoint.render()
404
+ ```
405
+
406
+ ---
407
+
408
+ ### IndexPoint / IndexLine — 序号标注
409
+
410
+ 用于带序号的点和线,适合标注泊位、航线节点等有序信息。
411
+
412
+ ```ts
413
+ import { IndexPoint, IndexLine } from 'cmap-core'
414
+
415
+ const p = new IndexPoint(cmap.getMap(), {
416
+ id: 'idx-1',
417
+ visibility: 'visible',
418
+ position: [120.38, 36.07],
419
+ index: 1,
420
+ })
421
+ p.render()
422
+ ```
423
+
424
+ ---
425
+
426
+ ## IconManager — 图标管理
427
+
428
+ `CMap` 实例内置 `icon` 属性,也可独立使用。
429
+
430
+ ```ts
431
+ import { IconManager } from 'cmap-core'
432
+
433
+ const iconManager = new IconManager(cmap.getMap())
434
+
435
+ // 加载 URL 图片
436
+ await iconManager.load([
437
+ { name: 'port', url: 'https://example.com/port.png' }
438
+ ])
439
+
440
+ // 加载 SVG
441
+ await iconManager.loadSvg([
442
+ { name: 'ship-online', svg: '<svg>...</svg>' }
443
+ ])
444
+
445
+ iconManager.has('port') // true
446
+ iconManager.getImage('port') // HTMLImageElement | undefined
447
+ iconManager.delete('port') // 从缓存中移除
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Tooltip — 提示框
453
+
454
+ ```ts
455
+ import { Tooltip } from 'cmap-core'
456
+
457
+ const tooltip = new Tooltip(cmap.getMap(), {
458
+ id: 'tip-1',
459
+ position: [120.38, 36.07],
460
+ element: document.getElementById('popup')!,
461
+ anchor: 'bottom', // 9 种锚点方向
462
+ offsetX: 0,
463
+ offsetY: -10,
464
+ line: true, // 是否绘制连接线
465
+ visible: true,
466
+ })
467
+
468
+ tooltip.render()
469
+ tooltip.show()
470
+ tooltip.hide()
471
+ tooltip.update({ position: [120.39, 36.08] })
472
+ tooltip.remove()
473
+ ```
474
+
475
+ **锚点方向**:`center` `top` `bottom` `left` `right` `top-left` `top-right` `bottom-left` `bottom-right`
476
+
477
+ ---
478
+
479
+ ## Collision — 碰撞检测
480
+
481
+ 基于 [RBush](https://github.com/mourner/rbush) 空间索引,用于检测 Tooltip 等元素是否重叠。
482
+
483
+ ```ts
484
+ import { Collision, CollisionItem } from 'cmap-core'
485
+
486
+ const collision = new Collision()
487
+
488
+ collision.load([
489
+ { id: 'a', bbox: [x1, y1, x2, y2] },
490
+ { id: 'b', bbox: [x1, y1, x2, y2] },
491
+ ])
492
+
493
+ const collided = collision.collides() // 返回发生碰撞的元素
494
+ collision.clear()
495
+ ```
496
+
497
+ ---
498
+
499
+ ## 完整示例
500
+
501
+ ```ts
502
+ import { CMap, Ship, AisShip, Track, Fill } from 'cmap-core'
503
+ import 'cmap-core/dist/cmap-core.css'
504
+
505
+ // 1. 初始化地图
506
+ const cmap = new CMap({
507
+ container: 'map',
508
+ center: [120.38, 36.07],
509
+ zoom: 10,
510
+ type: CMap.LAND,
511
+ TDTToken: 'your-token',
512
+ })
513
+
514
+ await cmap.mapLoaded()
515
+
516
+ // 2. 加载船舶
517
+ const ship = new Ship(cmap.getMap(), { plugins: [AisShip] })
518
+ ship.load([
519
+ {
520
+ id: 1, name: '船舶001', type: 'AisShip',
521
+ position: [120.38, 36.07],
522
+ direction: 45, speed: 10, hdg: 45, cog: 45, rot: 0,
523
+ time: new Date(), tooltip: true,
524
+ },
525
+ ])
526
+
527
+ // 3. 绘制轨迹
528
+ const track = new Track(cmap.getMap(), {})
529
+ track.load([
530
+ { id: 't1', pId: 'track-a', index: 0, position: [120.38, 36.07], time: new Date('2024-01-01 08:00') },
531
+ { id: 't2', pId: 'track-a', index: 1, position: [120.45, 36.12], time: new Date('2024-01-01 09:00') },
532
+ ])
533
+ track.render()
534
+
535
+ // 4. 绘制区域
536
+ const fill = new Fill(cmap.getMap(), {
537
+ id: 'zone-1',
538
+ name: '监控区域',
539
+ visibility: 'visible',
540
+ position: [[[120.36, 36.05], [120.46, 36.05], [120.46, 36.15], [120.36, 36.05]]],
541
+ style: { 'fill-color': '#3B82F6', 'fill-opacity': 0.2 },
542
+ })
543
+ fill.render()
544
+
545
+ // 5. 切换底图(自动保留 zone-1 等自定义图层)
546
+ cmap.change(CMap.SATELLITE)
547
+ ```
548
+
549
+ ---
550
+
551
+ ## 类型参考
552
+
553
+ ```ts
554
+ import type {
555
+ ICMapOptions,
556
+ MapType,
557
+ IBaseShipOptions,
558
+ TrackItem,
559
+ IPoiOptions,
560
+ IPointOptions,
561
+ ILineOptions,
562
+ IFillOptions,
563
+ ICircleOptions,
564
+ IArrowLineOptions,
565
+ IIconPointOptions,
566
+ PlotVisibility,
567
+ } from 'cmap-core'
568
+ ```