@openlayer-utils/draw-grid 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 loongbao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # 使用手册
2
+
3
+ ## 快速开始
4
+
5
+ ### 1. 安装依赖
6
+
7
+ ```bash
8
+ pnpm add @openlayer-utils/draw-grid
9
+ ```
10
+
11
+ **当前工具不会将ol、vue打包,需要额外安装**
12
+
13
+ ### 2. 创建地图
14
+
15
+ ```javascript
16
+ import OlMap from 'ol/Map.js';
17
+ import View from 'ol/View.js';
18
+ import TileLayer from 'ol/layer/Tile.js';
19
+ import XYZ from 'ol/source/XYZ.js';
20
+
21
+ const map = new OlMap({
22
+ target: 'map',
23
+ layers: [
24
+ new TileLayer({
25
+ source: new XYZ({ url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png' }),
26
+ }),
27
+ ],
28
+ view: new View({ center: [116.404, 39.915], zoom: 12 }),
29
+ });
30
+ ```
31
+
32
+ ### 3. 初始化绘制工具
33
+
34
+ ```javascript
35
+ import { OpenLayerAdapter, DrawTool } from '@openlayer-utils/draw-grid';
36
+
37
+ const adapter = new OpenLayerAdapter(map);
38
+ const tool = new DrawTool(adapter, {
39
+ insertBiz: (feature) => {
40
+ // 业务侧保存数据,返回 true 表示成功
41
+ return saveToDatabase(feature);
42
+ },
43
+ delBiz: (feature) => {
44
+ // 业务侧删除数据,返回 true 表示成功
45
+ return deleteFromDatabase(feature.id);
46
+ },
47
+ });
48
+ ```
49
+
50
+ ### 4. 开始绘制
51
+
52
+ ```javascript
53
+ tool.startDraw({
54
+ type: 'h3', // 网格类型: 'h3' | 'geohash'
55
+ accuracy: 6, // 精度
56
+ drawNeighbor: false, // 是否绘制相邻网格
57
+ lineWidth: 2, // 线条宽度
58
+ primaryColor: '#0000FF', // 颜色
59
+ lineOpacity: 1, // 线条透明度
60
+ fillOpacity: 0.3, // 填充透明度
61
+ enableDel: true, // 允许点击删除
62
+ showLen: true, // 显示边长
63
+ showArea: true, // 显示面积
64
+ debug: false, // 调试模式
65
+ });
66
+ ```
67
+
68
+ ### 5. 绑定地图事件
69
+
70
+ ```javascript
71
+ import { toLonLat } from 'ol/proj.js';
72
+
73
+ map.on('dblclick', (evt) => {
74
+ const coord = map.getCoordinateFromPixel(evt.pixel);
75
+ const lonLat = toLonLat(coord, map.getView().getProjection());
76
+ tool.onDblClick(lonLat); // 双击绘制
77
+ });
78
+
79
+ map.on('singleclick', (evt) => {
80
+ const coord = map.getCoordinateFromPixel(evt.pixel);
81
+ const lonLat = toLonLat(coord, map.getView().getProjection());
82
+ tool.onClick(lonLat); // 单击选择/删除
83
+ });
84
+ ```
85
+
86
+ ### 6. 停止绘制
87
+
88
+ ```javascript
89
+ tool.stopDraw();
90
+ ```
91
+
92
+ ---
93
+
94
+ ## 交互流程
95
+
96
+ ### 绘制流程
97
+
98
+ ```
99
+ 业务方调用 startDraw() → 进入绘制模式(仅允许绘制一次)
100
+
101
+ 用户双击地图 → 触发 onDblClick(坐标)
102
+
103
+ 工具生成要素 → 调用 insertBiz 回调
104
+ ↓ (返回 true)
105
+ 要素添加到 WebGL 图层 → 生成边长/面积标签(如启用)
106
+
107
+ 绘制模式自动结束,如需连续绘制需重新调用 startDraw()
108
+ ```
109
+
110
+ ### 删除流程
111
+
112
+ ```
113
+ startDraw({ enableDel: true }) → 启用删除
114
+
115
+ 用户单击要素 → 触发 onClick(坐标)
116
+
117
+ 工具检测命中要素 → 显示删除图标
118
+
119
+ 用户单击删除图标 → 弹出 Vue3 确认框
120
+
121
+ 确认: delBiz 回调 → 返回 true 则删除要素+标签
122
+ 返回 false 则还原要素
123
+ 取消: 不做任何操作
124
+ ```
125
+
126
+ ---
127
+
128
+ ## API 设计
129
+
130
+ ### DrawTool
131
+
132
+ 核心绘制工具类。
133
+
134
+ ```typescript
135
+ class DrawTool {
136
+ readonly drawEnabled: boolean;
137
+
138
+ constructor(adapter: IMapAdapter, callbacks?: DrawCallbacks);
139
+
140
+ startDraw(options?: DrawOptions): void;
141
+ stopDraw(): void;
142
+ onDblClick(coordinate: [number, number]): void;
143
+ onClick(coordinate: [number, number]): void;
144
+ getFeatures(): FeatureInfo[];
145
+ getOptions(): Readonly<Required<Omit<DrawOptions, 'oldFeatures'>>>;
146
+ }
147
+ ```
148
+
149
+ | 方法 | 说明 |
150
+ |------|------|
151
+ | `startDraw(options)` | 开始绘制模式,创建图层并配置参数。每次调用仅允许一次绘制,绘后自动结束 |
152
+ | `stopDraw()` | 停止绘制,销毁图层及所有要素、标签、弹窗 |
153
+ | `onDblClick([lng, lat])` | 处理双击事件,在点击位置生成网格要素 |
154
+ | `onClick([lng, lat])` | 处理单击事件,检测要素命中并触发删除交互 |
155
+ | `getFeatures()` | 返回当前图层中所有要素的副本 |
156
+ | `drawEnabled` | 只读,当前是否处于可绘制状态 |
157
+
158
+ ### DrawOptions
159
+
160
+ ```typescript
161
+ interface DrawOptions {
162
+ type?: GridType; // 网格类型,默认 'h3'
163
+ accuracy?: number; // 精度,H3:1-15, GeoHash:1-12,默认 6
164
+ drawNeighbor?: boolean; // 是否绘制相邻网格,默认 false
165
+ lineWidth?: number; // 线条宽度(px),默认 2
166
+ primaryColor?: string; // 主题色(CSS颜色),默认 '#0000FF'
167
+ lineOpacity?: number; // 线条透明度 0-1,默认 1
168
+ fillOpacity?: number; // 填充透明度 0-1,默认 0.3
169
+ enableDel?: boolean; // 是否允许删除,默认 false
170
+ showLen?: boolean; // 是否展示边长,默认 false
171
+ showArea?: boolean; // 是否展示面积,默认 false
172
+ debug?: boolean; // 调试模式,控制台打印日志,默认 false
173
+ oldFeatures?: FeatureInfo[]; // 已有要素列表
174
+ }
175
+ ```
176
+
177
+ ### DrawCallbacks
178
+
179
+ ```typescript
180
+ interface DrawCallbacks {
181
+ insertBiz?: (info: FeatureInfo) => boolean;
182
+ delBiz?: (info: FeatureInfo) => boolean;
183
+ }
184
+ ```
185
+
186
+ | 回调 | 说明 |
187
+ |------|------|
188
+ | `insertBiz(feature)` | 新增要素时调用。返回 `true` 要素生效,`false` 取消绘制 |
189
+ | `delBiz(feature)` | 删除要素时调用。返回 `true` 删除生效,`false` 还原要素 |
190
+
191
+ ### FeatureInfo
192
+
193
+ ```typescript
194
+ interface FeatureInfo {
195
+ id: number;
196
+ type: GridType;
197
+ grids: GridInfo[];
198
+ drawNeighbor: boolean;
199
+ accuracy: number;
200
+ primaryColor: string;
201
+ lineWidth: number;
202
+ lineOpacity: number;
203
+ fillOpacity: number;
204
+ }
205
+
206
+ interface GridInfo {
207
+ primary: boolean;
208
+ gridKey: string;
209
+ wkt: string;
210
+ locationType: LocationType;
211
+ }
212
+ ```
213
+
214
+ ### 枚举
215
+
216
+ ```typescript
217
+ enum GridType {
218
+ H3 = 'h3',
219
+ GEOHASH = 'geohash',
220
+ }
221
+
222
+ enum LocationType {
223
+ CENTER = 'CENTER',
224
+ NORTH = 'NORTH',
225
+ SOUTH = 'SOUTH',
226
+ EAST = 'EAST',
227
+ WEST = 'WEST',
228
+ NORTHEAST = 'NORTHEAST',
229
+ NORTHWEST = 'NORTHWEST',
230
+ SOUTHEAST = 'SOUTHEAST',
231
+ SOUTHWEST = 'SOUTHWEST',
232
+ }
233
+ ```
234
+
235
+ ### IMapAdapter(适配器接口)
236
+
237
+ ```typescript
238
+ interface ILayerHandle {
239
+ readonly id: symbol;
240
+ }
241
+
242
+ interface IMapAdapter {
243
+ readonly mapInstance: unknown;
244
+
245
+ createLayer(): ILayerHandle;
246
+ addFeature(layer: ILayerHandle, feature: FeatureInfo): void;
247
+ removeFeature(layer: ILayerHandle, featureId: number): void;
248
+ clearLayer(layer: ILayerHandle): void;
249
+ destroyLayer(layer: ILayerHandle): void;
250
+ findFeatureIdAtCoordinate(layer: ILayerHandle, coordinate: [number, number]): number | null;
251
+ showDeleteWithConfirm(layer: ILayerHandle, feature: FeatureInfo, coordinate: [number, number]): Promise<boolean>;
252
+ hideDeleteIcon(layer: ILayerHandle): void;
253
+ showFeatureLabel(layer: ILayerHandle, featureId: number, text: string, coordinate: [number, number], labelKind?: string, color?: string): void;
254
+ hideFeatureLabel(layer: ILayerHandle, featureId: number, labelKind?: string): void;
255
+ }
256
+ ```
257
+
258
+ ### GridStrategy(网格策略接口)
259
+
260
+ ```typescript
261
+ interface GridStrategy {
262
+ readonly type: GridType;
263
+ latLngToGridKey(lng: number, lat: number, accuracy: number): string;
264
+ getNeighbors(gridKey: string, accuracy: number): NeighborInfo[];
265
+ gridKeyToWKT(gridKey: string): string;
266
+ calculateEdgeLength(gridKey: string, accuracy: number): number;
267
+ calculateArea(gridKey: string, accuracy: number): number;
268
+ formatLength(meters: number): string;
269
+ formatArea(squareMeters: number): string;
270
+ buildFeature(id, lng, lat, accuracy, drawNeighbor, primaryColor, lineWidth, lineOpacity, fillOpacity): FeatureInfo;
271
+ }
272
+ ```
273
+
274
+ 通过工厂函数获取策略实例:
275
+
276
+ ```typescript
277
+ import { getGridStrategy, GridType } from '@openlayer-utils/draw-grid';
278
+ const h3Strategy = getGridStrategy(GridType.H3);
279
+ const key = h3Strategy.latLngToGridKey(116.404, 39.915, 6);
280
+ ```
281
+
282
+ ### OpenLayerAdapter
283
+
284
+ ```typescript
285
+ import { OpenLayerAdapter } from '@openlayer-utils/draw-grid';
286
+
287
+ const adapter = new OpenLayerAdapter(map); // map: ol/Map 实例
288
+ ```
289
+
290
+ 基于 WebGLVectorLayer 的高性能渲染适配器,实现 IMapAdapter 的全部方法。
291
+
292
+ ---
293
+
294
+ ## 整体架构设计
295
+
296
+ ### 分层架构
297
+
298
+ ```
299
+ ┌─────────────────────────────────────────┐
300
+ │ 业务集成方 │
301
+ │ (传入 Map 实例、事件、回调) │
302
+ └──────────────┬──────────────────────────┘
303
+
304
+ ┌──────────────▼──────────────────────────┐
305
+ │ DrawTool (core) │
306
+ │ 核心绘制逻辑、要素管理、去重、ID生成 │
307
+ └──────┬────────────────────┬─────────────┘
308
+ │ │
309
+ ┌──────▼──────┐ ┌────────▼──────────────┐
310
+ │ GridStrategy │ │ IMapAdapter │
311
+ │ (策略模式) │ │ (适配器接口) │
312
+ │ H3 / GeoHash│ │ createLayer() │
313
+ │ 网格计算 │ │ addFeature() │
314
+ │ WKT 生成 │ │ removeFeature() │
315
+ │ 边/面积计算 │ │ showDeleteWithConfirm│
316
+ └──────────────┘ │ ... │
317
+ └────────┬─────────────┘
318
+
319
+ ┌────────▼─────────────┐
320
+ │ OpenLayerAdapter │
321
+ │ WebGLVectorLayer │
322
+ │ OL Overlay / Style │
323
+ └───────────────────────┘
324
+ ```
325
+
326
+ ### 核心模块职责
327
+
328
+ | 模块 | 位置 | 职责 |
329
+ |------|---------------------|------|
330
+ | `DrawTool` | `packages/src/core` | 绘制生命周期管理、要素增删、事件处理 |
331
+ | `GridStrategy` | `packages/src/core` | H3/GeoHash 网格的坐标转换、邻居计算、WKT生成 |
332
+ | `IMapAdapter` | `packages/src/core` | 定义 GIS 框架适配器的接口契约 |
333
+ | `OpenLayerAdapter` | `packages/src` | 实现 IMapAdapter,对接 OpenLayer WebGLVectorLayer |
334
+
335
+ ### 设计模式
336
+
337
+ **Adapter 模式** — 核心层定义 `IMapAdapter` 接口,各 GIS 框架通过实现该接口完成适配。当前支持 OpenLayer,后续可扩展 MapLibre、Cesium 等
338
+
339
+ **Strategy 模式** — `GridStrategy` 抽象 H3 和 GeoHash 的网格计算差异,通过 `GridStrategyFactory` 按类型获取对应策略实例
340
+
341
+ ### 坐标系统透明化
342
+
343
+ 核心工具层(DrawTool / GridStrategy)对坐标系统完全透明:
344
+ - 所有坐标以 `[number, number]` 透传,不做任何投影变换
345
+ - 投影相关处理由适配器层(OpenLayerAdapter)在读写数据时根据地图实例动态完成
346
+
347
+ ---
348
+
349
+ ## 注意事项
350
+
351
+ 1. **多实例**: DrawTool 支持并行实例化,每个实例独立管理图层
352
+ 2. **去重**: 同一类型、同一精度的网格不允许完全重叠绘制
353
+ 3. **ID 生成**: 工具自动生成全局唯一的 Number 类型 ID
354
+ 4. **oldFeatures**: 支持传入已有要素,其 ID 不会被修改
355
+ 5. **删除确认**: 删除确认框由工具负责维护,基于 Vue 3 实现
356
+ 6. **单次绘制**: 每次 startDraw() 只允许一次绘制,绘后自动结束,连续绘制需重复调用