my-openlayer 2.4.3 → 2.4.4
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 +50 -0
- package/README.md +1 -1
- package/core/SelectHandler.d.ts +28 -83
- package/core/SelectHandler.js +248 -306
- package/docs/SelectHandler.md +77 -10
- package/package.json +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.4] - 2026-01-27
|
|
4
|
+
|
|
5
|
+
### ✨ Features
|
|
6
|
+
|
|
7
|
+
- **SelectHandler:** 重构选择交互以支持独立样式渲染和多选隔离
|
|
8
|
+
- 重构Polygon样式处理并添加示例组件
|
|
9
|
+
- **core:** 添加基于GeoJSON数据的地图定位功能
|
|
10
|
+
- **core:** 添加地图定位功能并重构点位相关方法
|
|
11
|
+
- **Point:** 添加闪烁点功能及示例
|
|
12
|
+
- **core:** 为Polygon添加layerName选项并调整图层添加逻辑
|
|
13
|
+
- **Point:** 添加setOneVisibleByProp方法控制点显示
|
|
14
|
+
- **Polygon:** 优化遮罩功能支持多几何图形处理
|
|
15
|
+
- **测量工具:** 添加测量距离和面积功能及工具栏样式
|
|
16
|
+
- **地图:** 添加自定义投影配置支持
|
|
17
|
+
- **日志管理:** 实现统一日志开关与级别控制
|
|
18
|
+
- **SelectHandler:** 新增要素选择处理器模块
|
|
19
|
+
- upgrade OpenLayers to v10.6.1 and update project to v2.0.0
|
|
20
|
+
- update MapBaseLayers to handle optional token and improve annotation layer management
|
|
21
|
+
- 重新添加已清理的App.vue文件,移除了所有敏感token信息
|
|
22
|
+
- 重构Vue模板点位功能并优化事件管理
|
|
23
|
+
- **Polygon:** 改进图像图层处理逻辑并添加更新功能
|
|
24
|
+
- 添加验证工具类并重构现有验证逻辑
|
|
25
|
+
- **地图工具:** 重构地图工具类并增强错误处理
|
|
26
|
+
- 重构类型系统并增强核心功能
|
|
27
|
+
|
|
28
|
+
### 🐛 Bug Fixes
|
|
29
|
+
|
|
30
|
+
- **core:** 改进Vue依赖检测逻辑并更新版本号
|
|
31
|
+
- **VueTemplatePoint:** 修复Vue3环境下pointData的类型定义
|
|
32
|
+
- **core:** 修正Line类中layerName属性设置错误
|
|
33
|
+
- resolve TypeScript build error in MapBaseLayers.ts
|
|
34
|
+
|
|
35
|
+
### ⚡ Performance
|
|
36
|
+
|
|
37
|
+
- **Polygon:** 优化GeoJSON解析性能,直接注入layerName
|
|
38
|
+
|
|
39
|
+
### ♻️ Refactor
|
|
40
|
+
|
|
41
|
+
- **MapBaseLayers:** 重构图层管理逻辑,优化代码结构和可维护性
|
|
42
|
+
- **ConfigManager:** 集中管理默认配置并移除无用接口
|
|
43
|
+
- **样式处理:** 统一处理自定义样式逻辑并修复代码格式
|
|
44
|
+
- 将nameKey重命名为textKey以提高语义清晰度
|
|
45
|
+
- 更新依赖项并优化类型导出结构
|
|
46
|
+
- **core:** 重构DomPoint和MapBaseLayers类,优化代码结构和功能
|
|
47
|
+
|
|
48
|
+
### 📝 Documentation
|
|
49
|
+
|
|
50
|
+
- 添加详细模块文档并更新发布配置
|
|
51
|
+
- update README with branch strategy and add migration guide
|
|
52
|
+
|
|
3
53
|
## [2.4.3] - 2026-01-27
|
|
4
54
|
|
|
5
55
|
### ✨ Features
|
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ point.addPoint([{ lgtd: 119.81, lttd: 29.969, name: '示例点' }], {
|
|
|
109
109
|
- **[RiverLayerManager](docs/RiverLayerManager.md)**: 河流图层管理。
|
|
110
110
|
|
|
111
111
|
### 交互与工具
|
|
112
|
-
- **[SelectHandler](docs/SelectHandler.md)**:
|
|
112
|
+
- **[SelectHandler](docs/SelectHandler.md)**: 要素选择交互(支持独立样式渲染、多选隔离)。
|
|
113
113
|
- **[MeasureHandler](docs/MeasureHandler.md)**: 测量工具。
|
|
114
114
|
- **[MapTools](docs/MapTools.md)**: 通用地图工具。
|
|
115
115
|
- **[ValidationUtils](docs/ValidationUtils.md)**: 验证工具。
|
package/core/SelectHandler.d.ts
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import OlMap from "ol/Map";
|
|
2
2
|
import { FeatureLike } from "ol/Feature";
|
|
3
3
|
import { Style } from "ol/style";
|
|
4
4
|
import { SelectOptions, SelectMode, ProgrammaticSelectOptions } from "../types";
|
|
5
5
|
/**
|
|
6
6
|
* 要素选择处理器类
|
|
7
7
|
* 用于在地图上选择和高亮显示要素,支持单选、多选等多种选择模式
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```typescript
|
|
11
|
-
* const selectHandler = new SelectHandler(map);
|
|
12
|
-
*
|
|
13
|
-
* // 启用点击选择
|
|
14
|
-
* selectHandler.enableSelect('click', {
|
|
15
|
-
* layerFilter: ['pointLayer', 'polygonLayer'],
|
|
16
|
-
* multi: false,
|
|
17
|
-
* onSelect: (event) => {
|
|
18
|
-
* console.log('选中要素:', event.selected);
|
|
19
|
-
* }
|
|
20
|
-
* });
|
|
21
|
-
*
|
|
22
|
-
* // 获取当前选中的要素
|
|
23
|
-
* const selected = selectHandler.getSelectedFeatures();
|
|
24
|
-
*
|
|
25
|
-
* // 清除选择
|
|
26
|
-
* selectHandler.clearSelection();
|
|
27
|
-
*
|
|
28
|
-
* // 禁用选择
|
|
29
|
-
* selectHandler.disableSelect();
|
|
30
|
-
* ```
|
|
31
8
|
*/
|
|
32
9
|
export default class SelectHandler {
|
|
33
10
|
/** OpenLayers 地图实例 */
|
|
@@ -36,14 +13,18 @@ export default class SelectHandler {
|
|
|
36
13
|
private readonly eventManager;
|
|
37
14
|
/** 错误处理器实例 */
|
|
38
15
|
private readonly errorHandler;
|
|
39
|
-
/** Select
|
|
40
|
-
private
|
|
16
|
+
/** 主 Select 交互实例(只负责交互,不负责渲染) */
|
|
17
|
+
private mainSelectInteraction?;
|
|
18
|
+
/** 额外的 Select 交互实例列表(用于编程式选择) */
|
|
19
|
+
private extraSelectInteractions;
|
|
20
|
+
/** 渲染用 Select 交互实例映射(用于交互式选择的高亮渲染) featureUID -> Select */
|
|
21
|
+
private renderInteractions;
|
|
41
22
|
/** 当前选择模式 */
|
|
42
23
|
private currentMode?;
|
|
43
|
-
/** 当前配置选项 */
|
|
44
|
-
private currentOptions?;
|
|
45
24
|
/** 是否已启用选择 */
|
|
46
25
|
private isEnabled;
|
|
26
|
+
/** 当前自定义样式函数(用于交互式选择) */
|
|
27
|
+
private currentSelectStyle?;
|
|
47
28
|
/** 默认选中样式 - 点要素 */
|
|
48
29
|
private readonly defaultPointStyle;
|
|
49
30
|
/** 默认选中样式 - 线要素 */
|
|
@@ -54,7 +35,7 @@ export default class SelectHandler {
|
|
|
54
35
|
* 构造函数
|
|
55
36
|
* @param map OpenLayers地图实例
|
|
56
37
|
*/
|
|
57
|
-
constructor(map:
|
|
38
|
+
constructor(map: OlMap);
|
|
58
39
|
/**
|
|
59
40
|
* 启用要素选择
|
|
60
41
|
* @param mode 选择模式:'click'(点击)、'hover'(悬停)、'ctrl'(Ctrl+点击)
|
|
@@ -68,7 +49,7 @@ export default class SelectHandler {
|
|
|
68
49
|
*/
|
|
69
50
|
disableSelect(): this;
|
|
70
51
|
/**
|
|
71
|
-
*
|
|
52
|
+
* 获取当前选中的要素(仅返回主交互中的要素)
|
|
72
53
|
* @returns 选中的要素数组
|
|
73
54
|
*/
|
|
74
55
|
getSelectedFeatures(): FeatureLike[];
|
|
@@ -79,81 +60,45 @@ export default class SelectHandler {
|
|
|
79
60
|
clearSelection(): this;
|
|
80
61
|
/**
|
|
81
62
|
* 通过要素ID选择要素
|
|
82
|
-
* @param featureIds 要素ID数组
|
|
83
|
-
* @param options 编程式选择配置选项
|
|
84
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
85
63
|
*/
|
|
86
64
|
selectByIds(featureIds: string[], options?: ProgrammaticSelectOptions): this;
|
|
87
65
|
/**
|
|
88
66
|
* 通过属性选择要素
|
|
89
|
-
* @param propertyName 属性名称
|
|
90
|
-
* @param propertyValue 属性值
|
|
91
|
-
* @param options 编程式选择配置选项
|
|
92
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
93
67
|
*/
|
|
94
68
|
selectByProperty(propertyName: string, propertyValue: any, options?: ProgrammaticSelectOptions): this;
|
|
95
69
|
/**
|
|
96
|
-
*
|
|
97
|
-
* @returns 是否已启用
|
|
70
|
+
* 应用选择(编程式)
|
|
98
71
|
*/
|
|
99
|
-
|
|
72
|
+
private applySelection;
|
|
100
73
|
/**
|
|
101
|
-
*
|
|
102
|
-
* @returns 当前选择模式
|
|
74
|
+
* 添加额外的交互实例(用于函数式样式结果)
|
|
103
75
|
*/
|
|
76
|
+
private addExtraInteraction;
|
|
77
|
+
private findFeaturesByIds;
|
|
78
|
+
private findFeaturesByProperty;
|
|
79
|
+
private ensureMainInteraction;
|
|
80
|
+
isSelectEnabled(): boolean;
|
|
104
81
|
getCurrentMode(): SelectMode | undefined;
|
|
105
|
-
|
|
106
|
-
* 定位至要素
|
|
107
|
-
* @private
|
|
108
|
-
* @param features 要素数组
|
|
109
|
-
* @param duration 动画持续时间(毫秒),默认500
|
|
110
|
-
* @param padding 边距(像素),默认100
|
|
111
|
-
*/
|
|
82
|
+
updateSelectStyle(selectStyle: Style | Style[] | ((feature: FeatureLike, resolution: number) => Style | Style[])): this;
|
|
112
83
|
private fitToFeatures;
|
|
113
|
-
/**
|
|
114
|
-
* 销毁选择处理器,清理所有资源
|
|
115
|
-
*/
|
|
116
84
|
destroy(): void;
|
|
117
|
-
/**
|
|
118
|
-
* 合并选项配置
|
|
119
|
-
* @private
|
|
120
|
-
*/
|
|
121
|
-
private mergeOptions;
|
|
122
|
-
/**
|
|
123
|
-
* 创建 Select 交互
|
|
124
|
-
* @private
|
|
125
|
-
*/
|
|
126
|
-
private createSelectInteraction;
|
|
127
|
-
/**
|
|
128
|
-
* 获取选择条件
|
|
129
|
-
* @private
|
|
130
|
-
*/
|
|
131
85
|
private getSelectCondition;
|
|
132
|
-
/**
|
|
133
|
-
* 创建图层过滤器
|
|
134
|
-
* @private
|
|
135
|
-
*/
|
|
136
86
|
private createLayerFilter;
|
|
137
87
|
/**
|
|
138
|
-
*
|
|
139
|
-
* @private
|
|
88
|
+
* 计算样式(解析函数或返回默认)
|
|
140
89
|
*/
|
|
141
|
-
private
|
|
90
|
+
private calculateStyle;
|
|
142
91
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @param selectStyle 新的选择样式
|
|
145
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
92
|
+
* 为单个要素创建并添加渲染交互
|
|
146
93
|
*/
|
|
147
|
-
|
|
94
|
+
private createRenderInteraction;
|
|
148
95
|
/**
|
|
149
|
-
*
|
|
150
|
-
* 如果当前未启用选择交互,则创建一个不响应用户操作的交互实例用于编程式选择
|
|
151
|
-
* @private
|
|
96
|
+
* 移除单个要素的渲染交互
|
|
152
97
|
*/
|
|
153
|
-
private
|
|
98
|
+
private removeRenderInteraction;
|
|
154
99
|
/**
|
|
155
|
-
*
|
|
156
|
-
* @private
|
|
100
|
+
* 清理所有交互式渲染交互
|
|
157
101
|
*/
|
|
102
|
+
private clearRenderInteractions;
|
|
158
103
|
private attachEventListeners;
|
|
159
104
|
}
|
package/core/SelectHandler.js
CHANGED
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
import { Select } from "ol/interaction";
|
|
2
2
|
import { click, pointerMove, platformModifierKeyOnly } from "ol/events/condition";
|
|
3
|
+
import Feature from "ol/Feature";
|
|
3
4
|
import VectorLayer from "ol/layer/Vector";
|
|
4
5
|
import { Style, Fill, Stroke, Circle as CircleStyle } from "ol/style";
|
|
6
|
+
import Collection from 'ol/Collection';
|
|
7
|
+
import { getUid } from "ol/util";
|
|
5
8
|
import { EventManager } from "./EventManager";
|
|
6
9
|
import { ValidationUtils } from "../utils/ValidationUtils";
|
|
7
10
|
import { ErrorHandler, MyOpenLayersError, ErrorType } from "../utils/ErrorHandler";
|
|
8
11
|
/**
|
|
9
12
|
* 要素选择处理器类
|
|
10
13
|
* 用于在地图上选择和高亮显示要素,支持单选、多选等多种选择模式
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* const selectHandler = new SelectHandler(map);
|
|
15
|
-
*
|
|
16
|
-
* // 启用点击选择
|
|
17
|
-
* selectHandler.enableSelect('click', {
|
|
18
|
-
* layerFilter: ['pointLayer', 'polygonLayer'],
|
|
19
|
-
* multi: false,
|
|
20
|
-
* onSelect: (event) => {
|
|
21
|
-
* console.log('选中要素:', event.selected);
|
|
22
|
-
* }
|
|
23
|
-
* });
|
|
24
|
-
*
|
|
25
|
-
* // 获取当前选中的要素
|
|
26
|
-
* const selected = selectHandler.getSelectedFeatures();
|
|
27
|
-
*
|
|
28
|
-
* // 清除选择
|
|
29
|
-
* selectHandler.clearSelection();
|
|
30
|
-
*
|
|
31
|
-
* // 禁用选择
|
|
32
|
-
* selectHandler.disableSelect();
|
|
33
|
-
* ```
|
|
34
14
|
*/
|
|
35
15
|
export default class SelectHandler {
|
|
36
16
|
/**
|
|
@@ -38,6 +18,10 @@ export default class SelectHandler {
|
|
|
38
18
|
* @param map OpenLayers地图实例
|
|
39
19
|
*/
|
|
40
20
|
constructor(map) {
|
|
21
|
+
/** 额外的 Select 交互实例列表(用于编程式选择) */
|
|
22
|
+
this.extraSelectInteractions = [];
|
|
23
|
+
/** 渲染用 Select 交互实例映射(用于交互式选择的高亮渲染) featureUID -> Select */
|
|
24
|
+
this.renderInteractions = new Map();
|
|
41
25
|
/** 是否已启用选择 */
|
|
42
26
|
this.isEnabled = false;
|
|
43
27
|
/** 默认选中样式 - 点要素 */
|
|
@@ -69,8 +53,6 @@ export default class SelectHandler {
|
|
|
69
53
|
this.map = map;
|
|
70
54
|
this.eventManager = new EventManager(map);
|
|
71
55
|
this.errorHandler = ErrorHandler.getInstance();
|
|
72
|
-
// 默认启用点击选择模式,支持多选
|
|
73
|
-
this.enableSelect('click', { multi: true });
|
|
74
56
|
}
|
|
75
57
|
/**
|
|
76
58
|
* 启用要素选择
|
|
@@ -84,17 +66,25 @@ export default class SelectHandler {
|
|
|
84
66
|
if (this.isEnabled) {
|
|
85
67
|
this.disableSelect();
|
|
86
68
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
this.
|
|
69
|
+
this.currentSelectStyle = options?.selectStyle;
|
|
70
|
+
// 创建主 Select 交互
|
|
71
|
+
// 这里的 style 设置为 null,使其只负责交互捕获,不负责渲染
|
|
72
|
+
// 渲染由独立的 renderInteractions 负责
|
|
73
|
+
this.mainSelectInteraction = new Select({
|
|
74
|
+
condition: this.getSelectCondition(mode),
|
|
75
|
+
layers: this.createLayerFilter(options?.layerFilter),
|
|
76
|
+
filter: options?.featureFilter,
|
|
77
|
+
style: null,
|
|
78
|
+
multi: options?.multi ?? false,
|
|
79
|
+
hitTolerance: options?.hitTolerance ?? 0
|
|
80
|
+
});
|
|
92
81
|
// 添加事件监听器
|
|
93
|
-
this.attachEventListeners(
|
|
82
|
+
this.attachEventListeners(this.mainSelectInteraction, options);
|
|
94
83
|
// 添加到地图
|
|
95
|
-
this.map.addInteraction(this.
|
|
84
|
+
this.map.addInteraction(this.mainSelectInteraction);
|
|
96
85
|
this.isEnabled = true;
|
|
97
|
-
this.
|
|
86
|
+
this.currentMode = mode;
|
|
87
|
+
this.errorHandler.debug('要素选择已启用', { mode, options });
|
|
98
88
|
return this;
|
|
99
89
|
}
|
|
100
90
|
catch (error) {
|
|
@@ -108,12 +98,15 @@ export default class SelectHandler {
|
|
|
108
98
|
*/
|
|
109
99
|
disableSelect() {
|
|
110
100
|
try {
|
|
111
|
-
if (this.
|
|
112
|
-
this.map.removeInteraction(this.
|
|
113
|
-
this.
|
|
101
|
+
if (this.mainSelectInteraction) {
|
|
102
|
+
this.map.removeInteraction(this.mainSelectInteraction);
|
|
103
|
+
this.mainSelectInteraction = undefined;
|
|
114
104
|
}
|
|
105
|
+
// 清理交互式渲染实例(但不清理编程式的 extraSelectInteractions)
|
|
106
|
+
this.clearRenderInteractions();
|
|
115
107
|
this.isEnabled = false;
|
|
116
108
|
this.currentMode = undefined;
|
|
109
|
+
this.currentSelectStyle = undefined;
|
|
117
110
|
this.errorHandler.debug('要素选择已禁用');
|
|
118
111
|
return this;
|
|
119
112
|
}
|
|
@@ -123,84 +116,47 @@ export default class SelectHandler {
|
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
/**
|
|
126
|
-
*
|
|
119
|
+
* 获取当前选中的要素(仅返回主交互中的要素)
|
|
127
120
|
* @returns 选中的要素数组
|
|
128
121
|
*/
|
|
129
122
|
getSelectedFeatures() {
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
const features = [];
|
|
124
|
+
if (this.mainSelectInteraction) {
|
|
125
|
+
features.push(...this.mainSelectInteraction.getFeatures().getArray());
|
|
132
126
|
}
|
|
133
|
-
|
|
134
|
-
return features.getArray();
|
|
127
|
+
return features;
|
|
135
128
|
}
|
|
136
129
|
/**
|
|
137
130
|
* 清除所有选择
|
|
138
131
|
* @returns SelectHandler 实例(支持链式调用)
|
|
139
132
|
*/
|
|
140
133
|
clearSelection() {
|
|
141
|
-
|
|
142
|
-
|
|
134
|
+
// 1. 清除主交互的选择
|
|
135
|
+
if (this.mainSelectInteraction) {
|
|
136
|
+
this.mainSelectInteraction.getFeatures().clear();
|
|
143
137
|
}
|
|
138
|
+
// 2. 清理交互式渲染实例
|
|
139
|
+
this.clearRenderInteractions();
|
|
140
|
+
// 3. 移除并销毁所有额外交互(编程式选择)
|
|
141
|
+
this.extraSelectInteractions.forEach(interaction => {
|
|
142
|
+
this.map.removeInteraction(interaction);
|
|
143
|
+
});
|
|
144
|
+
this.extraSelectInteractions = [];
|
|
144
145
|
return this;
|
|
145
146
|
}
|
|
146
147
|
/**
|
|
147
148
|
* 通过要素ID选择要素
|
|
148
|
-
* @param featureIds 要素ID数组
|
|
149
|
-
* @param options 编程式选择配置选项
|
|
150
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
151
149
|
*/
|
|
152
150
|
selectByIds(featureIds, options) {
|
|
153
151
|
try {
|
|
154
|
-
// 确保交互实例存在
|
|
155
|
-
this.ensureSelectInteraction();
|
|
156
|
-
if (!this.selectInteraction) {
|
|
157
|
-
// 理论上 ensureSelectInteraction 后不应为空,这里做双重保险
|
|
158
|
-
return this;
|
|
159
|
-
}
|
|
160
152
|
if (!featureIds || featureIds.length === 0) {
|
|
161
153
|
this.errorHandler.warn('要素ID列表为空');
|
|
162
154
|
return this;
|
|
163
155
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
// 获取所有图层
|
|
169
|
-
const layers = this.map.getLayers().getArray();
|
|
170
|
-
for (const layer of layers) {
|
|
171
|
-
// 过滤图层
|
|
172
|
-
if (options?.layerName && layer.get('layerName') !== options.layerName) {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (layer instanceof VectorLayer) {
|
|
176
|
-
const source = layer.getSource();
|
|
177
|
-
if (!source)
|
|
178
|
-
continue;
|
|
179
|
-
// 查找并选择要素
|
|
180
|
-
for (const featureId of featureIds) {
|
|
181
|
-
const feature = source.getFeatureById(featureId);
|
|
182
|
-
if (feature) {
|
|
183
|
-
selectedFeatures.push(feature);
|
|
184
|
-
this.selectInteraction.getFeatures().push(feature);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// 如果传入了自定义样式,为选中的要素设置样式
|
|
190
|
-
if (options?.selectStyle && selectedFeatures.length > 0) {
|
|
191
|
-
for (const feature of selectedFeatures) {
|
|
192
|
-
if (typeof options.selectStyle === 'function') {
|
|
193
|
-
feature.setStyle(options.selectStyle(feature));
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
feature.setStyle(options.selectStyle);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// 定位至选中要素
|
|
201
|
-
if (options?.fitView && selectedFeatures.length > 0) {
|
|
202
|
-
this.fitToFeatures(selectedFeatures, options.fitDuration ?? 500, options.fitPadding ?? 100);
|
|
203
|
-
}
|
|
156
|
+
const selectedFeatures = this.findFeaturesByIds(featureIds, options?.layerName);
|
|
157
|
+
if (selectedFeatures.length === 0)
|
|
158
|
+
return this;
|
|
159
|
+
this.applySelection(selectedFeatures, options);
|
|
204
160
|
return this;
|
|
205
161
|
}
|
|
206
162
|
catch (error) {
|
|
@@ -210,62 +166,15 @@ export default class SelectHandler {
|
|
|
210
166
|
}
|
|
211
167
|
/**
|
|
212
168
|
* 通过属性选择要素
|
|
213
|
-
* @param propertyName 属性名称
|
|
214
|
-
* @param propertyValue 属性值
|
|
215
|
-
* @param options 编程式选择配置选项
|
|
216
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
217
169
|
*/
|
|
218
170
|
selectByProperty(propertyName, propertyValue, options) {
|
|
219
171
|
try {
|
|
220
|
-
|
|
221
|
-
this.ensureSelectInteraction();
|
|
222
|
-
if (!this.selectInteraction) {
|
|
223
|
-
// 理论上 ensureSelectInteraction 后不应为空,这里做双重保险
|
|
224
|
-
return this;
|
|
225
|
-
}
|
|
226
|
-
if (!propertyName) {
|
|
172
|
+
if (!propertyName)
|
|
227
173
|
throw new Error('属性名称不能为空');
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const selectedFeatures = [];
|
|
233
|
-
// 获取所有图层
|
|
234
|
-
const layers = this.map.getLayers().getArray();
|
|
235
|
-
for (const layer of layers) {
|
|
236
|
-
// 过滤图层
|
|
237
|
-
if (options?.layerName && layer.get('layerName') !== options.layerName) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (layer instanceof VectorLayer) {
|
|
241
|
-
const source = layer.getSource();
|
|
242
|
-
if (!source)
|
|
243
|
-
continue;
|
|
244
|
-
// 查找并选择要素
|
|
245
|
-
const features = source.getFeatures();
|
|
246
|
-
for (const feature of features) {
|
|
247
|
-
if (feature.get(propertyName) === propertyValue) {
|
|
248
|
-
selectedFeatures.push(feature);
|
|
249
|
-
this.selectInteraction.getFeatures().push(feature);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// 如果传入了自定义样式,为选中的要素设置样式
|
|
255
|
-
if (options?.selectStyle && selectedFeatures.length > 0) {
|
|
256
|
-
for (const feature of selectedFeatures) {
|
|
257
|
-
if (typeof options.selectStyle === 'function') {
|
|
258
|
-
feature.setStyle(options.selectStyle(feature));
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
feature.setStyle(options.selectStyle);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
// 定位至选中要素
|
|
266
|
-
if (options?.fitView && selectedFeatures.length > 0) {
|
|
267
|
-
this.fitToFeatures(selectedFeatures, options.fitDuration ?? 500, options.fitPadding ?? 100);
|
|
268
|
-
}
|
|
174
|
+
const selectedFeatures = this.findFeaturesByProperty(propertyName, propertyValue, options?.layerName);
|
|
175
|
+
if (selectedFeatures.length === 0)
|
|
176
|
+
return this;
|
|
177
|
+
this.applySelection(selectedFeatures, options);
|
|
269
178
|
return this;
|
|
270
179
|
}
|
|
271
180
|
catch (error) {
|
|
@@ -274,42 +183,139 @@ export default class SelectHandler {
|
|
|
274
183
|
}
|
|
275
184
|
}
|
|
276
185
|
/**
|
|
277
|
-
*
|
|
278
|
-
* @returns 是否已启用
|
|
186
|
+
* 应用选择(编程式)
|
|
279
187
|
*/
|
|
280
|
-
|
|
281
|
-
|
|
188
|
+
applySelection(features, options) {
|
|
189
|
+
if (options?.selectStyle) {
|
|
190
|
+
if (typeof options.selectStyle === 'function') {
|
|
191
|
+
features.forEach(feature => {
|
|
192
|
+
const resolution = this.map.getView().getResolution() || 1;
|
|
193
|
+
const style = options.selectStyle(feature, resolution);
|
|
194
|
+
this.addExtraInteraction(feature, style);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
const customSelect = new Select({
|
|
199
|
+
condition: () => false,
|
|
200
|
+
style: options.selectStyle,
|
|
201
|
+
features: new Collection(features),
|
|
202
|
+
hitTolerance: 0
|
|
203
|
+
});
|
|
204
|
+
this.map.addInteraction(customSelect);
|
|
205
|
+
this.extraSelectInteractions.push(customSelect);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// 使用默认样式,添加到主交互
|
|
210
|
+
this.ensureMainInteraction();
|
|
211
|
+
this.mainSelectInteraction.getFeatures().extend(features);
|
|
212
|
+
// 手动触发渲染交互的创建(因为 mainSelectInteraction 不自动渲染,且直接 extend 可能不会触发 select 事件)
|
|
213
|
+
// 注意:Select 交互的 'select' 事件通常在用户交互时触发,手动 extend 集合不会触发该事件
|
|
214
|
+
features.forEach(feature => {
|
|
215
|
+
this.createRenderInteraction(feature);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (options?.fitView) {
|
|
219
|
+
this.fitToFeatures(features, options.fitDuration ?? 500, options.fitPadding ?? 100);
|
|
220
|
+
}
|
|
282
221
|
}
|
|
283
222
|
/**
|
|
284
|
-
*
|
|
285
|
-
* @returns 当前选择模式
|
|
223
|
+
* 添加额外的交互实例(用于函数式样式结果)
|
|
286
224
|
*/
|
|
225
|
+
addExtraInteraction(feature, style) {
|
|
226
|
+
const customSelect = new Select({
|
|
227
|
+
condition: () => false,
|
|
228
|
+
style: style,
|
|
229
|
+
features: new Collection([feature]),
|
|
230
|
+
hitTolerance: 0
|
|
231
|
+
});
|
|
232
|
+
this.map.addInteraction(customSelect);
|
|
233
|
+
this.extraSelectInteractions.push(customSelect);
|
|
234
|
+
}
|
|
235
|
+
// ... 查找方法保持不变 ...
|
|
236
|
+
findFeaturesByIds(featureIds, layerName) {
|
|
237
|
+
const selectedFeatures = [];
|
|
238
|
+
const layers = this.map.getLayers().getArray();
|
|
239
|
+
for (const layer of layers) {
|
|
240
|
+
if (layerName && layer.get('layerName') !== layerName)
|
|
241
|
+
continue;
|
|
242
|
+
if (!(layer instanceof VectorLayer))
|
|
243
|
+
continue;
|
|
244
|
+
const source = layer.getSource();
|
|
245
|
+
if (!source)
|
|
246
|
+
continue;
|
|
247
|
+
for (const featureId of featureIds) {
|
|
248
|
+
const feature = source.getFeatureById(featureId);
|
|
249
|
+
if (feature && feature instanceof Feature)
|
|
250
|
+
selectedFeatures.push(feature);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return selectedFeatures;
|
|
254
|
+
}
|
|
255
|
+
findFeaturesByProperty(key, value, layerName) {
|
|
256
|
+
const selectedFeatures = [];
|
|
257
|
+
const layers = this.map.getLayers().getArray();
|
|
258
|
+
for (const layer of layers) {
|
|
259
|
+
if (layerName && layer.get('layerName') !== layerName)
|
|
260
|
+
continue;
|
|
261
|
+
if (!(layer instanceof VectorLayer))
|
|
262
|
+
continue;
|
|
263
|
+
const source = layer.getSource();
|
|
264
|
+
if (!source)
|
|
265
|
+
continue;
|
|
266
|
+
const features = source.getFeatures();
|
|
267
|
+
for (const feature of features) {
|
|
268
|
+
if (feature.get(key) === value)
|
|
269
|
+
selectedFeatures.push(feature);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return selectedFeatures;
|
|
273
|
+
}
|
|
274
|
+
ensureMainInteraction() {
|
|
275
|
+
if (!this.mainSelectInteraction) {
|
|
276
|
+
this.mainSelectInteraction = new Select({
|
|
277
|
+
condition: () => false,
|
|
278
|
+
style: null // 主交互不直接渲染,依靠事件监听创建渲染实例
|
|
279
|
+
});
|
|
280
|
+
// 也要监听它的事件来处理默认样式的渲染
|
|
281
|
+
this.attachEventListeners(this.mainSelectInteraction, {});
|
|
282
|
+
this.map.addInteraction(this.mainSelectInteraction);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
isSelectEnabled() {
|
|
286
|
+
return this.isEnabled;
|
|
287
|
+
}
|
|
287
288
|
getCurrentMode() {
|
|
288
289
|
return this.currentMode;
|
|
289
290
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
updateSelectStyle(selectStyle) {
|
|
292
|
+
if (!this.mainSelectInteraction) {
|
|
293
|
+
this.errorHandler.warn('主选择交互未启用,无法更新样式');
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
this.currentSelectStyle = selectStyle;
|
|
297
|
+
// 强制刷新:重新生成所有选中要素的渲染实例
|
|
298
|
+
const features = this.mainSelectInteraction.getFeatures().getArray();
|
|
299
|
+
// 清除旧的渲染
|
|
300
|
+
this.clearRenderInteractions();
|
|
301
|
+
// 重新创建渲染
|
|
302
|
+
features.forEach(feature => {
|
|
303
|
+
this.createRenderInteraction(feature);
|
|
304
|
+
});
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
297
307
|
fitToFeatures(features, duration = 500, padding = 100) {
|
|
298
308
|
try {
|
|
299
|
-
if (!features || features.length === 0)
|
|
309
|
+
if (!features || features.length === 0)
|
|
300
310
|
return;
|
|
301
|
-
}
|
|
302
|
-
// 创建一个包含所有要素的范围
|
|
303
311
|
let extent;
|
|
304
312
|
for (const feature of features) {
|
|
305
313
|
const geometry = feature.getGeometry();
|
|
306
314
|
if (geometry) {
|
|
307
315
|
const featureExtent = geometry.getExtent();
|
|
308
|
-
if (!extent)
|
|
316
|
+
if (!extent)
|
|
309
317
|
extent = featureExtent;
|
|
310
|
-
}
|
|
311
318
|
else {
|
|
312
|
-
// 扩展范围以包含当前要素
|
|
313
319
|
extent = [
|
|
314
320
|
Math.min(extent[0], featureExtent[0]),
|
|
315
321
|
Math.min(extent[1], featureExtent[1]),
|
|
@@ -322,7 +328,8 @@ export default class SelectHandler {
|
|
|
322
328
|
if (extent) {
|
|
323
329
|
this.map.getView().fit(extent, {
|
|
324
330
|
duration,
|
|
325
|
-
padding: [padding, padding, padding, padding]
|
|
331
|
+
padding: [padding, padding, padding, padding],
|
|
332
|
+
maxZoom: 18
|
|
326
333
|
});
|
|
327
334
|
}
|
|
328
335
|
}
|
|
@@ -330,195 +337,130 @@ export default class SelectHandler {
|
|
|
330
337
|
this.errorHandler.error('定位至要素失败:', error);
|
|
331
338
|
}
|
|
332
339
|
}
|
|
333
|
-
/**
|
|
334
|
-
* 销毁选择处理器,清理所有资源
|
|
335
|
-
*/
|
|
336
340
|
destroy() {
|
|
337
341
|
try {
|
|
338
342
|
this.disableSelect();
|
|
343
|
+
this.clearSelection();
|
|
339
344
|
this.errorHandler.debug('选择处理器已销毁');
|
|
340
345
|
}
|
|
341
346
|
catch (error) {
|
|
342
347
|
this.errorHandler.handleError(new MyOpenLayersError(`销毁选择处理器失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.COMPONENT_ERROR));
|
|
343
348
|
}
|
|
344
349
|
}
|
|
345
|
-
/**
|
|
346
|
-
* 合并选项配置
|
|
347
|
-
* @private
|
|
348
|
-
*/
|
|
349
|
-
mergeOptions(options) {
|
|
350
|
-
return {
|
|
351
|
-
multi: false,
|
|
352
|
-
layerFilter: undefined,
|
|
353
|
-
featureFilter: undefined,
|
|
354
|
-
hitTolerance: 0,
|
|
355
|
-
selectStyle: undefined,
|
|
356
|
-
onSelect: undefined,
|
|
357
|
-
onDeselect: undefined,
|
|
358
|
-
...options
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* 创建 Select 交互
|
|
363
|
-
* @private
|
|
364
|
-
*/
|
|
365
|
-
createSelectInteraction(mode, options) {
|
|
366
|
-
// 确定选择条件
|
|
367
|
-
const condition = this.getSelectCondition(mode);
|
|
368
|
-
// 创建图层过滤器
|
|
369
|
-
const layerFilter = this.createLayerFilter(options.layerFilter);
|
|
370
|
-
// 创建要素过滤器
|
|
371
|
-
const filter = options.featureFilter;
|
|
372
|
-
// 创建选择样式
|
|
373
|
-
const style = this.createSelectStyle(options.selectStyle);
|
|
374
|
-
return new Select({
|
|
375
|
-
condition,
|
|
376
|
-
layers: layerFilter,
|
|
377
|
-
filter,
|
|
378
|
-
style,
|
|
379
|
-
multi: options.multi,
|
|
380
|
-
hitTolerance: options.hitTolerance
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* 获取选择条件
|
|
385
|
-
* @private
|
|
386
|
-
*/
|
|
387
350
|
getSelectCondition(mode) {
|
|
388
351
|
switch (mode) {
|
|
389
|
-
case 'click':
|
|
390
|
-
|
|
391
|
-
case '
|
|
392
|
-
|
|
393
|
-
case 'ctrl':
|
|
394
|
-
return platformModifierKeyOnly;
|
|
395
|
-
default:
|
|
396
|
-
return click;
|
|
352
|
+
case 'click': return click;
|
|
353
|
+
case 'hover': return pointerMove;
|
|
354
|
+
case 'ctrl': return platformModifierKeyOnly;
|
|
355
|
+
default: return click;
|
|
397
356
|
}
|
|
398
357
|
}
|
|
399
|
-
/**
|
|
400
|
-
* 创建图层过滤器
|
|
401
|
-
* @private
|
|
402
|
-
*/
|
|
403
358
|
createLayerFilter(layerNames) {
|
|
404
|
-
if (!layerNames || layerNames.length === 0)
|
|
359
|
+
if (!layerNames || layerNames.length === 0)
|
|
405
360
|
return undefined;
|
|
406
|
-
}
|
|
407
361
|
return (layer) => {
|
|
408
362
|
const layerName = layer.get('layerName') || layer.get('name');
|
|
409
363
|
return layerNames.includes(layerName);
|
|
410
364
|
};
|
|
411
365
|
}
|
|
412
366
|
/**
|
|
413
|
-
*
|
|
414
|
-
* @private
|
|
367
|
+
* 计算样式(解析函数或返回默认)
|
|
415
368
|
*/
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
return (feature) => {
|
|
422
|
-
const geometry = feature.getGeometry();
|
|
423
|
-
if (!geometry) {
|
|
424
|
-
return this.defaultPointStyle;
|
|
369
|
+
calculateStyle(feature, resolution) {
|
|
370
|
+
const styleSource = this.currentSelectStyle;
|
|
371
|
+
if (styleSource) {
|
|
372
|
+
if (typeof styleSource === 'function') {
|
|
373
|
+
return styleSource(feature, resolution);
|
|
425
374
|
}
|
|
426
|
-
|
|
427
|
-
switch (geometryType) {
|
|
428
|
-
case 'Point':
|
|
429
|
-
case 'MultiPoint':
|
|
430
|
-
return this.defaultPointStyle;
|
|
431
|
-
case 'LineString':
|
|
432
|
-
case 'MultiLineString':
|
|
433
|
-
return this.defaultLineStyle;
|
|
434
|
-
case 'Polygon':
|
|
435
|
-
case 'MultiPolygon':
|
|
436
|
-
return this.defaultPolygonStyle;
|
|
437
|
-
default:
|
|
438
|
-
return this.defaultPointStyle;
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* 更新选择样式
|
|
444
|
-
* @param selectStyle 新的选择样式
|
|
445
|
-
* @returns SelectHandler 实例(支持链式调用)
|
|
446
|
-
*/
|
|
447
|
-
updateSelectStyle(selectStyle) {
|
|
448
|
-
if (!this.selectInteraction) {
|
|
449
|
-
this.errorHandler.warn('选择交互未启用,无法更新样式');
|
|
450
|
-
return this;
|
|
375
|
+
return styleSource;
|
|
451
376
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return this;
|
|
464
|
-
|
|
465
|
-
catch (error) {
|
|
466
|
-
this.errorHandler.handleError(new MyOpenLayersError(`更新选择样式失败: ${error instanceof Error ? error.message : '未知错误'}`, ErrorType.COMPONENT_ERROR, { selectStyle }));
|
|
467
|
-
throw error;
|
|
377
|
+
// 默认样式逻辑
|
|
378
|
+
const geometry = feature.getGeometry();
|
|
379
|
+
if (!geometry)
|
|
380
|
+
return this.defaultPointStyle;
|
|
381
|
+
const geometryType = geometry.getType();
|
|
382
|
+
switch (geometryType) {
|
|
383
|
+
case 'Point':
|
|
384
|
+
case 'MultiPoint': return this.defaultPointStyle;
|
|
385
|
+
case 'LineString':
|
|
386
|
+
case 'MultiLineString': return this.defaultLineStyle;
|
|
387
|
+
case 'Polygon':
|
|
388
|
+
case 'MultiPolygon': return this.defaultPolygonStyle;
|
|
389
|
+
default: return this.defaultPointStyle;
|
|
468
390
|
}
|
|
469
391
|
}
|
|
470
392
|
/**
|
|
471
|
-
*
|
|
472
|
-
* 如果当前未启用选择交互,则创建一个不响应用户操作的交互实例用于编程式选择
|
|
473
|
-
* @private
|
|
393
|
+
* 为单个要素创建并添加渲染交互
|
|
474
394
|
*/
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
395
|
+
createRenderInteraction(feature) {
|
|
396
|
+
const uid = getUid(feature);
|
|
397
|
+
if (this.renderInteractions.has(uid))
|
|
398
|
+
return; // 已存在
|
|
399
|
+
const resolution = this.map.getView().getResolution() || 1;
|
|
400
|
+
const style = this.calculateStyle(feature, resolution);
|
|
401
|
+
const renderSelect = new Select({
|
|
482
402
|
condition: () => false,
|
|
483
|
-
style:
|
|
403
|
+
style: style,
|
|
404
|
+
features: new Collection([feature]),
|
|
405
|
+
hitTolerance: 0
|
|
484
406
|
});
|
|
485
|
-
this.map.addInteraction(
|
|
486
|
-
this.
|
|
487
|
-
// 注意:这里我们不设置 currentMode,因为这是一种特殊的编程式模式
|
|
488
|
-
// 也不需要 attachEventListeners,因为这种交互不会触发用户事件
|
|
489
|
-
this.errorHandler.debug('已自动创建编程式选择交互实例');
|
|
407
|
+
this.map.addInteraction(renderSelect);
|
|
408
|
+
this.renderInteractions.set(uid, renderSelect);
|
|
490
409
|
}
|
|
491
410
|
/**
|
|
492
|
-
*
|
|
493
|
-
* @private
|
|
411
|
+
* 移除单个要素的渲染交互
|
|
494
412
|
*/
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
413
|
+
removeRenderInteraction(feature) {
|
|
414
|
+
const uid = getUid(feature);
|
|
415
|
+
const interaction = this.renderInteractions.get(uid);
|
|
416
|
+
if (interaction) {
|
|
417
|
+
this.map.removeInteraction(interaction);
|
|
418
|
+
this.renderInteractions.delete(uid);
|
|
498
419
|
}
|
|
499
|
-
|
|
500
|
-
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 清理所有交互式渲染交互
|
|
423
|
+
*/
|
|
424
|
+
clearRenderInteractions() {
|
|
425
|
+
this.renderInteractions.forEach(interaction => {
|
|
426
|
+
this.map.removeInteraction(interaction);
|
|
427
|
+
});
|
|
428
|
+
this.renderInteractions.clear();
|
|
429
|
+
}
|
|
430
|
+
attachEventListeners(interaction, options) {
|
|
431
|
+
interaction.on('select', (event) => {
|
|
432
|
+
// 1. 处理渲染:选中时创建渲染实例
|
|
433
|
+
event.selected.forEach(feature => {
|
|
434
|
+
if (feature instanceof Feature) {
|
|
435
|
+
this.createRenderInteraction(feature);
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
// 2. 处理渲染:取消选中时销毁渲染实例
|
|
439
|
+
event.deselected.forEach(feature => {
|
|
440
|
+
if (feature instanceof Feature) {
|
|
441
|
+
this.removeRenderInteraction(feature);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
// 3. 触发回调
|
|
501
445
|
const callbackEvent = {
|
|
502
446
|
selected: event.selected,
|
|
503
447
|
deselected: event.deselected,
|
|
504
448
|
mapBrowserEvent: event.mapBrowserEvent
|
|
505
449
|
};
|
|
506
|
-
|
|
507
|
-
if (options.onSelect && event.selected.length > 0) {
|
|
450
|
+
if (options?.onSelect && event.selected.length > 0) {
|
|
508
451
|
try {
|
|
509
452
|
options.onSelect(callbackEvent);
|
|
510
453
|
}
|
|
511
|
-
catch (
|
|
512
|
-
this.errorHandler.error('
|
|
454
|
+
catch (e) {
|
|
455
|
+
this.errorHandler.error('选择回调失败:', e);
|
|
513
456
|
}
|
|
514
457
|
}
|
|
515
|
-
|
|
516
|
-
if (options.onDeselect && event.deselected.length > 0) {
|
|
458
|
+
if (options?.onDeselect && event.deselected.length > 0) {
|
|
517
459
|
try {
|
|
518
460
|
options.onDeselect(callbackEvent);
|
|
519
461
|
}
|
|
520
|
-
catch (
|
|
521
|
-
this.errorHandler.error('
|
|
462
|
+
catch (e) {
|
|
463
|
+
this.errorHandler.error('取消选择回调失败:', e);
|
|
522
464
|
}
|
|
523
465
|
}
|
|
524
466
|
});
|
package/docs/SelectHandler.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
`SelectHandler` 负责地图要素的交互选择,支持点击、悬停等多种模式,以及编程式选择。
|
|
4
4
|
|
|
5
|
+
## 核心特性
|
|
6
|
+
|
|
7
|
+
- **独立渲染机制**:采用独立的 Select 实例渲染选中要素,确保样式完全隔离,互不干扰。
|
|
8
|
+
- **静态样式固化**:在选中瞬间计算并固化样式,支持基于原始样式的复杂计算(如 `feature.getStyle()`),彻底避免递归调用风险。
|
|
9
|
+
- **多选隔离**:支持不同要素同时应用完全不同的高亮样式。
|
|
10
|
+
- **自动清理**:自动管理渲染实例的生命周期,防止内存泄漏。
|
|
11
|
+
|
|
5
12
|
## 方法
|
|
6
13
|
|
|
7
14
|
### 交互选择
|
|
@@ -13,24 +20,26 @@
|
|
|
13
20
|
- `layerFilter`: 图层过滤器(图层名称数组)。
|
|
14
21
|
- `onSelect`: 选中回调。
|
|
15
22
|
- `onDeselect`: 取消选中回调。
|
|
16
|
-
- `selectStyle`:
|
|
23
|
+
- `selectStyle`: 选中时的样式。支持 `Style` 对象、数组或函数 `(feature, resolution) => Style`。**推荐使用函数**以实现基于原始样式的动态高亮。
|
|
17
24
|
|
|
18
|
-
- **disableSelect()**:
|
|
25
|
+
- **disableSelect()**: 禁用选择交互(停止响应用户操作,并清理交互式高亮)。
|
|
19
26
|
- **isSelectEnabled()**: 检查是否已启用。
|
|
20
27
|
|
|
21
28
|
### 编程式选择
|
|
22
29
|
|
|
23
30
|
- **selectByIds(featureIds: string[], options?)**: 根据 ID 选中要素。
|
|
24
31
|
- **selectByProperty(propertyName: string, propertyValue: any, options?)**: 根据属性值选中要素。
|
|
25
|
-
- **clearSelection()**:
|
|
26
|
-
- **getSelectedFeatures()**:
|
|
32
|
+
- **clearSelection()**: 清除当前所有选中状态(包括交互式和编程式)。
|
|
33
|
+
- **getSelectedFeatures()**: 获取当前选中的要素列表(仅包含交互式选择的要素)。
|
|
27
34
|
|
|
28
35
|
## 使用示例
|
|
29
36
|
|
|
37
|
+
### 1. 基础用法
|
|
38
|
+
|
|
30
39
|
```typescript
|
|
31
40
|
const selectHandler = map.getSelectHandler();
|
|
32
41
|
|
|
33
|
-
//
|
|
42
|
+
// 启用点击选择
|
|
34
43
|
selectHandler.enableSelect('click', {
|
|
35
44
|
multi: false, // 单选
|
|
36
45
|
layerFilter: ['target-layer'], // 仅选择特定图层的要素
|
|
@@ -41,7 +50,7 @@ selectHandler.enableSelect('click', {
|
|
|
41
50
|
onDeselect: (event) => {
|
|
42
51
|
console.log('取消选中:', event.deselected);
|
|
43
52
|
},
|
|
44
|
-
//
|
|
53
|
+
// 自定义选中样式(简单对象)
|
|
45
54
|
selectStyle: new Style({
|
|
46
55
|
image: new Circle({
|
|
47
56
|
radius: 8,
|
|
@@ -50,10 +59,68 @@ selectHandler.enableSelect('click', {
|
|
|
50
59
|
})
|
|
51
60
|
})
|
|
52
61
|
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. 高级用法:基于原始样式的动态高亮
|
|
65
|
+
|
|
66
|
+
利用函数式 `selectStyle`,可以在选中时动态获取要素的原始样式,并在此基础上修改(例如只改边框颜色,保留填充)。由于采用了**样式固化**机制,这种写法是安全的。
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
selectHandler.enableSelect('click', {
|
|
70
|
+
selectStyle: (feature, resolution) => {
|
|
71
|
+
// 获取要素的原始样式
|
|
72
|
+
// 注意:feature.getStyle() 可能返回 Style 对象、数组或函数,需要自行处理归一化
|
|
73
|
+
const originStyleLike = feature.getStyle();
|
|
74
|
+
let originStyle = originStyleLike;
|
|
75
|
+
|
|
76
|
+
// 如果原始样式是函数,执行它获取 Style 对象
|
|
77
|
+
if (typeof originStyleLike === 'function') {
|
|
78
|
+
originStyle = originStyleLike(feature, resolution);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 归一化为数组
|
|
82
|
+
const styles = Array.isArray(originStyle) ? originStyle : [originStyle];
|
|
83
|
+
|
|
84
|
+
// 克隆并修改样式(例如:将边框改为青色)
|
|
85
|
+
return styles.map(s => {
|
|
86
|
+
const clone = s.clone();
|
|
87
|
+
const stroke = clone.getStroke();
|
|
88
|
+
if (stroke) {
|
|
89
|
+
stroke.setColor('#00FFFF');
|
|
90
|
+
stroke.setWidth(3);
|
|
91
|
+
} else {
|
|
92
|
+
// 如果没有边框,添加一个
|
|
93
|
+
clone.setStroke(new Stroke({ color: '#00FFFF', width: 3 }));
|
|
94
|
+
}
|
|
95
|
+
return clone;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
```
|
|
53
100
|
|
|
54
|
-
|
|
55
|
-
|
|
101
|
+
### 3. 编程式选择(带独立样式)
|
|
102
|
+
|
|
103
|
+
编程式选择支持传入特定的样式,该样式将独立于主交互样式生效,且支持多要素使用不同样式。
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// 选中 ID 为 'feature-1' 的要素,并用红色高亮
|
|
107
|
+
selectHandler.selectByIds(['feature-1'], {
|
|
108
|
+
selectStyle: new Style({ stroke: new Stroke({ color: 'red', width: 4 }) }),
|
|
109
|
+
fitView: true // 自动定位
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// 同时选中 ID 为 'feature-2' 的要素,但用蓝色高亮(互不冲突)
|
|
113
|
+
selectHandler.selectByIds(['feature-2'], {
|
|
114
|
+
selectStyle: new Style({ stroke: new Stroke({ color: 'blue', width: 4 }) })
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. 禁用与清理
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// 禁用用户交互(停止响应点击,但保留编程式高亮)
|
|
122
|
+
selectHandler.disableSelect();
|
|
56
123
|
|
|
57
|
-
//
|
|
58
|
-
|
|
124
|
+
// 清除所有选择(包括交互式和编程式的高亮)
|
|
125
|
+
selectHandler.clearSelection();
|
|
59
126
|
```
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-openlayer",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.4",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"types": "index.d.ts",
|
|
8
9
|
"keywords": [
|
|
@@ -12,6 +13,14 @@
|
|
|
12
13
|
"map",
|
|
13
14
|
"vue"
|
|
14
15
|
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/cuteyuchen/my-openlayer.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/cuteyuchen/my-openlayer/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/cuteyuchen/my-openlayer#readme",
|
|
15
24
|
"files": [
|
|
16
25
|
"**/*",
|
|
17
26
|
"LICENSE",
|