my-openlayer 2.5.4 → 3.0.1

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/MyOl.d.ts +3 -22
  3. package/MyOl.js +52 -112
  4. package/README.md +116 -29
  5. package/core/line/Line.d.ts +24 -5
  6. package/core/line/Line.js +38 -10
  7. package/core/map/ConfigManager.d.ts +169 -89
  8. package/core/map/ConfigManager.js +157 -175
  9. package/core/map/MapBaseLayers.d.ts +6 -0
  10. package/core/map/MapBaseLayers.js +9 -0
  11. package/core/map/MapTools.d.ts +17 -1
  12. package/core/map/MapTools.js +39 -5
  13. package/core/point/Point.d.ts +54 -14
  14. package/core/point/Point.js +132 -76
  15. package/core/point/PointOverlay.d.ts +2 -4
  16. package/core/point/PointOverlay.js +2 -1
  17. package/core/point/PointPulseLayer.js +6 -4
  18. package/core/polygon/Polygon.d.ts +32 -17
  19. package/core/polygon/Polygon.js +87 -64
  20. package/core/polygon/PolygonHeatmapLayer.js +15 -2
  21. package/core/polygon/PolygonMaskLayer.d.ts +2 -2
  22. package/core/polygon/PolygonMaskLayer.js +3 -1
  23. package/core/polygon/PolygonStyleFactory.d.ts +4 -3
  24. package/core/projection/ProjectionManager.d.ts +66 -0
  25. package/core/projection/ProjectionManager.js +144 -0
  26. package/core/projection/index.d.ts +2 -0
  27. package/core/projection/index.js +1 -0
  28. package/core/select/SelectHandler.d.ts +1 -1
  29. package/core/vue-template-point/VueTemplatePoint.d.ts +2 -4
  30. package/core/vue-template-point/VueTemplatePoint.js +16 -2
  31. package/docs/.vitepress/config.mts +1 -0
  32. package/docs/Line.md +4 -4
  33. package/docs/MIGRATION-3.0.md +221 -0
  34. package/docs/Point.md +24 -6
  35. package/docs/Polygon.md +14 -5
  36. package/index.d.ts +6 -3
  37. package/index.js +4 -1
  38. package/package.json +6 -3
  39. package/types/base.d.ts +4 -4
  40. package/types/common.d.ts +8 -6
  41. package/types/handle.d.ts +34 -0
  42. package/types/handle.js +1 -0
  43. package/types/index.d.ts +1 -0
  44. package/types/line.d.ts +3 -2
  45. package/types/map.d.ts +1 -1
  46. package/types/point.d.ts +11 -3
  47. package/utils/ErrorHandler.d.ts +12 -0
  48. package/utils/ErrorHandler.js +21 -0
@@ -2,7 +2,7 @@
2
2
  import VectorLayer from "ol/layer/Vector";
3
3
  import VectorSource from "ol/source/Vector";
4
4
  import GeoJSON from "ol/format/GeoJSON";
5
- import { ErrorHandler } from '../../utils/ErrorHandler';
5
+ import { ErrorHandler, InvalidGeoJSONError, LayerNotFoundError } from '../../utils/ErrorHandler';
6
6
  import ProjectionUtils from '../../utils/ProjectionUtils';
7
7
  import ValidationUtils from '../../utils/ValidationUtils';
8
8
  import { ConfigManager, MapTools } from "../map";
@@ -20,11 +20,50 @@ export default class Polygon {
20
20
  * @param map OpenLayers 地图实例
21
21
  */
22
22
  constructor(map) {
23
+ /**
24
+ * 由本实例创建的所有图层句柄(含 mask 子图层等)。destroyAll 时统一回收,避免地图销毁后图层泄漏。
25
+ */
26
+ this.managedLayers = new Set();
23
27
  if (!map) {
24
28
  throw new Error('Map instance is required');
25
29
  }
26
30
  this.map = map;
27
31
  }
32
+ /**
33
+ * 跟踪一个本实例创建的图层,便于销毁阶段统一清理。
34
+ * @internal
35
+ */
36
+ trackLayer(layer) {
37
+ this.managedLayers.add(layer);
38
+ return layer;
39
+ }
40
+ /** *********************统一句柄:OL 图层*********************/
41
+ toLayerHandle(layer) {
42
+ const map = this.map;
43
+ const managedLayers = this.managedLayers;
44
+ return {
45
+ layer,
46
+ setVisible(visible) { layer.setVisible(visible); },
47
+ remove() {
48
+ managedLayers.delete(layer);
49
+ map.removeLayer(layer);
50
+ }
51
+ };
52
+ }
53
+ /**
54
+ * 销毁本实例创建的所有图层。供 MyOl.destroy 调用。
55
+ */
56
+ destroyAll() {
57
+ this.managedLayers.forEach(layer => {
58
+ try {
59
+ this.map.removeLayer(layer);
60
+ }
61
+ catch {
62
+ // ignore
63
+ }
64
+ });
65
+ this.managedLayers.clear();
66
+ }
28
67
  /**
29
68
  * 添加地图边框图层
30
69
  * @param data 图层数据,必须是有效的 GeoJSON 格式
@@ -36,13 +75,14 @@ export default class Polygon {
36
75
  ValidationUtils.validateGeoJSONData(data);
37
76
  const mergedOptions = {
38
77
  fillColor: 'rgba(255, 255, 255, 0)',
39
- ...options
78
+ ...options,
79
+ layerName: options?.layerName ?? 'border'
40
80
  };
41
- const layer = this.addPolygon(data, mergedOptions);
81
+ const handle = this.addPolygon(data, mergedOptions);
42
82
  if (mergedOptions.mask) {
43
83
  this.setOutLayer(data);
44
84
  }
45
- return layer;
85
+ return handle;
46
86
  }
47
87
  /**
48
88
  * 从URL添加地图边框图层
@@ -51,14 +91,13 @@ export default class Polygon {
51
91
  * @returns 创建的图层实例
52
92
  * @throws 当数据格式无效时抛出错误
53
93
  */
54
- addBorderPolygonByUrl(url, options) {
94
+ async addBorderPolygonByUrl(url, options) {
55
95
  const mergedOptions = {
56
- layerName: 'border',
57
96
  fillColor: 'rgba(255, 255, 255, 0)',
58
- ...options
97
+ ...options,
98
+ layerName: options?.layerName ?? 'border'
59
99
  };
60
- const layer = this.addPolygonByUrl(url, mergedOptions);
61
- return layer;
100
+ return this.addPolygonByUrl(url, mergedOptions);
62
101
  }
63
102
  /**
64
103
  * 添加多边形图层
@@ -67,7 +106,8 @@ export default class Polygon {
67
106
  * @returns 创建的矢量图层
68
107
  * @throws 当数据格式无效时抛出错误
69
108
  */
70
- addPolygon(dataJSON, options) {
109
+ /** *********************创建多边形图层*********************/
110
+ createPolygonLayer(dataJSON, options) {
71
111
  ValidationUtils.validateGeoJSONData(dataJSON);
72
112
  const mergedOptions = {
73
113
  ...ConfigManager.DEFAULT_POLYGON_OPTIONS,
@@ -92,7 +132,7 @@ export default class Polygon {
92
132
  features = format.readFeatures(dataJSON, ProjectionUtils.getGeoJSONReadOptions(mergedOptions));
93
133
  }
94
134
  catch (error) {
95
- throw new Error(`Failed to parse GeoJSON data: ${error}`);
135
+ throw new InvalidGeoJSONError(error instanceof Error ? error.message : String(error), { layerName: mergedOptions.layerName });
96
136
  }
97
137
  const layer = new VectorLayer({
98
138
  properties: {
@@ -105,60 +145,26 @@ export default class Polygon {
105
145
  });
106
146
  layer.setVisible(mergedOptions.visible);
107
147
  this.map.addLayer(layer);
148
+ this.trackLayer(layer);
108
149
  // 如果需要适应视图
109
150
  if (mergedOptions.fitView) {
110
151
  this.fitViewToLayer(layer);
111
152
  }
112
153
  return layer;
113
154
  }
114
- /**
115
- * 从URL添加多边形图层
116
- * @param url 数据URL
117
- * @param options 图层配置选项
118
- * @returns 创建的矢量图层
119
- * @throws 当数据格式无效时抛出错误
120
- */
121
- addPolygonByUrl(url, options) {
122
- const mergedOptions = {
123
- ...ConfigManager.DEFAULT_POLYGON_OPTIONS,
124
- ...options
125
- };
126
- // 如果指定了图层名称,先移除同名图层
127
- if (mergedOptions.layerName) {
128
- new MapTools(this.map).removeLayer(mergedOptions.layerName);
129
- }
130
- const format = new GeoJSON(ProjectionUtils.getGeoJSONReadOptions(mergedOptions));
131
- // 优化:在解析 Feature 时直接注入 layerName,利用解析过程的遍历,避免解析后的二次循环
132
- if (mergedOptions.layerName) {
133
- const originalReadFeatureFromObject = format.readFeatureFromObject;
134
- format.readFeatureFromObject = function (object, options) {
135
- const feature = originalReadFeatureFromObject.call(this, object, options);
136
- feature.set('layerName', mergedOptions.layerName, true); // true 表示静默设置,不触发事件
137
- return feature;
138
- };
139
- }
140
- const source = new VectorSource({
141
- url,
142
- format
143
- });
144
- const layer = new VectorLayer({
145
- properties: {
146
- name: mergedOptions.layerName,
147
- layerName: mergedOptions.layerName
148
- },
149
- source,
150
- style: PolygonStyleFactory.createStyle(mergedOptions),
151
- zIndex: mergedOptions.zIndex
152
- });
153
- layer.setVisible(mergedOptions.visible);
154
- this.map.addLayer(layer);
155
- // 如果需要适应视图
156
- if (mergedOptions.fitView) {
157
- source.once('featuresloadend', () => {
158
- this.fitViewToLayer(layer);
159
- });
155
+ /** *********************添加多边形图层*********************/
156
+ addPolygon(dataJSON, options) {
157
+ return this.toLayerHandle(this.createPolygonLayer(dataJSON, options));
158
+ }
159
+ /** *********************从 URL 添加多边形图层*********************/
160
+ async addPolygonByUrl(url, options) {
161
+ ValidationUtils.validateNonEmptyString(url, 'Polygon url is required');
162
+ const response = await fetch(url);
163
+ if (!response.ok) {
164
+ throw new Error(`Failed to fetch polygon GeoJSON: ${response.status}`);
160
165
  }
161
- return layer;
166
+ const json = await response.json();
167
+ return this.addPolygon(json, options);
162
168
  }
163
169
  /**
164
170
  * 适应图层视图
@@ -181,7 +187,7 @@ export default class Polygon {
181
187
  ValidationUtils.validateLayerName(layerName);
182
188
  const layers = MapTools.getLayerByLayerName(this.map, layerName);
183
189
  if (layers.length === 0) {
184
- throw new Error(`Layer with name '${layerName}' not found`);
190
+ throw new LayerNotFoundError(layerName, { method: 'updateFeatureColor' });
185
191
  }
186
192
  const layer = layers[0];
187
193
  if (!(layer instanceof VectorLayer)) {
@@ -211,7 +217,11 @@ export default class Polygon {
211
217
  * @param options
212
218
  */
213
219
  setOutLayer(data, options) {
214
- return PolygonMaskLayer.setOutLayer(this.map, data, options);
220
+ const layer = PolygonMaskLayer.setOutLayer(this.map, data, options);
221
+ if (layer) {
222
+ this.trackLayer(layer);
223
+ }
224
+ return layer;
215
225
  }
216
226
  /**
217
227
  * 添加图片图层
@@ -221,7 +231,9 @@ export default class Polygon {
221
231
  * @throws 当数据格式无效时抛出错误
222
232
  */
223
233
  addImageLayer(imageData, options) {
224
- return PolygonImageLayer.addImageLayer(this.map, imageData, options);
234
+ const layer = PolygonImageLayer.addImageLayer(this.map, imageData, options);
235
+ this.trackLayer(layer);
236
+ return this.toLayerHandle(layer);
225
237
  }
226
238
  /**
227
239
  * 添加热力图图层
@@ -229,7 +241,11 @@ export default class Polygon {
229
241
  * @param options 热力图配置
230
242
  */
231
243
  addHeatmap(pointData, options) {
232
- return PolygonHeatmapLayer.addHeatmap(this.map, pointData, options);
244
+ const layer = PolygonHeatmapLayer.addHeatmap(this.map, pointData, options);
245
+ if (layer) {
246
+ this.trackLayer(layer);
247
+ }
248
+ return this.toLayerHandle(layer);
233
249
  }
234
250
  /**
235
251
  * 添加遮罩图层
@@ -239,10 +255,17 @@ export default class Polygon {
239
255
  * @throws 当数据格式无效时抛出错误
240
256
  */
241
257
  addMaskLayer(data, options) {
242
- return PolygonMaskLayer.addMaskLayer(this.map, data, options);
258
+ const layer = PolygonMaskLayer.addMaskLayer(this.map, data, options);
259
+ this.trackLayer(layer);
260
+ return this.toLayerHandle(layer);
243
261
  }
244
262
  removePolygonLayer(layerName) {
245
263
  new MapTools(this.map).removeLayer(layerName);
246
- this[layerName] = null;
264
+ // managedLayers 中也移除对应记录
265
+ this.managedLayers.forEach(layer => {
266
+ if (layer.get('layerName') === layerName || layer.get('name') === layerName) {
267
+ this.managedLayers.delete(layer);
268
+ }
269
+ });
247
270
  }
248
271
  }
@@ -3,6 +3,7 @@ import { Point } from "ol/geom";
3
3
  import { Heatmap } from "ol/layer";
4
4
  import VectorSource from "ol/source/Vector";
5
5
  import { ConfigManager, MapTools } from "../map";
6
+ import { ErrorHandler } from "../../utils/ErrorHandler";
6
7
  /**
7
8
  * 面热力图辅助类。
8
9
  */
@@ -28,11 +29,23 @@ export default class PolygonHeatmapLayer {
28
29
  }
29
30
  map.addLayer(heatmapLayer);
30
31
  const valueKey = mergedOptions.valueKey || ConfigManager.DEFAULT_HEATMAP_VALUE_KEY;
31
- const max = Math.max(...pointData.map(item => item[valueKey]));
32
+ // 计算归一化最大值。当数据里没有 valueKey 或值全为非数字时,max NaN/-Infinity,
33
+ // 会导致 weight = value/max = NaN,OL 热力图整层不渲染。
34
+ // 此时回退到等权重 1(让用户至少看到热力图,再去补 valueKey)。
35
+ const numericValues = pointData
36
+ .map(item => Number(item[valueKey]))
37
+ .filter(v => Number.isFinite(v));
38
+ const max = numericValues.length > 0 ? Math.max(...numericValues) : 0;
39
+ const useFallbackWeight = max <= 0;
40
+ if (useFallbackWeight) {
41
+ ErrorHandler.getInstance().warn(`[Heatmap] 数据中未找到有效的 "${valueKey}" 字段,已回退为等权重渲染。请通过 options.valueKey 指定正确字段。`);
42
+ }
32
43
  pointData.forEach(item => {
44
+ const raw = Number(item[valueKey]);
45
+ const weight = useFallbackWeight || !Number.isFinite(raw) ? 1 : raw / max;
33
46
  heatmapLayer.getSource().addFeature(new Feature({
34
47
  geometry: new Point([item.lgtd, item.lttd]),
35
- weight: item[valueKey] / max
48
+ weight
36
49
  }));
37
50
  });
38
51
  return heatmapLayer;
@@ -10,11 +10,11 @@ import type { MapJSONData, MaskLayerOptions } from "../../types";
10
10
  export default class PolygonMaskLayer {
11
11
  static setOutLayer(map: Map, data: MapJSONData, options?: {
12
12
  layerName?: string;
13
- extent?: any;
13
+ extent?: number[];
14
14
  fillColor?: string;
15
15
  strokeWidth?: number;
16
16
  strokeColor?: string;
17
17
  zIndex?: number;
18
18
  }): VectorLayer<VectorSource<Feature<Geometry>>, Feature<Geometry>>;
19
- static addMaskLayer(map: Map, data: any, options?: MaskLayerOptions): VectorLayer<VectorSource>;
19
+ static addMaskLayer(map: Map, data: MapJSONData, options?: MaskLayerOptions): VectorLayer<VectorSource>;
20
20
  }
@@ -98,7 +98,9 @@ export default class PolygonMaskLayer {
98
98
  }) : undefined
99
99
  }),
100
100
  opacity: mergedOptions.opacity,
101
- visible: mergedOptions.visible
101
+ visible: mergedOptions.visible,
102
+ // 显式传 zIndex,否则被天地图底图(zIndex 9)盖住,用户看不到 mask
103
+ zIndex: mergedOptions.zIndex
102
104
  });
103
105
  maskLayer.set('layerName', mergedOptions.layerName);
104
106
  map.addLayer(maskLayer);
@@ -1,11 +1,12 @@
1
- import Feature from "ol/Feature";
1
+ import Feature, { type FeatureLike } from "ol/Feature";
2
+ import { Style } from "ol/style";
2
3
  import type { FeatureColorUpdateOptions, PolygonOptions } from "../../types";
3
4
  /**
4
5
  * 面图层样式工厂。
5
6
  */
6
7
  export default class PolygonStyleFactory {
7
- static getFeatureText(feature: Feature, options: PolygonOptions | FeatureColorUpdateOptions): string;
8
- static createStyle(options: PolygonOptions): any;
8
+ static getFeatureText(feature: FeatureLike, options: PolygonOptions | FeatureColorUpdateOptions): string;
9
+ static createStyle(options: PolygonOptions): Style | Style[] | ((feature: FeatureLike) => Style | Style[]);
9
10
  static updateSingleFeatureColor(feature: Feature, colorObj?: {
10
11
  [propName: string]: string;
11
12
  }, options?: FeatureColorUpdateOptions): void;
@@ -0,0 +1,66 @@
1
+ import { Projection as olProjProjection } from "ol/proj";
2
+ import type { Units } from "ol/proj/Units";
3
+ import type { MapInitType } from "../../types";
4
+ /**
5
+ * 项目内置 EPSG 编码。
6
+ */
7
+ export declare const PROJECTIONS: {
8
+ readonly WGS84: "EPSG:4326";
9
+ readonly CGCS2000: "EPSG:4490";
10
+ readonly CGCS2000_3_DEGREE: "EPSG:4549";
11
+ };
12
+ /**
13
+ * 自定义投影注册元数据。
14
+ */
15
+ export interface CustomProjectionRegistration {
16
+ /** EPSG 编码,例如 "EPSG:4528" */
17
+ code: string;
18
+ /** proj4 定义字符串 */
19
+ def: string;
20
+ /** 投影范围 [minX, minY, maxX, maxY] */
21
+ extent?: number[];
22
+ /** 世界范围 [minX, minY, maxX, maxY] */
23
+ worldExtent?: number[];
24
+ /** OL 投影单位(degrees / m / ft 等),未提供时从 proj4 定义自动推导 */
25
+ units?: Units;
26
+ }
27
+ export default class ProjectionManager {
28
+ /** 项目内置 EPSG 编码常量。 */
29
+ static readonly PROJECTIONS: {
30
+ readonly WGS84: "EPSG:4326";
31
+ readonly CGCS2000: "EPSG:4490";
32
+ readonly CGCS2000_3_DEGREE: "EPSG:4549";
33
+ };
34
+ /** 默认投影 = EPSG:4490 (CGCS2000)。 */
35
+ static readonly DEFAULT_PROJECTION: "EPSG:4490";
36
+ /**
37
+ * 注册内置投影 + 应用 options.projection 自定义投影。
38
+ *
39
+ * 幂等:重复调用安全。被 MyOl 构造函数和 MyOl.createView 都会调,外部一般无需直接调用。
40
+ */
41
+ static initialize(options?: MapInitType): void;
42
+ /**
43
+ * 注册一个任意 EPSG 投影到 proj4 + OL。
44
+ *
45
+ * 这是 MyOl 之外的便利入口。units 未提供时由 proj4 自动推导。
46
+ */
47
+ static register(registration: CustomProjectionRegistration): void;
48
+ /**
49
+ * 解析视图投影,优先复用已注册投影,避免丢失 proj4 推导的单位信息。
50
+ *
51
+ * 仅当 options 显式覆盖 extent / worldExtent / units 之一时才新建 Projection 实例。
52
+ */
53
+ static resolveViewProjection(options: MapInitType, code: string): olProjProjection;
54
+ /**
55
+ * 缺失时注册 proj4 投影定义,避免生产构建依赖第三方模块默认副作用。
56
+ */
57
+ private static ensureProj4Definition;
58
+ /**
59
+ * 应用用户显式提供的投影元数据。
60
+ *
61
+ * - 提供 units:以用户输入为权威,覆盖已注册投影
62
+ * - 未提供 units 但已注册:仅刷新 extent / worldExtent
63
+ * - 未提供 units 且未注册:新建 Projection(OL 会从 proj4 推导单位)
64
+ */
65
+ private static applyCustomProjectionMetadata;
66
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * 投影管理器
3
+ *
4
+ * 集中管理所有投影注册与解析逻辑,对外暴露内置常量(EPSG:4326 / 4490 / 4549)以及
5
+ * 自定义投影注册入口。MyOl 内部委托此类初始化投影,使用户也可以在 MyOl 实例之外
6
+ * 主动注册新的 EPSG。
7
+ */
8
+ import proj4 from "proj4";
9
+ import { register as olProj4Register } from "ol/proj/proj4";
10
+ import { Projection as olProjProjection, addProjection as olProjAddProjection, get as olProjGetProjection } from "ol/proj";
11
+ /**
12
+ * 项目内置 EPSG 编码。
13
+ */
14
+ export const PROJECTIONS = {
15
+ WGS84: "EPSG:4326",
16
+ CGCS2000: "EPSG:4490",
17
+ CGCS2000_3_DEGREE: "EPSG:4549"
18
+ };
19
+ /**
20
+ * 内置 EPSG 的 proj4 定义字符串。
21
+ */
22
+ const PROJECTION_DEFINITIONS = {
23
+ [PROJECTIONS.WGS84]: "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",
24
+ [PROJECTIONS.CGCS2000]: "+proj=longlat +ellps=GRS80 +no_defs",
25
+ [PROJECTIONS.CGCS2000_3_DEGREE]: "+proj=tmerc +lat_0=0 +lon_0=120 +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs"
26
+ };
27
+ class ProjectionManager {
28
+ /**
29
+ * 注册内置投影 + 应用 options.projection 自定义投影。
30
+ *
31
+ * 幂等:重复调用安全。被 MyOl 构造函数和 MyOl.createView 都会调,外部一般无需直接调用。
32
+ */
33
+ static initialize(options) {
34
+ ProjectionManager.ensureProj4Definition(PROJECTIONS.WGS84, PROJECTION_DEFINITIONS[PROJECTIONS.WGS84]);
35
+ ProjectionManager.ensureProj4Definition(PROJECTIONS.CGCS2000, PROJECTION_DEFINITIONS[PROJECTIONS.CGCS2000]);
36
+ ProjectionManager.ensureProj4Definition(PROJECTIONS.CGCS2000_3_DEGREE, PROJECTION_DEFINITIONS[PROJECTIONS.CGCS2000_3_DEGREE]);
37
+ // 用户自定义投影的 proj4 定义优先注入
38
+ if (options?.projection?.code && options.projection.def) {
39
+ proj4.defs(options.projection.code, options.projection.def);
40
+ }
41
+ // 注入 proj4 → OpenLayers
42
+ olProj4Register(proj4);
43
+ // 注册 CGCS2000 OL Projection(带 degrees 单位)
44
+ olProjAddProjection(new olProjProjection({
45
+ code: PROJECTIONS.CGCS2000,
46
+ extent: [-180, -90, 180, 90],
47
+ worldExtent: [-180, -90, 180, 90],
48
+ units: "degrees"
49
+ }));
50
+ if (options?.projection?.code) {
51
+ ProjectionManager.applyCustomProjectionMetadata(options.projection);
52
+ }
53
+ }
54
+ /**
55
+ * 注册一个任意 EPSG 投影到 proj4 + OL。
56
+ *
57
+ * 这是 MyOl 之外的便利入口。units 未提供时由 proj4 自动推导。
58
+ */
59
+ static register(registration) {
60
+ const { code, def, extent, worldExtent, units } = registration;
61
+ ProjectionManager.ensureProj4Definition(code, def);
62
+ olProj4Register(proj4);
63
+ ProjectionManager.applyCustomProjectionMetadata({
64
+ code,
65
+ def,
66
+ extent,
67
+ worldExtent,
68
+ units
69
+ });
70
+ }
71
+ /**
72
+ * 解析视图投影,优先复用已注册投影,避免丢失 proj4 推导的单位信息。
73
+ *
74
+ * 仅当 options 显式覆盖 extent / worldExtent / units 之一时才新建 Projection 实例。
75
+ */
76
+ static resolveViewProjection(options, code) {
77
+ const registeredProjection = olProjGetProjection(code);
78
+ if (registeredProjection
79
+ && !options.projection?.extent
80
+ && !options.projection?.worldExtent
81
+ && !options.projection?.units) {
82
+ return registeredProjection;
83
+ }
84
+ return new olProjProjection({
85
+ code,
86
+ extent: options.projection?.extent
87
+ ?? registeredProjection?.getExtent()
88
+ ?? [-180, -90, 180, 90],
89
+ worldExtent: options.projection?.worldExtent
90
+ ?? registeredProjection?.getWorldExtent()
91
+ ?? [-180, -90, 180, 90],
92
+ units: options.projection?.units
93
+ ?? registeredProjection?.getUnits()
94
+ ?? "degrees"
95
+ });
96
+ }
97
+ /**
98
+ * 缺失时注册 proj4 投影定义,避免生产构建依赖第三方模块默认副作用。
99
+ */
100
+ static ensureProj4Definition(code, definition) {
101
+ if (!proj4.defs(code)) {
102
+ proj4.defs(code, definition);
103
+ }
104
+ }
105
+ /**
106
+ * 应用用户显式提供的投影元数据。
107
+ *
108
+ * - 提供 units:以用户输入为权威,覆盖已注册投影
109
+ * - 未提供 units 但已注册:仅刷新 extent / worldExtent
110
+ * - 未提供 units 且未注册:新建 Projection(OL 会从 proj4 推导单位)
111
+ */
112
+ static applyCustomProjectionMetadata(projection) {
113
+ const { code, extent, worldExtent, units } = projection;
114
+ const registeredProjection = olProjGetProjection(code);
115
+ if (units) {
116
+ olProjAddProjection(new olProjProjection({
117
+ code,
118
+ extent: extent ?? registeredProjection?.getExtent(),
119
+ worldExtent: worldExtent ?? registeredProjection?.getWorldExtent(),
120
+ units
121
+ }));
122
+ return;
123
+ }
124
+ if (registeredProjection) {
125
+ if (extent)
126
+ registeredProjection.setExtent(extent);
127
+ if (worldExtent)
128
+ registeredProjection.setWorldExtent(worldExtent);
129
+ return;
130
+ }
131
+ if (extent || worldExtent) {
132
+ olProjAddProjection(new olProjProjection({
133
+ code,
134
+ extent,
135
+ worldExtent
136
+ }));
137
+ }
138
+ }
139
+ }
140
+ /** 项目内置 EPSG 编码常量。 */
141
+ ProjectionManager.PROJECTIONS = PROJECTIONS;
142
+ /** 默认投影 = EPSG:4490 (CGCS2000)。 */
143
+ ProjectionManager.DEFAULT_PROJECTION = PROJECTIONS.CGCS2000;
144
+ export default ProjectionManager;
@@ -0,0 +1,2 @@
1
+ export { default as ProjectionManager, PROJECTIONS } from './ProjectionManager';
2
+ export type { CustomProjectionRegistration } from './ProjectionManager';
@@ -0,0 +1 @@
1
+ export { default as ProjectionManager, PROJECTIONS } from './ProjectionManager';
@@ -64,7 +64,7 @@ export default class SelectHandler {
64
64
  /**
65
65
  * 通过属性选择要素
66
66
  */
67
- selectByProperty(propertyName: string, propertyValue: any, options?: ProgrammaticSelectOptions): this;
67
+ selectByProperty(propertyName: string, propertyValue: unknown, options?: ProgrammaticSelectOptions): this;
68
68
  /**
69
69
  * 应用选择(编程式)
70
70
  */
@@ -1,5 +1,5 @@
1
1
  import { Map as OLMap } from 'ol';
2
- import { VueTemplatePointInstance } from '../../types';
2
+ import { ControlHandle, VueTemplatePointInstance } from '../../types';
3
3
  /**
4
4
  * Vue模板点位管理类
5
5
  * 用于在地图上添加和管理Vue组件覆盖物
@@ -23,10 +23,8 @@ export default class VueTemplatePoint {
23
23
  addVueTemplatePoint(pointDataList: any[], template: any, options?: {
24
24
  positioning?: 'bottom-left' | 'bottom-center' | 'bottom-right' | 'center-left' | 'center-center' | 'center-right' | 'top-left' | 'top-center' | 'top-right';
25
25
  stopEvent?: boolean;
26
- }): {
27
- setVisible: (visible: boolean) => void;
26
+ }): ControlHandle<VueTemplatePointInstance[]> & {
28
27
  setOneVisibleByProp: (propName: string, propValue: any, visible: boolean) => void;
29
- remove: () => void;
30
28
  getPoints: () => VueTemplatePointInstance[];
31
29
  };
32
30
  /**
@@ -113,6 +113,7 @@ export default class VueTemplatePoint {
113
113
  this.vuePoints.set(instance.id, instance);
114
114
  });
115
115
  return {
116
+ target: instances,
116
117
  setVisible: (visible) => {
117
118
  instances.forEach((instance) => {
118
119
  instance.setVisible(visible);
@@ -378,9 +379,22 @@ class VueTemplatePointInstanceImpl {
378
379
  throw new Error('Cannot update props on destroyed DOM point');
379
380
  }
380
381
  try {
381
- // 重新创建Vue应用实例
382
+ // 重新创建Vue应用实例。
383
+ // 注意:createVueApp 把 options.props 当 Vue 组件的 props 声明(非传值),
384
+ // 根组件没有父组件传值,因此必须把每个值包成 { default: value } 才能被
385
+ // Vue 当 prop 默认值消费,render 里才能读到。
382
386
  this.destroyVueApp();
383
- this.options.props = { ...this.options.props, ...newProps };
387
+ const normalized = { ...this.options.props };
388
+ for (const key of Object.keys(newProps)) {
389
+ const value = newProps[key];
390
+ // 已经是 { type, default } 形式则原样合并,否则包装为默认值
391
+ const isAlreadyDeclaration = value !== null
392
+ && typeof value === 'object'
393
+ && (Object.prototype.hasOwnProperty.call(value, 'default')
394
+ || Object.prototype.hasOwnProperty.call(value, 'type'));
395
+ normalized[key] = isAlreadyDeclaration ? value : { default: value };
396
+ }
397
+ this.options.props = normalized;
384
398
  this.createVueApp();
385
399
  }
386
400
  catch (error) {
@@ -10,6 +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
14
  { text: 'GitHub', link: 'https://github.com/cuteyuchen/my-openlayer' }
14
15
  ],
15
16
 
package/docs/Line.md CHANGED
@@ -65,18 +65,18 @@ interface FlowLineLayerHandle {
65
65
  ### addLine
66
66
 
67
67
  ```typescript
68
- addLine(data: MapJSONData, options?: LineOptions): VectorLayer<VectorSource>
68
+ addLine(data: MapJSONData, options: LineOptions & { layerName: string }): LayerHandle<VectorLayer<VectorSource>>
69
69
  ```
70
70
 
71
- 添加静态线图层。
71
+ 添加静态线图层,返回统一 `LayerHandle`。
72
72
 
73
73
  ### addLineByUrl
74
74
 
75
75
  ```typescript
76
- addLineByUrl(url: string, options?: LineOptions): VectorLayer<VectorSource>
76
+ addLineByUrl(url: string, options: LineOptions & { layerName: string }): Promise<LayerHandle<VectorLayer<VectorSource>>>
77
77
  ```
78
78
 
79
- 从 URL 加载并添加静态线图层。
79
+ 从 URL 加载 GeoJSON 后添加静态线图层,await 后返回完整 `LayerHandle`。
80
80
 
81
81
  ### removeLineLayer
82
82