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 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)**: 验证工具。
@@ -1,33 +1,10 @@
1
- import Map from "ol/Map";
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 selectInteraction?;
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: 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
- isSelectEnabled(): boolean;
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 createSelectStyle;
90
+ private calculateStyle;
142
91
  /**
143
- * 更新选择样式
144
- * @param selectStyle 新的选择样式
145
- * @returns SelectHandler 实例(支持链式调用)
92
+ * 为单个要素创建并添加渲染交互
146
93
  */
147
- updateSelectStyle(selectStyle: Style | Style[] | ((feature: FeatureLike) => Style | Style[])): this;
94
+ private createRenderInteraction;
148
95
  /**
149
- * 确保 Select 交互已创建
150
- * 如果当前未启用选择交互,则创建一个不响应用户操作的交互实例用于编程式选择
151
- * @private
96
+ * 移除单个要素的渲染交互
152
97
  */
153
- private ensureSelectInteraction;
98
+ private removeRenderInteraction;
154
99
  /**
155
- * 附加事件监听器
156
- * @private
100
+ * 清理所有交互式渲染交互
157
101
  */
102
+ private clearRenderInteractions;
158
103
  private attachEventListeners;
159
104
  }
@@ -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
- const mergedOptions = this.mergeOptions(options);
88
- this.currentMode = mode;
89
- this.currentOptions = mergedOptions;
90
- // 创建 Select 交互
91
- this.selectInteraction = this.createSelectInteraction(mode, mergedOptions);
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(mergedOptions);
82
+ this.attachEventListeners(this.mainSelectInteraction, options);
94
83
  // 添加到地图
95
- this.map.addInteraction(this.selectInteraction);
84
+ this.map.addInteraction(this.mainSelectInteraction);
96
85
  this.isEnabled = true;
97
- this.errorHandler.debug('要素选择已启用', { mode, options: mergedOptions });
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.selectInteraction) {
112
- this.map.removeInteraction(this.selectInteraction);
113
- this.selectInteraction = undefined;
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
- if (!this.selectInteraction) {
131
- return [];
123
+ const features = [];
124
+ if (this.mainSelectInteraction) {
125
+ features.push(...this.mainSelectInteraction.getFeatures().getArray());
132
126
  }
133
- const features = this.selectInteraction.getFeatures();
134
- return features.getArray();
127
+ return features;
135
128
  }
136
129
  /**
137
130
  * 清除所有选择
138
131
  * @returns SelectHandler 实例(支持链式调用)
139
132
  */
140
133
  clearSelection() {
141
- if (this.selectInteraction) {
142
- this.selectInteraction.getFeatures().clear();
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
- this.clearSelection();
166
- // 临时存储选中的要素
167
- const selectedFeatures = [];
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
- this.clearSelection();
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
- isSelectEnabled() {
281
- return this.isEnabled;
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
- * @private
293
- * @param features 要素数组
294
- * @param duration 动画持续时间(毫秒),默认500
295
- * @param padding 边距(像素),默认100
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
- return click;
391
- case 'hover':
392
- return pointerMove;
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
- createSelectStyle(customStyle) {
417
- if (customStyle) {
418
- return customStyle;
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
- const geometryType = geometry.getType();
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
- try {
453
- // 更新选择交互的样式
454
- this.selectInteraction.getStyle = () => {
455
- if (typeof selectStyle === 'function') {
456
- return selectStyle;
457
- }
458
- return selectStyle;
459
- };
460
- // 触发样式更新
461
- const features = this.selectInteraction.getFeatures();
462
- features.changed();
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
- * 确保 Select 交互已创建
472
- * 如果当前未启用选择交互,则创建一个不响应用户操作的交互实例用于编程式选择
473
- * @private
393
+ * 为单个要素创建并添加渲染交互
474
394
  */
475
- ensureSelectInteraction() {
476
- if (this.selectInteraction) {
477
- return;
478
- }
479
- // 创建一个不响应任何用户操作的 Select 交互
480
- // condition 返回 false 表示不响应任何事件
481
- this.selectInteraction = new Select({
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: this.createSelectStyle()
403
+ style: style,
404
+ features: new Collection([feature]),
405
+ hitTolerance: 0
484
406
  });
485
- this.map.addInteraction(this.selectInteraction);
486
- this.isEnabled = true;
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
- attachEventListeners(options) {
496
- if (!this.selectInteraction) {
497
- return;
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
- this.selectInteraction.on('select', (event) => {
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 (error) {
512
- this.errorHandler.error('选择回调执行失败:', 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 (error) {
521
- this.errorHandler.error('取消选择回调执行失败:', error);
462
+ catch (e) {
463
+ this.errorHandler.error('取消选择回调失败:', e);
522
464
  }
523
465
  }
524
466
  });
@@ -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
- // 1. 启用点击选择
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
- // 2. 编程式选择(例如在列表中点击条目,高亮地图要素)
55
- selectHandler.selectByProperty('id', 'feature-123');
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
- // 3. 禁用选择
58
- // selectHandler.disableSelect();
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.3",
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",