my-openlayer 3.0.1 → 3.1.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/CHANGELOG.md +21 -0
- package/MyOl.d.ts +13 -0
- package/MyOl.js +72 -30
- package/README.md +43 -4
- package/core/geojson/GeoJSONRenderer.d.ts +30 -0
- package/core/geojson/GeoJSONRenderer.js +251 -0
- package/core/geojson/index.d.ts +2 -0
- package/core/geojson/index.js +1 -0
- package/core/line/Line.js +4 -4
- package/core/line/index.d.ts +0 -2
- package/core/line/index.js +0 -1
- package/core/map/ConfigManager.d.ts +0 -42
- package/core/map/ConfigManager.js +1 -13
- package/core/map/MapBaseLayers.js +11 -11
- package/core/map/MapTools.js +15 -15
- package/core/map/MeasureHandler.js +2 -2
- package/core/point/Point.d.ts +17 -14
- package/core/point/Point.js +38 -34
- package/core/polygon/Polygon.js +4 -4
- package/core/select/SelectHandler.js +7 -11
- package/core/vue-template-point/VueTemplatePoint.js +13 -13
- package/docs/.vitepress/config.mts +1 -2
- package/docs/MIGRATION-3.0.md +24 -0
- package/docs/Point.md +64 -9
- package/index.d.ts +4 -3
- package/index.js +3 -1
- package/package.json +1 -1
- package/types/geojson.d.ts +113 -0
- package/types/geojson.js +4 -0
- package/types/index.d.ts +2 -1
- package/types/point.d.ts +13 -0
- package/utils/GeoJSONProcessor.d.ts +77 -0
- package/utils/GeoJSONProcessor.js +299 -0
- package/utils/ValidationUtils.d.ts +0 -4
- package/utils/ValidationUtils.js +16 -15
- package/core/line/RiverLayerManager.d.ts +0 -93
- package/core/line/RiverLayerManager.js +0 -340
- package/docs/RiverLayerManager.md +0 -187
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.1.0] - 2026-06-09
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **GeoJSON:** 新增 `MyOl.addGeoJSON(data, options)` 综合渲染入口,自动识别点、线、面几何类型并返回统一 `GeoJSONRenderHandle`。
|
|
8
|
+
- **GeoJSON:** 支持单个 GeoJSON、数组、Record 多数据集输入,并支持 `layerName` 使用字符串、数组、Record 或回调自定义。
|
|
9
|
+
- **GeoJSON:** 支持 `groupBy` 分组渲染,以及点要素 `styleByProperties` 按 `properties` 返回逐要素样式。
|
|
10
|
+
- **Point:** `addPoint`、`addClusterPoint`、`addPulsePointLayer` 支持直接传入 GeoJSON 点数据。
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **ErrorHandler:** 修复 `MyOpenLayersError` 被外层 `catch` 二次包装的问题,避免错误类型被覆盖和错误回调重复触发。
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- 更新 README、VitePress 文档和 `skills/my-openlayer-helper`,同步 GeoJSON 综合渲染、点 API GeoJSON 输入和统一错误类型说明。
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
|
|
22
|
+
- 新增 GeoJSON 渲染、点 GeoJSON 输入、GeoJSON 处理器和错误处理回归测试。
|
|
23
|
+
|
|
3
24
|
## [3.0.0] - 2026-05-22
|
|
4
25
|
|
|
5
26
|
### 🚀 BREAKING CHANGE: 总览
|
package/MyOl.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { MapBaseLayers, MapTools, EventManager, ConfigManager } from "./core/map
|
|
|
7
7
|
import { SelectHandler } from "./core/select";
|
|
8
8
|
import { ErrorHandler } from './utils/ErrorHandler';
|
|
9
9
|
import { MapInitType } from './types';
|
|
10
|
+
import type { AddGeoJSONInput, AddGeoJSONOptions, GeoJSONRenderHandle } from './types';
|
|
10
11
|
/**
|
|
11
12
|
* MyOl 地图核心类
|
|
12
13
|
* 提供完整的地图操作功能,包括点、线、面要素管理,底图切换,工具操作等
|
|
@@ -101,11 +102,23 @@ export default class MyOl {
|
|
|
101
102
|
* @param latitude 纬度
|
|
102
103
|
* @param zoom 缩放级别
|
|
103
104
|
* @param duration 动画持续时间(毫秒)
|
|
105
|
+
* @param projection
|
|
104
106
|
*/
|
|
105
107
|
locationAction(longitude: number, latitude: number, zoom?: number, duration?: number, projection?: {
|
|
106
108
|
dataProjection?: string;
|
|
107
109
|
featureProjection?: string;
|
|
108
110
|
}): void;
|
|
111
|
+
/**
|
|
112
|
+
* 综合 GeoJSON 渲染方法。
|
|
113
|
+
*
|
|
114
|
+
* 自动识别点/线/面几何类型,按分组创建图层,返回统一句柄。
|
|
115
|
+
* 支持单个 GeoJSON、GeoJSON 数组、或 `{ key: json }` 对象。
|
|
116
|
+
*
|
|
117
|
+
* @param data GeoJSON 输入数据
|
|
118
|
+
* @param options 配置选项(含 layerName、groupBy、各类型样式)
|
|
119
|
+
* @returns GeoJSONRenderHandle 统一句柄
|
|
120
|
+
*/
|
|
121
|
+
addGeoJSON(data: AddGeoJSONInput, options: AddGeoJSONOptions): GeoJSONRenderHandle;
|
|
109
122
|
/**
|
|
110
123
|
* 获取错误处理器实例
|
|
111
124
|
* @returns ErrorHandler 错误处理器
|
package/MyOl.js
CHANGED
|
@@ -11,7 +11,8 @@ import { Line } from "./core/line";
|
|
|
11
11
|
import { MapBaseLayers, MapTools, EventManager, ConfigManager } from "./core/map";
|
|
12
12
|
import { SelectHandler } from "./core/select";
|
|
13
13
|
import { ProjectionManager } from "./core/projection";
|
|
14
|
-
import { ErrorHandler,
|
|
14
|
+
import { ErrorHandler, ErrorType, MyOpenLayersError } from './utils/ErrorHandler';
|
|
15
|
+
import { renderGeoJSON } from './core/geojson';
|
|
15
16
|
/**
|
|
16
17
|
* MyOl 地图核心类
|
|
17
18
|
* 提供完整的地图操作功能,包括点、线、面要素管理,底图切换,工具操作等
|
|
@@ -56,8 +57,11 @@ class MyOl {
|
|
|
56
57
|
this.initializeEventListeners();
|
|
57
58
|
}
|
|
58
59
|
catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
// 已处理过的 MyOpenLayersError 直接抛出,避免二次包装和重复触发回调
|
|
61
|
+
if (error instanceof MyOpenLayersError) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
throw this.errorHandler.createAndHandleError(`地图初始化失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.MAP_ERROR, { id, options });
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
/**
|
|
@@ -66,18 +70,18 @@ class MyOl {
|
|
|
66
70
|
*/
|
|
67
71
|
validateConstructorParams(id, options) {
|
|
68
72
|
if (!id) {
|
|
69
|
-
throw
|
|
73
|
+
throw ErrorHandler.getInstance().createAndHandleError('地图容器 ID 或 HTMLElement 不能为空', ErrorType.VALIDATION_ERROR);
|
|
70
74
|
}
|
|
71
75
|
if (typeof id === 'string' && id.trim() === '') {
|
|
72
|
-
throw
|
|
76
|
+
throw ErrorHandler.getInstance().createAndHandleError('地图容器 ID 必须是非空字符串', ErrorType.VALIDATION_ERROR);
|
|
73
77
|
}
|
|
74
78
|
if (!options || typeof options !== 'object') {
|
|
75
|
-
throw
|
|
79
|
+
throw ErrorHandler.getInstance().createAndHandleError('地图配置选项不能为空', ErrorType.VALIDATION_ERROR);
|
|
76
80
|
}
|
|
77
81
|
// 检查 DOM 元素是否存在
|
|
78
82
|
const element = typeof id === 'string' ? document.getElementById(id) : id;
|
|
79
83
|
if (!element) {
|
|
80
|
-
throw
|
|
84
|
+
throw ErrorHandler.getInstance().createAndHandleError(typeof id === 'string' ? `找不到 ID 为 '${id}' 的 DOM 元素` : '提供的 DOM 元素无效', ErrorType.VALIDATION_ERROR);
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
/**
|
|
@@ -103,7 +107,7 @@ class MyOl {
|
|
|
103
107
|
}, { once: true });
|
|
104
108
|
// 地图错误事件
|
|
105
109
|
eventManager.on('error', (eventData) => {
|
|
106
|
-
this.errorHandler.
|
|
110
|
+
this.errorHandler.createAndHandleError('地图渲染错误', ErrorType.MAP_ERROR, { error: eventData.error });
|
|
107
111
|
});
|
|
108
112
|
}
|
|
109
113
|
/**
|
|
@@ -128,7 +132,11 @@ class MyOl {
|
|
|
128
132
|
return new View(viewOptions);
|
|
129
133
|
}
|
|
130
134
|
catch (error) {
|
|
131
|
-
|
|
135
|
+
// 已处理过的 MyOpenLayersError 直接抛出,避免二次包装和重复触发回调
|
|
136
|
+
if (error instanceof MyOpenLayersError) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
throw ErrorHandler.getInstance().createAndHandleError(`视图创建失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.MAP_ERROR, { options });
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
/**
|
|
@@ -155,8 +163,9 @@ class MyOl {
|
|
|
155
163
|
return this._polygon;
|
|
156
164
|
}
|
|
157
165
|
catch (error) {
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
if (error instanceof MyOpenLayersError)
|
|
167
|
+
throw error;
|
|
168
|
+
throw this.errorHandler.createAndHandleError('面要素模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
160
169
|
}
|
|
161
170
|
}
|
|
162
171
|
/**
|
|
@@ -184,8 +193,9 @@ class MyOl {
|
|
|
184
193
|
return this._baseLayers;
|
|
185
194
|
}
|
|
186
195
|
catch (error) {
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
if (error instanceof MyOpenLayersError)
|
|
197
|
+
throw error;
|
|
198
|
+
throw this.errorHandler.createAndHandleError('基础图层模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
/**
|
|
@@ -201,8 +211,9 @@ class MyOl {
|
|
|
201
211
|
return this._point;
|
|
202
212
|
}
|
|
203
213
|
catch (error) {
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
if (error instanceof MyOpenLayersError)
|
|
215
|
+
throw error;
|
|
216
|
+
throw this.errorHandler.createAndHandleError('点要素模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
206
217
|
}
|
|
207
218
|
}
|
|
208
219
|
/**
|
|
@@ -218,8 +229,9 @@ class MyOl {
|
|
|
218
229
|
return this._line;
|
|
219
230
|
}
|
|
220
231
|
catch (error) {
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
if (error instanceof MyOpenLayersError)
|
|
233
|
+
throw error;
|
|
234
|
+
throw this.errorHandler.createAndHandleError('线要素模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
/**
|
|
@@ -235,8 +247,9 @@ class MyOl {
|
|
|
235
247
|
return this._selectHandler;
|
|
236
248
|
}
|
|
237
249
|
catch (error) {
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
if (error instanceof MyOpenLayersError)
|
|
251
|
+
throw error;
|
|
252
|
+
throw this.errorHandler.createAndHandleError('要素选择模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
240
253
|
}
|
|
241
254
|
}
|
|
242
255
|
/**
|
|
@@ -252,8 +265,9 @@ class MyOl {
|
|
|
252
265
|
return this._mapTools;
|
|
253
266
|
}
|
|
254
267
|
catch (error) {
|
|
255
|
-
|
|
256
|
-
|
|
268
|
+
if (error instanceof MyOpenLayersError)
|
|
269
|
+
throw error;
|
|
270
|
+
throw this.errorHandler.createAndHandleError('工具模块初始化失败', ErrorType.COMPONENT_ERROR, { error });
|
|
257
271
|
}
|
|
258
272
|
}
|
|
259
273
|
// ==========================================
|
|
@@ -266,13 +280,17 @@ class MyOl {
|
|
|
266
280
|
resetPosition(duration = 3000) {
|
|
267
281
|
try {
|
|
268
282
|
if (!this.options.center) {
|
|
269
|
-
|
|
283
|
+
this.errorHandler.createAndHandleError('未设置中心点,无法重置位置', ErrorType.MAP_ERROR);
|
|
284
|
+
return;
|
|
270
285
|
}
|
|
271
286
|
const [longitude, latitude] = this.options.center;
|
|
272
287
|
this.locationAction(longitude, latitude, this.options.zoom, duration);
|
|
273
288
|
}
|
|
274
289
|
catch (error) {
|
|
275
|
-
|
|
290
|
+
if (error instanceof MyOpenLayersError) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
this.errorHandler.createAndHandleError(`重置地图位置失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.MAP_ERROR, { center: this.options.center, duration });
|
|
276
294
|
}
|
|
277
295
|
}
|
|
278
296
|
/**
|
|
@@ -281,23 +299,24 @@ class MyOl {
|
|
|
281
299
|
* @param latitude 纬度
|
|
282
300
|
* @param zoom 缩放级别
|
|
283
301
|
* @param duration 动画持续时间(毫秒)
|
|
302
|
+
* @param projection
|
|
284
303
|
*/
|
|
285
304
|
locationAction(longitude, latitude, zoom = 20, duration = 3000, projection) {
|
|
286
305
|
try {
|
|
287
306
|
// 参数验证
|
|
288
307
|
if (typeof longitude !== 'number' || typeof latitude !== 'number') {
|
|
289
|
-
throw
|
|
308
|
+
throw ErrorHandler.getInstance().createAndHandleError('经纬度必须是数字类型', ErrorType.VALIDATION_ERROR);
|
|
290
309
|
}
|
|
291
310
|
const hasProjection = !!projection?.dataProjection || !!projection?.featureProjection;
|
|
292
311
|
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
|
|
293
|
-
throw
|
|
312
|
+
throw ErrorHandler.getInstance().createAndHandleError('经纬度必须是有效数字', ErrorType.VALIDATION_ERROR);
|
|
294
313
|
}
|
|
295
314
|
if (!hasProjection) {
|
|
296
315
|
if (longitude < -180 || longitude > 180) {
|
|
297
|
-
throw
|
|
316
|
+
throw ErrorHandler.getInstance().createAndHandleError('经度值必须在 -180 到 180 之间', ErrorType.COORDINATE_ERROR);
|
|
298
317
|
}
|
|
299
318
|
if (latitude < -90 || latitude > 90) {
|
|
300
|
-
throw
|
|
319
|
+
throw ErrorHandler.getInstance().createAndHandleError('纬度值必须在 -90 到 90 之间', ErrorType.COORDINATE_ERROR);
|
|
301
320
|
}
|
|
302
321
|
}
|
|
303
322
|
this.getTools().locationAction(longitude, latitude, zoom, duration, projection);
|
|
@@ -310,11 +329,34 @@ class MyOl {
|
|
|
310
329
|
});
|
|
311
330
|
}
|
|
312
331
|
catch (error) {
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
// 已处理过的 MyOpenLayersError 直接抛出,避免二次包装和重复触发回调
|
|
333
|
+
if (error instanceof MyOpenLayersError) {
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
throw this.errorHandler.createAndHandleError(`地图定位失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.MAP_ERROR, { longitude, latitude, zoom, duration });
|
|
315
337
|
}
|
|
316
338
|
}
|
|
317
339
|
// ==========================================
|
|
340
|
+
// 综合 GeoJSON 渲染
|
|
341
|
+
// ==========================================
|
|
342
|
+
/**
|
|
343
|
+
* 综合 GeoJSON 渲染方法。
|
|
344
|
+
*
|
|
345
|
+
* 自动识别点/线/面几何类型,按分组创建图层,返回统一句柄。
|
|
346
|
+
* 支持单个 GeoJSON、GeoJSON 数组、或 `{ key: json }` 对象。
|
|
347
|
+
*
|
|
348
|
+
* @param data GeoJSON 输入数据
|
|
349
|
+
* @param options 配置选项(含 layerName、groupBy、各类型样式)
|
|
350
|
+
* @returns GeoJSONRenderHandle 统一句柄
|
|
351
|
+
*/
|
|
352
|
+
addGeoJSON(data, options) {
|
|
353
|
+
return renderGeoJSON(data, options, {
|
|
354
|
+
getPoint: () => this.getPoint(),
|
|
355
|
+
getLine: () => this.getLine(),
|
|
356
|
+
getPolygon: () => this.getPolygon(),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// ==========================================
|
|
318
360
|
// 管理器访问方法
|
|
319
361
|
// ==========================================
|
|
320
362
|
/**
|
|
@@ -407,7 +449,7 @@ class MyOl {
|
|
|
407
449
|
this.errorHandler.debug('地图实例已销毁', { map: this.map });
|
|
408
450
|
}
|
|
409
451
|
catch (error) {
|
|
410
|
-
this.errorHandler.
|
|
452
|
+
this.errorHandler.createAndHandleError(`销毁地图失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.MAP_ERROR);
|
|
411
453
|
}
|
|
412
454
|
}
|
|
413
455
|
}
|
package/README.md
CHANGED
|
@@ -16,11 +16,13 @@ my-openlayer 是一个基于 [OpenLayers](https://openlayers.org/) 的现代地
|
|
|
16
16
|
|
|
17
17
|
- **统一 LayerHandle** — 真实图层类 `add*` 返回 `{ layer, remove(), setVisible() }` 形态的句柄,跨 Point / Line / Polygon 复用同一套 lifecycle 模型
|
|
18
18
|
- **统一 ControlHandle** — `addDomPoint` / `addVueTemplatePoint` 返回 `{ target, remove(), setVisible() }`,并保留 `anchors` / `getPoints()` 等原有能力
|
|
19
|
+
- **点 API GeoJSON 直接输入** — `addPoint` / `addClusterPoint` / `addPulsePointLayer` 直接接受 `PointData[]`、GeoJSON FeatureCollection、Feature、MultiPoint geometry,内部统一标准化,`*ByUrl` 方法共享同一套解析逻辑
|
|
20
|
+
- **addGeoJSON 综合渲染** — `MyOl.addGeoJSON(data, options)` 自动识别点/线/面几何类型,按分组创建图层,返回统一 `GeoJSONRenderHandle`,支持 `groupBy` 分组、`styleByProperties` 逐要素样式、数组/Record 多数据集输入
|
|
19
21
|
- ***ByUrl 异步化** — `addPointByUrl` / `addLineByUrl` / `addPolygonByUrl` 统一先获取 JSON,再返回完整 Handle
|
|
20
22
|
- **destroy 级联清理** — `MyOl.destroy()` 现在依次调用 `SelectHandler.destroy` / `Line.destroyAllFlowLines` / `Point.destroyAll` / `Polygon.destroyAll`,确保 rAF / Overlay / Vue 实例 / Select interaction 全部释放
|
|
21
23
|
- **ProjectionManager** — 投影逻辑从 MyOl 内部抽取为独立类,支持 `ProjectionManager.register({ code, def })` 在 MyOl 实例之外注册任意 EPSG
|
|
22
24
|
- **ConfigManager.setDefaults** — 运行时修改全局默认配置,所有未提供该字段的后续调用都生效
|
|
23
|
-
-
|
|
25
|
+
- **统一错误类型** — 全部 `throw` 使用 `MyOpenLayersError` 并携带 `ErrorType`(`VALIDATION_ERROR` / `MAP_ERROR` / `LAYER_ERROR` / `COORDINATE_ERROR` / `DATA_ERROR` / `COMPONENT_ERROR`),另有 `LayerNotFoundError` / `InvalidGeoJSONError` / `ProjectionError` 子类,方便 `instanceof` 判别
|
|
24
26
|
- **layerName 必填** — 公开 `add*` 方法签名上 `layerName` 变成必填(编译时强制)
|
|
25
27
|
- **PulsePointIconOptions `src` → `img`** — 统一命名,旧 `src` 标 `@deprecated`
|
|
26
28
|
|
|
@@ -47,7 +49,6 @@ my-openlayer 是一个基于 [OpenLayers](https://openlayers.org/) 的现代地
|
|
|
47
49
|
- **线要素绘制 (Line)**:支持静态线、流光线、流动线图标动画,详见 [Line 动画文档](docs/Line.md#流动线--动态图标线)
|
|
48
50
|
- **面要素 (Polygon)**:面要素绘制、分区高亮、遮罩层
|
|
49
51
|
- **Vue组件支持 (VueTemplatePoint)**:支持将 Vue 组件作为地图点位渲染
|
|
50
|
-
- **河流图层 (RiverLayerManager)**:支持分级显示的河流图层管理
|
|
51
52
|
|
|
52
53
|
- **🛠️ 地图工具**
|
|
53
54
|
- **测量工具 (MeasureHandler)**:距离和面积测量
|
|
@@ -124,6 +125,14 @@ const handle = point.addPoint(
|
|
|
124
125
|
{ layerName: 'example-point', img: 'marker.png' }
|
|
125
126
|
);
|
|
126
127
|
handle?.remove(); // 统一的生命周期管理
|
|
128
|
+
|
|
129
|
+
// 也支持直接传 GeoJSON FeatureCollection
|
|
130
|
+
const geoHandle = point.addPoint(
|
|
131
|
+
{ type: 'FeatureCollection', features: [
|
|
132
|
+
{ type: 'Feature', properties: { name: '杭州' }, geometry: { type: 'Point', coordinates: [120.15, 30.27] } }
|
|
133
|
+
]},
|
|
134
|
+
{ layerName: 'geojson-points', textKey: 'name' }
|
|
135
|
+
);
|
|
127
136
|
```
|
|
128
137
|
|
|
129
138
|
### 3. 添加高性能闪烁点
|
|
@@ -222,6 +231,38 @@ ConfigManager.setDefaults('LINE_OPTIONS', { strokeWidth: 4 });
|
|
|
222
231
|
ConfigManager.resetDefaults('LINE_OPTIONS');
|
|
223
232
|
```
|
|
224
233
|
|
|
234
|
+
### 8. addGeoJSON 综合渲染
|
|
235
|
+
|
|
236
|
+
自动识别点/线/面几何类型,按分组创建图层,返回统一句柄:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// 混合 FeatureCollection — 自动拆分为点、线、面图层
|
|
240
|
+
const handle = map.addGeoJSON(geojsonData, {
|
|
241
|
+
layerName: 'risk',
|
|
242
|
+
groupBy: 'level', // 按 properties.level 分组
|
|
243
|
+
point: { textKey: 'name', textVisible: true },
|
|
244
|
+
line: { strokeColor: '#3b82f6', strokeWidth: 3 },
|
|
245
|
+
polygon: { fillColor: 'rgba(239,68,68,0.15)' }
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// 句柄操作
|
|
249
|
+
handle.setVisible(false); // 隐藏全部
|
|
250
|
+
handle.setGroupVisible('high', false); // 只隐藏 high 组
|
|
251
|
+
handle.removeGroup('low'); // 只移除 low 组
|
|
252
|
+
handle.remove(); // 移除全部
|
|
253
|
+
|
|
254
|
+
// styleByProperties — 按 feature 属性返回不同样式
|
|
255
|
+
map.addGeoJSON(points, {
|
|
256
|
+
layerName: 'styled',
|
|
257
|
+
point: {
|
|
258
|
+
styleByProperties: (props) => ({
|
|
259
|
+
circleColor: props.risk === 'high' ? '#ef4444' : '#22c55e',
|
|
260
|
+
circleRadius: props.risk === 'high' ? 10 : 6
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
225
266
|
## 文档索引
|
|
226
267
|
|
|
227
268
|
详细文档请访问 **[在线文档](https://cuteyuchen.github.io/my-openlayer/)**,交互式 Demo 请访问 **[Demo 站点](https://cuteyuchen.github.io/my-openlayer/demo/)**。
|
|
@@ -236,11 +277,9 @@ ConfigManager.resetDefaults('LINE_OPTIONS');
|
|
|
236
277
|
|
|
237
278
|
### 要素操作
|
|
238
279
|
- **[Point](docs/Point.md)**: 点要素(含聚合、DOM 点位、高性能闪烁点、统一 Handle)。
|
|
239
|
-
- **[Point](docs/Point.md)**: 点要素(含聚合、DOM 点位、高性能闪烁点、统一 Handle)。
|
|
240
280
|
- **[Line](docs/Line.md)**: 线要素(含静态线、流动线、URL 异步加载)。
|
|
241
281
|
- **[Polygon](docs/Polygon.md)**: 面要素(含热力图、图片层、遮罩层、统一 Handle)。
|
|
242
282
|
- **[VueTemplatePoint](docs/VueTemplatePoint.md)**: Vue 组件点位。
|
|
243
|
-
- **[RiverLayerManager](docs/RiverLayerManager.md)**: 河流图层管理。
|
|
244
283
|
|
|
245
284
|
### 交互与工具
|
|
246
285
|
- **[SelectHandler](docs/SelectHandler.md)**: 要素选择交互(支持独立样式渲染、多选隔离)。
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoJSON 渲染模块
|
|
3
|
+
* 负责将混合几何类型的 GeoJSON 数据渲染为点/线/面图层,返回统一句柄。
|
|
4
|
+
* 从 MyOl.addGeoJSON 提取,保持对外 API 不变。
|
|
5
|
+
*/
|
|
6
|
+
import type { Point } from '../point';
|
|
7
|
+
import type { Line } from '../line';
|
|
8
|
+
import type { Polygon } from '../polygon';
|
|
9
|
+
import type { AddGeoJSONInput, AddGeoJSONOptions, GeoJSONRenderHandle } from '../../types';
|
|
10
|
+
/**
|
|
11
|
+
* renderGeoJSON 的依赖注入接口
|
|
12
|
+
* 避免直接依赖 MyOl 实例,便于测试和解耦
|
|
13
|
+
*/
|
|
14
|
+
export interface GeoJSONRenderDeps {
|
|
15
|
+
getPoint(): Point;
|
|
16
|
+
getLine(): Line;
|
|
17
|
+
getPolygon(): Polygon;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 综合 GeoJSON 渲染
|
|
21
|
+
*
|
|
22
|
+
* 自动识别点/线/面几何类型,按分组创建图层,返回统一句柄。
|
|
23
|
+
* 支持单个 GeoJSON、GeoJSON 数组、或 `{ key: json }` 对象。
|
|
24
|
+
*
|
|
25
|
+
* @param data GeoJSON 输入数据
|
|
26
|
+
* @param options 配置选项(含 layerName、groupBy、各类型样式)
|
|
27
|
+
* @param deps 依赖注入(getPoint / getLine / getPolygon)
|
|
28
|
+
* @returns GeoJSONRenderHandle 统一句柄
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderGeoJSON(data: AddGeoJSONInput, options: AddGeoJSONOptions, deps: GeoJSONRenderDeps): GeoJSONRenderHandle;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeoJSON 渲染模块
|
|
3
|
+
* 负责将混合几何类型的 GeoJSON 数据渲染为点/线/面图层,返回统一句柄。
|
|
4
|
+
* 从 MyOl.addGeoJSON 提取,保持对外 API 不变。
|
|
5
|
+
*/
|
|
6
|
+
import { normalizeGeoJSONInputs, splitByGroupAndGeometry, } from '../../utils/GeoJSONProcessor';
|
|
7
|
+
/**
|
|
8
|
+
* 从 feature 中剥离 _datasetKey 元数据
|
|
9
|
+
*/
|
|
10
|
+
function stripDatasetMeta(f) {
|
|
11
|
+
const { _datasetKey, ...rest } = f;
|
|
12
|
+
return rest;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 解析 GeoJSON 图层名称
|
|
16
|
+
*/
|
|
17
|
+
function resolveGeoJSONLayerName(layerName, groupKey, geometryType, datasetKey, index, singleGroup) {
|
|
18
|
+
if (typeof layerName === 'function') {
|
|
19
|
+
return layerName({ datasetKey, groupKey, geometryType, index });
|
|
20
|
+
}
|
|
21
|
+
// 确定 base 名称
|
|
22
|
+
let base;
|
|
23
|
+
if (typeof layerName === 'string') {
|
|
24
|
+
base = layerName;
|
|
25
|
+
}
|
|
26
|
+
else if (Array.isArray(layerName)) {
|
|
27
|
+
base = layerName[Number(datasetKey)] ?? `${layerName[0] ?? 'geojson'}_${datasetKey}`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
base = layerName[datasetKey] ?? datasetKey;
|
|
31
|
+
}
|
|
32
|
+
// 单 group 无 groupBy 时省略 groupKey
|
|
33
|
+
if (singleGroup) {
|
|
34
|
+
return `${base}__${geometryType}`;
|
|
35
|
+
}
|
|
36
|
+
return `${base}__${groupKey}__${geometryType}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 创建点图层 handle(含 styleByProperties 分支)
|
|
40
|
+
*/
|
|
41
|
+
function createPointLayerHandle(pointFeatures, options, groupKey, datasetKey, singleGroup, pointModule) {
|
|
42
|
+
const pointOpts = options.point;
|
|
43
|
+
const name = resolveGeoJSONLayerName(options.layerName, groupKey, 'point', datasetKey, 0, singleGroup);
|
|
44
|
+
if (pointOpts?.styleByProperties) {
|
|
45
|
+
// 通过 feature style resolver 实现逐 feature 样式,保持单图层
|
|
46
|
+
const baseOpts = { ...pointOpts };
|
|
47
|
+
delete baseOpts.styleByProperties;
|
|
48
|
+
const pointIndexKey = Symbol('geojsonPointIndex');
|
|
49
|
+
// 为传入点图层的数据挂临时索引标记,
|
|
50
|
+
// normalizePointData 的 spread 会将标记带入 rawData,
|
|
51
|
+
// resolver 再用它反查无内部字段的真实 GeoJSON Feature。
|
|
52
|
+
const cleanPointFeatures = pointFeatures.map(stripDatasetMeta);
|
|
53
|
+
const taggedPointFeatures = [];
|
|
54
|
+
for (let i = 0; i < pointFeatures.length; i++) {
|
|
55
|
+
const feature = cleanPointFeatures[i];
|
|
56
|
+
const tagged = {
|
|
57
|
+
...feature,
|
|
58
|
+
properties: { ...feature.properties, [pointIndexKey]: i },
|
|
59
|
+
};
|
|
60
|
+
taggedPointFeatures.push(tagged);
|
|
61
|
+
}
|
|
62
|
+
const styleResolver = ((olFeature) => {
|
|
63
|
+
const rawProps = (olFeature.get('rawData') ?? {});
|
|
64
|
+
// 从临时标记读取组内索引,并从 rawData 中移除内部字段
|
|
65
|
+
const rawWithIndex = rawProps;
|
|
66
|
+
const idx = Number.isInteger(rawWithIndex[pointIndexKey]) ? rawWithIndex[pointIndexKey] : 0;
|
|
67
|
+
delete rawWithIndex[pointIndexKey];
|
|
68
|
+
// 使用 stripped feature 的 properties(不含 lgtd/lttd)作为回调第一个参数
|
|
69
|
+
const cleanProps = cleanPointFeatures[idx]?.properties ?? rawProps;
|
|
70
|
+
const override = pointOpts.styleByProperties(cleanProps, {
|
|
71
|
+
datasetKey,
|
|
72
|
+
groupKey,
|
|
73
|
+
// 传入去除了 _datasetKey 元数据的真实 GeoJSON Feature
|
|
74
|
+
feature: cleanPointFeatures[idx] ?? rawProps,
|
|
75
|
+
// 传入该 feature 在点分组内的真实索引
|
|
76
|
+
index: idx,
|
|
77
|
+
});
|
|
78
|
+
// rawProps 仍包含 lgtd/lttd,传给 createPointStyle 用于坐标计算
|
|
79
|
+
return pointModule.createPointStyle(override ? { ...baseOpts, ...override } : baseOpts, rawProps);
|
|
80
|
+
});
|
|
81
|
+
const h = pointModule.addPoint(taggedPointFeatures, { ...baseOpts, layerName: name, style: styleResolver });
|
|
82
|
+
return { handle: h ?? null };
|
|
83
|
+
}
|
|
84
|
+
const h = pointModule.addPoint(pointFeatures.map(stripDatasetMeta), { ...pointOpts, layerName: name });
|
|
85
|
+
return { handle: h ?? null };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 创建线图层 handle
|
|
89
|
+
*/
|
|
90
|
+
function createLineHandle(lineFeatures, options, groupKey, datasetKey, singleGroup, lineModule) {
|
|
91
|
+
const name = resolveGeoJSONLayerName(options.layerName, groupKey, 'line', datasetKey, 0, singleGroup);
|
|
92
|
+
const lineData = { type: 'FeatureCollection', features: lineFeatures.map(stripDatasetMeta) };
|
|
93
|
+
return lineModule.addLine(lineData, {
|
|
94
|
+
...options.line,
|
|
95
|
+
layerName: name,
|
|
96
|
+
dataProjection: options.dataProjection,
|
|
97
|
+
featureProjection: options.featureProjection,
|
|
98
|
+
}) ?? null;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 创建面图层 handle
|
|
102
|
+
*/
|
|
103
|
+
function createPolygonHandle(polygonFeatures, options, groupKey, datasetKey, singleGroup, polygonModule) {
|
|
104
|
+
const name = resolveGeoJSONLayerName(options.layerName, groupKey, 'polygon', datasetKey, 0, singleGroup);
|
|
105
|
+
const polyData = { type: 'FeatureCollection', features: polygonFeatures.map(stripDatasetMeta) };
|
|
106
|
+
return polygonModule.addPolygon(polyData, {
|
|
107
|
+
...options.polygon,
|
|
108
|
+
layerName: name,
|
|
109
|
+
dataProjection: options.dataProjection,
|
|
110
|
+
featureProjection: options.featureProjection,
|
|
111
|
+
fitView: options.fitView,
|
|
112
|
+
}) ?? null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 组装 GeoJSONRenderHandle
|
|
116
|
+
* 通过闭包维护 allHandles / groupHandles / pointMap / lineMap / polygonMap,
|
|
117
|
+
* removeGroup / remove 不依赖 this 上下文。
|
|
118
|
+
*/
|
|
119
|
+
function createRenderHandle(allHandles, groupHandles, pointMap, lineMap, polygonMap) {
|
|
120
|
+
return {
|
|
121
|
+
groups: groupHandles,
|
|
122
|
+
handles: allHandles,
|
|
123
|
+
point: pointMap,
|
|
124
|
+
line: lineMap,
|
|
125
|
+
polygon: polygonMap,
|
|
126
|
+
setVisible(visible) {
|
|
127
|
+
for (const handle of allHandles) {
|
|
128
|
+
handle.setVisible(visible);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
setGroupVisible(groupKey, visible) {
|
|
132
|
+
groupHandles[groupKey]?.setVisible(visible);
|
|
133
|
+
},
|
|
134
|
+
removeGroup(groupKey) {
|
|
135
|
+
const gh = groupHandles[groupKey];
|
|
136
|
+
if (gh) {
|
|
137
|
+
gh.remove();
|
|
138
|
+
// 从 allHandles 中移除
|
|
139
|
+
for (const h of gh.handles) {
|
|
140
|
+
const idx = allHandles.indexOf(h);
|
|
141
|
+
if (idx >= 0)
|
|
142
|
+
allHandles.splice(idx, 1);
|
|
143
|
+
}
|
|
144
|
+
delete groupHandles[groupKey];
|
|
145
|
+
// 同步清理 point/line/polygon 索引
|
|
146
|
+
delete pointMap[groupKey];
|
|
147
|
+
delete lineMap[groupKey];
|
|
148
|
+
delete polygonMap[groupKey];
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
remove() {
|
|
152
|
+
for (const handle of allHandles) {
|
|
153
|
+
handle.remove();
|
|
154
|
+
}
|
|
155
|
+
allHandles.length = 0;
|
|
156
|
+
for (const key of Object.keys(groupHandles)) {
|
|
157
|
+
delete groupHandles[key];
|
|
158
|
+
}
|
|
159
|
+
for (const key of Object.keys(pointMap)) {
|
|
160
|
+
delete pointMap[key];
|
|
161
|
+
}
|
|
162
|
+
for (const key of Object.keys(lineMap)) {
|
|
163
|
+
delete lineMap[key];
|
|
164
|
+
}
|
|
165
|
+
for (const key of Object.keys(polygonMap)) {
|
|
166
|
+
delete polygonMap[key];
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 综合 GeoJSON 渲染
|
|
173
|
+
*
|
|
174
|
+
* 自动识别点/线/面几何类型,按分组创建图层,返回统一句柄。
|
|
175
|
+
* 支持单个 GeoJSON、GeoJSON 数组、或 `{ key: json }` 对象。
|
|
176
|
+
*
|
|
177
|
+
* @param data GeoJSON 输入数据
|
|
178
|
+
* @param options 配置选项(含 layerName、groupBy、各类型样式)
|
|
179
|
+
* @param deps 依赖注入(getPoint / getLine / getPolygon)
|
|
180
|
+
* @returns GeoJSONRenderHandle 统一句柄
|
|
181
|
+
*/
|
|
182
|
+
export function renderGeoJSON(data, options, deps) {
|
|
183
|
+
/** *********************输入标准化*********************/
|
|
184
|
+
const normalized = normalizeGeoJSONInputs(data);
|
|
185
|
+
const groups = splitByGroupAndGeometry(normalized, options.groupBy);
|
|
186
|
+
const allHandles = [];
|
|
187
|
+
const groupHandles = {};
|
|
188
|
+
const singleGroup = Object.keys(groups).length === 1 && !options.groupBy;
|
|
189
|
+
// 用于组装返回句柄的索引
|
|
190
|
+
const pointMap = {};
|
|
191
|
+
const lineMap = {};
|
|
192
|
+
const polygonMap = {};
|
|
193
|
+
/** *********************遍历分组创建图层*********************/
|
|
194
|
+
for (const [groupKey, { point: pointFeatures, line: lineFeatures, polygon: polygonFeatures }] of Object.entries(groups)) {
|
|
195
|
+
const groupAllHandles = [];
|
|
196
|
+
let pointHandle = null;
|
|
197
|
+
let lineHandle = null;
|
|
198
|
+
let polygonHandle = null;
|
|
199
|
+
// 提取各几何类型的 datasetKey(取第一个 feature 的即可,同组内 datasetKey 相同)
|
|
200
|
+
const pointDatasetKey = pointFeatures[0]?._datasetKey ?? 'default';
|
|
201
|
+
const lineDatasetKey = lineFeatures[0]?._datasetKey ?? 'default';
|
|
202
|
+
const polygonDatasetKey = polygonFeatures[0]?._datasetKey ?? 'default';
|
|
203
|
+
// 点图层
|
|
204
|
+
if (pointFeatures.length > 0) {
|
|
205
|
+
const result = createPointLayerHandle(pointFeatures, options, groupKey, pointDatasetKey, singleGroup, deps.getPoint());
|
|
206
|
+
pointHandle = result.handle;
|
|
207
|
+
if (pointHandle) {
|
|
208
|
+
groupAllHandles.push(pointHandle);
|
|
209
|
+
allHandles.push(pointHandle);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 线图层
|
|
213
|
+
if (lineFeatures.length > 0) {
|
|
214
|
+
lineHandle = createLineHandle(lineFeatures, options, groupKey, lineDatasetKey, singleGroup, deps.getLine());
|
|
215
|
+
if (lineHandle) {
|
|
216
|
+
groupAllHandles.push(lineHandle);
|
|
217
|
+
allHandles.push(lineHandle);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// 面图层
|
|
221
|
+
if (polygonFeatures.length > 0) {
|
|
222
|
+
polygonHandle = createPolygonHandle(polygonFeatures, options, groupKey, polygonDatasetKey, singleGroup, deps.getPolygon());
|
|
223
|
+
if (polygonHandle) {
|
|
224
|
+
groupAllHandles.push(polygonHandle);
|
|
225
|
+
allHandles.push(polygonHandle);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** *********************组装分组句柄*********************/
|
|
229
|
+
groupHandles[groupKey] = {
|
|
230
|
+
point: pointHandle,
|
|
231
|
+
line: lineHandle,
|
|
232
|
+
polygon: polygonHandle,
|
|
233
|
+
handles: groupAllHandles,
|
|
234
|
+
setVisible(visible) {
|
|
235
|
+
for (const handle of groupAllHandles) {
|
|
236
|
+
handle.setVisible(visible);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
remove() {
|
|
240
|
+
for (const handle of groupAllHandles) {
|
|
241
|
+
handle.remove();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
pointMap[groupKey] = pointHandle;
|
|
246
|
+
lineMap[groupKey] = lineHandle;
|
|
247
|
+
polygonMap[groupKey] = polygonHandle;
|
|
248
|
+
}
|
|
249
|
+
/** *********************组装总句柄*********************/
|
|
250
|
+
return createRenderHandle(allHandles, groupHandles, pointMap, lineMap, polygonMap);
|
|
251
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderGeoJSON } from './GeoJSONRenderer';
|