my-openlayer 3.1.1 → 3.1.3

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
  });
@@ -155,7 +154,10 @@ export default class PointPulseLayer {
155
154
  return styles;
156
155
  };
157
156
  const layer = new VectorLayer({
158
- layerName: options.layerName,
157
+ properties: {
158
+ name: options.layerName,
159
+ layerName: options.layerName
160
+ },
159
161
  source,
160
162
  style: createStyles,
161
163
  zIndex: options.zIndex || ConfigManager.DEFAULT_POINT_OPTIONS.zIndex,
@@ -193,19 +193,16 @@ export default class Polygon {
193
193
  if (!(layer instanceof VectorLayer)) {
194
194
  throw ErrorHandler.getInstance().createAndHandleError(`Layer '${layerName}' is not a vector layer`, ErrorType.LAYER_ERROR);
195
195
  }
196
- const mergedOptions = {
197
- textFont: '14px Calibri,sans-serif',
198
- textFillColor: '#FFF',
199
- textStrokeWidth: 2,
200
- ...options
201
- };
202
196
  const features = layer.getSource()?.getFeatures();
203
197
  if (!features) {
204
198
  ErrorHandler.getInstance().warn(`No features found in layer '${layerName}'`);
205
199
  return;
206
200
  }
201
+ const resolution = this.map.getView().getResolution() ?? 1;
207
202
  features.forEach((feature) => {
208
- PolygonStyleFactory.updateSingleFeatureColor(feature, colorObj, mergedOptions);
203
+ const currentStyle = feature.getStyle() ?? layer.getStyle();
204
+ const previousStyles = PolygonStyleFactory.resolveFeatureStyles(currentStyle, feature, resolution);
205
+ PolygonStyleFactory.updateSingleFeatureColor(feature, colorObj, options, previousStyles);
209
206
  });
210
207
  }
211
208
  /**
@@ -6,8 +6,13 @@ import type { FeatureColorUpdateOptions, PolygonOptions } from "../../types";
6
6
  */
7
7
  export default class PolygonStyleFactory {
8
8
  static getFeatureText(feature: FeatureLike, options: PolygonOptions | FeatureColorUpdateOptions): string;
9
+ private static normalizeStyles;
10
+ static resolveFeatureStyles(styleLike: unknown, feature: FeatureLike, resolution?: number): Style[];
11
+ private static getPreviousStroke;
12
+ private static getPreviousFill;
13
+ private static getPreviousText;
9
14
  static createStyle(options: PolygonOptions): Style | Style[] | ((feature: FeatureLike) => Style | Style[]);
10
15
  static updateSingleFeatureColor(feature: Feature, colorObj?: {
11
16
  [propName: string]: string;
12
- }, options?: FeatureColorUpdateOptions): void;
17
+ }, options?: FeatureColorUpdateOptions, previousStyles?: Style[]): void;
13
18
  }
@@ -12,6 +12,28 @@ export default class PolygonStyleFactory {
12
12
  }
13
13
  return '';
14
14
  }
15
+ static normalizeStyles(styles) {
16
+ if (!styles)
17
+ return [];
18
+ return Array.isArray(styles) ? styles : [styles];
19
+ }
20
+ static resolveFeatureStyles(styleLike, feature, resolution = 1) {
21
+ if (!styleLike)
22
+ return [];
23
+ if (typeof styleLike === 'function') {
24
+ return PolygonStyleFactory.normalizeStyles(styleLike(feature, resolution));
25
+ }
26
+ return PolygonStyleFactory.normalizeStyles(styleLike);
27
+ }
28
+ static getPreviousStroke(styles) {
29
+ return styles.map(style => style.getStroke()).find((stroke) => !!stroke);
30
+ }
31
+ static getPreviousFill(styles) {
32
+ return styles.map(style => style.getFill()).find((fill) => !!fill);
33
+ }
34
+ static getPreviousText(styles) {
35
+ return styles.map(style => style.getText()).find((text) => !!text);
36
+ }
15
37
  static createStyle(options) {
16
38
  if (options.style) {
17
39
  return options.style;
@@ -56,6 +78,7 @@ export default class PolygonStyleFactory {
56
78
  text: new Text({
57
79
  text,
58
80
  font: options.textFont,
81
+ overflow: options.textOverflow,
59
82
  fill: new Fill({ color: options.textFillColor }),
60
83
  stroke: new Stroke({
61
84
  color: options.textStrokeColor,
@@ -68,30 +91,43 @@ export default class PolygonStyleFactory {
68
91
  return styles;
69
92
  };
70
93
  }
71
- static updateSingleFeatureColor(feature, colorObj, options) {
72
- const name = options?.textKey ? feature.get(options.textKey) : '';
73
- const withDefaultStroke = options?.withDefaultStroke ?? true;
74
- const withDefaultFill = options?.withDefaultFill ?? true;
75
- const strokeColor = options?.strokeColor ?? (withDefaultStroke ? '#EBEEF5' : undefined);
76
- const strokeWidth = options?.strokeWidth ?? 2;
77
- const defaultFillColor = 'rgba(255, 255, 255, 0.3)';
78
- const resolvedFillColor = options?.fillColor ?? (withDefaultFill ? defaultFillColor : undefined);
79
- const newColor = colorObj?.[name] || resolvedFillColor;
94
+ static updateSingleFeatureColor(feature, colorObj, options, previousStyles = []) {
95
+ const name = options?.textKey ? String(feature.get(options.textKey) ?? '') : '';
96
+ const hasMappedColor = !!colorObj && Object.prototype.hasOwnProperty.call(colorObj, name);
97
+ const previousStroke = PolygonStyleFactory.getPreviousStroke(previousStyles);
98
+ const previousFill = PolygonStyleFactory.getPreviousFill(previousStyles);
99
+ const previousText = PolygonStyleFactory.getPreviousText(previousStyles);
100
+ const withDefaultStroke = options?.withDefaultStroke !== false;
101
+ const withDefaultFill = options?.withDefaultFill !== false;
102
+ const strokeColor = withDefaultStroke ? options?.strokeColor ?? previousStroke?.getColor() : undefined;
103
+ const strokeWidth = withDefaultStroke ? options?.strokeWidth ?? previousStroke?.getWidth() : undefined;
104
+ const lineDash = withDefaultStroke ? options?.lineDash ?? previousStroke?.getLineDash() ?? undefined : undefined;
105
+ const lineDashOffset = withDefaultStroke ? options?.lineDashOffset ?? previousStroke?.getLineDashOffset() : undefined;
106
+ const newColor = hasMappedColor
107
+ ? colorObj[name]
108
+ : options?.fillColor ?? (withDefaultFill ? previousFill?.getColor() : undefined);
80
109
  const featureStyle = new Style({
81
- stroke: strokeColor ? new Stroke({ color: strokeColor, width: strokeWidth }) : undefined,
110
+ stroke: strokeColor !== undefined || strokeWidth !== undefined || lineDash !== undefined || lineDashOffset !== undefined
111
+ ? new Stroke({ color: strokeColor, width: strokeWidth, lineDash, lineDashOffset })
112
+ : undefined,
82
113
  fill: newColor ? new Fill({ color: newColor }) : undefined
83
114
  });
84
- if (options?.textVisible) {
85
- const text = PolygonStyleFactory.getFeatureText(feature, options);
115
+ const textVisible = options?.textVisible ?? !!previousText;
116
+ if (textVisible) {
117
+ const text = options ? PolygonStyleFactory.getFeatureText(feature, options) || previousText?.getText() : previousText?.getText();
86
118
  if (text) {
119
+ const textFillColor = options?.textFillColor ?? previousText?.getFill()?.getColor();
120
+ const textStrokeColor = options?.textStrokeColor ?? previousText?.getStroke()?.getColor();
121
+ const textStrokeWidth = options?.textStrokeWidth ?? previousText?.getStroke()?.getWidth();
87
122
  featureStyle.setText(new Text({
88
123
  text,
89
- font: options.textFont,
90
- fill: new Fill({ color: options.textFillColor }),
91
- stroke: new Stroke({
92
- color: options.textStrokeColor,
93
- width: options.textStrokeWidth
94
- })
124
+ font: options?.textFont ?? previousText?.getFont(),
125
+ overflow: options?.textOverflow ?? previousText?.getOverflow(),
126
+ offsetY: options?.textOffsetY ?? previousText?.getOffsetY(),
127
+ fill: textFillColor !== undefined ? new Fill({ color: textFillColor }) : undefined,
128
+ stroke: textStrokeColor !== undefined || textStrokeWidth !== undefined
129
+ ? new Stroke({ color: textStrokeColor, width: textStrokeWidth })
130
+ : undefined
95
131
  }));
96
132
  }
97
133
  }
@@ -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/docs/Polygon.md CHANGED
@@ -37,6 +37,8 @@ const polygon = new Polygon(map: Map);
37
37
  | `textFont` | `string` | 字体样式 |
38
38
  | `textFillColor` | `string` | 文本颜色 |
39
39
  | `textStrokeColor` | `string` | 文本描边颜色 |
40
+ | `textStrokeWidth` | `number` | 文本描边宽度 |
41
+ | `textOverflow` | `boolean` | 是否允许文字溢出面范围。设为 `true` 时,文字不会因为当前缩放下放不进面内而被 OpenLayers 隐藏 |
40
42
  | **其他** | | |
41
43
  | `mask` | `boolean` | 是否作为蒙版(配合 `setOutLayer` 使用) |
42
44
 
@@ -112,6 +114,8 @@ updateFeatureColor(
112
114
  | `colorObj` | `Object` | 颜色映射对象 `{ '区域名': '颜色值' }` |
113
115
  | `options` | `FeatureColorUpdateOptions` | 包含 textKey 等配置以匹配要素 |
114
116
 
117
+ 未在 `options` 中指定的样式会沿用要素当前样式,例如 `strokeColor`、`strokeWidth`、`textFont`、`textFillColor`、`textStrokeColor`、`textStrokeWidth`、`textOverflow` 等。只传颜色映射时不会重置原有文字样式。
118
+
115
119
  ### addImageLayer
116
120
 
117
121
  添加静态图片图层(如叠加平面图、卫星图)。
@@ -183,6 +187,7 @@ polygonModule.addPolygon(polygonData, {
183
187
  textFillColor: '#fff',
184
188
  textStrokeColor: 'blue',
185
189
  textStrokeWidth: 2,
190
+ textOverflow: true, // 允许文字溢出面范围,避免随缩放尺寸不足被隐藏
186
191
  fitView: true
187
192
  });
188
193
  ```
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.1",
4
+ "version": "3.1.3",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "index.js",
package/types/base.d.ts CHANGED
@@ -44,4 +44,9 @@ export interface TextOptions {
44
44
  textStrokeColor?: string;
45
45
  textStrokeWidth?: number;
46
46
  textOffsetY?: number;
47
+ /**
48
+ * 多边形或沿线文本是否允许溢出要素范围。
49
+ * 设置为 true 时,OpenLayers 不会因为当前缩放下文字放不进面/线而隐藏文本。
50
+ */
51
+ textOverflow?: boolean;
47
52
  }
package/types/common.d.ts CHANGED
@@ -50,6 +50,7 @@ export type OptionsType = {
50
50
  textStrokeColor?: string;
51
51
  textStrokeWidth?: number;
52
52
  textOffsetY?: number;
53
+ textOverflow?: boolean;
53
54
  textKey?: string;
54
55
  img?: string;
55
56
  scale?: number;
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;