my-openlayer 2.0.0 → 2.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/core/Polygon.js CHANGED
@@ -1,529 +1,605 @@
1
- "use strict";
2
- import VectorLayer from "ol/layer/Vector";
3
- import VectorSource from "ol/source/Vector";
4
- import GeoJSON from "ol/format/GeoJSON";
5
- import { Fill, Stroke, Style, Text } from "ol/style";
6
- import { Image as ImageLayer, Heatmap } from "ol/layer";
7
- import { Geometry, LinearRing, Point } from "ol/geom";
8
- import { fromExtent } from "ol/geom/Polygon";
9
- import Feature from "ol/Feature";
10
- import ImageStatic from "ol/source/ImageStatic";
11
- import MapTools from "./MapTools";
12
- import { ValidationUtils } from '../utils/ValidationUtils';
13
- /**
14
- * Polygon 类用于处理地图上的面要素操作
15
- * 包括添加多边形、边框、图片图层、热力图等功能
16
- */
17
- export default class Polygon {
18
- /**
19
- * 构造函数
20
- * @param map OpenLayers 地图实例
21
- */
22
- constructor(map) {
23
- this.colorMap = {
24
- '0': 'rgba(255, 0, 0, 0.6)',
25
- '1': 'rgba(245, 154, 35, 0.6)',
26
- '2': 'rgba(255, 238, 0, 0.6)',
27
- '3': 'rgba(1, 111, 255, 0.6)'
28
- };
29
- if (!map) {
30
- throw new Error('Map instance is required');
31
- }
32
- this.map = map;
33
- }
34
- /**
35
- * 获取等级颜色
36
- * @param lev 等级值,支持字符串或数字
37
- * @returns 对应等级的颜色值,如果等级不存在则返回默认颜色
38
- */
39
- getLevColor(lev) {
40
- const key = lev.toString();
41
- return this.colorMap[key] || 'rgba(128, 128, 128, 0.6)';
42
- }
43
- /**
44
- * 添加地图边框图层
45
- * @param data 图层数据,必须是有效的 GeoJSON 格式
46
- * @param options 图层配置选项
47
- * @returns 创建的图层实例
48
- * @throws 当数据格式无效时抛出错误
49
- */
50
- addBorderPolygon(data, options) {
51
- ValidationUtils.validateGeoJSONData(data);
52
- const mergedOptions = {
53
- layerName: 'border',
54
- fillColor: 'rgba(255, 255, 255, 0)',
55
- ...options
56
- };
57
- const layer = this.addPolygon(data, mergedOptions);
58
- if (mergedOptions.mask) {
59
- this.setOutLayer(data);
60
- }
61
- return layer;
62
- }
63
- /**
64
- * 添加多边形图层
65
- * @param dataJSON GeoJSON 数据
66
- * @param options 图层配置选项
67
- * @returns 创建的矢量图层
68
- * @throws 当数据格式无效时抛出错误
69
- */
70
- addPolygon(dataJSON, options) {
71
- ValidationUtils.validateGeoJSONData(dataJSON);
72
- const mergedOptions = {
73
- zIndex: 11,
74
- visible: true,
75
- strokeColor: '#EBEEF5',
76
- strokeWidth: 2,
77
- fillColor: 'rgba(255, 255, 255, 0)',
78
- textFont: '14px Calibri,sans-serif',
79
- textFillColor: '#FFF',
80
- textStrokeColor: '#409EFF',
81
- textStrokeWidth: 2,
82
- ...options
83
- };
84
- // 如果指定了图层名称,先移除同名图层
85
- if (mergedOptions.layerName) {
86
- new MapTools(this.map).removeLayer(mergedOptions.layerName);
87
- }
88
- let features;
89
- try {
90
- features = new GeoJSON().readFeatures(dataJSON, mergedOptions.projectionOptOptions ?? {});
91
- }
92
- catch (error) {
93
- throw new Error(`Failed to parse GeoJSON data: ${error}`);
94
- }
95
- const layer = new VectorLayer({
96
- properties: {
97
- name: mergedOptions.layerName,
98
- layerName: mergedOptions.layerName
99
- },
100
- source: new VectorSource({ features }),
101
- zIndex: mergedOptions.zIndex
102
- });
103
- // 设置要素样式
104
- this.setFeatureStyles(features, mergedOptions);
105
- layer.setVisible(mergedOptions.visible);
106
- this.map.addLayer(layer);
107
- // 如果需要适应视图
108
- if (mergedOptions.fitView) {
109
- this.fitViewToLayer(layer);
110
- }
111
- return layer;
112
- }
113
- /**
114
- * 设置要素样式
115
- * @param features 要素数组
116
- * @param options 样式配置选项
117
- */
118
- setFeatureStyles(features, options) {
119
- features.forEach(feature => {
120
- feature.set('type', options.layerName);
121
- feature.set('layerName', options.layerName);
122
- const fillColor = options.fillColorCallBack ? options.fillColorCallBack(feature) : options.fillColor;
123
- const featureStyle = new Style({
124
- stroke: new Stroke({
125
- color: options.strokeColor,
126
- width: options.strokeWidth,
127
- lineDash: options.lineDash,
128
- lineDashOffset: options.lineDashOffset
129
- }),
130
- fill: new Fill({ color: fillColor })
131
- });
132
- // 添加文本样式
133
- if (options.textVisible) {
134
- const text = this.getFeatureText(feature, options);
135
- if (text) {
136
- featureStyle.setText(new Text({
137
- text,
138
- font: options.textFont,
139
- fill: new Fill({ color: options.textFillColor }),
140
- stroke: new Stroke({
141
- color: options.textStrokeColor,
142
- width: options.textStrokeWidth
143
- })
144
- }));
145
- }
146
- }
147
- feature.setStyle(featureStyle);
148
- });
149
- }
150
- /**
151
- * 获取要素文本
152
- * @param feature 要素对象
153
- * @param options 配置选项
154
- * @returns 文本内容
155
- */
156
- getFeatureText(feature, options) {
157
- if (options.textCallBack) {
158
- return options.textCallBack(feature) || '';
159
- }
160
- if (options.textKey) {
161
- return feature.get(options.textKey) || '';
162
- }
163
- return '';
164
- }
165
- /**
166
- * 适应图层视图
167
- * @param layer 图层对象
168
- */
169
- fitViewToLayer(layer) {
170
- const extent = layer.getSource()?.getExtent();
171
- if (extent) {
172
- this.map.getView().fit(extent, { duration: 500 });
173
- }
174
- }
175
- /**
176
- * 根据数据数组更新某个面颜色
177
- * @param layerName 图层名称
178
- * @param colorObj 颜色映射对象,键为要素属性值,值为颜色字符串
179
- * @param options 配置项
180
- * @throws 当图层不存在时抛出错误
181
- */
182
- updateFeatureColor(layerName, colorObj, options) {
183
- ValidationUtils.validateLayerName(layerName);
184
- const layers = MapTools.getLayerByLayerName(this.map, layerName);
185
- if (layers.length === 0) {
186
- throw new Error(`Layer with name '${layerName}' not found`);
187
- }
188
- const layer = layers[0];
189
- if (!(layer instanceof VectorLayer)) {
190
- throw new Error(`Layer '${layerName}' is not a vector layer`);
191
- }
192
- const mergedOptions = {
193
- strokeColor: '#EBEEF5',
194
- strokeWidth: 2,
195
- fillColor: 'rgba(255, 255, 255, 0.3)',
196
- textFont: '14px Calibri,sans-serif',
197
- textFillColor: '#FFF',
198
- textStrokeWidth: 2,
199
- ...options
200
- };
201
- const features = layer.getSource()?.getFeatures();
202
- if (!features) {
203
- console.warn(`No features found in layer '${layerName}'`);
204
- return;
205
- }
206
- features.forEach((feature) => {
207
- this.updateSingleFeatureColor(feature, colorObj, mergedOptions);
208
- });
209
- }
210
- /**
211
- * 更新单个要素的颜色
212
- * @param feature 要素对象
213
- * @param colorObj 颜色映射对象
214
- * @param options 配置选项
215
- */
216
- updateSingleFeatureColor(feature, colorObj, options) {
217
- const name = options?.textKey ? feature.get(options.textKey) : '';
218
- const newColor = colorObj?.[name] || options?.fillColor;
219
- const featureStyle = new Style({
220
- stroke: new Stroke({
221
- color: options?.strokeColor,
222
- width: options?.strokeWidth
223
- }),
224
- fill: new Fill({ color: newColor })
225
- });
226
- // 添加文本样式
227
- if (options?.textVisible) {
228
- const text = this.getFeatureText(feature, options);
229
- if (text) {
230
- featureStyle.setText(new Text({
231
- text,
232
- font: options.textFont,
233
- fill: new Fill({ color: options.textFillColor }),
234
- stroke: new Stroke({
235
- color: options.textStrokeColor,
236
- width: options.textStrokeWidth
237
- })
238
- }));
239
- }
240
- }
241
- feature.setStyle(featureStyle);
242
- }
243
- /**
244
- * 设置外围蒙版图层
245
- *
246
- * 详细文档参考 https_blog.csdn.net/?url=https%3A%2F%2Fblog.csdn.net%2Fu012413551%2Farticle%2Fdetails%2F122739501
247
- *
248
- * @param data
249
- * @param options
250
- */
251
- setOutLayer(data, options) {
252
- /** geom转坐标数组 **/
253
- function getCoordsGroup(geom) {
254
- let group = []; //
255
- const geomType = geom.getType();
256
- if (geomType === 'LineString') {
257
- group.push(geom.getCoordinates());
258
- }
259
- else if (geomType === 'MultiLineString') {
260
- group = geom.getCoordinates();
261
- }
262
- else if (geomType === 'Polygon') {
263
- group = geom.getCoordinates();
264
- }
265
- else if (geomType === 'MultiPolygon') {
266
- geom.getPolygons().forEach((poly) => {
267
- const coords = poly.getCoordinates();
268
- group = group.concat(coords);
269
- });
270
- }
271
- else {
272
- console.log('暂时不支持的类型');
273
- }
274
- return group;
275
- }
276
- /** 擦除操作 **/
277
- function erase(geom, view) {
278
- const part = getCoordsGroup(geom);
279
- if (!part) {
280
- return;
281
- }
282
- const extent = view.getProjection().getExtent();
283
- const polygonRing = fromExtent(extent);
284
- part.forEach((item) => {
285
- const linearRing = new LinearRing(item);
286
- polygonRing.appendLinearRing(linearRing);
287
- });
288
- return polygonRing;
289
- }
290
- /** 添加遮罩 **/
291
- function createShade(geom, view) {
292
- if (geom instanceof Geometry) {
293
- const source = geom.clone();
294
- const polygon = erase(source, view);
295
- const feature = new Feature({
296
- geometry: polygon
297
- });
298
- return {
299
- feature,
300
- shade: source
301
- };
302
- }
303
- }
304
- // 遮罩样式
305
- const shadeStyle = new Style({
306
- fill: new Fill({
307
- color: options?.fillColor ?? 'rgba(0,27,59,0.8)'
308
- }),
309
- stroke: new Stroke({
310
- width: options?.strokeWidth ?? 1,
311
- color: options?.strokeColor ?? 'rgba(0,27,59,0.8)'
312
- })
313
- });
314
- // 遮罩数据源
315
- const vtSource = new VectorSource();
316
- // 遮罩图层
317
- const vtLayer = new VectorLayer({
318
- source: vtSource,
319
- style: shadeStyle,
320
- zIndex: options?.zIndex ?? 12
321
- });
322
- this.map.addLayer(vtLayer);
323
- const features = new GeoJSON().readFeatures(data);
324
- const ft = features[0];
325
- const bound = ft.getGeometry();
326
- const result = createShade(bound, this.map.getView());
327
- if (result) {
328
- vtSource.addFeature(result.feature);
329
- if (options?.extent)
330
- this.map.getView().fit(result.shade);
331
- }
332
- }
333
- /**
334
- * 添加图片图层
335
- * @param imageData 图片数据,包含url和extent
336
- * @param options 配置项
337
- * @returns 创建的图片图层
338
- * @throws 当数据格式无效时抛出错误
339
- */
340
- addImageLayer(imageData, options) {
341
- // 检查是否允许空img(当存在layerName且存在同名图层时)
342
- const allowEmptyImg = !imageData.img && !!options?.layerName;
343
- ValidationUtils.validateImageData(imageData, allowEmptyImg);
344
- const mergedOptions = {
345
- opacity: 1,
346
- visible: true,
347
- zIndex: 11,
348
- layerName: 'imageLayer',
349
- ...options
350
- };
351
- // 尝试更新现有图层
352
- if (mergedOptions.layerName) {
353
- const existingLayer = this.tryUpdateExistingImageLayer(imageData, mergedOptions);
354
- if (existingLayer) {
355
- return existingLayer;
356
- }
357
- }
358
- // 创建新图层
359
- return this.createNewImageLayer(imageData, mergedOptions);
360
- }
361
- /**
362
- * 尝试更新现有图层
363
- * @private
364
- */
365
- tryUpdateExistingImageLayer(imageData, options) {
366
- const existingLayers = MapTools.getLayerByLayerName(this.map, options.layerName);
367
- if (existingLayers.length === 0) {
368
- return null;
369
- }
370
- const existingLayer = existingLayers[0];
371
- // 如果没有extent,直接设置source为undefined
372
- if (!imageData.extent) {
373
- existingLayer.setSource(undefined);
374
- }
375
- else {
376
- // 创建新的source
377
- const url = imageData.img || existingLayer.getSource()?.getUrl() || '';
378
- const newSource = new ImageStatic({
379
- url,
380
- imageExtent: imageData.extent
381
- });
382
- existingLayer.setSource(newSource);
383
- }
384
- // 更新图层属性
385
- this.updateImageLayerProperties(existingLayer, options);
386
- return existingLayer;
387
- }
388
- /**
389
- * 创建新的图像图层
390
- * @private
391
- */
392
- createNewImageLayer(imageData, options) {
393
- let source = undefined;
394
- // 只有当extent存在时才创建ImageStatic source
395
- if (imageData.extent) {
396
- source = new ImageStatic({
397
- url: imageData.img || '',
398
- imageExtent: imageData.extent
399
- });
400
- }
401
- const imageLayer = new ImageLayer({
402
- source,
403
- opacity: options.opacity,
404
- visible: options.visible
405
- });
406
- this.configureImageLayer(imageLayer, options);
407
- return this.addImageLayerToMap(imageLayer, options);
408
- }
409
- /**
410
- * 更新图层属性
411
- * @private
412
- */
413
- updateImageLayerProperties(layer, options) {
414
- if (options.opacity !== undefined) {
415
- layer.setOpacity(options.opacity);
416
- }
417
- if (options.visible !== undefined) {
418
- layer.setVisible(options.visible);
419
- }
420
- if (options.zIndex !== undefined) {
421
- layer.setZIndex(options.zIndex);
422
- }
423
- }
424
- /**
425
- * 配置图层基本属性
426
- * @private
427
- */
428
- configureImageLayer(layer, options) {
429
- layer.set('name', options.layerName);
430
- layer.set('layerName', options.layerName);
431
- layer.setZIndex(options.zIndex);
432
- }
433
- /**
434
- * 添加图层到地图并应用裁剪
435
- * @private
436
- */
437
- addImageLayerToMap(layer, options) {
438
- if (options.mapClip && options.mapClipData) {
439
- const clippedLayer = MapTools.setMapClip(layer, options.mapClipData);
440
- this.map.addLayer(clippedLayer);
441
- return clippedLayer;
442
- }
443
- this.map.addLayer(layer);
444
- return layer;
445
- }
446
- /**
447
- * 添加热力图图层
448
- * @param pointData 点数据数组
449
- * @param options 热力图配置
450
- */
451
- addHeatmap(pointData, options) {
452
- // 只有在指定layerName时才移除已存在的同名图层
453
- if (options?.layerName) {
454
- new MapTools(this.map).removeLayer(options.layerName);
455
- }
456
- const heatmapLayer = new Heatmap({
457
- source: new VectorSource(),
458
- weight: function (fea) {
459
- return fea.get('weight');
460
- },
461
- blur: options?.blur ?? 15,
462
- radius: options?.radius ?? 10,
463
- zIndex: options?.zIndex ?? 11,
464
- opacity: options?.opacity ?? 1,
465
- });
466
- // 只有在指定layerName时才设置layerName
467
- if (options?.layerName) {
468
- heatmapLayer.set('layerName', options.layerName);
469
- }
470
- this.map.addLayer(heatmapLayer);
471
- const valueKey = options?.valueKey || 'value';
472
- const max = Math.max(...pointData.map(item => item[valueKey]));
473
- pointData.forEach((item) => {
474
- heatmapLayer?.getSource().addFeature(new Feature({
475
- geometry: new Point([item.lgtd, item.lttd]),
476
- weight: item[valueKey] / max //热力值范围是【0,1】;热力值计算 = 找出数据集中的最大值,然后用值除以最大值
477
- }));
478
- });
479
- return heatmapLayer;
480
- }
481
- /**
482
- * 添加遮罩图层
483
- * @param data GeoJSON格式的遮罩数据
484
- * @param options 配置项
485
- * @returns 创建的遮罩图层
486
- * @throws 当数据格式无效时抛出错误
487
- */
488
- addMaskLayer(data, options) {
489
- ValidationUtils.validateMaskData(data);
490
- const mergedOptions = {
491
- fillColor: 'rgba(0, 0, 0, 0.5)',
492
- opacity: 1,
493
- visible: true,
494
- layerName: 'maskLayer',
495
- ...options
496
- };
497
- let features;
498
- try {
499
- features = new GeoJSON().readFeatures(data);
500
- }
501
- catch (error) {
502
- throw new Error(`Invalid GeoJSON data: ${error}`);
503
- }
504
- if (!features || features.length === 0) {
505
- console.warn('No features found in mask data');
506
- }
507
- const maskLayer = new VectorLayer({
508
- source: new VectorSource({ features }),
509
- style: new Style({
510
- fill: new Fill({
511
- color: mergedOptions.fillColor
512
- }),
513
- stroke: mergedOptions.strokeColor ? new Stroke({
514
- color: mergedOptions.strokeColor,
515
- width: mergedOptions.strokeWidth || 1
516
- }) : undefined
517
- }),
518
- opacity: mergedOptions.opacity,
519
- visible: mergedOptions.visible
520
- });
521
- maskLayer.set('layerName', mergedOptions.layerName);
522
- this.map.addLayer(maskLayer);
523
- return maskLayer;
524
- }
525
- removePolygonLayer(layerName) {
526
- new MapTools(this.map).removeLayer(layerName);
527
- this[layerName] = null;
528
- }
529
- }
1
+ "use strict";
2
+ import VectorLayer from "ol/layer/Vector";
3
+ import VectorSource from "ol/source/Vector";
4
+ import GeoJSON from "ol/format/GeoJSON";
5
+ import { Fill, Stroke, Style, Text } from "ol/style";
6
+ import { Image as ImageLayer, Heatmap } from "ol/layer";
7
+ import { Geometry, LinearRing, Point } from "ol/geom";
8
+ import { fromExtent } from "ol/geom/Polygon";
9
+ import Feature from "ol/Feature";
10
+ import ImageStatic from "ol/source/ImageStatic";
11
+ import MapTools from "./MapTools";
12
+ import { ValidationUtils } from '../utils/ValidationUtils';
13
+ /**
14
+ * Polygon 类用于处理地图上的面要素操作
15
+ * 包括添加多边形、边框、图片图层、热力图等功能
16
+ */
17
+ export default class Polygon {
18
+ /**
19
+ * 构造函数
20
+ * @param map OpenLayers 地图实例
21
+ */
22
+ constructor(map) {
23
+ this.colorMap = {
24
+ '0': 'rgba(255, 0, 0, 0.6)',
25
+ '1': 'rgba(245, 154, 35, 0.6)',
26
+ '2': 'rgba(255, 238, 0, 0.6)',
27
+ '3': 'rgba(1, 111, 255, 0.6)'
28
+ };
29
+ if (!map) {
30
+ throw new Error('Map instance is required');
31
+ }
32
+ this.map = map;
33
+ }
34
+ /**
35
+ * 获取等级颜色
36
+ * @param lev 等级值,支持字符串或数字
37
+ * @returns 对应等级的颜色值,如果等级不存在则返回默认颜色
38
+ */
39
+ getLevColor(lev) {
40
+ const key = lev.toString();
41
+ return this.colorMap[key] || 'rgba(128, 128, 128, 0.6)';
42
+ }
43
+ /**
44
+ * 添加地图边框图层
45
+ * @param data 图层数据,必须是有效的 GeoJSON 格式
46
+ * @param options 图层配置选项
47
+ * @returns 创建的图层实例
48
+ * @throws 当数据格式无效时抛出错误
49
+ */
50
+ addBorderPolygon(data, options) {
51
+ ValidationUtils.validateGeoJSONData(data);
52
+ const mergedOptions = {
53
+ fillColor: 'rgba(255, 255, 255, 0)',
54
+ ...options
55
+ };
56
+ const layer = this.addPolygon(data, mergedOptions);
57
+ if (mergedOptions.mask) {
58
+ this.setOutLayer(data);
59
+ }
60
+ return layer;
61
+ }
62
+ /**
63
+ * 从URL添加地图边框图层
64
+ * @param url 数据URL
65
+ * @param options 图层配置选项
66
+ * @returns 创建的图层实例
67
+ * @throws 当数据格式无效时抛出错误
68
+ */
69
+ addBorderPolygonByUrl(url, options) {
70
+ const mergedOptions = {
71
+ layerName: 'border',
72
+ fillColor: 'rgba(255, 255, 255, 0)',
73
+ ...options
74
+ };
75
+ const layer = this.addPolygonByUrl(url, mergedOptions);
76
+ return layer;
77
+ }
78
+ /**
79
+ * 添加多边形图层
80
+ * @param dataJSON GeoJSON 数据
81
+ * @param options 图层配置选项
82
+ * @returns 创建的矢量图层
83
+ * @throws 当数据格式无效时抛出错误
84
+ */
85
+ addPolygon(dataJSON, options) {
86
+ ValidationUtils.validateGeoJSONData(dataJSON);
87
+ const mergedOptions = {
88
+ zIndex: 11,
89
+ visible: true,
90
+ strokeColor: '#EBEEF5',
91
+ strokeWidth: 2,
92
+ fillColor: 'rgba(255, 255, 255, 0)',
93
+ textFont: '14px Calibri,sans-serif',
94
+ textFillColor: '#FFF',
95
+ textStrokeColor: '#409EFF',
96
+ textStrokeWidth: 2,
97
+ ...options
98
+ };
99
+ // 如果指定了图层名称,先移除同名图层
100
+ if (mergedOptions.layerName) {
101
+ new MapTools(this.map).removeLayer(mergedOptions.layerName);
102
+ }
103
+ let features;
104
+ try {
105
+ features = new GeoJSON().readFeatures(dataJSON, mergedOptions.projectionOptOptions ?? {});
106
+ }
107
+ catch (error) {
108
+ throw new Error(`Failed to parse GeoJSON data: ${error}`);
109
+ }
110
+ const layer = new VectorLayer({
111
+ properties: {
112
+ name: mergedOptions.layerName,
113
+ layerName: mergedOptions.layerName
114
+ },
115
+ source: new VectorSource({ features }),
116
+ zIndex: mergedOptions.zIndex
117
+ });
118
+ // 设置要素样式
119
+ this.setFeatureStyles(features, mergedOptions);
120
+ layer.setVisible(mergedOptions.visible);
121
+ this.map.addLayer(layer);
122
+ // 如果需要适应视图
123
+ if (mergedOptions.fitView) {
124
+ this.fitViewToLayer(layer);
125
+ }
126
+ return layer;
127
+ }
128
+ /**
129
+ * 从URL添加多边形图层
130
+ * @param url 数据URL
131
+ * @param options 图层配置选项
132
+ * @returns 创建的矢量图层
133
+ * @throws 当数据格式无效时抛出错误
134
+ */
135
+ addPolygonByUrl(url, options) {
136
+ const mergedOptions = {
137
+ zIndex: 11,
138
+ visible: true,
139
+ strokeColor: '#EBEEF5',
140
+ strokeWidth: 2,
141
+ fillColor: 'rgba(255, 255, 255, 0)',
142
+ textFont: '14px Calibri,sans-serif',
143
+ textFillColor: '#FFF',
144
+ textStrokeColor: '#409EFF',
145
+ textStrokeWidth: 2,
146
+ ...options
147
+ };
148
+ // 如果指定了图层名称,先移除同名图层
149
+ if (mergedOptions.layerName) {
150
+ new MapTools(this.map).removeLayer(mergedOptions.layerName);
151
+ }
152
+ const source = new VectorSource({
153
+ url,
154
+ format: new GeoJSON(mergedOptions.projectionOptOptions ?? {})
155
+ });
156
+ const layer = new VectorLayer({
157
+ properties: {
158
+ name: mergedOptions.layerName,
159
+ layerName: mergedOptions.layerName
160
+ },
161
+ source,
162
+ zIndex: mergedOptions.zIndex
163
+ });
164
+ // 在数据加载后设置样式
165
+ source.once('featuresloadend', () => {
166
+ const loadedFeatures = source.getFeatures();
167
+ this.setFeatureStyles(loadedFeatures, mergedOptions);
168
+ });
169
+ layer.setVisible(mergedOptions.visible);
170
+ this.map.addLayer(layer);
171
+ // 如果需要适应视图
172
+ if (mergedOptions.fitView) {
173
+ source.once('featuresloadend', () => {
174
+ this.fitViewToLayer(layer);
175
+ });
176
+ }
177
+ return layer;
178
+ }
179
+ /**
180
+ * 设置要素样式
181
+ * @param features 要素数组
182
+ * @param options 样式配置选项
183
+ */
184
+ setFeatureStyles(features, options) {
185
+ features.forEach(feature => {
186
+ feature.set('type', options.layerName);
187
+ feature.set('layerName', options.layerName);
188
+ // 如果传入了自定义样式,直接使用
189
+ if (options.style) {
190
+ if (typeof options.style === 'function') {
191
+ feature.setStyle(options.style(feature));
192
+ }
193
+ else {
194
+ feature.setStyle(options.style);
195
+ }
196
+ return;
197
+ }
198
+ const fillColor = options.fillColorCallBack ? options.fillColorCallBack(feature) : options.fillColor;
199
+ const featureStyle = new Style({
200
+ stroke: new Stroke({
201
+ color: options.strokeColor,
202
+ width: options.strokeWidth,
203
+ lineDash: options.lineDash,
204
+ lineDashOffset: options.lineDashOffset
205
+ }),
206
+ fill: new Fill({ color: fillColor })
207
+ });
208
+ // 添加文本样式
209
+ if (options.textVisible) {
210
+ const text = this.getFeatureText(feature, options);
211
+ if (text) {
212
+ featureStyle.setText(new Text({
213
+ text,
214
+ font: options.textFont,
215
+ fill: new Fill({ color: options.textFillColor }),
216
+ stroke: new Stroke({
217
+ color: options.textStrokeColor,
218
+ width: options.textStrokeWidth
219
+ })
220
+ }));
221
+ }
222
+ }
223
+ feature.setStyle(featureStyle);
224
+ });
225
+ }
226
+ /**
227
+ * 获取要素文本
228
+ * @param feature 要素对象
229
+ * @param options 配置选项
230
+ * @returns 文本内容
231
+ */
232
+ getFeatureText(feature, options) {
233
+ if (options.textCallBack) {
234
+ return options.textCallBack(feature) || '';
235
+ }
236
+ if (options.textKey) {
237
+ return feature.get(options.textKey) || '';
238
+ }
239
+ return '';
240
+ }
241
+ /**
242
+ * 适应图层视图
243
+ * @param layer 图层对象
244
+ */
245
+ fitViewToLayer(layer) {
246
+ const extent = layer.getSource()?.getExtent();
247
+ if (extent) {
248
+ this.map.getView().fit(extent, { duration: 500 });
249
+ }
250
+ }
251
+ /**
252
+ * 根据数据数组更新某个面颜色
253
+ * @param layerName 图层名称
254
+ * @param colorObj 颜色映射对象,键为要素属性值,值为颜色字符串
255
+ * @param options 配置项
256
+ * @throws 当图层不存在时抛出错误
257
+ */
258
+ updateFeatureColor(layerName, colorObj, options) {
259
+ ValidationUtils.validateLayerName(layerName);
260
+ const layers = MapTools.getLayerByLayerName(this.map, layerName);
261
+ if (layers.length === 0) {
262
+ throw new Error(`Layer with name '${layerName}' not found`);
263
+ }
264
+ const layer = layers[0];
265
+ if (!(layer instanceof VectorLayer)) {
266
+ throw new Error(`Layer '${layerName}' is not a vector layer`);
267
+ }
268
+ const mergedOptions = {
269
+ strokeColor: '#EBEEF5',
270
+ strokeWidth: 2,
271
+ fillColor: 'rgba(255, 255, 255, 0.3)',
272
+ textFont: '14px Calibri,sans-serif',
273
+ textFillColor: '#FFF',
274
+ textStrokeWidth: 2,
275
+ ...options
276
+ };
277
+ const features = layer.getSource()?.getFeatures();
278
+ if (!features) {
279
+ console.warn(`No features found in layer '${layerName}'`);
280
+ return;
281
+ }
282
+ features.forEach((feature) => {
283
+ this.updateSingleFeatureColor(feature, colorObj, mergedOptions);
284
+ });
285
+ }
286
+ /**
287
+ * 更新单个要素的颜色
288
+ * @param feature 要素对象
289
+ * @param colorObj 颜色映射对象
290
+ * @param options 配置选项
291
+ */
292
+ updateSingleFeatureColor(feature, colorObj, options) {
293
+ const name = options?.textKey ? feature.get(options.textKey) : '';
294
+ const newColor = colorObj?.[name] || options?.fillColor;
295
+ const featureStyle = new Style({
296
+ stroke: new Stroke({
297
+ color: options?.strokeColor,
298
+ width: options?.strokeWidth
299
+ }),
300
+ fill: new Fill({ color: newColor })
301
+ });
302
+ // 添加文本样式
303
+ if (options?.textVisible) {
304
+ const text = this.getFeatureText(feature, options);
305
+ if (text) {
306
+ featureStyle.setText(new Text({
307
+ text,
308
+ font: options.textFont,
309
+ fill: new Fill({ color: options.textFillColor }),
310
+ stroke: new Stroke({
311
+ color: options.textStrokeColor,
312
+ width: options.textStrokeWidth
313
+ })
314
+ }));
315
+ }
316
+ }
317
+ feature.setStyle(featureStyle);
318
+ }
319
+ /**
320
+ * 设置外围蒙版图层
321
+ *
322
+ * 详细文档参考 https_blog.csdn.net/?url=https%3A%2F%2Fblog.csdn.net%2Fu012413551%2Farticle%2Fdetails%2F122739501
323
+ *
324
+ * @param data
325
+ * @param options
326
+ */
327
+ setOutLayer(data, options) {
328
+ /** geom转坐标数组 **/
329
+ function getCoordsGroup(geom) {
330
+ let group = []; //
331
+ const geomType = geom.getType();
332
+ if (geomType === 'LineString') {
333
+ group.push(geom.getCoordinates());
334
+ }
335
+ else if (geomType === 'MultiLineString') {
336
+ group = geom.getCoordinates();
337
+ }
338
+ else if (geomType === 'Polygon') {
339
+ group = geom.getCoordinates();
340
+ }
341
+ else if (geomType === 'MultiPolygon') {
342
+ geom.getPolygons().forEach((poly) => {
343
+ const coords = poly.getCoordinates();
344
+ group = group.concat(coords);
345
+ });
346
+ }
347
+ else {
348
+ console.log('暂时不支持的类型');
349
+ }
350
+ return group;
351
+ }
352
+ /** 擦除操作 **/
353
+ function erase(geom, view) {
354
+ const part = getCoordsGroup(geom);
355
+ if (!part) {
356
+ return;
357
+ }
358
+ const extent = view.getProjection().getExtent();
359
+ const polygonRing = fromExtent(extent);
360
+ part.forEach((item) => {
361
+ const linearRing = new LinearRing(item);
362
+ polygonRing.appendLinearRing(linearRing);
363
+ });
364
+ return polygonRing;
365
+ }
366
+ /** 添加遮罩 **/
367
+ function createShade(geom, view) {
368
+ if (geom instanceof Geometry) {
369
+ const source = geom.clone();
370
+ const polygon = erase(source, view);
371
+ const feature = new Feature({
372
+ geometry: polygon
373
+ });
374
+ return {
375
+ feature,
376
+ shade: source
377
+ };
378
+ }
379
+ }
380
+ // 遮罩样式
381
+ const shadeStyle = new Style({
382
+ fill: new Fill({
383
+ color: options?.fillColor ?? 'rgba(0,27,59,0.8)'
384
+ }),
385
+ stroke: new Stroke({
386
+ width: options?.strokeWidth ?? 1,
387
+ color: options?.strokeColor ?? 'rgba(0,27,59,0.8)'
388
+ })
389
+ });
390
+ // 遮罩数据源
391
+ const vtSource = new VectorSource();
392
+ // 遮罩图层
393
+ const vtLayer = new VectorLayer({
394
+ source: vtSource,
395
+ style: shadeStyle,
396
+ zIndex: options?.zIndex ?? 12
397
+ });
398
+ this.map.addLayer(vtLayer);
399
+ const features = new GeoJSON().readFeatures(data);
400
+ const ft = features[0];
401
+ const bound = ft.getGeometry();
402
+ const result = createShade(bound, this.map.getView());
403
+ if (result) {
404
+ vtSource.addFeature(result.feature);
405
+ if (options?.extent)
406
+ this.map.getView().fit(result.shade);
407
+ }
408
+ }
409
+ /**
410
+ * 添加图片图层
411
+ * @param imageData 图片数据,包含url和extent
412
+ * @param options 配置项
413
+ * @returns 创建的图片图层
414
+ * @throws 当数据格式无效时抛出错误
415
+ */
416
+ addImageLayer(imageData, options) {
417
+ // 检查是否允许空img(当存在layerName且存在同名图层时)
418
+ const allowEmptyImg = !imageData.img && !!options?.layerName;
419
+ ValidationUtils.validateImageData(imageData, allowEmptyImg);
420
+ const mergedOptions = {
421
+ opacity: 1,
422
+ visible: true,
423
+ zIndex: 11,
424
+ layerName: 'imageLayer',
425
+ ...options
426
+ };
427
+ // 尝试更新现有图层
428
+ if (mergedOptions.layerName) {
429
+ const existingLayer = this.tryUpdateExistingImageLayer(imageData, mergedOptions);
430
+ if (existingLayer) {
431
+ return existingLayer;
432
+ }
433
+ }
434
+ // 创建新图层
435
+ return this.createNewImageLayer(imageData, mergedOptions);
436
+ }
437
+ /**
438
+ * 尝试更新现有图层
439
+ * @private
440
+ */
441
+ tryUpdateExistingImageLayer(imageData, options) {
442
+ const existingLayers = MapTools.getLayerByLayerName(this.map, options.layerName);
443
+ if (existingLayers.length === 0) {
444
+ return null;
445
+ }
446
+ const existingLayer = existingLayers[0];
447
+ // 如果没有extent,直接设置source为undefined
448
+ if (!imageData.extent) {
449
+ existingLayer.setSource(undefined);
450
+ }
451
+ else {
452
+ // 创建新的source
453
+ const url = imageData.img || existingLayer.getSource()?.getUrl() || '';
454
+ const newSource = new ImageStatic({
455
+ url,
456
+ imageExtent: imageData.extent
457
+ });
458
+ existingLayer.setSource(newSource);
459
+ }
460
+ // 更新图层属性
461
+ this.updateImageLayerProperties(existingLayer, options);
462
+ return existingLayer;
463
+ }
464
+ /**
465
+ * 创建新的图像图层
466
+ * @private
467
+ */
468
+ createNewImageLayer(imageData, options) {
469
+ let source = undefined;
470
+ // 只有当extent存在时才创建ImageStatic source
471
+ if (imageData.extent) {
472
+ source = new ImageStatic({
473
+ url: imageData.img || '',
474
+ imageExtent: imageData.extent
475
+ });
476
+ }
477
+ const imageLayer = new ImageLayer({
478
+ source,
479
+ opacity: options.opacity,
480
+ visible: options.visible
481
+ });
482
+ this.configureImageLayer(imageLayer, options);
483
+ return this.addImageLayerToMap(imageLayer, options);
484
+ }
485
+ /**
486
+ * 更新图层属性
487
+ * @private
488
+ */
489
+ updateImageLayerProperties(layer, options) {
490
+ if (options.opacity !== undefined) {
491
+ layer.setOpacity(options.opacity);
492
+ }
493
+ if (options.visible !== undefined) {
494
+ layer.setVisible(options.visible);
495
+ }
496
+ if (options.zIndex !== undefined) {
497
+ layer.setZIndex(options.zIndex);
498
+ }
499
+ }
500
+ /**
501
+ * 配置图层基本属性
502
+ * @private
503
+ */
504
+ configureImageLayer(layer, options) {
505
+ layer.set('name', options.layerName);
506
+ layer.set('layerName', options.layerName);
507
+ layer.setZIndex(options.zIndex);
508
+ }
509
+ /**
510
+ * 添加图层到地图并应用裁剪
511
+ * @private
512
+ */
513
+ addImageLayerToMap(layer, options) {
514
+ if (options.mapClip && options.mapClipData) {
515
+ const clippedLayer = MapTools.setMapClip(layer, options.mapClipData);
516
+ this.map.addLayer(clippedLayer);
517
+ return clippedLayer;
518
+ }
519
+ this.map.addLayer(layer);
520
+ return layer;
521
+ }
522
+ /**
523
+ * 添加热力图图层
524
+ * @param pointData 点数据数组
525
+ * @param options 热力图配置
526
+ */
527
+ addHeatmap(pointData, options) {
528
+ // 只有在指定layerName时才移除已存在的同名图层
529
+ if (options?.layerName) {
530
+ new MapTools(this.map).removeLayer(options.layerName);
531
+ }
532
+ const heatmapLayer = new Heatmap({
533
+ source: new VectorSource(),
534
+ weight: function (fea) {
535
+ return fea.get('weight');
536
+ },
537
+ blur: options?.blur ?? 15,
538
+ radius: options?.radius ?? 10,
539
+ zIndex: options?.zIndex ?? 11,
540
+ opacity: options?.opacity ?? 1,
541
+ });
542
+ // 只有在指定layerName时才设置layerName
543
+ if (options?.layerName) {
544
+ heatmapLayer.set('layerName', options.layerName);
545
+ }
546
+ this.map.addLayer(heatmapLayer);
547
+ const valueKey = options?.valueKey || 'value';
548
+ const max = Math.max(...pointData.map(item => item[valueKey]));
549
+ pointData.forEach((item) => {
550
+ heatmapLayer?.getSource().addFeature(new Feature({
551
+ geometry: new Point([item.lgtd, item.lttd]),
552
+ weight: item[valueKey] / max //热力值范围是【0,1】;热力值计算 = 找出数据集中的最大值,然后用值除以最大值
553
+ }));
554
+ });
555
+ return heatmapLayer;
556
+ }
557
+ /**
558
+ * 添加遮罩图层
559
+ * @param data GeoJSON格式的遮罩数据
560
+ * @param options 配置项
561
+ * @returns 创建的遮罩图层
562
+ * @throws 当数据格式无效时抛出错误
563
+ */
564
+ addMaskLayer(data, options) {
565
+ ValidationUtils.validateMaskData(data);
566
+ const mergedOptions = {
567
+ fillColor: 'rgba(0, 0, 0, 0.5)',
568
+ opacity: 1,
569
+ visible: true,
570
+ layerName: 'maskLayer',
571
+ ...options
572
+ };
573
+ let features;
574
+ try {
575
+ features = new GeoJSON().readFeatures(data);
576
+ }
577
+ catch (error) {
578
+ throw new Error(`Invalid GeoJSON data: ${error}`);
579
+ }
580
+ if (!features || features.length === 0) {
581
+ console.warn('No features found in mask data');
582
+ }
583
+ const maskLayer = new VectorLayer({
584
+ source: new VectorSource({ features }),
585
+ style: new Style({
586
+ fill: new Fill({
587
+ color: mergedOptions.fillColor
588
+ }),
589
+ stroke: mergedOptions.strokeColor ? new Stroke({
590
+ color: mergedOptions.strokeColor,
591
+ width: mergedOptions.strokeWidth || 1
592
+ }) : undefined
593
+ }),
594
+ opacity: mergedOptions.opacity,
595
+ visible: mergedOptions.visible
596
+ });
597
+ maskLayer.set('layerName', mergedOptions.layerName);
598
+ this.map.addLayer(maskLayer);
599
+ return maskLayer;
600
+ }
601
+ removePolygonLayer(layerName) {
602
+ new MapTools(this.map).removeLayer(layerName);
603
+ this[layerName] = null;
604
+ }
605
+ }