smap-leaflet 0.0.0-stage → 2.6.1
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/CHANGELOG.md +199 -0
- package/LICENSE +15 -0
- package/README.md +663 -2
- package/dist/index.d.ts +1240 -0
- package/dist/measure-BV-2_Fc1.cjs +2 -0
- package/dist/measure-BV-2_Fc1.cjs.map +1 -0
- package/dist/measure-DJVSnHDg.js +10 -0
- package/dist/measure-DJVSnHDg.js.map +1 -0
- package/dist/smap-leaflet.cjs +2 -0
- package/dist/smap-leaflet.cjs.map +1 -0
- package/dist/smap-leaflet.mjs +2381 -0
- package/dist/smap-leaflet.mjs.map +1 -0
- package/dist/style.css +1 -0
- package/llms.txt +557 -0
- package/package.json +74 -4
package/README.md
CHANGED
|
@@ -1,3 +1,664 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">smap-leaflet</h1>
|
|
2
|
+
<p align="center">
|
|
3
|
+
<strong>基于 Leaflet.js 二次封装的地图库</strong><br>
|
|
4
|
+
TypeScript 编写 · 支持国内主流底图 · 坐标纠偏 · 测量工具 · 天地图服务 · WFS/WMS 图层
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/smap-leaflet"><img src="https://img.shields.io/npm/v/smap-leaflet" alt="npm" /></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/leaflet-%3E%3D1.8.0-green" alt="leaflet" />
|
|
10
|
+
<img src="https://img.shields.io/badge/typescript-5-blue" alt="typescript" />
|
|
11
|
+
<img src="https://img.shields.io/badge/build-vite%205-purple" alt="vite" />
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="https://robeshell.github.io/leaflet-smap/"><strong>在线示例</strong></a> ·
|
|
16
|
+
<a href="https://github.com/robeshell/leaflet-smap">GitHub</a> ·
|
|
17
|
+
<a href="https://github.com/robeshell/leaflet-smap/blob/main/packages/core/CHANGELOG.md">更新日志</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 特性
|
|
23
|
+
|
|
24
|
+
- **多底图支持** — 天地图、高德、谷歌、百度、腾讯、Geoq、OSM、Mapbox,也可以注册自己的瓦片服务
|
|
25
|
+
- **坐标纠偏** — 自动处理 WGS-84 / GCJ-02 / BD-09 偏移
|
|
26
|
+
- **测量工具** — 测距、测面、测圆、测矩形
|
|
27
|
+
- **绘制与编辑** — 画点、线、面、矩形、圆,编辑后导出 / 载入 GeoJSON
|
|
28
|
+
- **天地图服务** — POI 搜索、地理编码、逆地理编码、行政区划查询、IP 定位
|
|
29
|
+
- **图层管理** — 按 id 登记图层,WFS / WMS、区域遮罩、海量点、点聚合
|
|
30
|
+
- **控件与交互** — 水印、缩放、比例尺、底图切换、图层开关、右键菜单、浏览器定位
|
|
31
|
+
- **导出** — 把当前地图导出为图片
|
|
32
|
+
- **多实例** — 支持同页面多个地图实例
|
|
33
|
+
- **TypeScript** — 完整类型定义
|
|
34
|
+
- **Tree-shakable** — ESM + CJS 双格式输出
|
|
35
|
+
|
|
36
|
+
## 安装
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install smap-leaflet leaflet
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> `leaflet` 为 peerDependency,需要手动安装
|
|
43
|
+
|
|
44
|
+
## 快速上手
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<div id="map" style="width: 100vw; height: 100vh;"></div>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { SMap } from 'smap-leaflet'
|
|
52
|
+
import 'smap-leaflet/style.css'
|
|
53
|
+
|
|
54
|
+
const smap = new SMap('#map', {
|
|
55
|
+
tileType: 'GaoDe.Normal.Map',
|
|
56
|
+
center: [31.82, 117.23],
|
|
57
|
+
zoom: 12,
|
|
58
|
+
minZoom: 6,
|
|
59
|
+
maxZoom: 18,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// 获取底层 Leaflet 实例
|
|
63
|
+
const map = smap.instance
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
### `new SMap(container, options)`
|
|
69
|
+
|
|
70
|
+
创建地图实例。
|
|
71
|
+
|
|
72
|
+
| 参数 | 类型 | 说明 |
|
|
73
|
+
| :---------- | :---------------------- | :------------------------------------ |
|
|
74
|
+
| `container` | `string \| HTMLElement` | 容器(`'#map'`、`'map'` 或 DOM 元素) |
|
|
75
|
+
| `options` | `SMapOptions` | 地图配置 |
|
|
76
|
+
|
|
77
|
+
#### SMapOptions
|
|
78
|
+
|
|
79
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
80
|
+
| :------------- | :------------------- | :----- | :-------------------------------- |
|
|
81
|
+
| `tileType` | `string \| string[]` | — | 底图类型,如 `'GaoDe.Normal.Map'` |
|
|
82
|
+
| `center` | `[number, number]` | — | 中心坐标 `[lat, lng]` |
|
|
83
|
+
| `zoom` | `number` | `12` | 初始缩放 |
|
|
84
|
+
| `minZoom` | `number` | — | 最小缩放 |
|
|
85
|
+
| `maxZoom` | `number` | — | 最大缩放 |
|
|
86
|
+
| `tdtKey` | `string` | — | 天地图密钥,仅用于 `TianDiTu.*` |
|
|
87
|
+
| `mapboxToken` | `string` | — | Mapbox 令牌,仅用于 `Mapbox.*` |
|
|
88
|
+
| `preferCanvas` | `boolean` | `true` | 是否使用 Canvas 渲染 |
|
|
89
|
+
|
|
90
|
+
> 支持传入所有 [Leaflet MapOptions](https://leafletjs.cn/reference.html#map-option)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### 底图
|
|
95
|
+
|
|
96
|
+
#### `smap.setTileLayer(type)`
|
|
97
|
+
|
|
98
|
+
替换当前底图。
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
smap.setTileLayer('TianDiTu.Normal.Map')
|
|
102
|
+
|
|
103
|
+
// 多图层(底图 + 标注)
|
|
104
|
+
smap.setTileLayer(['TianDiTu.Satellite.Map', 'TianDiTu.Satellite.Annotion'])
|
|
105
|
+
|
|
106
|
+
smap.getTileType() // 当前底图的类型数组
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
类型不存在时会抛错,并保留当前底图不变。
|
|
110
|
+
|
|
111
|
+
#### `smap.addTileLayer(type)`
|
|
112
|
+
|
|
113
|
+
在现有底图上叠加图层(不移除已有图层)。
|
|
114
|
+
|
|
115
|
+
#### `smap.clearTileLayers()`
|
|
116
|
+
|
|
117
|
+
移除所有底图。
|
|
118
|
+
|
|
119
|
+
#### `smap.getTileLayers()`
|
|
120
|
+
|
|
121
|
+
获取当前底图图层数组。
|
|
122
|
+
|
|
123
|
+
#### 支持的底图类型
|
|
124
|
+
|
|
125
|
+
| 提供商 | 类型 |
|
|
126
|
+
| :--------- | :------------------------------------------------------------------------------------------------------ |
|
|
127
|
+
| **天地图** | `TianDiTu.Normal.Map/Annotion` · `TianDiTu.Satellite.Map/Annotion` · `TianDiTu.Terrain.Map/Annotion` |
|
|
128
|
+
| **高德** | `GaoDe.Normal.Map` · `GaoDe.Satellite.Map/Annotion` · `GaoDe.Brief.Map` |
|
|
129
|
+
| **谷歌** | `Google.Normal.Map` · `Google.Satellite.Map/Annotion` |
|
|
130
|
+
| **百度** | `Baidu.Normal.Map` · `Baidu.Satellite.Map/Annotion` · `BaiduV3.Normal.Map`(需设置 `crs: L.CRS.Baidu`) |
|
|
131
|
+
| **Geoq** | `Geoq.Normal.Map/PurplishBlue/Gray/Warm` · `Geoq.Theme.Hydro` |
|
|
132
|
+
| **腾讯** | `Tencent.Normal.Map` · `Tencent.Satellite.Map` · `Tencent.Terrain.Map` |
|
|
133
|
+
| **OSM** | `OSM.Normal.Map` |
|
|
134
|
+
| **Mapbox** | `Mapbox.Satellite.Map`(需 `mapboxToken`) |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 测量工具
|
|
139
|
+
|
|
140
|
+
`getMeasure` 返回 Promise:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// 每种类型共用一个工具,首次调用时创建
|
|
144
|
+
const measure = await smap.getMeasure('polyline', { showMarker: true })
|
|
145
|
+
|
|
146
|
+
measure.enable() // 开始一次测量;画完自动结束,再量需再次 enable()
|
|
147
|
+
measure.disable() // 取消正在进行的绘制(已完成的图形保留)
|
|
148
|
+
measure.measuring // 是否正在绘制
|
|
149
|
+
measure.clearAll() // 清除该工具的全部结果
|
|
150
|
+
measure.destroy() // 销毁
|
|
151
|
+
|
|
152
|
+
smap.clearMeasures() // 销毁所有测量工具
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| 类型 | 说明 |
|
|
156
|
+
| :------------ | :----- |
|
|
157
|
+
| `'polyline'` | 测距 |
|
|
158
|
+
| `'polygon'` | 测面 |
|
|
159
|
+
| `'circle'` | 测圆 |
|
|
160
|
+
| `'rectangle'` | 测矩形 |
|
|
161
|
+
|
|
162
|
+
`smap.createMeasure(type, options)` 每次都会新建一个独立的工具,适合需要多套不同样式的场景;一般情况用 `getMeasure` 即可。
|
|
163
|
+
|
|
164
|
+
#### MeasureOptions
|
|
165
|
+
|
|
166
|
+
| 属性 | 类型 | 说明 |
|
|
167
|
+
| :------------- | :-------------------------------- | :------------------------------------- |
|
|
168
|
+
| `showMarker` | `boolean` | 是否在地图上显示结果标签 |
|
|
169
|
+
| `shapeOptions` | `L.PathOptions` | 图形样式;顶点和标签的颜色跟随 `color` |
|
|
170
|
+
| `icon` | `L.DivIcon` | 顶点图标 |
|
|
171
|
+
| `formatLabel` | `(label: MeasureLabel) => string` | 自定义标签文字 |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
await smap.getMeasure('polyline', {
|
|
175
|
+
showMarker: true,
|
|
176
|
+
// label: { type, distance?, area?, radius?, center?, latlngs, partial }
|
|
177
|
+
// partial 为 true 表示测距过程中各节点上的累计值,false 表示最终结果
|
|
178
|
+
formatLabel: (label) =>
|
|
179
|
+
label.partial ? `${label.distance.toFixed(0)} m` : `总长 ${label.distance.toFixed(0)} m`,
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
单位和编辑:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// 默认标签的单位:长度 'auto' | 'm' | 'km',面积 'auto' | 'm2' | 'km2' | 'ha'(公顷)| 'mu'(亩)
|
|
187
|
+
const area = await smap.getMeasure('polygon', { showMarker: true, areaUnit: 'mu' })
|
|
188
|
+
area.setOptions({ areaUnit: 'ha' }) // 之后再改,地图上已有的标签会跟着换
|
|
189
|
+
|
|
190
|
+
// 调整已经量好的图形,数值和标签随拖动实时变化
|
|
191
|
+
area.startEdit()
|
|
192
|
+
area.finishEdit() // 保存;finishEdit(false) 放弃
|
|
193
|
+
area.undo() // 撤销上一步
|
|
194
|
+
area.on('updating', (result) => {}) // 拖动过程中持续触发
|
|
195
|
+
area.on('updated', (result) => {}) // 保存后触发;地图上对应 measure:updating / measure:updated
|
|
196
|
+
|
|
197
|
+
// 测量也可以吸附到之前量过的图形(以及 layers 指定的图层)
|
|
198
|
+
await smap.getMeasure('polyline', { showMarker: true, snap: true })
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> 旧的 `valueFormatter` 选项仍然可用但已弃用:它在不同工具下收到的参数含义不一样(测距是距离、测圆是半径、测面是顶点数组)。
|
|
202
|
+
|
|
203
|
+
> 同一地图上可以同时存在多个测量工具,各自只响应自己发起的绘制。
|
|
204
|
+
|
|
205
|
+
#### 获取测量结果
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
measure.on('created', (result) => {
|
|
209
|
+
result.type // 'polyline' | 'polygon' | 'circle' | 'rectangle'
|
|
210
|
+
result.distance // 总长度(米),测距
|
|
211
|
+
result.area // 面积(平方米),测面 / 测矩形 / 测圆
|
|
212
|
+
result.perimeter // 周长(米),测面 / 测矩形 / 测圆
|
|
213
|
+
result.radius // 半径(米),测圆
|
|
214
|
+
result.latlngs // 顶点;测圆时为圆心
|
|
215
|
+
result.layer // 画出来的 Leaflet 图层
|
|
216
|
+
})
|
|
217
|
+
measure.on('removed', ({ id }) => {})
|
|
218
|
+
measure.on('start', ({ type }) => {})
|
|
219
|
+
measure.on('stop', ({ type, completed }) => {}) // completed 为 false 表示被取消(含按 Esc)
|
|
220
|
+
|
|
221
|
+
measure.getResults() // 当前还在地图上的全部结果
|
|
222
|
+
measure.remove(result.id) // 删除某一次测量
|
|
223
|
+
|
|
224
|
+
// 不关心是哪个工具时,直接在地图上监听
|
|
225
|
+
smap.on('measure:created', (result) => {})
|
|
226
|
+
smap.on('measure:stop', ({ type, completed }) => {})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### 绘制与编辑
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const draw = await smap.createDraw({ shapeOptions: { color: '#0e7490' } })
|
|
235
|
+
|
|
236
|
+
draw.draw('polygon') // 'marker' | 'polyline' | 'polygon' | 'rectangle' | 'circle',画完自动结束
|
|
237
|
+
draw.cancel() // 放弃正在画的图形
|
|
238
|
+
|
|
239
|
+
draw.startEdit() // 进入编辑:拖动顶点、移动标记、调整圆的大小
|
|
240
|
+
draw.undo() // 撤销上一步(也可以按 Ctrl/⌘ + Z);draw.canUndo 表示有没有可撤销的
|
|
241
|
+
draw.finishEdit() // 保存修改;finishEdit(false) 放弃修改
|
|
242
|
+
|
|
243
|
+
draw.toGeoJSON() // 导出全部图形(WGS-84)
|
|
244
|
+
draw.load(geojson) // 载入 GeoJSON 供显示和编辑;load(geojson, { replace: false }) 为追加
|
|
245
|
+
draw.remove(layer) // 删除一个图形
|
|
246
|
+
draw.clear() // 清空
|
|
247
|
+
|
|
248
|
+
draw.on('created', ({ layer, type }) => {})
|
|
249
|
+
draw.on('change', (featureCollection) => {}) // 画完、保存编辑、删除、载入后都会触发
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
圆在 GeoJSON 里表示为带 `properties.radius`(米)的点,`load` 时会还原成圆。其他事件:`drawstart`、`drawstop`、`edited`、`removed`。
|
|
253
|
+
|
|
254
|
+
#### 操作方式
|
|
255
|
+
|
|
256
|
+
| 形状 | 绘制 |
|
|
257
|
+
| :-------- | :-------------------------------------------------------- |
|
|
258
|
+
| 点 | 单击放置 |
|
|
259
|
+
| 线 | 单击逐个添加节点;双击、再次单击最后一个点或按 Enter 结束 |
|
|
260
|
+
| 面 | 单击逐个添加顶点;单击第一个点、双击或按 Enter 闭合 |
|
|
261
|
+
| 矩形 / 圆 | 按住拖动后松开;也可以单击两次(触屏用这种方式) |
|
|
262
|
+
|
|
263
|
+
绘制中按 Backspace 撤销上一个点,按 Esc 取消。测量工具的操作方式相同。
|
|
264
|
+
|
|
265
|
+
编辑模式下:拖动顶点调整形状;拖动边中点的小圆点插入顶点;单击顶点删除;矩形拖四角缩放、拖中心移动;圆拖圆心移动、拖边上的点改半径;标记直接拖动。支持带洞的多边形和多部件图形。
|
|
266
|
+
|
|
267
|
+
开启吸附后,绘制和编辑时光标靠近已有图形的顶点或边会自动贴上去,画相邻地块时边界能对齐:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
await smap.createDraw({ snap: true })
|
|
271
|
+
// 调整距离,或者同时吸附到别的图层
|
|
272
|
+
await smap.createDraw({ snap: { distance: 16, layers: () => [parcelLayer] } })
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
提示文字可以改写:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { sketchText } from 'smap-leaflet'
|
|
279
|
+
|
|
280
|
+
sketchText.polyline.start = 'Click to start the line'
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> 绘制和编辑是库自己实现的,不依赖 leaflet-draw,也不需要全局的 `L`。
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### 图层管理 `smap.layer`
|
|
288
|
+
|
|
289
|
+
#### 按 id 管理
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
smap.layer.add('roads', anyLeafletLayer) // 登记并加到地图上;同 id 会替换旧图层
|
|
293
|
+
smap.layer.add('hidden', layer, { visible: false }) // 只登记,先不显示
|
|
294
|
+
|
|
295
|
+
smap.layer.toggle('roads') // 切换显示,返回切换后的状态
|
|
296
|
+
smap.layer.show('roads')
|
|
297
|
+
smap.layer.hide('roads') // 从地图上拿下来,但保留登记
|
|
298
|
+
smap.layer.remove('roads') // 移除并取消登记
|
|
299
|
+
smap.layer.get('roads')
|
|
300
|
+
smap.layer.ids()
|
|
301
|
+
smap.layer.clear()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`addWFS` / `addWMS` / `addMask` / `addPoints` 的选项里传 `id` 即可自动登记。`smap.destroy()` 会清掉所有登记过的图层。
|
|
305
|
+
|
|
306
|
+
#### 区域遮罩
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
// 压暗指定区域以外的部分;接受 Polygon / MultiPolygon 的 Feature、FeatureCollection 或 Geometry
|
|
310
|
+
smap.layer.addMask(geojson, {
|
|
311
|
+
id: 'mask',
|
|
312
|
+
fillColor: '#000',
|
|
313
|
+
fillOpacity: 0.5,
|
|
314
|
+
color: '#3388ff', // 边界线颜色;outline: false 不画边界
|
|
315
|
+
fit: true, // 添加后缩放到该区域(默认)
|
|
316
|
+
restrict: false, // 为 true 时限制地图只能在该区域内拖动,遮罩移除后自动解除
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### 海量点
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
// 所有点画在同一张 canvas 上,适合上万个点。点可以是 [lat, lng] 或带 lat / lng 的对象
|
|
324
|
+
smap.layer.addPoints(devices, {
|
|
325
|
+
id: 'devices',
|
|
326
|
+
radius: 3,
|
|
327
|
+
style: (device) => ({ fillColor: device.online ? 'green' : 'red' }),
|
|
328
|
+
tooltip: (device) => device.name,
|
|
329
|
+
onClick: (device) => console.log(device),
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### WFS
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
// 请求 WFS 并直接加到地图上
|
|
337
|
+
const layer = await smap.layer.addWFS({
|
|
338
|
+
url: 'http://example.com/geoserver/wfs',
|
|
339
|
+
layer: 'workspace:layerName',
|
|
340
|
+
style: { color: '#f00', weight: 2 },
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// 或者分两步:先取 GeoJSON,再自行创建图层
|
|
344
|
+
const data = await smap.layer.loadWFS({
|
|
345
|
+
url: 'http://example.com/geoserver/wfs',
|
|
346
|
+
layer: 'workspace:layerName',
|
|
347
|
+
})
|
|
348
|
+
const geoJsonLayer = smap.layer.createGeoJsonLayer(data, { color: '#f00', weight: 2 })
|
|
349
|
+
geoJsonLayer.addTo(smap.instance)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
> 除 `url` / `layer` / `style` / `outputFormat` 外的字段会作为 WFS 请求参数透传(如 `cql_filter`、`maxFeatures`)
|
|
353
|
+
|
|
354
|
+
#### WMS
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
// 创建并加到地图上
|
|
358
|
+
const wmsLayer = smap.layer.addWMS({
|
|
359
|
+
url: 'http://example.com/geoserver/wms',
|
|
360
|
+
layer: 'workspace:layerName',
|
|
361
|
+
format: 'image/png',
|
|
362
|
+
transparent: true,
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
// 只创建、不添加
|
|
366
|
+
const detached = smap.layer.createWMS({
|
|
367
|
+
url: 'http://example.com/geoserver/wms',
|
|
368
|
+
layer: 'workspace:layerName',
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
> `crs` 默认为 `L.CRS.EPSG4326`
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
#### 点聚合
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
// 点太密时,缩小后合并成带数量的气泡,放大后散开;点击气泡放大到它的范围
|
|
380
|
+
const cluster = smap.layer.addCluster(devices, {
|
|
381
|
+
id: 'devices',
|
|
382
|
+
clusterRadius: 60, // 多近(像素)算挤在一起,默认 60
|
|
383
|
+
disableClusteringAtZoom: 16, // 到这个级别就不再聚合
|
|
384
|
+
style: (d) => ({ fillColor: d.online ? 'green' : 'red' }), // 单个点的样式,同 addPoints
|
|
385
|
+
tooltip: (d) => d.name,
|
|
386
|
+
onClick: (d) => {},
|
|
387
|
+
onClusterClick: (points) => {}, // 可选;不传则点击气泡放大
|
|
388
|
+
})
|
|
389
|
+
cluster.setPoints(newDevices) // 换数据
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
气泡颜色用 CSS 变量 `--smap-cluster` 改。
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
### 天地图服务 `smap.service`
|
|
397
|
+
|
|
398
|
+
需要天地图密钥,[申请地址](https://console.tianditu.gov.cn/api/key)。创建地图时传了 `tdtKey` 就不用再逐次传;单次调用也可以用 `tk` 覆盖。返回值都有类型定义。
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
const smap = new SMap('#map', { tdtKey: 'your-key' /* ... */ })
|
|
402
|
+
|
|
403
|
+
// POI 搜索:范围和级别取地图当前视野
|
|
404
|
+
const { pois } = await smap.service.search({ keyword: '合肥南站', count: 10 })
|
|
405
|
+
pois?.forEach((poi) => {
|
|
406
|
+
const [lng, lat] = poi.lonlat.split(',').map(Number)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// 地理编码:地址 → 坐标(lon / lat 是字符串)
|
|
410
|
+
const { location } = await smap.service.geocode({ keyWord: '安徽省合肥市' })
|
|
411
|
+
|
|
412
|
+
// 逆地理编码:坐标 → 地址
|
|
413
|
+
const { result } = await smap.service.reverseGeocode({ lon: 117.23, lat: 31.82 })
|
|
414
|
+
result?.formatted_address
|
|
415
|
+
|
|
416
|
+
// 行政区划:名称或 9 位区划码;childLevel 取下级,extensions 带边界
|
|
417
|
+
const { data } = await smap.service.getAdministrative({
|
|
418
|
+
keyword: '156340100',
|
|
419
|
+
childLevel: 1,
|
|
420
|
+
})
|
|
421
|
+
data?.district[0].children
|
|
422
|
+
|
|
423
|
+
// IP 定位(无需密钥)
|
|
424
|
+
await smap.service.getLocationByIp()
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
> 行政区划查询使用天地图 v2 接口,区划码是 9 位(国家码 `156` + 6 位行政区划码)。旧的 v1 接口已不可用。
|
|
428
|
+
|
|
429
|
+
脱离地图单独使用:`new ServiceClient({ key: 'your-key' })`,此时 `search` 需要自己传 `level` 和 `mapBound`。
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### 控件管理 `smap.control`
|
|
434
|
+
|
|
435
|
+
```ts
|
|
436
|
+
smap.control.add('attribution', { prefix: '© My App', content: '自定义水印' })
|
|
437
|
+
smap.control.add('zoom', { position: 'topright' })
|
|
438
|
+
smap.control.add('scale', { position: 'bottomleft' })
|
|
439
|
+
|
|
440
|
+
// 底图切换控件,自动高亮当前底图
|
|
441
|
+
smap.control.add('layers', {
|
|
442
|
+
options: [
|
|
443
|
+
{ label: '矢量', tileType: 'GaoDe.Normal.Map' },
|
|
444
|
+
{ label: '影像', tileType: ['GaoDe.Satellite.Map', 'GaoDe.Satellite.Annotion'] },
|
|
445
|
+
],
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
// 业务图层开关:列出 smap.layer 里登记的图层,和代码里的 show / hide 双向同步
|
|
449
|
+
smap.control.add('overlays', {
|
|
450
|
+
title: '图层',
|
|
451
|
+
items: [
|
|
452
|
+
{ id: 'roads', label: '道路' },
|
|
453
|
+
{ id: 'pipes', label: '管线' },
|
|
454
|
+
], // 不传则列出全部已登记的图层
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
smap.control.remove('zoom') // 移除单个
|
|
458
|
+
smap.control.removeAll() // 移除全部
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
> 同一类型重复 `add` 会替换之前的控件
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### 右键菜单 `smap.contextMenu`
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
// 地图的右键菜单;'-' 是分隔线。传 null 取消
|
|
469
|
+
smap.contextMenu.setItems([
|
|
470
|
+
{ text: '在此处居中', onClick: ({ latlng, map }) => map.panTo(latlng) },
|
|
471
|
+
'-',
|
|
472
|
+
{ text: '放大', onClick: ({ map }) => map.zoomIn() },
|
|
473
|
+
])
|
|
474
|
+
|
|
475
|
+
// 也可以传函数,按点击位置动态生成
|
|
476
|
+
smap.contextMenu.setItems(({ latlng }) => [{ text: `纬度 ${latlng.lat.toFixed(4)}` }])
|
|
477
|
+
|
|
478
|
+
// 给某个图层单独的菜单,它不会和地图菜单叠在一起
|
|
479
|
+
const unbind = smap.contextMenu.bind(marker, [
|
|
480
|
+
{ text: '删除', danger: true, onClick: ({ layer }) => layer.remove() },
|
|
481
|
+
])
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
### 定位
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
// 浏览器定位:标出位置和精度圈并移动过去。用户拒绝授权时会 reject
|
|
490
|
+
const { latlng, accuracy } = await smap.locate({ zoom: 16 })
|
|
491
|
+
smap.clearLocation()
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
### 导出图片
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
const blob = await smap.exportImage({ type: 'image/png', scale: 2 })
|
|
500
|
+
await smap.downloadImage('map.png') // 直接保存成文件
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
- 图里有:底图、矢量图形(线、面、圆、点图层),以及库自己的标签:测量标签、聚合气泡、绘制的标记。`labels: false` 可以不画标签。
|
|
504
|
+
- 图里没有:其他 HTML 元素,包括自定义图标的标记、弹窗、悬停提示、控件。结构简单的元素(带背景和一行文字的方块)可以用 `include: ['.my-label']` 指定画进去。
|
|
505
|
+
- 底图服务必须允许跨域读取瓦片,否则会失败。高德、天地图、腾讯、OSM 可以;谷歌不行。
|
|
506
|
+
- 导出前会等底图加载完(最多 `timeout` 毫秒,默认 8000)。
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
### 视图操作
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
smap.setView([31.82, 117.23], 14)
|
|
514
|
+
smap.fitToLayer(someLayer)
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
### 事件
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
smap.on('loaded', (map) => {
|
|
523
|
+
/* 地图加载完成 */
|
|
524
|
+
})
|
|
525
|
+
smap.on('tileLoaded', (layers) => {
|
|
526
|
+
/* 底图加载完成 */
|
|
527
|
+
})
|
|
528
|
+
smap.off('loaded', handler)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
| 事件 | 参数 | 触发时机 |
|
|
532
|
+
| :----------------- | :-------------------- | :-------------------------------------------------- |
|
|
533
|
+
| `loaded` | `L.Map` | 地图创建完成(构造后的下一个微任务) |
|
|
534
|
+
| `tileLoaded` | `L.TileLayer[]` | 构造时带 `tileType`,或调用 `setTileLayer` |
|
|
535
|
+
| `measure:created` | `MeasureResult` | 任意测量工具完成一次测量 |
|
|
536
|
+
| `measure:removed` | `{ id, type }` | 一次测量被删除 |
|
|
537
|
+
| `measure:updating` | `MeasureResult` | 一次测量正在被拖动调整 |
|
|
538
|
+
| `measure:updated` | `MeasureResult` | 一次测量在编辑后保存 |
|
|
539
|
+
| `measure:start` | `{ type }` | 测量工具开始绘制 |
|
|
540
|
+
| `measure:stop` | `{ type, completed }` | 测量工具结束绘制;`completed` 为 `false` 表示被取消 |
|
|
541
|
+
| `measure:start` | `{ type }` | 测量工具开始绘制 |
|
|
542
|
+
| `measure:stop` | `{ type, completed }` | 测量工具结束绘制,`completed` 为 `false` 表示被取消 |
|
|
543
|
+
|
|
544
|
+
> `loaded` 与首次 `tileLoaded` 会延迟到微任务中触发,需在 `new SMap(...)` 之后**同步**注册监听;`await` 之后再注册会错过。
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
### 坐标转换工具
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
import { wgs84ToGcj02, gcj02ToWgs84, gcj02ToBd09, bd09ToGcj02 } from 'smap-leaflet'
|
|
552
|
+
|
|
553
|
+
const { lng, lat } = wgs84ToGcj02(117.23, 31.82)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
| 方法 | 说明 |
|
|
557
|
+
| :----------------------- | :-------------- |
|
|
558
|
+
| `wgs84ToGcj02(lng, lat)` | WGS-84 → GCJ-02 |
|
|
559
|
+
| `gcj02ToWgs84(lng, lat)` | GCJ-02 → WGS-84 |
|
|
560
|
+
| `gcj02ToBd09(lng, lat)` | GCJ-02 → BD-09 |
|
|
561
|
+
| `bd09ToGcj02(lng, lat)` | BD-09 → GCJ-02 |
|
|
562
|
+
| `wgs84ToBd09(lng, lat)` | WGS-84 → BD-09 |
|
|
563
|
+
| `bd09ToWgs84(lng, lat)` | BD-09 → WGS-84 |
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
#### 转换 GeoJSON
|
|
568
|
+
|
|
569
|
+
地图上的数据必须是 WGS-84。第三方给的 GCJ-02 / BD-09 数据先转一遍:
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
import { transformGeoJSON, transformCoord } from 'smap-leaflet'
|
|
573
|
+
|
|
574
|
+
const wgs = transformGeoJSON(gcjData, 'gcj02', 'wgs84') // 返回深拷贝,不改动原数据
|
|
575
|
+
const { lng, lat } = transformCoord(117.23, 31.82, 'bd09', 'wgs84')
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
### 工具函数
|
|
581
|
+
|
|
582
|
+
```ts
|
|
583
|
+
import { getLocation, loadScript, loadStyle } from 'smap-leaflet'
|
|
584
|
+
|
|
585
|
+
// HTML5 定位
|
|
586
|
+
const pos = await getLocation({ enableHighAccuracy: true })
|
|
587
|
+
|
|
588
|
+
// 动态加载外部资源
|
|
589
|
+
await loadScript('https://example.com/lib.js')
|
|
590
|
+
await loadStyle('https://example.com/style.css')
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### 销毁
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
smap.destroy()
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
会自动清理所有测量工具、控件、底图、事件监听和 DOM 观察器。
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## 高级用法
|
|
606
|
+
|
|
607
|
+
### 注册自定义底图
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
import { registerProvider } from 'smap-leaflet'
|
|
611
|
+
|
|
612
|
+
registerProvider('Intranet', {
|
|
613
|
+
layers: {
|
|
614
|
+
Satellite: { Map: 'https://tiles{s}.example.com/{z}/{x}/{y}.png?token={key}' },
|
|
615
|
+
},
|
|
616
|
+
subdomains: ['1', '2', '3'],
|
|
617
|
+
coordType: 'gcj02', // 瓦片的坐标系,非 WGS-84 时会自动纠偏;默认 'wgs84'
|
|
618
|
+
key: 'xxx', // 替换 URL 里的 {key}
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
smap.setTileLayer('Intranet.Satellite.Map')
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
用内置的提供商名注册会覆盖内置定义(例如把天地图换成自己的代理地址)。`getTileTypes()` 返回当前全部可用类型。
|
|
625
|
+
|
|
626
|
+
### 子模块
|
|
627
|
+
|
|
628
|
+
直接使用子模块:
|
|
629
|
+
|
|
630
|
+
```ts
|
|
631
|
+
import { LayerManager, ControlManager, ServiceClient, createChinaTileLayer } from 'smap-leaflet'
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## 配合 Vue 使用
|
|
637
|
+
|
|
638
|
+
Vue 3 项目可以直接用 [`smap-leaflet-vue`](../vue/README.md),它把地图、图层、控件包装成组件,把测量和绘制包装成组合式函数。
|
|
639
|
+
|
|
640
|
+
## 开发
|
|
641
|
+
|
|
642
|
+
本包位于 monorepo 的 `packages/core`,在仓库根目录安装依赖和运行命令,见[根目录 README](../../README.md) 和 [CONTRIBUTING.md](../../CONTRIBUTING.md)。版本变更见 [CHANGELOG.md](CHANGELOG.md)。
|
|
643
|
+
|
|
644
|
+
## 给 AI 助手
|
|
645
|
+
|
|
646
|
+
- [llms.txt](llms.txt):API 速查,随包发布(`node_modules/smap-leaflet/llms.txt`)。在业务项目里让 AI 写地图代码时,把它加进上下文。
|
|
647
|
+
- [AGENTS.md](../../AGENTS.md):在本仓库里改代码的 AI 需要遵守的约定和已知的坑。
|
|
648
|
+
|
|
649
|
+
## 产出
|
|
650
|
+
|
|
651
|
+
| 文件 | 说明 |
|
|
652
|
+
| :---------------------- | :------------------ |
|
|
653
|
+
| `dist/smap-leaflet.mjs` | ESM 格式 |
|
|
654
|
+
| `dist/smap-leaflet.cjs` | CJS 格式 |
|
|
655
|
+
| `dist/style.css` | 样式文件 |
|
|
656
|
+
| `dist/index.d.ts` | TypeScript 类型定义 |
|
|
657
|
+
|
|
658
|
+
## 关于底图服务
|
|
659
|
+
|
|
660
|
+
本库只是把各家地图服务的瓦片地址整理到一起,不提供也不代理任何地图数据。使用某家底图前,请自行确认符合该服务的使用条款,并按要求申请密钥、保留版权标注。
|
|
661
|
+
|
|
662
|
+
## License
|
|
663
|
+
|
|
664
|
+
[ISC](LICENSE)
|