my-openlayer 3.1.2 → 3.1.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/MyOl.js CHANGED
@@ -406,10 +406,10 @@ class MyOl {
406
406
  }
407
407
  if (this._line) {
408
408
  try {
409
- this._line.destroyAllFlowLines();
409
+ this._line.destroyAll();
410
410
  }
411
411
  catch (e) {
412
- this.errorHandler.warn('Line.destroyAllFlowLines 失败:', e);
412
+ this.errorHandler.warn('Line.destroyAll 失败:', e);
413
413
  }
414
414
  }
415
415
  if (this._point) {
package/README.md CHANGED
@@ -16,10 +16,10 @@ my-openlayer 是一个基于 [OpenLayers](https://openlayers.org/) 的现代地
16
16
 
17
17
  - **统一 LayerHandle** — 真实图层类 `add*` 返回 `{ layer, remove(), setVisible() }` 形态的句柄,跨 Point / Line / Polygon 复用同一套 lifecycle 模型
18
18
  - **统一 ControlHandle** — `addDomPoint` / `addVueTemplatePoint` 返回 `{ target, remove(), setVisible() }`,并保留 `anchors` / `getPoints()` 等原有能力
19
- - **点 API GeoJSON 直接输入** — `addPoint` / `addClusterPoint` / `addPulsePointLayer` 直接接受 `PointData[]`、GeoJSON FeatureCollection、Feature、MultiPoint geometry,内部统一标准化,`*ByUrl` 方法共享同一套解析逻辑
19
+ - **点 API GeoJSON 直接输入** — `addPoint` / `addClusterPoint` / `addPulsePointLayer` 直接接受 `PointData[]`、GeoJSON FeatureCollection、Feature、MultiPoint geometry,内部统一标准化,创建后的 Feature 可直接通过 `feature.get('name')` / `feature.getProperties()` 读取业务字段
20
20
  - **addGeoJSON 综合渲染** — `MyOl.addGeoJSON(data, options)` 自动识别点/线/面几何类型,按分组创建图层,返回统一 `GeoJSONRenderHandle`,支持 `groupBy` 分组、`styleByProperties` 逐要素样式、数组/Record 多数据集输入
21
21
  - ***ByUrl 异步化** — `addPointByUrl` / `addLineByUrl` / `addPolygonByUrl` 统一先获取 JSON,再返回完整 Handle
22
- - **destroy 级联清理** — `MyOl.destroy()` 现在依次调用 `SelectHandler.destroy` / `Line.destroyAllFlowLines` / `Point.destroyAll` / `Polygon.destroyAll`,确保 rAF / Overlay / Vue 实例 / Select interaction 全部释放
22
+ - **destroy 级联清理** — `MyOl.destroy()` 现在依次调用 `SelectHandler.destroy` / `Line.destroyAll` / `Point.destroyAll` / `Polygon.destroyAll`,确保静态线、流动线 rAF / Overlay / Vue 实例 / Select interaction 全部释放
23
23
  - **ProjectionManager** — 投影逻辑从 MyOl 内部抽取为独立类,支持 `ProjectionManager.register({ code, def })` 在 MyOl 实例之外注册任意 EPSG
24
24
  - **ConfigManager.setDefaults** — 运行时修改全局默认配置,所有未提供该字段的后续调用都生效
25
25
  - **统一错误类型** — 全部 `throw` 使用 `MyOpenLayersError` 并携带 `ErrorType`(`VALIDATION_ERROR` / `MAP_ERROR` / `LAYER_ERROR` / `COORDINATE_ERROR` / `DATA_ERROR` / `COMPONENT_ERROR`),另有 `LayerNotFoundError` / `InvalidGeoJSONError` / `ProjectionError` 子类,方便 `instanceof` 判别
@@ -133,6 +133,10 @@ const geoHandle = point.addPoint(
133
133
  ]},
134
134
  { layerName: 'geojson-points', textKey: 'name' }
135
135
  );
136
+
137
+ const feature = geoHandle?.layer.getSource()?.getFeatures()[0];
138
+ feature?.get('name'); // '杭州'
139
+ // feature.get('rawData') 仅保留兼容旧代码,新代码优先读取顶层业务字段。
136
140
  ```
137
141
 
138
142
  ### 3. 添加高性能闪烁点
@@ -9,8 +9,15 @@ import type { FlowLineLayerHandle, FlowLineOptions, LineOptions, MapJSONData, La
9
9
  export default class Line {
10
10
  private readonly map;
11
11
  private readonly flowLineRegistry;
12
+ /** 由本实例创建的静态线图层。destroyAll 时统一移除,避免 MyOl.destroy 后残留。 */
13
+ private readonly managedLayers;
12
14
  private readonly styleFactory;
13
15
  constructor(map: Map);
16
+ /**
17
+ * 跟踪一个本实例创建的静态线图层,便于销毁阶段统一清理。
18
+ * @internal
19
+ */
20
+ private trackLayer;
14
21
  /**
15
22
  * 合并静态线默认配置。
16
23
  */
@@ -56,4 +63,9 @@ export default class Line {
56
63
  * 确保地图销毁后所有 requestAnimationFrame / postrender 监听被回收。
57
64
  */
58
65
  destroyAllFlowLines(): void;
66
+ /**
67
+ * 销毁本实例创建的所有线图层。静态线由 Line 自身托管,
68
+ * 流动线继续复用 destroyAllFlowLines,保证动画监听与 rAF 一并释放。
69
+ */
70
+ destroyAll(): void;
59
71
  }
package/core/line/Line.js CHANGED
@@ -14,10 +14,20 @@ import LineStyleFactory from "./LineStyleFactory";
14
14
  export default class Line {
15
15
  constructor(map) {
16
16
  this.flowLineRegistry = new globalThis.Map();
17
+ /** 由本实例创建的静态线图层。destroyAll 时统一移除,避免 MyOl.destroy 后残留。 */
18
+ this.managedLayers = new Set();
17
19
  this.styleFactory = new LineStyleFactory();
18
20
  ValidationUtils.validateMapInstance(map);
19
21
  this.map = map;
20
22
  }
23
+ /**
24
+ * 跟踪一个本实例创建的静态线图层,便于销毁阶段统一清理。
25
+ * @internal
26
+ */
27
+ trackLayer(layer) {
28
+ this.managedLayers.add(layer);
29
+ return layer;
30
+ }
21
31
  /**
22
32
  * 合并静态线默认配置。
23
33
  */
@@ -59,15 +69,19 @@ export default class Line {
59
69
  });
60
70
  layer.setVisible(options.visible ?? true);
61
71
  this.map.addLayer(layer);
62
- return layer;
72
+ return this.trackLayer(layer);
63
73
  }
64
74
  /** *********************统一句柄:静态线图层*********************/
65
75
  toLayerHandle(layer) {
66
76
  const map = this.map;
77
+ const managedLayers = this.managedLayers;
67
78
  return {
68
79
  layer,
69
80
  setVisible(visible) { layer.setVisible(visible); },
70
- remove() { map.removeLayer(layer); }
81
+ remove() {
82
+ managedLayers.delete(layer);
83
+ map.removeLayer(layer);
84
+ }
71
85
  };
72
86
  }
73
87
  /**
@@ -106,6 +120,12 @@ export default class Line {
106
120
  removeLineLayer(layerName) {
107
121
  ValidationUtils.validateLayerName(layerName);
108
122
  MapTools.removeLayer(this.map, layerName);
123
+ // 同步清理托管记录,避免后续 destroyAll 重复持有已移除图层。
124
+ this.managedLayers.forEach(layer => {
125
+ if (layer.get('layerName') === layerName || layer.get('name') === layerName) {
126
+ this.managedLayers.delete(layer);
127
+ }
128
+ });
109
129
  }
110
130
  addFlowLine(data, options) {
111
131
  const mergedOptions = this.mergeFlowLineOptions(options);
@@ -169,4 +189,18 @@ export default class Line {
169
189
  });
170
190
  this.flowLineRegistry.clear();
171
191
  }
192
+ /**
193
+ * 销毁本实例创建的所有线图层。静态线由 Line 自身托管,
194
+ * 流动线继续复用 destroyAllFlowLines,保证动画监听与 rAF 一并释放。
195
+ */
196
+ destroyAll() {
197
+ this.destroyAllFlowLines();
198
+ this.managedLayers.forEach(layer => {
199
+ try {
200
+ this.map.removeLayer(layer);
201
+ }
202
+ catch { /* ignore */ }
203
+ });
204
+ this.managedLayers.clear();
205
+ }
172
206
  }
@@ -3,7 +3,7 @@ import { Style } from "ol/style";
3
3
  import VectorLayer from "ol/layer/Vector";
4
4
  import VectorSource from "ol/source/Vector";
5
5
  import Overlay from 'ol/Overlay';
6
- import { PointOptions, ClusterOptions, PointData, VueTemplatePointInstance, TwinkleItem, PulsePointOptions, PulsePointLayerHandle, LayerHandle, ControlHandle } from '../../types';
6
+ import { PointOptions, PointLayerOptions, ClusterOptions, ClusterPointLayerOptions, PointData, VueTemplatePointInstance, TwinkleItem, PulsePointLayerOptions, PulsePointLayerHandle, LayerHandle, ControlHandle } from '../../types';
7
7
  import { type PointJSONInput } from '../../utils/GeoJSONProcessor';
8
8
  export default class Point {
9
9
  private map;
@@ -74,13 +74,9 @@ export default class Point {
74
74
  /** *********************创建聚合点图层*********************/
75
75
  private createClusterPointLayer;
76
76
  /** *********************添加普通点*********************/
77
- addPoint(pointData: PointJSONInput, options: PointOptions & {
78
- layerName: string;
79
- }): LayerHandle<VectorLayer<VectorSource>> | null;
77
+ addPoint(pointData: PointJSONInput, options: PointLayerOptions): LayerHandle<VectorLayer<VectorSource>> | null;
80
78
  /** *********************添加聚合点*********************/
81
- addClusterPoint(pointData: PointJSONInput, options: ClusterOptions & {
82
- layerName: string;
83
- }): LayerHandle<VectorLayer<VectorSource>> | null;
79
+ addClusterPoint(pointData: PointJSONInput, options: ClusterPointLayerOptions): LayerHandle<VectorLayer<VectorSource>> | null;
84
80
  /** @internal 把 VectorLayer 包成 LayerHandle 的内部工具。 */
85
81
  private toLayerHandle;
86
82
  /**
@@ -89,24 +85,18 @@ export default class Point {
89
85
  * 与 addDomPoint 不同,该方法使用 VectorLayer 批量渲染点位,并通过单个
90
86
  * requestAnimationFrame 驱动闪烁圈,适合村庄预警等大量点位场景。
91
87
  */
92
- addPulsePointLayer(pointData: PointJSONInput, options: PulsePointOptions & {
93
- layerName: string;
94
- }): PulsePointLayerHandle | null;
88
+ addPulsePointLayer(pointData: PointJSONInput, options: PulsePointLayerOptions): PulsePointLayerHandle | null;
95
89
  /**
96
90
  * P1-2:从 URL 加载点位数据并添加为静态点图层。
97
91
  *
98
92
  * 期望 URL 返回 `PointData[]` 形态的 JSON 数组(含 lgtd / lttd)或 FeatureCollection。
99
93
  * features 加载/解析完成后 Promise resolve 为 LayerHandle。
100
94
  */
101
- addPointByUrl(url: string, options: PointOptions & {
102
- layerName: string;
103
- }): Promise<LayerHandle<VectorLayer<VectorSource>> | null>;
95
+ addPointByUrl(url: string, options: PointLayerOptions): Promise<LayerHandle<VectorLayer<VectorSource>> | null>;
104
96
  /**
105
97
  * P1-2:从 URL 加载点位数据并添加为高性能闪烁点图层。
106
98
  */
107
- addPulsePointLayerByUrl(url: string, options: PulsePointOptions & {
108
- layerName: string;
109
- }): Promise<PulsePointLayerHandle | null>;
99
+ addPulsePointLayerByUrl(url: string, options: PulsePointLayerOptions): Promise<PulsePointLayerHandle | null>;
110
100
  /**
111
101
  * 添加闪烁点
112
102
  * @param twinkleList 闪烁点数据
@@ -12,6 +12,7 @@ import { ConfigManager, MapTools } from "../map";
12
12
  import PointClusterLayer from './PointClusterLayer';
13
13
  import PointOverlay from './PointOverlay';
14
14
  import PointPulseLayer from './PointPulseLayer';
15
+ import { createPointFeatureProperties } from './PointFeatureProperties';
15
16
  export default class Point {
16
17
  constructor(map) {
17
18
  /** 由本实例创建的纯图层(addPoint / addClusterPoint)。destroyAll 时统一移除。 */
@@ -167,9 +168,7 @@ export default class Point {
167
168
  return;
168
169
  }
169
170
  const pointFeature = new Feature({
170
- rawData: item,
171
- type: options.layerName,
172
- layerName: options.layerName,
171
+ ...createPointFeatureProperties(item, options.layerName),
173
172
  geometry: new olPoint(ProjectionUtils.transformCoordinate([item.lgtd, item.lttd], options))
174
173
  });
175
174
  if (options.style) {
@@ -1,10 +1,10 @@
1
1
  import Map from "ol/Map";
2
2
  import VectorLayer from "ol/layer/Vector";
3
3
  import VectorSource from "ol/source/Vector";
4
- import type { ClusterOptions, PointData } from "../../types";
4
+ import type { ClusterOptions, ClusterPointLayerOptions, PointData } from "../../types";
5
5
  /**
6
6
  * 点聚合图层构建器。
7
7
  */
8
8
  export default class PointClusterLayer {
9
- static create(map: Map, pointData: PointData[], options: ClusterOptions, createClusterStyle: (options: ClusterOptions, name: string) => any): VectorLayer<VectorSource>;
9
+ static create(map: Map, pointData: PointData[], options: ClusterPointLayerOptions, createClusterStyle: (options: ClusterOptions, name: string) => any): VectorLayer<VectorSource>;
10
10
  }
@@ -6,6 +6,7 @@ import { Cluster } from "ol/source";
6
6
  import ProjectionUtils from "../../utils/ProjectionUtils";
7
7
  import ValidationUtils from "../../utils/ValidationUtils";
8
8
  import { ConfigManager } from "../map";
9
+ import { createPointFeatureProperties } from "./PointFeatureProperties";
9
10
  /**
10
11
  * 点聚合图层构建器。
11
12
  */
@@ -17,11 +18,8 @@ export default class PointClusterLayer {
17
18
  return;
18
19
  }
19
20
  pointFeatureList.push(new Feature({
20
- type: options.layerName,
21
- layerName: options.layerName,
21
+ ...createPointFeatureProperties(item, options.layerName),
22
22
  geometry: new OlPoint(ProjectionUtils.transformCoordinate([item.lgtd, item.lttd], options)),
23
- name: options.textKey ? item[options.textKey] : '',
24
- rawData: item,
25
23
  }));
26
24
  });
27
25
  const source = new VectorSource({ features: pointFeatureList });
@@ -31,7 +29,10 @@ export default class PointClusterLayer {
31
29
  source,
32
30
  });
33
31
  const clusterLayer = new VectorLayer({
34
- layerName: options.layerName,
32
+ properties: {
33
+ name: options.layerName,
34
+ layerName: options.layerName
35
+ },
35
36
  source: clusterSource,
36
37
  style: (feature) => {
37
38
  if (options.style) {
@@ -40,7 +41,9 @@ export default class PointClusterLayer {
40
41
  }
41
42
  return options.style;
42
43
  }
43
- const name = feature.get('features')[0].get('name');
44
+ const rawFeature = feature.get('features')[0];
45
+ const rawData = rawFeature.get('rawData');
46
+ const name = options.textKey ? String(rawData?.[options.textKey] ?? '') : '';
44
47
  return createClusterStyle(options, name);
45
48
  },
46
49
  zIndex: options.zIndex || ConfigManager.DEFAULT_CLUSTER_OPTIONS.zIndex,
@@ -0,0 +1,9 @@
1
+ import type { PointData } from "../../types";
2
+ /** *********************点 Feature 属性构建*********************/
3
+ /**
4
+ * 构建写入 OpenLayers Feature 顶层的点属性。
5
+ *
6
+ * lgtd / lttd 只用于几何坐标,不作为业务属性平铺;rawData 保留给旧代码兼容,
7
+ * 新代码应优先通过 feature.get('字段名') 或 feature.getProperties() 读取业务字段。
8
+ */
9
+ export declare function createPointFeatureProperties(item: PointData, layerName?: string): Record<string, unknown>;
@@ -0,0 +1,21 @@
1
+ /** *********************点 Feature 属性构建*********************/
2
+ /**
3
+ * 构建写入 OpenLayers Feature 顶层的点属性。
4
+ *
5
+ * lgtd / lttd 只用于几何坐标,不作为业务属性平铺;rawData 保留给旧代码兼容,
6
+ * 新代码应优先通过 feature.get('字段名') 或 feature.getProperties() 读取业务字段。
7
+ */
8
+ export function createPointFeatureProperties(item, layerName) {
9
+ const businessProperties = {};
10
+ Object.keys(item).forEach(key => {
11
+ if (key !== 'lgtd' && key !== 'lttd') {
12
+ businessProperties[key] = item[key];
13
+ }
14
+ });
15
+ return {
16
+ ...businessProperties,
17
+ rawData: item,
18
+ type: layerName,
19
+ layerName,
20
+ };
21
+ }
@@ -1,13 +1,13 @@
1
1
  import Feature from "ol/Feature";
2
2
  import Map from "ol/Map";
3
3
  import { Text } from "ol/style";
4
- import type { PointData, PulsePointLayerHandle, PulsePointOptions } from "../../types";
4
+ import type { PointData, PulsePointLayerHandle, PulsePointLayerOptions, PulsePointOptions } from "../../types";
5
5
  /**
6
6
  * 高性能闪烁点图层构建器。
7
7
  */
8
8
  export default class PointPulseLayer {
9
9
  static createTextStyle(options: PulsePointOptions, text: string): Text;
10
10
  static withOpacity(color: string, opacity: number): string;
11
- static createFeatures(pointData: PointData[], options: PulsePointOptions): Feature[];
12
- static create(map: Map, pointData: PointData[], options: PulsePointOptions): PulsePointLayerHandle;
11
+ static createFeatures(pointData: PointData[], options: PulsePointLayerOptions): Feature[];
12
+ static create(map: Map, pointData: PointData[], options: PulsePointLayerOptions): PulsePointLayerHandle;
13
13
  }
@@ -6,6 +6,7 @@ import { Circle as CircleStyle, Fill, Icon, Stroke, Style, Text } from "ol/style
6
6
  import ProjectionUtils from "../../utils/ProjectionUtils";
7
7
  import ValidationUtils from "../../utils/ValidationUtils";
8
8
  import { ConfigManager } from "../map";
9
+ import { createPointFeatureProperties } from "./PointFeatureProperties";
9
10
  /**
10
11
  * 高性能闪烁点图层构建器。
11
12
  */
@@ -49,9 +50,7 @@ export default class PointPulseLayer {
49
50
  return;
50
51
  }
51
52
  pointFeatureList.push(new Feature({
52
- rawData: item,
53
- type: options.layerName,
54
- layerName: options.layerName,
53
+ ...createPointFeatureProperties(item, options.layerName),
55
54
  geometry: new OlPoint(ProjectionUtils.transformCoordinate([item.lgtd, item.lttd], options))
56
55
  }));
57
56
  });
@@ -82,7 +81,15 @@ export default class PointPulseLayer {
82
81
  const source = new VectorSource({
83
82
  features: PointPulseLayer.createFeatures(pointData, options)
84
83
  });
85
- const createStyles = (feature) => {
84
+ const baseZIndex = options.zIndex || ConfigManager.DEFAULT_POINT_OPTIONS.zIndex;
85
+ const iconImg = options.icon?.img ?? options.icon?.src;
86
+ /**
87
+ * 仅负责绘制脉冲动画,避免动画覆盖图标。
88
+ */
89
+ const createPulseStyle = (feature) => {
90
+ if (!pulseOptions.enabled) {
91
+ return undefined;
92
+ }
86
93
  const rawData = feature.get('rawData');
87
94
  const level = String(rawData?.[levelKey] ?? 'default');
88
95
  const progress = frameIndex / Math.max(1, pulseOptions.frameCount - 1);
@@ -91,28 +98,28 @@ export default class PointPulseLayer {
91
98
  const opacity = 1 - progress;
92
99
  const fillColor = pulseOptions.colorMap[level] ?? pulseOptions.colorMap.default ?? 'rgba(6, 183, 253, 0.32)';
93
100
  const strokeColor = pulseOptions.strokeColorMap?.[level] ?? pulseOptions.strokeColorMap?.default;
94
- const styles = [];
95
- if (pulseOptions.enabled) {
96
- const pulseCacheKey = `${level}_${frameIndex}`;
97
- let pulseStyle = pulseStyleCache.get(pulseCacheKey);
98
- if (!pulseStyle) {
99
- pulseStyle = new Style({
100
- zIndex: 0,
101
- image: new CircleStyle({
102
- radius,
103
- fill: new Fill({ color: PointPulseLayer.withOpacity(fillColor, opacity) }),
104
- stroke: strokeColor && pulseOptions.strokeWidth > 0
105
- ? new Stroke({ color: PointPulseLayer.withOpacity(strokeColor, opacity), width: pulseOptions.strokeWidth })
106
- : undefined
107
- })
108
- });
109
- pulseStyleCache.set(pulseCacheKey, pulseStyle);
110
- }
111
- styles.push(pulseStyle);
101
+ const pulseCacheKey = `${level}_${frameIndex}`;
102
+ let pulseStyle = pulseStyleCache.get(pulseCacheKey);
103
+ if (!pulseStyle) {
104
+ pulseStyle = new Style({
105
+ image: new CircleStyle({
106
+ radius,
107
+ fill: new Fill({ color: PointPulseLayer.withOpacity(fillColor, opacity) }),
108
+ stroke: strokeColor && pulseOptions.strokeWidth > 0
109
+ ? new Stroke({ color: PointPulseLayer.withOpacity(strokeColor, opacity), width: pulseOptions.strokeWidth })
110
+ : undefined
111
+ })
112
+ });
113
+ pulseStyleCache.set(pulseCacheKey, pulseStyle);
112
114
  }
115
+ return pulseStyle;
116
+ };
117
+ /**
118
+ * 仅负责绘制图标与文本,固定放在脉冲层之上。
119
+ */
120
+ const createStaticStyle = (feature) => {
121
+ const rawData = feature.get('rawData');
113
122
  const text = options.textVisible && options.textKey && rawData ? String(rawData[options.textKey] ?? '') : '';
114
- // P1-4: icon.img 是新名,icon.src 保留兼容(@deprecated)
115
- const iconImg = options.icon?.img ?? options.icon?.src;
116
123
  const staticCacheKey = [
117
124
  options.img ?? iconImg ?? '',
118
125
  options.scale ?? options.icon?.scale ?? ConfigManager.DEFAULT_POINT_ICON_SCALE,
@@ -121,8 +128,7 @@ export default class PointPulseLayer {
121
128
  ].join('|');
122
129
  let pointStyle = staticStyleCache.get(staticCacheKey);
123
130
  if (pointStyle) {
124
- styles.push(pointStyle);
125
- return styles;
131
+ return pointStyle;
126
132
  }
127
133
  const pointStyleOptions = {};
128
134
  const iconSrc = options.img ?? iconImg;
@@ -148,20 +154,34 @@ export default class PointPulseLayer {
148
154
  }
149
155
  if (pointStyleOptions.image || pointStyleOptions.text) {
150
156
  pointStyle = new Style(pointStyleOptions);
151
- pointStyle.setZIndex(1);
152
157
  staticStyleCache.set(staticCacheKey, pointStyle);
153
- styles.push(pointStyle);
158
+ return pointStyle;
154
159
  }
155
- return styles;
160
+ return undefined;
156
161
  };
157
- const layer = new VectorLayer({
158
- layerName: options.layerName,
162
+ const pulseLayer = new VectorLayer({
163
+ properties: {
164
+ name: options.layerName,
165
+ layerName: options.layerName
166
+ },
159
167
  source,
160
- style: createStyles,
161
- zIndex: options.zIndex || ConfigManager.DEFAULT_POINT_OPTIONS.zIndex,
168
+ style: createPulseStyle,
169
+ zIndex: baseZIndex,
162
170
  });
163
- layer.setVisible(options.visible === undefined ? true : options.visible);
164
- map.addLayer(layer);
171
+ const visible = options.visible === undefined ? true : options.visible;
172
+ pulseLayer.setVisible(visible);
173
+ map.addLayer(pulseLayer);
174
+ const staticLayer = new VectorLayer({
175
+ properties: {
176
+ name: options.layerName,
177
+ layerName: options.layerName
178
+ },
179
+ source,
180
+ style: createStaticStyle,
181
+ zIndex: baseZIndex + 1,
182
+ });
183
+ staticLayer.setVisible(visible);
184
+ map.addLayer(staticLayer);
165
185
  const tick = () => {
166
186
  if (!running)
167
187
  return;
@@ -171,7 +191,7 @@ export default class PointPulseLayer {
171
191
  if (nextFrameIndex !== renderedFrameIndex) {
172
192
  frameIndex = nextFrameIndex;
173
193
  renderedFrameIndex = nextFrameIndex;
174
- layer.changed();
194
+ pulseLayer.changed();
175
195
  }
176
196
  rafId = requestAnimationFrame(tick);
177
197
  };
@@ -190,11 +210,14 @@ export default class PointPulseLayer {
190
210
  };
191
211
  start();
192
212
  return {
193
- layer,
213
+ layer: pulseLayer,
194
214
  source,
195
215
  start,
196
216
  stop,
197
- setVisible: (visible) => layer.setVisible(visible),
217
+ setVisible: (visible) => {
218
+ pulseLayer.setVisible(visible);
219
+ staticLayer.setVisible(visible);
220
+ },
198
221
  updateData: (nextData) => {
199
222
  source.clear();
200
223
  source.addFeatures(PointPulseLayer.createFeatures(nextData, options));
@@ -202,7 +225,8 @@ export default class PointPulseLayer {
202
225
  remove: () => {
203
226
  stop();
204
227
  source.clear();
205
- map.removeLayer(layer);
228
+ map.removeLayer(pulseLayer);
229
+ map.removeLayer(staticLayer);
206
230
  }
207
231
  };
208
232
  }
@@ -103,9 +103,20 @@ export default class PolygonStyleFactory {
103
103
  const strokeWidth = withDefaultStroke ? options?.strokeWidth ?? previousStroke?.getWidth() : undefined;
104
104
  const lineDash = withDefaultStroke ? options?.lineDash ?? previousStroke?.getLineDash() ?? undefined : undefined;
105
105
  const lineDashOffset = withDefaultStroke ? options?.lineDashOffset ?? previousStroke?.getLineDashOffset() : undefined;
106
- const newColor = hasMappedColor
107
- ? colorObj[name]
108
- : options?.fillColor ?? (withDefaultFill ? previousFill?.getColor() : undefined);
106
+ /** *********************颜色回退策略*********************/
107
+ // 1) colorObj 命中映射:使用映射颜色
108
+ // 2) 未命中但 options.fillColor 传入:使用传入的 fillColor
109
+ // 3) 都未提供:使用透明填充,避免沿用上一次更新的颜色
110
+ let newColor;
111
+ if (hasMappedColor) {
112
+ newColor = colorObj[name];
113
+ }
114
+ else if (options?.fillColor !== undefined) {
115
+ newColor = options.fillColor;
116
+ }
117
+ else {
118
+ newColor = withDefaultFill ? 'rgba(0, 0, 0, 0)' : undefined;
119
+ }
109
120
  const featureStyle = new Style({
110
121
  stroke: strokeColor !== undefined || strokeWidth !== undefined || lineDash !== undefined || lineDashOffset !== undefined
111
122
  ? new Stroke({ color: strokeColor, width: strokeWidth, lineDash, lineDashOffset })
@@ -10,7 +10,7 @@ export default defineConfig({
10
10
  { text: '首页', link: '/' },
11
11
  { text: '指南', link: '/MyOl' },
12
12
  { text: 'API', link: '/MyOl' },
13
- { text: 'Demo', link: '/demo/' },
13
+ { text: 'Demo', link: 'https://cuteyuchen.github.io/my-openlayer/demo/' },
14
14
  { text: 'GitHub', link: 'https://github.com/cuteyuchen/my-openlayer' }
15
15
  ],
16
16
 
package/docs/Line.md CHANGED
@@ -69,6 +69,7 @@ addLine(data: MapJSONData, options: LineOptions & { layerName: string }): LayerH
69
69
  ```
70
70
 
71
71
  添加静态线图层,返回统一 `LayerHandle`。
72
+ `handle.remove()` 会同步退出 `Line` 内部托管集合,后续再调用 `destroyAll()` 不会重复持有已移除图层。
72
73
 
73
74
  ### addLineByUrl
74
75
 
@@ -85,6 +86,7 @@ removeLineLayer(layerName: string): void
85
86
  ```
86
87
 
87
88
  移除静态线图层。
89
+ 该方法会同步清理 `Line` 内部托管记录,避免后续 `destroyAll()` 重复处理同名图层。
88
90
 
89
91
  ## 流动线 / 动态图标线
90
92
 
@@ -113,6 +115,22 @@ removeFlowLineLayer(layerName: string): void
113
115
 
114
116
  按图层名移除流动线的基础图层与动画图层。
115
117
 
118
+ ### destroyAll
119
+
120
+ ```typescript
121
+ destroyAll(): void
122
+ ```
123
+
124
+ 销毁当前 `Line` 实例创建的所有线图层。包括 `addLine` / `addLineByUrl` 创建的静态线,以及 `addFlowLine` / `addFlowLineByUrl` 创建的流动线基础图层、动画图层和 rAF 动画。`MyOl.destroy()` 会自动调用该方法。
125
+
126
+ ### destroyAllFlowLines
127
+
128
+ ```typescript
129
+ destroyAllFlowLines(): void
130
+ ```
131
+
132
+ 仅销毁当前 `Line` 实例创建的流动线动画和对应图层。该方法保留用于兼容旧代码;新代码需要完整清理 Line 模块时优先使用 `destroyAll()`。
133
+
116
134
  ## 使用示例
117
135
 
118
136
  ### 基础静态线
@@ -27,6 +27,7 @@ line.addLine(data, { layerName: 'my-line', strokeColor: 'red' })
27
27
  > **为什么**:依赖默认 `layerName` 的两次调用会互相覆盖,是 2.x 最常见的"图层莫名消失"陷阱。
28
28
 
29
29
  `PointOptions / LineOptions / PolygonOptions` interface **本身**没改(`layerName?: string` 仍然可选),所以已有的工厂函数、配置对象不受影响 —— 只是公开方法签名收紧了。
30
+ 点图层入口同时导出 `PointLayerOptions`、`ClusterPointLayerOptions`、`PulsePointLayerOptions`,新代码如果要单独声明配置变量,优先使用这些命名类型,避免手写 `PointOptions & { layerName: string }`。
30
31
 
31
32
  ### 1.2 `add*` 返回值改为 Handle
32
33
 
package/docs/MyOl.md CHANGED
@@ -207,7 +207,7 @@ getMapOptions(): Readonly<MapInitType>
207
207
 
208
208
  #### destroy
209
209
 
210
- 销毁地图实例,清理所有事件监听和资源。
210
+ 销毁地图实例,并级联清理子模块资源:`SelectHandler.destroy()`、`Line.destroyAll()`、`Point.destroyAll()`、`Polygon.destroyAll()`、`EventManager.clear()`。其中 `Line.destroyAll()` 会同时移除静态线和流动线图层。
211
211
 
212
212
  ```typescript
213
213
  destroy(): void
package/docs/Point.md CHANGED
@@ -24,7 +24,7 @@ constructor(map: Map)
24
24
  | iconColor | `string` | 图标颜色(用于改变图标色调) |
25
25
  | circleColor | `string` | 圆点填充颜色。未设置 img 时生效,与 circleRadius 搭配绘制纯色圆点 |
26
26
  | circleRadius | `number` | 圆点半径(像素),默认 6。仅在未设置 img 时生效 |
27
- | layerName | `string` | 图层名称(必填,继承自 BaseOptions) |
27
+ | layerName | `string` | 图层名称(基础配置中可选;公开 add 图层入口使用 `*LayerOptions` 要求必填) |
28
28
  | zIndex | `number` | 图层层级 |
29
29
  | visible | `boolean` | 是否可见 |
30
30
  | style | `Style \| Style[] \| ((feature: FeatureLike) => Style \| Style[])` | 自定义样式函数 |
@@ -38,6 +38,24 @@ constructor(map: Map)
38
38
  | distance | `number` | 聚合距离(像素),默认为 20 |
39
39
  | minDistance | `number` | 最小聚合距离 |
40
40
 
41
+ ### PointLayerOptions / ClusterPointLayerOptions / PulsePointLayerOptions
42
+
43
+ 公开点图层入口使用的配置类型,分别继承自 `PointOptions`、`ClusterOptions`、`PulsePointOptions`,并把 `layerName` 收紧为必填。
44
+
45
+ ```typescript
46
+ interface PointLayerOptions extends PointOptions {
47
+ layerName: string
48
+ }
49
+
50
+ interface ClusterPointLayerOptions extends ClusterOptions {
51
+ layerName: string
52
+ }
53
+
54
+ interface PulsePointLayerOptions extends PulsePointOptions {
55
+ layerName: string
56
+ }
57
+ ```
58
+
41
59
  ### PulsePointOptions
42
60
 
43
61
  继承自 `PointOptions`,因此与 `addPoint` 一致支持 `img`、`scale`、`iconColor`、`textKey`、`textVisible` 等参数。
@@ -91,6 +109,7 @@ type PointJSONInput =
91
109
  - `FeatureCollection` 中只提取 `Point` / `MultiPoint` 类型的 Feature,其他 geometry 类型被跳过。
92
110
  - `MultiPoint` 会被拆分为多个独立的 `PointData`。
93
111
  - GeoJSON Feature 的 `properties` 会被保留,与 `lgtd` / `lttd` 合并。
112
+ - 创建 OpenLayers Feature 时,业务字段会平铺到 Feature 顶层,可通过 `feature.get('name')` 或 `feature.getProperties().name` 读取;`rawData` 仅保留兼容旧代码,不推荐新代码继续依赖。
94
113
  - 坐标含 `NaN`、`Infinity` 或缺失的点会被过滤。
95
114
  - 不支持 `lng/lat`、`longitude/latitude`、`x/y` 等字段别名。
96
115
 
@@ -118,7 +137,7 @@ type PointJSONInput =
118
137
  添加普通点图层。支持 `PointJSONInput` 任意形态(`PointData[]`、GeoJSON FeatureCollection、Feature、裸 geometry)。
119
138
 
120
139
  ```typescript
121
- addPoint(pointData: PointJSONInput, options: PointOptions & { layerName: string }): LayerHandle<VectorLayer<VectorSource>> | null
140
+ addPoint(pointData: PointJSONInput, options: PointLayerOptions): LayerHandle<VectorLayer<VectorSource>> | null
122
141
  ```
123
142
 
124
143
  - **pointData**: 点位数据,支持 `PointData[]` 或标准 GeoJSON 点数据。
@@ -130,7 +149,7 @@ addPoint(pointData: PointJSONInput, options: PointOptions & { layerName: string
130
149
  添加聚合点图层。支持 `PointJSONInput` 任意形态。
131
150
 
132
151
  ```typescript
133
- addClusterPoint(pointData: PointJSONInput, options: ClusterOptions & { layerName: string }): LayerHandle<VectorLayer<VectorSource>> | null
152
+ addClusterPoint(pointData: PointJSONInput, options: ClusterPointLayerOptions): LayerHandle<VectorLayer<VectorSource>> | null
134
153
  ```
135
154
 
136
155
  - **pointData**: 点位数据,支持 `PointData[]` 或标准 GeoJSON 点数据。
@@ -142,7 +161,7 @@ addClusterPoint(pointData: PointJSONInput, options: ClusterOptions & { layerName
142
161
  从 URL 加载点位数据,支持 `PointData[]` 数组或 GeoJSON FeatureCollection。
143
162
 
144
163
  ```typescript
145
- addPointByUrl(url: string, options: PointOptions & { layerName: string }): Promise<LayerHandle<VectorLayer<VectorSource>> | null>
164
+ addPointByUrl(url: string, options: PointLayerOptions): Promise<LayerHandle<VectorLayer<VectorSource>> | null>
146
165
  ```
147
166
 
148
167
  ### addDomPoint
@@ -167,7 +186,7 @@ addDomPoint(twinkleList: TwinkleItem[], callback?: Function): {
167
186
  添加高性能闪烁点图层。支持 `PointJSONInput` 任意形态。
168
187
 
169
188
  ```typescript
170
- addPulsePointLayer(pointData: PointJSONInput, options: PulsePointOptions & { layerName: string }): PulsePointLayerHandle | null
189
+ addPulsePointLayer(pointData: PointJSONInput, options: PulsePointLayerOptions): PulsePointLayerHandle | null
171
190
  ```
172
191
 
173
192
  - **pointData**: 点位数据,支持 `PointData[]` 或标准 GeoJSON 点数据。
@@ -179,7 +198,7 @@ addPulsePointLayer(pointData: PointJSONInput, options: PulsePointOptions & { lay
179
198
  从 URL 加载点位数据并添加高性能闪烁点图层。
180
199
 
181
200
  ```typescript
182
- addPulsePointLayerByUrl(url: string, options: PulsePointOptions & { layerName: string }): Promise<PulsePointLayerHandle | null>
201
+ addPulsePointLayerByUrl(url: string, options: PulsePointLayerOptions): Promise<PulsePointLayerHandle | null>
183
202
  ```
184
203
 
185
204
  ### addVueTemplatePoint
@@ -288,6 +307,9 @@ const fc = {
288
307
  ]
289
308
  };
290
309
  const handle = point.addPoint(fc, { layerName: 'cities', textKey: 'name' });
310
+ const firstFeature = handle?.layer.getSource()?.getFeatures()[0];
311
+ firstFeature?.get('name'); // '北京'
312
+ // firstFeature.get('rawData') 仅保留兼容旧代码,新代码优先读取顶层业务字段。
291
313
 
292
314
  // 方式二:单个 Feature
293
315
  const feature = { type: 'Feature', properties: { name: '杭州' }, geometry: { type: 'Point', coordinates: [120.15, 30.27] } };
package/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { default as ValidationUtils } from './utils/ValidationUtils';
14
14
  export { normalizePointData } from './utils/GeoJSONProcessor';
15
15
  export type { PointJSONInput } from './types';
16
16
  export type { BaseOptions, StyleOptions, TextOptions, LayerHandle, AnimatedLayerHandle } from './types';
17
- export type { PointOptions, LineOptions, FlowLineOptions, FlowLineLayerHandle, PolygonOptions, PulsePointOptions, PulsePointLayerHandle } from './types';
17
+ export type { PointOptions, PointLayerOptions, LineOptions, FlowLineOptions, FlowLineLayerHandle, PolygonOptions, PulsePointOptions, PulsePointLayerOptions, PulsePointLayerHandle } from './types';
18
18
  export type { OptionsType } from './types';
19
- export type { MapInitType, MapLayersOptions, HeatMapOptions, ImageLayerData, MaskLayerOptions, FeatureColorUpdateOptions, PointData, PulsePointIconOptions, LineData, ClusterOptions, MeasureHandlerType, VueTemplatePointOptions, VueTemplatePointInstance, TwinkleItem, MapJSONData, FeatureData, AddGeoJSONInput, GeoJSONGeometryType, GeoJSONGroupBy, GeoJSONLayerName, AddGeoJSONPointOptions, AddGeoJSONOptions, GeoJSONGroupHandle, GeoJSONRenderHandle, AnnotationType, TiandituType, MapLayers, AnnotationLayerOptions, SelectOptions, SelectMode, SelectCallbackEvent, ProgrammaticSelectOptions } from './types';
19
+ export type { MapInitType, MapLayersOptions, HeatMapOptions, ImageLayerData, MaskLayerOptions, FeatureColorUpdateOptions, PointData, PulsePointIconOptions, LineData, ClusterOptions, ClusterPointLayerOptions, MeasureHandlerType, VueTemplatePointOptions, VueTemplatePointInstance, TwinkleItem, MapJSONData, FeatureData, AddGeoJSONInput, GeoJSONGeometryType, GeoJSONGroupBy, GeoJSONLayerName, AddGeoJSONPointOptions, AddGeoJSONOptions, GeoJSONGroupHandle, GeoJSONRenderHandle, AnnotationType, TiandituType, MapLayers, AnnotationLayerOptions, SelectOptions, SelectMode, SelectCallbackEvent, ProgrammaticSelectOptions } from './types';
20
20
  export { VueTemplatePointState } from './types';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "my-openlayer",
3
3
  "private": false,
4
- "version": "3.1.2",
4
+ "version": "3.1.4",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "index.js",
package/types/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export type { BaseOptions, StyleOptions, TextOptions } from './base';
2
2
  export type { LayerHandle, AnimatedLayerHandle, ControlHandle } from './handle';
3
3
  export type { FeatureData, MapJSONData, OptionsType, MeasureHandlerType, EventType } from './common';
4
4
  export type { MapInitType, MapLayersOptions, MapLayers, AnnotationType, TiandituType, AnnotationLayerOptions, HeatMapOptions, ImageLayerData, MaskLayerOptions } from './map';
5
- export type { PointOptions, PointData, PointJSONInput, ClusterOptions, PulsePointIconOptions, PulsePointOptions, PulsePointLayerHandle, TwinkleItem } from './point';
5
+ export type { PointOptions, PointLayerOptions, PointData, PointJSONInput, ClusterOptions, ClusterPointLayerOptions, PulsePointIconOptions, PulsePointOptions, PulsePointLayerOptions, PulsePointLayerHandle, TwinkleItem } from './point';
6
6
  export type { LineOptions, LineData, FlowLineOptions, FlowLineLayerHandle } from './line';
7
7
  export type { PolygonOptions, FeatureColorUpdateOptions } from './polygon';
8
8
  export type { AddGeoJSONInput, GeoJSONGeometryType, GeoJSONGroupBy, GeoJSONLayerName, AddGeoJSONPointOptions, AddGeoJSONOptions, GeoJSONGroupHandle, GeoJSONRenderHandle } from './geojson';
package/types/point.d.ts CHANGED
@@ -13,6 +13,10 @@ export interface PointOptions extends BaseOptions, StyleOptions, TextOptions {
13
13
  /** 圆点半径(像素),默认 6。仅在未设置 img 时生效。 */
14
14
  circleRadius?: number;
15
15
  }
16
+ /** addPoint / addPointByUrl 的公开入口配置,要求 layerName 必填。 */
17
+ export interface PointLayerOptions extends PointOptions {
18
+ layerName: string;
19
+ }
16
20
  export interface PointData {
17
21
  lgtd: number;
18
22
  lttd: number;
@@ -30,6 +34,10 @@ export interface ClusterOptions extends PointOptions {
30
34
  distance?: number;
31
35
  minDistance?: number;
32
36
  }
37
+ /** addClusterPoint 的公开入口配置,要求 layerName 必填。 */
38
+ export interface ClusterPointLayerOptions extends ClusterOptions {
39
+ layerName: string;
40
+ }
33
41
  export interface PulsePointIconOptions {
34
42
  /**
35
43
  * 图标资源 URL。
@@ -59,6 +67,10 @@ export interface PulsePointOptions extends PointOptions {
59
67
  frameCount?: number;
60
68
  };
61
69
  }
70
+ /** addPulsePointLayer / addPulsePointLayerByUrl 的公开入口配置,要求 layerName 必填。 */
71
+ export interface PulsePointLayerOptions extends PulsePointOptions {
72
+ layerName: string;
73
+ }
62
74
  export interface PulsePointLayerHandle extends AnimatedLayerHandle<VectorLayer<VectorSource>> {
63
75
  layer: VectorLayer<VectorSource>;
64
76
  source: VectorSource;