my-openlayer 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,299 @@
1
+ /**
2
+ * GeoJSON 数据标准化工具。
3
+ *
4
+ * 本模块借鉴 mapshaper 对地理数据输入的统一处理思路,
5
+ * 但仅提供轻量的点数据标准化能力,不包含拓扑简化、dissolve、clip/erase 等 GIS 编辑功能。
6
+ * 参见 https://github.com/mbloch/mapshaper (MPL-2.0, Node >=20.11)。
7
+ */
8
+ /** *********************内部工具函数*********************/
9
+ /**
10
+ * 判断输入是否为合法的 GeoJSON Feature(type 为 'Feature' 且包含 geometry)。
11
+ */
12
+ function isFeature(obj) {
13
+ return (typeof obj === 'object' &&
14
+ obj !== null &&
15
+ obj.type === 'Feature' &&
16
+ typeof obj.geometry === 'object');
17
+ }
18
+ /**
19
+ * 判断输入是否为 GeoJSON FeatureCollection。
20
+ */
21
+ function isFeatureCollection(obj) {
22
+ return (typeof obj === 'object' &&
23
+ obj !== null &&
24
+ obj.type === 'FeatureCollection' &&
25
+ Array.isArray(obj.features));
26
+ }
27
+ /**
28
+ * 判断输入是否为裸 GeoJSON Point geometry。
29
+ */
30
+ function isPointGeometry(obj) {
31
+ return (typeof obj === 'object' &&
32
+ obj !== null &&
33
+ obj.type === 'Point' &&
34
+ Array.isArray(obj.coordinates));
35
+ }
36
+ /**
37
+ * 判断输入是否为裸 GeoJSON MultiPoint geometry。
38
+ */
39
+ function isMultiPointGeometry(obj) {
40
+ return (typeof obj === 'object' &&
41
+ obj !== null &&
42
+ obj.type === 'MultiPoint' &&
43
+ Array.isArray(obj.coordinates));
44
+ }
45
+ /**
46
+ * 验证单个坐标对 [lng, lat] 是否合法。
47
+ * 要求两个分量均为有限数。
48
+ */
49
+ function isValidCoordinate(coord) {
50
+ return (Array.isArray(coord) &&
51
+ coord.length >= 2 &&
52
+ Number.isFinite(coord[0]) &&
53
+ Number.isFinite(coord[1]));
54
+ }
55
+ /**
56
+ * 从 GeoJSON Feature 提取单个点。
57
+ * 仅处理 geometry.type === 'Point' 的 Feature,其他类型返回 undefined。
58
+ */
59
+ function extractPointFromFeature(feature) {
60
+ if (feature.geometry?.type !== 'Point')
61
+ return undefined;
62
+ const coords = feature.geometry.coordinates;
63
+ if (!isValidCoordinate(coords))
64
+ return undefined;
65
+ return {
66
+ ...feature.properties,
67
+ lgtd: coords[0],
68
+ lttd: coords[1],
69
+ };
70
+ }
71
+ /**
72
+ * 从 GeoJSON MultiPoint Feature 提取多个点。
73
+ */
74
+ function extractPointsFromMultiPointFeature(feature) {
75
+ if (feature.geometry?.type !== 'MultiPoint')
76
+ return [];
77
+ const coords = feature.geometry.coordinates;
78
+ const results = [];
79
+ for (const pair of coords) {
80
+ if (!isValidCoordinate(pair))
81
+ continue;
82
+ results.push({
83
+ ...feature.properties,
84
+ lgtd: pair[0],
85
+ lttd: pair[1],
86
+ });
87
+ }
88
+ return results;
89
+ }
90
+ /** *********************公开 API*********************/
91
+ /**
92
+ * 将多种形态的点数据输入统一标准化为 `PointData[]`。
93
+ *
94
+ * 支持的输入形态:
95
+ * - `PointData[]`:原样返回(深拷贝后过滤非法坐标)
96
+ * - GeoJSON `FeatureCollection`:遍历 features,提取 Point / MultiPoint geometry
97
+ * - 单个 GeoJSON `Feature`:提取其 Point 或 MultiPoint geometry
98
+ * - 裸 `Point` geometry:转为单元素 PointData 数组
99
+ * - 裸 `MultiPoint` geometry:拆成多个 PointData
100
+ *
101
+ * 过滤规则:
102
+ * - 非 Point / MultiPoint geometry 的 Feature 会被跳过
103
+ * - 坐标含 NaN / Infinity / 缺失的点会被跳过
104
+ * - null / undefined 输入返回空数组
105
+ *
106
+ * @param input 任意兼容的点数据输入
107
+ * @returns 标准化后的 PointData 数组
108
+ */
109
+ export function normalizePointData(input) {
110
+ if (input == null)
111
+ return [];
112
+ // 数组 — 逐项处理,兼容 PointData[] 和 FeatureData[] 混合
113
+ if (Array.isArray(input)) {
114
+ const results = [];
115
+ for (const item of input) {
116
+ if (typeof item !== 'object' || item === null)
117
+ continue;
118
+ // PointData 形状(有 lgtd/lttd)
119
+ if (Number.isFinite(item.lgtd) && Number.isFinite(item.lttd)) {
120
+ results.push({ ...item });
121
+ continue;
122
+ }
123
+ // Feature 形状
124
+ if (isFeature(item)) {
125
+ const pt = extractPointFromFeature(item);
126
+ if (pt)
127
+ results.push(pt);
128
+ continue;
129
+ }
130
+ // 裸 Point geometry
131
+ if (isPointGeometry(item) && isValidCoordinate(item.coordinates)) {
132
+ results.push({ lgtd: item.coordinates[0], lttd: item.coordinates[1] });
133
+ }
134
+ }
135
+ return results;
136
+ }
137
+ // 非对象直接返回空
138
+ if (typeof input !== 'object')
139
+ return [];
140
+ // GeoJSON FeatureCollection
141
+ if (isFeatureCollection(input)) {
142
+ const results = [];
143
+ for (const feature of input.features) {
144
+ if (feature.geometry?.type === 'Point') {
145
+ const pt = extractPointFromFeature(feature);
146
+ if (pt)
147
+ results.push(pt);
148
+ }
149
+ else if (feature.geometry?.type === 'MultiPoint') {
150
+ results.push(...extractPointsFromMultiPointFeature(feature));
151
+ }
152
+ // 其他 geometry 类型跳过
153
+ }
154
+ return results;
155
+ }
156
+ // 单个 GeoJSON Feature
157
+ if (isFeature(input)) {
158
+ if (input.geometry?.type === 'Point') {
159
+ const pt = extractPointFromFeature(input);
160
+ return pt ? [pt] : [];
161
+ }
162
+ if (input.geometry?.type === 'MultiPoint') {
163
+ return extractPointsFromMultiPointFeature(input);
164
+ }
165
+ return [];
166
+ }
167
+ // 裸 Point geometry
168
+ if (isPointGeometry(input)) {
169
+ if (!isValidCoordinate(input.coordinates))
170
+ return [];
171
+ return [{ lgtd: input.coordinates[0], lttd: input.coordinates[1] }];
172
+ }
173
+ // 裸 MultiPoint geometry
174
+ if (isMultiPointGeometry(input)) {
175
+ const results = [];
176
+ for (const pair of input.coordinates) {
177
+ if (!isValidCoordinate(pair))
178
+ continue;
179
+ results.push({ lgtd: pair[0], lttd: pair[1] });
180
+ }
181
+ return results;
182
+ }
183
+ return [];
184
+ }
185
+ /** *********************GeoJSON 分组处理*********************/
186
+ /**
187
+ * 将 GeoJSON geometry 类型归类为简化的三大类。
188
+ * 无法识别的类型返回 null。
189
+ */
190
+ export function classifyGeometryType(geometryType) {
191
+ switch (geometryType) {
192
+ case 'Point':
193
+ case 'MultiPoint':
194
+ return 'point';
195
+ case 'LineString':
196
+ case 'MultiLineString':
197
+ return 'line';
198
+ case 'Polygon':
199
+ case 'MultiPolygon':
200
+ return 'polygon';
201
+ default:
202
+ return null;
203
+ }
204
+ }
205
+ /**
206
+ * 将单个 GeoJSON 输入项标准化为 FeatureData 数组。
207
+ * @internal
208
+ */
209
+ function normalizeOneGeoJSON(input) {
210
+ // FeatureCollection
211
+ if (isFeatureCollection(input)) {
212
+ return input.features;
213
+ }
214
+ // Feature
215
+ if (isFeature(input)) {
216
+ return [input];
217
+ }
218
+ // 裸 geometry → 包装为 Feature
219
+ if (typeof input === 'object' && input !== null && 'type' in input && 'coordinates' in input) {
220
+ const geom = input;
221
+ return [{
222
+ type: 'Feature',
223
+ properties: {},
224
+ geometry: {
225
+ type: geom.type,
226
+ coordinates: geom.coordinates,
227
+ },
228
+ }];
229
+ }
230
+ return [];
231
+ }
232
+ /**
233
+ * 将 addGeoJSON 的多种输入形态统一标准化为带 datasetKey 的 FeatureData 数组。
234
+ *
235
+ * - 单个 JSON → datasetKey 为 `'default'`
236
+ * - 数组 → datasetKey 为字符串索引 `'0'`, `'1'`, ...
237
+ * - `Record<string, json>` → datasetKey 为 key
238
+ */
239
+ export function normalizeGeoJSONInputs(data) {
240
+ if (data == null)
241
+ return [];
242
+ // 数组
243
+ if (Array.isArray(data)) {
244
+ return data.map((item, index) => ({
245
+ datasetKey: String(index),
246
+ features: normalizeOneGeoJSON(item),
247
+ }));
248
+ }
249
+ // 非对象
250
+ if (typeof data !== 'object')
251
+ return [];
252
+ // Record<string, json> — 有 type 字段的当作单个 GeoJSON,不是 Record
253
+ const obj = data;
254
+ if ('type' in obj && typeof obj.type === 'string') {
255
+ // 单个 GeoJSON(FeatureCollection / Feature / Geometry)
256
+ return [{ datasetKey: 'default', features: normalizeOneGeoJSON(data) }];
257
+ }
258
+ // Record<string, json>
259
+ const results = [];
260
+ for (const key of Object.keys(obj)) {
261
+ const item = obj[key];
262
+ results.push({ datasetKey: key, features: normalizeOneGeoJSON(item) });
263
+ }
264
+ return results;
265
+ }
266
+ export function splitByGroupAndGeometry(normalized, groupBy) {
267
+ const result = {};
268
+ for (const { datasetKey, features } of normalized) {
269
+ for (let index = 0; index < features.length; index++) {
270
+ const feature = features[index];
271
+ const geomType = feature.geometry?.type;
272
+ if (!geomType)
273
+ continue;
274
+ const category = classifyGeometryType(geomType);
275
+ if (!category)
276
+ continue;
277
+ // 确定 groupKey
278
+ let groupKey;
279
+ if (groupBy === undefined) {
280
+ groupKey = datasetKey;
281
+ }
282
+ else if (typeof groupBy === 'string') {
283
+ const val = feature.properties?.[groupBy];
284
+ groupKey = val != null ? String(val) : datasetKey;
285
+ }
286
+ else {
287
+ const val = groupBy(feature.properties ?? {}, { datasetKey, geometryType: category, feature, index });
288
+ groupKey = val != null ? String(val) : datasetKey;
289
+ }
290
+ // 初始化分组
291
+ if (!result[groupKey]) {
292
+ result[groupKey] = { point: [], line: [], polygon: [] };
293
+ }
294
+ // 附加 datasetKey 元数据
295
+ result[groupKey][category].push({ ...feature, _datasetKey: datasetKey });
296
+ }
297
+ }
298
+ return result;
299
+ }
@@ -1,7 +1,3 @@
1
- /**
2
- * 验证工具类
3
- * 统一管理参数校验逻辑,错误记录由调用方或 ErrorHandler 负责。
4
- */
5
1
  export default class ValidationUtils {
6
2
  /**
7
3
  * 判断值是否为有效数字。
@@ -2,6 +2,7 @@
2
2
  * 验证工具类
3
3
  * 统一管理参数校验逻辑,错误记录由调用方或 ErrorHandler 负责。
4
4
  */
5
+ import { ErrorHandler, ErrorType } from './ErrorHandler';
5
6
  export default class ValidationUtils {
6
7
  /**
7
8
  * 判断值是否为有效数字。
@@ -102,7 +103,7 @@ export default class ValidationUtils {
102
103
  */
103
104
  static validateLayerName(layerName) {
104
105
  if (!ValidationUtils.isValidLayerName(layerName)) {
105
- throw new Error('Layer name is required');
106
+ throw ErrorHandler.getInstance().createAndHandleError('Layer name is required', ErrorType.VALIDATION_ERROR);
106
107
  }
107
108
  }
108
109
  /**
@@ -110,13 +111,13 @@ export default class ValidationUtils {
110
111
  */
111
112
  static validateImageData(imageData, allowEmptyImg = false) {
112
113
  if (!imageData) {
113
- throw new Error('Invalid image data: imageData is required');
114
+ throw ErrorHandler.getInstance().createAndHandleError('Invalid image data: imageData is required', ErrorType.VALIDATION_ERROR);
114
115
  }
115
116
  if (!allowEmptyImg && !imageData.img) {
116
- throw new Error('Invalid image data: img is required');
117
+ throw ErrorHandler.getInstance().createAndHandleError('Invalid image data: img is required', ErrorType.VALIDATION_ERROR);
117
118
  }
118
119
  if (imageData.extent && !ValidationUtils.isValidExtent(imageData.extent)) {
119
- throw new Error('Invalid extent: must be an array of 4 numbers [minX, minY, maxX, maxY]');
120
+ throw ErrorHandler.getInstance().createAndHandleError('Invalid extent: must be an array of 4 numbers [minX, minY, maxX, maxY]', ErrorType.VALIDATION_ERROR);
120
121
  }
121
122
  }
122
123
  /**
@@ -124,7 +125,7 @@ export default class ValidationUtils {
124
125
  */
125
126
  static validateMaskData(data) {
126
127
  if (data === undefined || data === null) {
127
- throw new Error('Mask data is required');
128
+ throw ErrorHandler.getInstance().createAndHandleError('Mask data is required', ErrorType.VALIDATION_ERROR);
128
129
  }
129
130
  }
130
131
  /**
@@ -138,7 +139,7 @@ export default class ValidationUtils {
138
139
  */
139
140
  static validateRequired(value, message) {
140
141
  if (value === undefined || value === null || value === '') {
141
- throw new Error(message);
142
+ throw ErrorHandler.getInstance().createAndHandleError(message, ErrorType.VALIDATION_ERROR);
142
143
  }
143
144
  }
144
145
  /**
@@ -146,13 +147,13 @@ export default class ValidationUtils {
146
147
  */
147
148
  static validateCoordinate(longitude, latitude) {
148
149
  if (!ValidationUtils.isFiniteNumber(longitude) || !ValidationUtils.isFiniteNumber(latitude)) {
149
- throw new Error('Longitude and latitude must be valid numbers');
150
+ throw ErrorHandler.getInstance().createAndHandleError('Longitude and latitude must be valid numbers', ErrorType.VALIDATION_ERROR);
150
151
  }
151
152
  if (longitude < -180 || longitude > 180) {
152
- throw new Error('Longitude must be between -180 and 180');
153
+ throw ErrorHandler.getInstance().createAndHandleError('Longitude must be between -180 and 180', ErrorType.VALIDATION_ERROR);
153
154
  }
154
155
  if (latitude < -90 || latitude > 90) {
155
- throw new Error('Latitude must be between -90 and 90');
156
+ throw ErrorHandler.getInstance().createAndHandleError('Latitude must be between -90 and 90', ErrorType.VALIDATION_ERROR);
156
157
  }
157
158
  }
158
159
  /**
@@ -160,7 +161,7 @@ export default class ValidationUtils {
160
161
  */
161
162
  static validateType(value, expectedType, message) {
162
163
  if (typeof value !== expectedType) {
163
- throw new Error(message);
164
+ throw ErrorHandler.getInstance().createAndHandleError(message, ErrorType.VALIDATION_ERROR);
164
165
  }
165
166
  }
166
167
  /**
@@ -168,7 +169,7 @@ export default class ValidationUtils {
168
169
  */
169
170
  static validateNonEmptyString(str, message) {
170
171
  if (!ValidationUtils.isValidLayerName(str)) {
171
- throw new Error(message);
172
+ throw ErrorHandler.getInstance().createAndHandleError(message, ErrorType.VALIDATION_ERROR);
172
173
  }
173
174
  }
174
175
  /**
@@ -176,7 +177,7 @@ export default class ValidationUtils {
176
177
  */
177
178
  static validateMap(map) {
178
179
  if (!map) {
179
- throw new Error('Map instance is required');
180
+ throw ErrorHandler.getInstance().createAndHandleError('Map instance is required', ErrorType.VALIDATION_ERROR);
180
181
  }
181
182
  }
182
183
  /**
@@ -184,7 +185,7 @@ export default class ValidationUtils {
184
185
  */
185
186
  static validateMeasureType(type) {
186
187
  if (type !== 'LineString' && type !== 'Polygon') {
187
- throw new Error('Invalid measure type. Must be "LineString" or "Polygon"');
188
+ throw ErrorHandler.getInstance().createAndHandleError('Invalid measure type. Must be "LineString" or "Polygon"', ErrorType.VALIDATION_ERROR);
188
189
  }
189
190
  }
190
191
  /**
@@ -192,7 +193,7 @@ export default class ValidationUtils {
192
193
  */
193
194
  static validatePositiveNumber(value, message) {
194
195
  if (!ValidationUtils.isFiniteNumber(value) || value <= 0) {
195
- throw new Error(message);
196
+ throw ErrorHandler.getInstance().createAndHandleError(message, ErrorType.VALIDATION_ERROR);
196
197
  }
197
198
  }
198
199
  /**
@@ -202,7 +203,7 @@ export default class ValidationUtils {
202
203
  const isValidString = typeof layerName === 'string' && layerName.trim().length > 0;
203
204
  const isValidArray = Array.isArray(layerName) && layerName.length > 0 && layerName.every(ValidationUtils.isValidLayerName);
204
205
  if (!isValidString && !isValidArray) {
205
- throw new Error('Valid layer name is required');
206
+ throw ErrorHandler.getInstance().createAndHandleError('Valid layer name is required', ErrorType.VALIDATION_ERROR);
206
207
  }
207
208
  }
208
209
  }
@@ -1,93 +0,0 @@
1
- import Map from "ol/Map";
2
- import VectorSource from "ol/source/Vector";
3
- import VectorLayer from "ol/layer/Vector";
4
- import { LineOptions, MapJSONData } from "../../types";
5
- import { EventManager } from "../map";
6
- /**
7
- * 河流图层管理类
8
- * 用于创建与管理河流相关矢量图层(分级显示、按级别线宽、显示控制与清理)
9
- *
10
- * 注意:
11
- * - 分级显示依赖地图缩放级别(zoom),通过 zoomOffset 控制显示阈值偏移
12
- * - removeExisting 为 true 时,会清理本管理器创建的分级河流图层
13
- */
14
- export interface RiverLevelWidthMap {
15
- [level: number]: number;
16
- }
17
- /**
18
- * 河流图层配置选项
19
- */
20
- export interface RiverLayerOptions extends LineOptions {
21
- /** 河流级别数量,默认为 5 */
22
- levelCount?: number;
23
- /** 缩放级别偏移量,默认为 8 */
24
- zoomOffset?: number;
25
- /** 河流级别线宽映射,key 为 level,value 为线宽 */
26
- levelWidthMap?: RiverLevelWidthMap;
27
- /** 是否删除已有分级河流图层(本管理器创建的) */
28
- removeExisting?: boolean;
29
- }
30
- export default class RiverLayerManager {
31
- private readonly map;
32
- private readonly eventManager;
33
- private riverLayerList;
34
- private riverLayerShow;
35
- private riverZoomOffset;
36
- private readonly defaultLevelWidthMap;
37
- /**
38
- * 构造函数
39
- * @param map OpenLayers 地图实例
40
- * @param eventManager 可选事件管理器,未传入则内部创建
41
- */
42
- constructor(map: Map, eventManager?: EventManager);
43
- /**
44
- * 添加分级河流图层:根据缩放级别显示不同 level 的河流
45
- * @param fyRiverJson 河流 GeoJSON 数据
46
- * @param options 河流图层配置选项
47
- */
48
- addRiverLayersByZoom(fyRiverJson: MapJSONData, options?: RiverLayerOptions): void;
49
- /**
50
- * 从 URL 添加分级河流图层:根据缩放级别显示不同 level 的河流
51
- * @param url 河流数据 URL(GeoJSON)
52
- * @param options 河流图层配置选项
53
- */
54
- addRiverLayersByZoomByUrl(url: string, options?: RiverLayerOptions): void;
55
- /**
56
- * 设置分级河流图层是否显示
57
- * @param show 是否显示
58
- */
59
- showRiverLayer(show: boolean): void;
60
- /**
61
- * 根据当前 zoom 显示对应分级河流图层
62
- * 缩放越大,显示的 riverLevel 越多
63
- */
64
- showRiverLayerByZoom(): void;
65
- /**
66
- * 添加按级别显示不同宽度的河流图层
67
- * @param data 河流 GeoJSON 数据
68
- * @param options 河流图层配置选项
69
- */
70
- addRiverWidthByLevel(data: MapJSONData, options?: RiverLayerOptions): VectorLayer<VectorSource>;
71
- /**
72
- * 从 URL 添加按级别显示不同宽度的河流图层
73
- * @param url 河流数据 URL(GeoJSON)
74
- * @param options 河流图层配置选项
75
- */
76
- addRiverWidthByLevelByUrl(url: string, options?: RiverLayerOptions): VectorLayer<VectorSource>;
77
- /**
78
- * 清除本管理器创建的分级河流图层
79
- */
80
- clearRiverLayers(): void;
81
- /**
82
- * 获取分级河流图层显示状态
83
- */
84
- getRiverLayerVisibility(): boolean;
85
- /**
86
- * 获取分级河流图层列表副本
87
- */
88
- getRiverLayers(): VectorLayer<VectorSource>[];
89
- /**
90
- * 销毁管理器,释放资源
91
- */
92
- destroy(): void;
93
- }