map-ol-utils 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -69
- package/dist/index.cjs +1 -1953
- package/dist/index.d.cts +33 -212
- package/dist/index.d.mts +33 -212
- package/dist/index.mjs +1 -1830
- package/package.json +11 -5
- package/dist/tdt-vts/decoder.worker.cjs +0 -46
- package/dist/tdt-vts/decoder.worker.d.cts +0 -1
- package/dist/tdt-vts/decoder.worker.d.mts +0 -1
- package/dist/tdt-vts/decoder.worker.mjs +0 -47
- package/dist/tile-loader-CtZwCurI.mjs +0 -630
- package/dist/tile-loader-D4cn6nMU.cjs +0 -683
package/dist/index.mjs
CHANGED
|
@@ -1,1830 +1 @@
|
|
|
1
|
-
import { a as normalizePrivateGeometry, c as resolveFeatureProperties, i as encodeStandardMvt, l as restoreTdtBytes, n as TDT_XOR_BYTE_INDEXES, o as normalizePrivateMvt, r as decodeMvt, s as privateTypeToStandard, t as loadAndNormalizeTdtVts } from "./tile-loader-CtZwCurI.mjs";
|
|
2
|
-
import MVT from "ol/format/MVT.js";
|
|
3
|
-
import TileState from "ol/TileState.js";
|
|
4
|
-
import VectorTileSource from "ol/source/VectorTile.js";
|
|
5
|
-
import mitt from "mitt";
|
|
6
|
-
import { WMTS, XYZ } from "ol/source.js";
|
|
7
|
-
import TileLayer from "ol/layer/Tile.js";
|
|
8
|
-
import { GeoJSON, WMTSCapabilities } from "ol/format.js";
|
|
9
|
-
import { optionsFromCapabilities } from "ol/source/WMTS.js";
|
|
10
|
-
import { TileGrid } from "ol/tilegrid.js";
|
|
11
|
-
import { Tile } from "ol/layer.js";
|
|
12
|
-
import WMTSTileGrid from "ol/tilegrid/WMTS.js";
|
|
13
|
-
import VectorImageLayer from "ol/layer/Image.js";
|
|
14
|
-
import RasterSource from "ol/source/Raster.js";
|
|
15
|
-
import Map$1 from "ol/Map.js";
|
|
16
|
-
import View from "ol/View.js";
|
|
17
|
-
import { defaults } from "ol/control.js";
|
|
18
|
-
import gcoord from "gcoord";
|
|
19
|
-
import { Feature, Overlay } from "ol";
|
|
20
|
-
import { Select, defaults as defaults$1 } from "ol/interaction.js";
|
|
21
|
-
import { boundingExtent, getCenter } from "ol/extent.js";
|
|
22
|
-
import { singleClick } from "ol/events/condition.js";
|
|
23
|
-
import Feature$1 from "ol/Feature.js";
|
|
24
|
-
import Point from "ol/geom/Point.js";
|
|
25
|
-
import VectorLayer from "ol/layer/Vector.js";
|
|
26
|
-
import VectorSource from "ol/source/Vector.js";
|
|
27
|
-
import { Circle, Fill, Icon, RegularShape, Stroke, Style, Text } from "ol/style.js";
|
|
28
|
-
import Draw from "ol/interaction/Draw.js";
|
|
29
|
-
import { getArea, getLength } from "ol/sphere.js";
|
|
30
|
-
import GeoJSON$1 from "ol/format/GeoJSON.js";
|
|
31
|
-
import { Point as Point$1 } from "ol/geom.js";
|
|
32
|
-
import Cluster from "ol/source/Cluster.js";
|
|
33
|
-
import CircleStyle from "ol/style/Circle.js";
|
|
34
|
-
|
|
35
|
-
//#region src/core/types.ts
|
|
36
|
-
let GeometryType = /* @__PURE__ */ function(GeometryType) {
|
|
37
|
-
GeometryType["LineString"] = "LineString";
|
|
38
|
-
GeometryType["MultiLineString"] = "MultiLineString";
|
|
39
|
-
GeometryType["Polygon"] = "Polygon";
|
|
40
|
-
GeometryType["MultiPolygon"] = "MultiPolygon";
|
|
41
|
-
GeometryType["Invalid"] = "Invalid";
|
|
42
|
-
return GeometryType;
|
|
43
|
-
}({});
|
|
44
|
-
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/tdt-vts/pk.ts
|
|
47
|
-
const PK_DIGIT_TO_CHAR = [
|
|
48
|
-
"a",
|
|
49
|
-
"d",
|
|
50
|
-
"g",
|
|
51
|
-
"j",
|
|
52
|
-
"m"
|
|
53
|
-
];
|
|
54
|
-
const PK_CHAR_TO_DIGIT = Object.freeze({
|
|
55
|
-
a: 0,
|
|
56
|
-
d: 1,
|
|
57
|
-
g: 2,
|
|
58
|
-
j: 3,
|
|
59
|
-
m: 4
|
|
60
|
-
});
|
|
61
|
-
const PERTURBED_POSITIONS = [
|
|
62
|
-
9,
|
|
63
|
-
8,
|
|
64
|
-
7,
|
|
65
|
-
6
|
|
66
|
-
];
|
|
67
|
-
function assertTileInteger(name, value) {
|
|
68
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${name} 必须是非负安全整数`);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* 将 XYZ 瓦片坐标编码为天地图 VTS 使用的 pk 参数。
|
|
72
|
-
*
|
|
73
|
-
* 算法按 main.0f98e6f9.js 中的实现还原:
|
|
74
|
-
* 1. 从 bit=z 到 bit=0 交织 x/y 位;
|
|
75
|
-
* 2. 反转;
|
|
76
|
-
* 3. 将 0-based 位置 9、8、7、6 加一;
|
|
77
|
-
* 4. 0/1/2/3/4 映射为 a/d/g/j/m;
|
|
78
|
-
* 5. 连续字符使用“额外重复次数”压缩。
|
|
79
|
-
*/
|
|
80
|
-
const encodeTdtPk = (x, y, z) => {
|
|
81
|
-
assertTileInteger("x", x);
|
|
82
|
-
assertTileInteger("y", y);
|
|
83
|
-
assertTileInteger("z", z);
|
|
84
|
-
if (z > 30) throw new RangeError("当前实现将 z 限制为 0~30;天地图底图通常不超过 18 级");
|
|
85
|
-
const maxCoordinate = 2 ** z;
|
|
86
|
-
if (x >= maxCoordinate || y >= maxCoordinate) throw new RangeError(`z=${z} 时 x/y 必须小于 ${maxCoordinate}`);
|
|
87
|
-
const bigX = BigInt(x);
|
|
88
|
-
const bigY = BigInt(y);
|
|
89
|
-
const digits = [];
|
|
90
|
-
for (let bit = z; bit >= 0; bit -= 1) {
|
|
91
|
-
const mask = 1n << BigInt(bit);
|
|
92
|
-
let digit = 0;
|
|
93
|
-
if ((bigX & mask) !== 0n) digit |= 1;
|
|
94
|
-
if ((bigY & mask) !== 0n) digit |= 2;
|
|
95
|
-
digits.push(digit);
|
|
96
|
-
}
|
|
97
|
-
digits.reverse();
|
|
98
|
-
for (const position of PERTURBED_POSITIONS) if (position < digits.length) {
|
|
99
|
-
const value = digits[position];
|
|
100
|
-
if (value === void 0) throw new Error("pk 中间状态异常");
|
|
101
|
-
digits[position] = value + 1;
|
|
102
|
-
}
|
|
103
|
-
let mapped = "";
|
|
104
|
-
for (const digit of digits) {
|
|
105
|
-
const char = PK_DIGIT_TO_CHAR[digit];
|
|
106
|
-
if (!char) throw new Error(`无法映射 pk 数字:${digit}`);
|
|
107
|
-
mapped += char;
|
|
108
|
-
}
|
|
109
|
-
return mapped.replace(/([adgjm])\1+/g, (run) => {
|
|
110
|
-
return `${run[0]}${run.length - 1}`;
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
/** 反解 pk;用于调试、单元测试和核对网络请求。 */
|
|
114
|
-
const decodeTdtPk = (pk) => {
|
|
115
|
-
if (!pk) throw new TypeError("pk 不能为空");
|
|
116
|
-
const transformed = [];
|
|
117
|
-
let cursor = 0;
|
|
118
|
-
while (cursor < pk.length) {
|
|
119
|
-
const char = pk[cursor];
|
|
120
|
-
if (!char || !(char in PK_CHAR_TO_DIGIT)) throw new Error(`pk 包含非法字符:${String(char)}`);
|
|
121
|
-
cursor += 1;
|
|
122
|
-
let repeatText = "";
|
|
123
|
-
while (cursor < pk.length && /\d/.test(pk[cursor] ?? "")) {
|
|
124
|
-
repeatText += pk[cursor];
|
|
125
|
-
cursor += 1;
|
|
126
|
-
}
|
|
127
|
-
const extraRepeats = repeatText ? Number(repeatText) : 0;
|
|
128
|
-
if (!Number.isSafeInteger(extraRepeats) || extraRepeats < 0) throw new Error(`pk 重复次数无效:${repeatText}`);
|
|
129
|
-
const digit = PK_CHAR_TO_DIGIT[char];
|
|
130
|
-
if (digit === void 0) throw new Error(`pk 字符无法映射:${char}`);
|
|
131
|
-
for (let i = 0; i < extraRepeats + 1; i += 1) transformed.push(digit);
|
|
132
|
-
}
|
|
133
|
-
for (const position of [
|
|
134
|
-
6,
|
|
135
|
-
7,
|
|
136
|
-
8,
|
|
137
|
-
9
|
|
138
|
-
]) if (position < transformed.length) {
|
|
139
|
-
const value = transformed[position];
|
|
140
|
-
if (value === void 0) throw new Error("pk 中间状态异常");
|
|
141
|
-
const restored = value - 1;
|
|
142
|
-
if (restored < 0 || restored > 3) throw new Error(`pk 在位置 ${position} 的扰动值无效`);
|
|
143
|
-
transformed[position] = restored;
|
|
144
|
-
}
|
|
145
|
-
const digits = transformed.reverse();
|
|
146
|
-
const z = digits.length - 1;
|
|
147
|
-
if (z < 0 || z > 30) throw new RangeError(`pk 推导出的 z=${z} 超出范围`);
|
|
148
|
-
let x = 0n;
|
|
149
|
-
let y = 0n;
|
|
150
|
-
for (let index = 0; index < digits.length; index += 1) {
|
|
151
|
-
const digit = digits[index];
|
|
152
|
-
if (digit === void 0 || digit < 0 || digit > 3) throw new Error(`pk 四叉数字无效:${String(digit)}`);
|
|
153
|
-
const bit = BigInt(z - index);
|
|
154
|
-
if ((digit & 1) !== 0) x |= 1n << bit;
|
|
155
|
-
if ((digit & 2) !== 0) y |= 1n << bit;
|
|
156
|
-
}
|
|
157
|
-
const numberX = Number(x);
|
|
158
|
-
const numberY = Number(y);
|
|
159
|
-
assertTileInteger("反解 x", numberX);
|
|
160
|
-
assertTileInteger("反解 y", numberY);
|
|
161
|
-
return {
|
|
162
|
-
z,
|
|
163
|
-
x: numberX,
|
|
164
|
-
y: numberY
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
/**
|
|
168
|
-
* 根据 OpenLayers 的 [z, x, y] 生成最终 VTS 地址,不依赖字符串替换。
|
|
169
|
-
*/
|
|
170
|
-
const createTdtVtsUrl = (tileCoord, options) => {
|
|
171
|
-
const [z, rawX, y] = tileCoord;
|
|
172
|
-
const { token, type = "vt", version = "1.0", maxZoom = 18, hosts = [
|
|
173
|
-
"/tile0",
|
|
174
|
-
"/tile1",
|
|
175
|
-
"/tile2"
|
|
176
|
-
], wrapX = true } = options;
|
|
177
|
-
if (!token || z < 0 || z > maxZoom || y < 0 || hosts.length === 0) return;
|
|
178
|
-
const matrixSize = 2 ** z;
|
|
179
|
-
if (y >= matrixSize) return void 0;
|
|
180
|
-
const x = wrapX ? (rawX % matrixSize + matrixSize) % matrixSize : rawX;
|
|
181
|
-
if (x < 0 || x >= matrixSize) return void 0;
|
|
182
|
-
const host = hosts[Math.abs(x + y) % hosts.length];
|
|
183
|
-
if (!host) return void 0;
|
|
184
|
-
const params = new URLSearchParams({
|
|
185
|
-
t: type,
|
|
186
|
-
tk: token,
|
|
187
|
-
v: version,
|
|
188
|
-
pk: encodeTdtPk(x, y, z)
|
|
189
|
-
});
|
|
190
|
-
return `${host.replace(/\/$/, "")}/vts?${params.toString()}`;
|
|
191
|
-
};
|
|
192
|
-
const redactTdtToken = (url) => {
|
|
193
|
-
try {
|
|
194
|
-
const parsed = new URL(url);
|
|
195
|
-
if (parsed.searchParams.has("tk")) parsed.searchParams.set("tk", "***");
|
|
196
|
-
return parsed.toString();
|
|
197
|
-
} catch {
|
|
198
|
-
return url.replace(/([?&]tk=)[^&]+/i, "$1***");
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
//#endregion
|
|
203
|
-
//#region src/tdt-vts/style-loader.ts
|
|
204
|
-
const TDT_VECTOR_SOURCE_ID = "base-tdt-vector-tile";
|
|
205
|
-
function cloneJson(value) {
|
|
206
|
-
return JSON.parse(JSON.stringify(value));
|
|
207
|
-
}
|
|
208
|
-
async function fetchJson(url, signal) {
|
|
209
|
-
const response = await fetch(url, {
|
|
210
|
-
mode: "cors",
|
|
211
|
-
credentials: "omit",
|
|
212
|
-
cache: "default",
|
|
213
|
-
signal
|
|
214
|
-
});
|
|
215
|
-
if (!response.ok) throw new Error(`样式请求失败:HTTP ${response.status} ${response.statusText}`);
|
|
216
|
-
return response.json();
|
|
217
|
-
}
|
|
218
|
-
function parseStyle0(value) {
|
|
219
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("style-0 不是 JSON 对象");
|
|
220
|
-
const style = value;
|
|
221
|
-
if (style.version !== 8) throw new Error(`style-0 version 必须为 8`);
|
|
222
|
-
if (!style.sources || typeof style.sources !== "object") throw new Error("style-0 缺少 sources");
|
|
223
|
-
if (!Array.isArray(style.layers)) throw new Error("style-0 缺少 layers 数组");
|
|
224
|
-
return style;
|
|
225
|
-
}
|
|
226
|
-
function parseStyle1Layers(value) {
|
|
227
|
-
if (Array.isArray(value)) return value;
|
|
228
|
-
if (value && typeof value === "object") {
|
|
229
|
-
const { layers } = value;
|
|
230
|
-
if (Array.isArray(layers)) return layers;
|
|
231
|
-
}
|
|
232
|
-
throw new Error("style-1 必须是图层数组或带 layers 的对象");
|
|
233
|
-
}
|
|
234
|
-
/** 按原 bundle 的 beforeLayerId 语义逐层插入。 */
|
|
235
|
-
const mergeTdtStyleLayers = (baseLayers, extraLayers) => {
|
|
236
|
-
const result = baseLayers.map((layer) => cloneJson(layer));
|
|
237
|
-
const ids = new Set(result.map((layer) => layer.id));
|
|
238
|
-
for (const input of extraLayers) {
|
|
239
|
-
const layer = cloneJson(input);
|
|
240
|
-
if (!layer.id) throw new Error("style-1 存在无 id 图层");
|
|
241
|
-
if (ids.has(layer.id)) throw new Error(`样式图层 id 重复:${layer.id}`);
|
|
242
|
-
const beforeLayerId = typeof layer.beforeLayerId === "string" ? layer.beforeLayerId : "";
|
|
243
|
-
delete layer.beforeLayerId;
|
|
244
|
-
const index = beforeLayerId ? result.findIndex((candidate) => candidate.id === beforeLayerId) : -1;
|
|
245
|
-
if (index >= 0) result.splice(index, 0, layer);
|
|
246
|
-
else result.push(layer);
|
|
247
|
-
ids.add(layer.id);
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
250
|
-
};
|
|
251
|
-
function resolveUrl(value, baseUrl) {
|
|
252
|
-
try {
|
|
253
|
-
return new URL(value, baseUrl).href;
|
|
254
|
-
} catch {
|
|
255
|
-
return value;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function resolveStyleResourceUrls(style, styleUrl) {
|
|
259
|
-
if (typeof style.glyphs === "string") style.glyphs = resolveUrl(style.glyphs, styleUrl);
|
|
260
|
-
if (typeof style.sprite === "string") style.sprite = resolveUrl(style.sprite, styleUrl);
|
|
261
|
-
else if (Array.isArray(style.sprite)) style.sprite = style.sprite.map((item) => {
|
|
262
|
-
if (item && typeof item === "object" && typeof item.url === "string") return {
|
|
263
|
-
...item,
|
|
264
|
-
url: resolveUrl(item.url, styleUrl)
|
|
265
|
-
};
|
|
266
|
-
return item;
|
|
267
|
-
});
|
|
268
|
-
for (const source of Object.values(style.sources)) {
|
|
269
|
-
if (typeof source.url === "string") source.url = resolveUrl(source.url, styleUrl);
|
|
270
|
-
if (Array.isArray(source.tiles)) source.tiles = source.tiles.map((url) => typeof url === "string" ? resolveUrl(url, styleUrl) : url);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
function removeUnsupportedLayers(layers) {
|
|
274
|
-
const skippedLayerIds = [];
|
|
275
|
-
return {
|
|
276
|
-
layers: layers.filter((layer) => {
|
|
277
|
-
if (layer.type === "pie") {
|
|
278
|
-
skippedLayerIds.push(layer.id);
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
return true;
|
|
282
|
-
}),
|
|
283
|
-
skippedLayerIds
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const loadTdtStyleBundle = async (options) => {
|
|
287
|
-
const sourceId = options.sourceId ?? "base-tdt-vector-tile";
|
|
288
|
-
const [raw0, raw1] = await Promise.all([fetchJson(options.style0Url, options.signal), fetchJson(options.style1Url, options.signal)]);
|
|
289
|
-
const style0 = parseStyle0(raw0);
|
|
290
|
-
const style1Layers = parseStyle1Layers(raw1);
|
|
291
|
-
const style = cloneJson(style0);
|
|
292
|
-
if (!style.sources[sourceId]) throw new Error(`style-0 中不存在 source:${sourceId}`);
|
|
293
|
-
if (style.sources[sourceId]?.type !== "vector") throw new Error(`source ${sourceId} 不是 vector 类型`);
|
|
294
|
-
style.layers = mergeTdtStyleLayers(style.layers, style1Layers);
|
|
295
|
-
const filtered = removeUnsupportedLayers(style.layers);
|
|
296
|
-
style.layers = filtered.layers;
|
|
297
|
-
resolveStyleResourceUrls(style, options.style0Url);
|
|
298
|
-
return {
|
|
299
|
-
style,
|
|
300
|
-
style0LayerCount: style0.layers.length,
|
|
301
|
-
style1LayerCount: style1Layers.length,
|
|
302
|
-
skippedLayerIds: filtered.skippedLayerIds
|
|
303
|
-
};
|
|
304
|
-
};
|
|
305
|
-
const getConstantBackgroundColor = (style) => {
|
|
306
|
-
for (const layer of style.layers) {
|
|
307
|
-
if (layer.type !== "background") continue;
|
|
308
|
-
const value = layer.paint?.["background-color"];
|
|
309
|
-
if (typeof value === "string") return value;
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region src/tdt-vts/worker-client.ts
|
|
315
|
-
const DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
316
|
-
const createCancellationError = (message) => {
|
|
317
|
-
const error = new Error(message);
|
|
318
|
-
error.name = "AbortError";
|
|
319
|
-
return error;
|
|
320
|
-
};
|
|
321
|
-
var TdtDecoderWorkerClient = class {
|
|
322
|
-
restoreMode;
|
|
323
|
-
worker;
|
|
324
|
-
pending = /* @__PURE__ */ new Map();
|
|
325
|
-
nextId = 1;
|
|
326
|
-
disposed = false;
|
|
327
|
-
requestTimeoutMs;
|
|
328
|
-
constructor(restoreMode, options = {}) {
|
|
329
|
-
this.restoreMode = restoreMode;
|
|
330
|
-
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
331
|
-
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 0) throw new RangeError("requestTimeoutMs 必须是非负有限数");
|
|
332
|
-
this.worker = options.workerFactory ? options.workerFactory() : new Worker(new URL("./tdt-vts/decoder.worker.mjs", import.meta.url), {
|
|
333
|
-
type: "module",
|
|
334
|
-
name: "tdt-private-mvt-decoder"
|
|
335
|
-
});
|
|
336
|
-
this.worker.addEventListener("message", this.handleMessage);
|
|
337
|
-
this.worker.addEventListener("error", this.handleWorkerError);
|
|
338
|
-
}
|
|
339
|
-
decode(url, options = {}) {
|
|
340
|
-
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error("解码 Worker 已销毁"));
|
|
341
|
-
if (options.signal?.aborted) return Promise.reject(createCancellationError("解码请求已取消"));
|
|
342
|
-
const id = this.nextId;
|
|
343
|
-
this.nextId += 1;
|
|
344
|
-
return new Promise((resolve, reject) => {
|
|
345
|
-
let timeout;
|
|
346
|
-
const handleAbort = () => {
|
|
347
|
-
this.cancelRequest(id, createCancellationError("解码请求已取消"));
|
|
348
|
-
};
|
|
349
|
-
const cleanup = () => {
|
|
350
|
-
if (timeout !== void 0) clearTimeout(timeout);
|
|
351
|
-
options.signal?.removeEventListener("abort", handleAbort);
|
|
352
|
-
};
|
|
353
|
-
this.pending.set(id, {
|
|
354
|
-
resolve,
|
|
355
|
-
reject,
|
|
356
|
-
cleanup
|
|
357
|
-
});
|
|
358
|
-
options.signal?.addEventListener("abort", handleAbort, { once: true });
|
|
359
|
-
if (this.requestTimeoutMs > 0) timeout = setTimeout(() => {
|
|
360
|
-
this.cancelRequest(id, /* @__PURE__ */ new Error("解码请求超时"));
|
|
361
|
-
}, this.requestTimeoutMs);
|
|
362
|
-
const request = {
|
|
363
|
-
type: "decode-tile",
|
|
364
|
-
id,
|
|
365
|
-
url,
|
|
366
|
-
restoreMode: this.restoreMode
|
|
367
|
-
};
|
|
368
|
-
try {
|
|
369
|
-
this.worker.postMessage(request);
|
|
370
|
-
} catch (cause) {
|
|
371
|
-
this.takePending(id)?.reject(cause instanceof Error ? cause : new Error(String(cause)));
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
dispose() {
|
|
376
|
-
if (this.disposed) return;
|
|
377
|
-
this.disposed = true;
|
|
378
|
-
this.worker.removeEventListener("message", this.handleMessage);
|
|
379
|
-
this.worker.removeEventListener("error", this.handleWorkerError);
|
|
380
|
-
this.worker.terminate();
|
|
381
|
-
const error = /* @__PURE__ */ new Error("解码 Worker 已销毁");
|
|
382
|
-
for (const request of this.pending.values()) {
|
|
383
|
-
request.cleanup();
|
|
384
|
-
request.reject(error);
|
|
385
|
-
}
|
|
386
|
-
this.pending.clear();
|
|
387
|
-
}
|
|
388
|
-
takePending(id) {
|
|
389
|
-
const pending = this.pending.get(id);
|
|
390
|
-
if (!pending) return void 0;
|
|
391
|
-
this.pending.delete(id);
|
|
392
|
-
pending.cleanup();
|
|
393
|
-
return pending;
|
|
394
|
-
}
|
|
395
|
-
cancelRequest(id, error) {
|
|
396
|
-
const pending = this.takePending(id);
|
|
397
|
-
if (!pending) return;
|
|
398
|
-
const request = {
|
|
399
|
-
type: "cancel-tile",
|
|
400
|
-
id
|
|
401
|
-
};
|
|
402
|
-
try {
|
|
403
|
-
this.worker.postMessage(request);
|
|
404
|
-
} catch {}
|
|
405
|
-
pending.reject(error);
|
|
406
|
-
}
|
|
407
|
-
fail(error) {
|
|
408
|
-
if (this.disposed) return;
|
|
409
|
-
this.disposed = true;
|
|
410
|
-
this.worker.removeEventListener("message", this.handleMessage);
|
|
411
|
-
this.worker.removeEventListener("error", this.handleWorkerError);
|
|
412
|
-
this.worker.terminate();
|
|
413
|
-
for (const request of this.pending.values()) {
|
|
414
|
-
request.cleanup();
|
|
415
|
-
request.reject(error);
|
|
416
|
-
}
|
|
417
|
-
this.pending.clear();
|
|
418
|
-
}
|
|
419
|
-
handleMessage = (event) => {
|
|
420
|
-
const response = event.data;
|
|
421
|
-
if (response.type !== "decode-result") return;
|
|
422
|
-
const pending = this.takePending(response.id);
|
|
423
|
-
if (!pending) return;
|
|
424
|
-
if (response.ok) pending.resolve({
|
|
425
|
-
buffer: response.buffer,
|
|
426
|
-
stats: response.stats
|
|
427
|
-
});
|
|
428
|
-
else pending.reject(new Error(response.error));
|
|
429
|
-
};
|
|
430
|
-
handleWorkerError = (event) => {
|
|
431
|
-
const error = new Error(event.message || "解码 Worker 发生未捕获错误");
|
|
432
|
-
this.fail(error);
|
|
433
|
-
};
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
//#endregion
|
|
437
|
-
//#region src/tdt-vts/source.ts
|
|
438
|
-
const createTdtVectorSource = (options) => {
|
|
439
|
-
const decoder = new TdtDecoderWorkerClient(options.restoreMode, options.decoderOptions);
|
|
440
|
-
const format = new MVT({ layerName: "mvt:layer" });
|
|
441
|
-
const source = new VectorTileSource({
|
|
442
|
-
projection: "EPSG:3857",
|
|
443
|
-
minZoom: options.minZoom ?? 1,
|
|
444
|
-
maxZoom: options.maxZoom ?? 18,
|
|
445
|
-
tileSize: 512,
|
|
446
|
-
wrapX: true,
|
|
447
|
-
format,
|
|
448
|
-
attributions: "© <a href=\"https://www.tianditu.gov.cn/\" target=\"_blank\" rel=\"noopener\">天地图</a>",
|
|
449
|
-
tileUrlFunction(tileCoord) {
|
|
450
|
-
if (!tileCoord) return void 0;
|
|
451
|
-
const [z, x, y] = tileCoord;
|
|
452
|
-
if (z === void 0 || x === void 0 || y === void 0) return void 0;
|
|
453
|
-
return createTdtVtsUrl([
|
|
454
|
-
z,
|
|
455
|
-
x,
|
|
456
|
-
y
|
|
457
|
-
], {
|
|
458
|
-
token: options.token,
|
|
459
|
-
maxZoom: options.maxZoom ?? 18,
|
|
460
|
-
wrapX: true
|
|
461
|
-
});
|
|
462
|
-
},
|
|
463
|
-
tileLoadFunction(tile, url) {
|
|
464
|
-
const vectorTile = tile;
|
|
465
|
-
vectorTile.setLoader(async (extent, _resolution, projection) => {
|
|
466
|
-
const redactedUrl = redactTdtToken(url);
|
|
467
|
-
try {
|
|
468
|
-
const decoded = await decoder.decode(url);
|
|
469
|
-
const features = format.readFeatures(decoded.buffer, {
|
|
470
|
-
extent,
|
|
471
|
-
featureProjection: projection
|
|
472
|
-
});
|
|
473
|
-
vectorTile.setFeatures(features);
|
|
474
|
-
options.diagnostics?.onDecoded?.(decoded.stats, redactedUrl);
|
|
475
|
-
} catch (cause) {
|
|
476
|
-
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
477
|
-
options.diagnostics?.onError?.(error, redactedUrl);
|
|
478
|
-
console.error("天地图私有 VTS 加载失败", redactedUrl, error);
|
|
479
|
-
vectorTile.setState(TileState.ERROR);
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
});
|
|
484
|
-
const disposeSource = source.dispose.bind(source);
|
|
485
|
-
source.dispose = () => {
|
|
486
|
-
decoder.dispose();
|
|
487
|
-
disposeSource();
|
|
488
|
-
};
|
|
489
|
-
return {
|
|
490
|
-
source,
|
|
491
|
-
decoder
|
|
492
|
-
};
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
//#endregion
|
|
496
|
-
//#region src/core/emitter.ts
|
|
497
|
-
const emitter = mitt();
|
|
498
|
-
|
|
499
|
-
//#endregion
|
|
500
|
-
//#region src/core/map-context.ts
|
|
501
|
-
/** 当前 OpenLayers 单例。集中存放以避免业务模块之间互相引用。 */
|
|
502
|
-
let instance;
|
|
503
|
-
/** @internal 更新当前地图单例。 */
|
|
504
|
-
const setMapInstance = (map) => {
|
|
505
|
-
instance = map;
|
|
506
|
-
};
|
|
507
|
-
/** @internal 清空当前地图单例。 */
|
|
508
|
-
const clearMapInstance = () => {
|
|
509
|
-
instance = void 0;
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
//#endregion
|
|
513
|
-
//#region src/core/helper.ts
|
|
514
|
-
const hexToRgba = (hex, alpha) => {
|
|
515
|
-
if (hex.startsWith("#")) {
|
|
516
|
-
let tempHex = hex.replace("#", "");
|
|
517
|
-
if (tempHex.length === 3) tempHex = tempHex[0] + tempHex[0] + tempHex[1] + tempHex[1] + tempHex[2] + tempHex[2];
|
|
518
|
-
return `rgba(${parseInt(tempHex.substring(0, 2), 16)}, ${parseInt(tempHex.substring(2, 4), 16)}, ${parseInt(tempHex.substring(4, 6), 16)}, ${alpha})`;
|
|
519
|
-
} else return hex;
|
|
520
|
-
};
|
|
521
|
-
const getLayersDataList = () => instance?.getAllLayers().map((layer) => layer.get("_dataList") || []).flat(2) || [];
|
|
522
|
-
const determineGeometryType = (coordinates) => {
|
|
523
|
-
const getArrayDepth = (arr) => {
|
|
524
|
-
if (!Array.isArray(arr) || arr.length === 0) return 0;
|
|
525
|
-
return 1 + getArrayDepth(arr[0]);
|
|
526
|
-
};
|
|
527
|
-
const isClosedRing = (coords) => {
|
|
528
|
-
if (!Array.isArray(coords) || coords.length < 3) return false;
|
|
529
|
-
const first = coords[0];
|
|
530
|
-
const last = coords[coords.length - 1];
|
|
531
|
-
return first[0] === last[0] && first[1] === last[1];
|
|
532
|
-
};
|
|
533
|
-
const isValidCoordinate = (coord) => {
|
|
534
|
-
return Array.isArray(coord) && coord.length >= 2 && typeof coord[0] === "number" && typeof coord[1] === "number";
|
|
535
|
-
};
|
|
536
|
-
const isValidLineString = (coords) => {
|
|
537
|
-
return Array.isArray(coords) && coords.length >= 2 && coords.every(isValidCoordinate);
|
|
538
|
-
};
|
|
539
|
-
const isValidPolygon = (coords) => {
|
|
540
|
-
if (!Array.isArray(coords)) return false;
|
|
541
|
-
return (getArrayDepth(coords) === 2 ? [coords] : coords).every((ring) => Array.isArray(ring) && ring.length >= 4 && isClosedRing(ring) && ring.every(isValidCoordinate));
|
|
542
|
-
};
|
|
543
|
-
const depth = getArrayDepth(coordinates);
|
|
544
|
-
try {
|
|
545
|
-
if (depth < 2 || !coordinates || coordinates.length === 0) return "Invalid";
|
|
546
|
-
else if (depth === 2 && isValidLineString(coordinates)) return isClosedRing(coordinates) && coordinates.length >= 4 ? "Polygon" : "LineString";
|
|
547
|
-
else if (depth === 3 && Array.isArray(coordinates) && coordinates.every(isValidLineString)) {
|
|
548
|
-
if (isValidPolygon(coordinates)) return "Polygon";
|
|
549
|
-
else if (coordinates.every((arr) => !isClosedRing(arr))) return "MultiLineString";
|
|
550
|
-
else return "Invalid";
|
|
551
|
-
} else if (depth === 4 && Array.isArray(coordinates) && coordinates.every((rings) => isValidPolygon(rings))) return "MultiPolygon";
|
|
552
|
-
else return "Invalid";
|
|
553
|
-
} catch (error) {
|
|
554
|
-
console.error("Error analyzing coordinates:", error);
|
|
555
|
-
return "Invalid";
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
//#endregion
|
|
560
|
-
//#region src/layer/ogc-utils.ts
|
|
561
|
-
const parser = new WMTSCapabilities();
|
|
562
|
-
const capabilitiesMap = /* @__PURE__ */ new Map();
|
|
563
|
-
const getCapabilities = (url) => {
|
|
564
|
-
return new Promise((resolve) => {
|
|
565
|
-
const capabilities = capabilitiesMap.get(url);
|
|
566
|
-
if (capabilities) resolve(capabilities);
|
|
567
|
-
else fetch(url).then((data) => data.text()).then((text) => {
|
|
568
|
-
const capabilities = parser.read(text);
|
|
569
|
-
capabilitiesMap.set(url, capabilities);
|
|
570
|
-
resolve(capabilities);
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
};
|
|
574
|
-
/**
|
|
575
|
-
* 创建WMTS图层
|
|
576
|
-
* @param serverUrl
|
|
577
|
-
*/
|
|
578
|
-
const createWmtsLayer = (serverUrl) => async (config) => {
|
|
579
|
-
const { style, format, version, layerName, matrixSet } = config;
|
|
580
|
-
const [workspace, datastore] = layerName.split(":");
|
|
581
|
-
const capabilitiesUrl = `${serverUrl}/${workspace}/${datastore}/gwc/service/wmts?request=GetCapabilities`;
|
|
582
|
-
return new Promise((resolve, reject) => {
|
|
583
|
-
getCapabilities(capabilitiesUrl).then((data) => {
|
|
584
|
-
const options = optionsFromCapabilities(data, {
|
|
585
|
-
Style: style || "",
|
|
586
|
-
layer: datastore,
|
|
587
|
-
version: version || "1.1.0",
|
|
588
|
-
format: format || "image/png",
|
|
589
|
-
matrixSet: matrixSet || "EPSG:4326"
|
|
590
|
-
});
|
|
591
|
-
if (options) resolve(new TileLayer({
|
|
592
|
-
zIndex: 3,
|
|
593
|
-
className: layerName,
|
|
594
|
-
source: new WMTS(options)
|
|
595
|
-
}));
|
|
596
|
-
else reject(/* @__PURE__ */ new Error("Unable to create TileLayer"));
|
|
597
|
-
});
|
|
598
|
-
});
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
//#endregion
|
|
602
|
-
//#region src/core/constants.ts
|
|
603
|
-
/**
|
|
604
|
-
* 天地图默认访问令牌(仅作缺省值,正式项目请通过参数 / config 覆盖)
|
|
605
|
-
*/
|
|
606
|
-
const DEFAULT_TIANDITU_TK = "c77eddc667c5bed016fded560baf93e7";
|
|
607
|
-
/**
|
|
608
|
-
* 浙江省天地图默认访问令牌(仅作缺省值,正式项目请通过参数覆盖)
|
|
609
|
-
*/
|
|
610
|
-
const DEFAULT_ZHEJIANG_TOKEN = "3b0cdd02-c366-4363-b6de-081a9f12b364";
|
|
611
|
-
|
|
612
|
-
//#endregion
|
|
613
|
-
//#region src/layer/layer-lifecycle.ts
|
|
614
|
-
/** @internal 从地图移除图层并释放其数据源与自身资源。 */
|
|
615
|
-
const disposeLayer = (map, layer) => {
|
|
616
|
-
const dynamicMarkerTimer = layer.get("_dynamicMarkerTimer");
|
|
617
|
-
if (dynamicMarkerTimer !== void 0) {
|
|
618
|
-
clearTimeout(dynamicMarkerTimer);
|
|
619
|
-
layer.unset("_dynamicMarkerTimer");
|
|
620
|
-
}
|
|
621
|
-
layer.getSource()?.dispose();
|
|
622
|
-
map.removeLayer(layer);
|
|
623
|
-
layer.dispose();
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
//#endregion
|
|
627
|
-
//#region src/layer/base-layer.ts
|
|
628
|
-
/**
|
|
629
|
-
* 创建杭州规自地图
|
|
630
|
-
* @param layerName - 'hzsyraster' | 'hzsyvector' | 'hzsyvector_dark'
|
|
631
|
-
*/
|
|
632
|
-
const createBaseLayer = (layerName = "hzsyvector_dark") => {
|
|
633
|
-
const config = {
|
|
634
|
-
zIndex: -5,
|
|
635
|
-
preload: 1,
|
|
636
|
-
minZoom: 8,
|
|
637
|
-
maxZoom: 21,
|
|
638
|
-
source: new XYZ({
|
|
639
|
-
tileGrid: new TileGrid({
|
|
640
|
-
tileSize: 256,
|
|
641
|
-
resolutions: [
|
|
642
|
-
.00549933137253187,
|
|
643
|
-
.00274966568626595,
|
|
644
|
-
.00137483284313297,
|
|
645
|
-
.000687416421566485,
|
|
646
|
-
.000343708210783242,
|
|
647
|
-
.000171854105391621,
|
|
648
|
-
859270526958104e-19,
|
|
649
|
-
429635263479052e-19,
|
|
650
|
-
214817631739526e-19,
|
|
651
|
-
107408815869763e-19,
|
|
652
|
-
537044079348816e-20,
|
|
653
|
-
268522039674407e-20,
|
|
654
|
-
134261019837204e-20
|
|
655
|
-
],
|
|
656
|
-
origin: [118.122911693886, 31.2869311022836],
|
|
657
|
-
extent: [
|
|
658
|
-
116.43263049006,
|
|
659
|
-
28.6201742126026,
|
|
660
|
-
123.705594256347,
|
|
661
|
-
31.9765041681924
|
|
662
|
-
]
|
|
663
|
-
}),
|
|
664
|
-
projection: "EPSG:4326",
|
|
665
|
-
urls: [
|
|
666
|
-
`https://ywtg.citybrain.hangzhou.gov.cn/${layerName}/{z}/{y}/{x}`,
|
|
667
|
-
`https://ywtg.citybrain.hangzhou.gov.cn:9456/${layerName}/{z}/{y}/{x}`,
|
|
668
|
-
`https://ywtg.citybrain.hangzhou.gov.cn:9457/${layerName}/{z}/{y}/{x}`,
|
|
669
|
-
`https://ywtg.citybrain.hangzhou.gov.cn:9458/${layerName}/{z}/{y}/{x}`,
|
|
670
|
-
`https://ywtg.citybrain.hangzhou.gov.cn:9459/${layerName}/{z}/{y}/{x}`
|
|
671
|
-
]
|
|
672
|
-
}),
|
|
673
|
-
properties: {
|
|
674
|
-
_isBaseLayer: true,
|
|
675
|
-
_layerName: layerName
|
|
676
|
-
}
|
|
677
|
-
};
|
|
678
|
-
return new Tile(config);
|
|
679
|
-
};
|
|
680
|
-
/**
|
|
681
|
-
* 创建全国天地图
|
|
682
|
-
* @param layerName - 图层名称
|
|
683
|
-
* @param tk - 天地图TK
|
|
684
|
-
*/
|
|
685
|
-
const createMapWorldLayer = (layerName, tk = DEFAULT_TIANDITU_TK) => {
|
|
686
|
-
const [name, matrixSet] = layerName.split("_");
|
|
687
|
-
const config = {
|
|
688
|
-
preload: 1,
|
|
689
|
-
zIndex: -10,
|
|
690
|
-
cacheSize: 20240,
|
|
691
|
-
properties: {
|
|
692
|
-
_isBaseLayer: true,
|
|
693
|
-
_layerName: layerName
|
|
694
|
-
},
|
|
695
|
-
source: new WMTS({
|
|
696
|
-
crossOrigin: "anonymous",
|
|
697
|
-
projection: "EPSG:4326",
|
|
698
|
-
wrapX: true,
|
|
699
|
-
layer: name,
|
|
700
|
-
style: "default",
|
|
701
|
-
version: "1.0.0",
|
|
702
|
-
format: "image/png",
|
|
703
|
-
matrixSet,
|
|
704
|
-
tileGrid: new WMTSTileGrid({
|
|
705
|
-
tileSize: 256,
|
|
706
|
-
origin: [-180, 90],
|
|
707
|
-
matrixIds: [
|
|
708
|
-
"1",
|
|
709
|
-
"2",
|
|
710
|
-
"3",
|
|
711
|
-
"4",
|
|
712
|
-
"5",
|
|
713
|
-
"6",
|
|
714
|
-
"7",
|
|
715
|
-
"8",
|
|
716
|
-
"9",
|
|
717
|
-
"10",
|
|
718
|
-
"11",
|
|
719
|
-
"12",
|
|
720
|
-
"13",
|
|
721
|
-
"14",
|
|
722
|
-
"15",
|
|
723
|
-
"16",
|
|
724
|
-
"17",
|
|
725
|
-
"18",
|
|
726
|
-
"19"
|
|
727
|
-
],
|
|
728
|
-
resolutions: [
|
|
729
|
-
.703125,
|
|
730
|
-
.3515625,
|
|
731
|
-
.17578125,
|
|
732
|
-
.087890625,
|
|
733
|
-
.0439453125,
|
|
734
|
-
.02197265625,
|
|
735
|
-
.010986328125,
|
|
736
|
-
.0054931640625,
|
|
737
|
-
.00274658203125,
|
|
738
|
-
.001373291015625,
|
|
739
|
-
.0006866455078125,
|
|
740
|
-
.00034332275390625,
|
|
741
|
-
.000171661376953125,
|
|
742
|
-
858306884765625e-19,
|
|
743
|
-
4291534423828125e-20,
|
|
744
|
-
21457672119140625e-21,
|
|
745
|
-
10728836059570312e-21,
|
|
746
|
-
5364418029785156e-21,
|
|
747
|
-
2682209014892578e-21
|
|
748
|
-
]
|
|
749
|
-
}),
|
|
750
|
-
url: `https://t{0-7}.tianditu.gov.cn/${layerName}/wmts?tk=${tk}`
|
|
751
|
-
})
|
|
752
|
-
};
|
|
753
|
-
return new Tile(config);
|
|
754
|
-
};
|
|
755
|
-
/**
|
|
756
|
-
* 创建天地图矢量/影像底图组合。
|
|
757
|
-
* @param tk - 天地图访问令牌
|
|
758
|
-
*/
|
|
759
|
-
const baseLayers = (tk) => ({
|
|
760
|
-
baseImgLayer: new Tile({
|
|
761
|
-
className: "img-layer",
|
|
762
|
-
source: new XYZ({
|
|
763
|
-
crossOrigin: "anonymous",
|
|
764
|
-
tileSize: 256,
|
|
765
|
-
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${tk}`
|
|
766
|
-
}),
|
|
767
|
-
zIndex: 1
|
|
768
|
-
}),
|
|
769
|
-
baseCiaLayer: new Tile({
|
|
770
|
-
className: "img-layer",
|
|
771
|
-
source: new XYZ({
|
|
772
|
-
crossOrigin: "anonymous",
|
|
773
|
-
tileSize: 256,
|
|
774
|
-
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${tk}`
|
|
775
|
-
}),
|
|
776
|
-
zIndex: 2
|
|
777
|
-
}),
|
|
778
|
-
baseVecLayer: new Tile({
|
|
779
|
-
className: "vec-layer",
|
|
780
|
-
source: new XYZ({
|
|
781
|
-
crossOrigin: "anonymous",
|
|
782
|
-
tileSize: 256,
|
|
783
|
-
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${tk}`
|
|
784
|
-
}),
|
|
785
|
-
zIndex: 1
|
|
786
|
-
}),
|
|
787
|
-
baseCvaLayer: new Tile({
|
|
788
|
-
className: "vec-layer",
|
|
789
|
-
source: new XYZ({
|
|
790
|
-
crossOrigin: "anonymous",
|
|
791
|
-
tileSize: 256,
|
|
792
|
-
url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${tk}`
|
|
793
|
-
}),
|
|
794
|
-
zIndex: 2
|
|
795
|
-
})
|
|
796
|
-
});
|
|
797
|
-
/**
|
|
798
|
-
* 浙江省天地图
|
|
799
|
-
* @param layerName - 图层名称
|
|
800
|
-
* @param tk -
|
|
801
|
-
*/
|
|
802
|
-
const createZheJiangLayer = (layerName = "emap", tk = DEFAULT_ZHEJIANG_TOKEN) => {
|
|
803
|
-
return new Tile({
|
|
804
|
-
preload: 1,
|
|
805
|
-
minZoom: 4,
|
|
806
|
-
maxZoom: 20,
|
|
807
|
-
zIndex: -10,
|
|
808
|
-
cacheSize: 20240,
|
|
809
|
-
source: new XYZ({
|
|
810
|
-
wrapX: true,
|
|
811
|
-
projection: "EPSG:4326",
|
|
812
|
-
crossOrigin: "anonymous",
|
|
813
|
-
url: `https://ditu{0-6}.zjzwfw.gov.cn/services/wmts/${layerName}/default/oss/getTile/{z}/{y}/{x}?token=${tk}`
|
|
814
|
-
})
|
|
815
|
-
});
|
|
816
|
-
};
|
|
817
|
-
/**
|
|
818
|
-
* 根据图层名称获取图层
|
|
819
|
-
* @param name
|
|
820
|
-
*/
|
|
821
|
-
const getLayerByName = (name) => instance.getAllLayers().find((item) => (item.get("_layerName") || item.getClassName()) === name);
|
|
822
|
-
/**
|
|
823
|
-
* 根据图层名称删除图层
|
|
824
|
-
* @param name
|
|
825
|
-
*/
|
|
826
|
-
const removeLayerByName = (name) => {
|
|
827
|
-
const layer = getLayerByName(name);
|
|
828
|
-
if (layer) disposeLayer(instance, layer);
|
|
829
|
-
};
|
|
830
|
-
/**
|
|
831
|
-
* 切换图层的显示、隐藏
|
|
832
|
-
* @param activeNames
|
|
833
|
-
* @param allLayerNames
|
|
834
|
-
*/
|
|
835
|
-
const changeLayersByNames = (activeNames, allLayerNames) => {
|
|
836
|
-
instance.getAllLayers().filter((item) => allLayerNames.includes(item.getClassName())).forEach((layer) => {
|
|
837
|
-
layer.setVisible(activeNames?.includes(layer.getClassName()));
|
|
838
|
-
});
|
|
839
|
-
};
|
|
840
|
-
|
|
841
|
-
//#endregion
|
|
842
|
-
//#region src/layer/raster-layer.ts
|
|
843
|
-
const getDefaultThreadCount = () => Math.max(1, Math.min(4, globalThis.navigator?.hardwareConcurrency ?? 2));
|
|
844
|
-
/** 使用 RGB 比例和目标颜色转换栅格图层。 */
|
|
845
|
-
const changeSource = (layer, rgbColorArray, ratioArray, options = {}) => {
|
|
846
|
-
const rasterSource = new RasterSource({
|
|
847
|
-
sources: [layer],
|
|
848
|
-
operationType: "image",
|
|
849
|
-
operation: (pixels, data) => {
|
|
850
|
-
const reverseFunc = (pixelArray, operationData) => {
|
|
851
|
-
const operationRatioArray = operationData.ratioArray;
|
|
852
|
-
const operationRgbColorArray = operationData.rgbColorArray;
|
|
853
|
-
if (operationRatioArray && operationRgbColorArray) {
|
|
854
|
-
const [redRatio = 0, greenRatio = 0, blueRatio = 0] = operationRatioArray;
|
|
855
|
-
for (let index = 0; index < pixelArray.length; index += 4) {
|
|
856
|
-
const red = pixelArray[index];
|
|
857
|
-
const green = pixelArray[index + 1];
|
|
858
|
-
const blue = pixelArray[index + 2];
|
|
859
|
-
const grey = red * redRatio + green * greenRatio + blue * blueRatio;
|
|
860
|
-
pixelArray[index] = operationRgbColorArray[0] - grey;
|
|
861
|
-
pixelArray[index + 1] = operationRgbColorArray[1] - grey;
|
|
862
|
-
pixelArray[index + 2] = operationRgbColorArray[2] - grey;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
};
|
|
866
|
-
reverseFunc(pixels[0].data, data);
|
|
867
|
-
return pixels[0];
|
|
868
|
-
},
|
|
869
|
-
threads: options.threads ?? getDefaultThreadCount()
|
|
870
|
-
});
|
|
871
|
-
rasterSource.on("beforeoperations", (event) => {
|
|
872
|
-
event.data.ratioArray = ratioArray;
|
|
873
|
-
event.data.rgbColorArray = rgbColorArray;
|
|
874
|
-
});
|
|
875
|
-
return rasterSource;
|
|
876
|
-
};
|
|
877
|
-
/** 使用 changeSource 创建可显示的 VectorImageLayer。 */
|
|
878
|
-
const changeLayer = (layer, rgbColorArray, ratioArray, options = {}) => new VectorImageLayer({ source: changeSource(layer, rgbColorArray, ratioArray, options) });
|
|
879
|
-
|
|
880
|
-
//#endregion
|
|
881
|
-
//#region src/overlay/overlay.ts
|
|
882
|
-
/**
|
|
883
|
-
* 移除带指定属性键的 Overlay(默认按 'position')
|
|
884
|
-
* @param key
|
|
885
|
-
*/
|
|
886
|
-
const removeOverlays = (key = "") => {
|
|
887
|
-
if (instance) instance.getOverlays().getArray().filter((overlay) => overlay.get(key || "position")).forEach((overlay) => instance.removeOverlay(overlay));
|
|
888
|
-
};
|
|
889
|
-
const removeAnimationMarker = () => removeOverlays("_isAnimationMarker");
|
|
890
|
-
/**
|
|
891
|
-
* 创建动画标注 Overlay
|
|
892
|
-
* @param imgUrl
|
|
893
|
-
* @param data
|
|
894
|
-
*/
|
|
895
|
-
const createAnimationMarker = (imgUrl, data) => {
|
|
896
|
-
removeAnimationMarker();
|
|
897
|
-
const { width, height, lnglat, crsForm } = data;
|
|
898
|
-
const element = document.createElement("div");
|
|
899
|
-
const image = document.createElement("img");
|
|
900
|
-
image.src = imgUrl;
|
|
901
|
-
image.alt = "";
|
|
902
|
-
if (width !== void 0) image.width = width;
|
|
903
|
-
if (height !== void 0) image.height = height;
|
|
904
|
-
element.append(image);
|
|
905
|
-
const overlay = new Overlay({
|
|
906
|
-
element,
|
|
907
|
-
stopEvent: false,
|
|
908
|
-
positioning: "bottom-center",
|
|
909
|
-
position: gcoord.transform([+lnglat[0], +lnglat[1]], crsForm || gcoord.WGS84, gcoord.WGS84)
|
|
910
|
-
});
|
|
911
|
-
overlay.set("_isAnimationMarker", true);
|
|
912
|
-
instance.addOverlay(overlay);
|
|
913
|
-
};
|
|
914
|
-
/**
|
|
915
|
-
* 创建 Overlay
|
|
916
|
-
* @param config
|
|
917
|
-
*/
|
|
918
|
-
const createOverlay = (config = {
|
|
919
|
-
id: "popup-container",
|
|
920
|
-
positioning: "top-center"
|
|
921
|
-
}) => {
|
|
922
|
-
return new Overlay(Object.assign({ element: document.getElementById(config.id) || void 0 }, config));
|
|
923
|
-
};
|
|
924
|
-
|
|
925
|
-
//#endregion
|
|
926
|
-
//#region src/layer/layer-manager.ts
|
|
927
|
-
/**
|
|
928
|
-
* 重置激活的矢量数据(清空 overlay 与各图层选择交互的要素集合)
|
|
929
|
-
*/
|
|
930
|
-
const resetActiveVector = () => {
|
|
931
|
-
removeOverlays();
|
|
932
|
-
instance.getAllLayers().forEach((layer) => {
|
|
933
|
-
layer.get("_selectInteraction")?.getFeatures().clear();
|
|
934
|
-
});
|
|
935
|
-
};
|
|
936
|
-
/**
|
|
937
|
-
* 移除覆盖物以及所有非底图自定义图层
|
|
938
|
-
*/
|
|
939
|
-
const removeAllLayers = () => {
|
|
940
|
-
removeOverlays();
|
|
941
|
-
if (instance) instance.getAllLayers().forEach((layer) => {
|
|
942
|
-
if (!layer.get("_isBaseLayer")) disposeLayer(instance, layer);
|
|
943
|
-
});
|
|
944
|
-
};
|
|
945
|
-
/**
|
|
946
|
-
* 重置激活 Feature
|
|
947
|
-
*/
|
|
948
|
-
const resetActiveFeature = () => {
|
|
949
|
-
removeOverlays();
|
|
950
|
-
instance.getAllLayers().filter((layer) => layer.get("_isVectorLayer")).forEach((layer) => {
|
|
951
|
-
const selectInteraction = layer.get("_selectInteraction");
|
|
952
|
-
if (selectInteraction) {
|
|
953
|
-
selectInteraction.dispatchEvent({
|
|
954
|
-
type: "select",
|
|
955
|
-
selected: [],
|
|
956
|
-
mapBrowserEvent: void 0,
|
|
957
|
-
deselected: selectInteraction.getFeatures().getArray()
|
|
958
|
-
});
|
|
959
|
-
selectInteraction.getFeatures().clear();
|
|
960
|
-
}
|
|
961
|
-
});
|
|
962
|
-
};
|
|
963
|
-
/**
|
|
964
|
-
* 按 _uuid 激活要素
|
|
965
|
-
* @param uid
|
|
966
|
-
*/
|
|
967
|
-
const activeFeatureByUid = (uid) => {
|
|
968
|
-
const targetLayers = instance.getAllLayers().filter((layer) => layer.get("_isVectorLayer"));
|
|
969
|
-
if (targetLayers.length) {
|
|
970
|
-
const targetLayer = targetLayers.find((layer) => layer.get("_uuidSet").has(uid) && layer.get("_selectInteraction"));
|
|
971
|
-
if (targetLayer) {
|
|
972
|
-
const targetFeature = (targetLayer.getSource()?.getFeatures() || []).find((feature) => feature.get("_uuid") === uid);
|
|
973
|
-
const selectInteraction = targetLayer.get("_selectInteraction");
|
|
974
|
-
if (selectInteraction) {
|
|
975
|
-
const collection = selectInteraction.getFeatures().getArray();
|
|
976
|
-
if (!collection.find((feature) => feature.get("_uuid") === uid)) {
|
|
977
|
-
selectInteraction.dispatchEvent({
|
|
978
|
-
type: "select",
|
|
979
|
-
deselected: collection,
|
|
980
|
-
selected: [targetFeature],
|
|
981
|
-
mapBrowserEvent: void 0
|
|
982
|
-
});
|
|
983
|
-
selectInteraction.getFeatures().push(targetFeature);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
};
|
|
989
|
-
|
|
990
|
-
//#endregion
|
|
991
|
-
//#region src/map/create-map.ts
|
|
992
|
-
let mapCenter = [119.71512016971076, 30.122490541963536];
|
|
993
|
-
const createMapInstance = (config = {}) => {
|
|
994
|
-
if (instance) return instance;
|
|
995
|
-
const map = createMap(config);
|
|
996
|
-
setMapInstance(map);
|
|
997
|
-
return map;
|
|
998
|
-
};
|
|
999
|
-
const createMap = (config = {}) => {
|
|
1000
|
-
const innerConfig = Object.assign({
|
|
1001
|
-
zoom: 9.4,
|
|
1002
|
-
maxZoom: 18,
|
|
1003
|
-
minZoom: 9.4,
|
|
1004
|
-
center: mapCenter,
|
|
1005
|
-
baseLayer: "vec-layer",
|
|
1006
|
-
projection: "EPSG:4326",
|
|
1007
|
-
layers: [createBaseLayer()],
|
|
1008
|
-
constrainResolution: false,
|
|
1009
|
-
smoothExtentConstraint: false,
|
|
1010
|
-
tk: DEFAULT_TIANDITU_TK
|
|
1011
|
-
}, config || {});
|
|
1012
|
-
const map = new Map$1({
|
|
1013
|
-
view: new View(innerConfig),
|
|
1014
|
-
controls: defaults({
|
|
1015
|
-
zoom: false,
|
|
1016
|
-
rotate: false,
|
|
1017
|
-
attribution: false
|
|
1018
|
-
}),
|
|
1019
|
-
interactions: defaults$1({
|
|
1020
|
-
pinchRotate: false,
|
|
1021
|
-
doubleClickZoom: false,
|
|
1022
|
-
altShiftDragRotate: false
|
|
1023
|
-
}),
|
|
1024
|
-
layers: innerConfig.layers || [createBaseLayer()]
|
|
1025
|
-
});
|
|
1026
|
-
mapCenter = innerConfig.center || mapCenter;
|
|
1027
|
-
map.activeBaseLayer = innerConfig.baseLayer;
|
|
1028
|
-
map.getLayerByName = getLayerByName;
|
|
1029
|
-
map.removeLayerByName = removeLayerByName;
|
|
1030
|
-
map.changeLayersByNames = changeLayersByNames;
|
|
1031
|
-
let pointerMoveFrame;
|
|
1032
|
-
let latestPixel;
|
|
1033
|
-
const cancelPointerMoveFrame = () => {
|
|
1034
|
-
if (pointerMoveFrame === void 0) return;
|
|
1035
|
-
if (typeof globalThis.cancelAnimationFrame === "function") globalThis.cancelAnimationFrame(pointerMoveFrame);
|
|
1036
|
-
else clearTimeout(pointerMoveFrame);
|
|
1037
|
-
pointerMoveFrame = void 0;
|
|
1038
|
-
};
|
|
1039
|
-
map.on("pointermove", (evt) => {
|
|
1040
|
-
latestPixel = evt.pixel;
|
|
1041
|
-
if (pointerMoveFrame !== void 0) return;
|
|
1042
|
-
const updateCursor = () => {
|
|
1043
|
-
pointerMoveFrame = void 0;
|
|
1044
|
-
if (!latestPixel) return;
|
|
1045
|
-
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(latestPixel) ? "pointer" : "";
|
|
1046
|
-
};
|
|
1047
|
-
pointerMoveFrame = typeof globalThis.requestAnimationFrame === "function" ? globalThis.requestAnimationFrame(updateCursor) : setTimeout(updateCursor, 16);
|
|
1048
|
-
});
|
|
1049
|
-
map.set("_disposePointerMove", cancelPointerMoveFrame);
|
|
1050
|
-
return map;
|
|
1051
|
-
};
|
|
1052
|
-
const disposeMap = () => {
|
|
1053
|
-
if (!instance) return;
|
|
1054
|
-
const map = instance;
|
|
1055
|
-
map.get("_disposePointerMove")?.();
|
|
1056
|
-
map.unset("_disposePointerMove");
|
|
1057
|
-
map.getOverlays().forEach((overlay) => {
|
|
1058
|
-
map.removeOverlay(overlay);
|
|
1059
|
-
});
|
|
1060
|
-
map.getInteractions().clear();
|
|
1061
|
-
map.getAllLayers().forEach((layer) => disposeLayer(map, layer));
|
|
1062
|
-
map.setTarget("");
|
|
1063
|
-
map.dispose();
|
|
1064
|
-
clearMapInstance();
|
|
1065
|
-
};
|
|
1066
|
-
/** 兼容别名;复用 map-ol-utils 现有的完整销毁语义。 */
|
|
1067
|
-
const disposeInstance = () => disposeMap();
|
|
1068
|
-
const getMapInstance = () => instance;
|
|
1069
|
-
const resetZoomAndCenter = (center = mapCenter) => {
|
|
1070
|
-
if (instance) instance.getView().animate({
|
|
1071
|
-
zoom: 9.4,
|
|
1072
|
-
center,
|
|
1073
|
-
duration: 1e3
|
|
1074
|
-
});
|
|
1075
|
-
};
|
|
1076
|
-
const resetMap = (config = {
|
|
1077
|
-
center: mapCenter,
|
|
1078
|
-
isClearAll: true
|
|
1079
|
-
}) => {
|
|
1080
|
-
removeAllLayers();
|
|
1081
|
-
resetZoomAndCenter(config.center || mapCenter);
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
//#endregion
|
|
1085
|
-
//#region src/map/view-fit.ts
|
|
1086
|
-
/** 视野自适应的默认参数(padding + 动画时长) */
|
|
1087
|
-
const DEFAULT_FIT_OPTIONS = {
|
|
1088
|
-
padding: [
|
|
1089
|
-
50,
|
|
1090
|
-
50,
|
|
1091
|
-
50,
|
|
1092
|
-
50
|
|
1093
|
-
],
|
|
1094
|
-
duration: 1e3
|
|
1095
|
-
};
|
|
1096
|
-
/**
|
|
1097
|
-
* 根据 Array<Coordinate> Fit
|
|
1098
|
-
* @param coords
|
|
1099
|
-
* @param mapInstance
|
|
1100
|
-
* @param fitOptions
|
|
1101
|
-
*/
|
|
1102
|
-
const setFitViewByCoords = (coords, mapInstance = instance, fitOptions) => {
|
|
1103
|
-
const view = mapInstance.getView();
|
|
1104
|
-
if (coords.length === 1) view.animate({
|
|
1105
|
-
zoom: 14,
|
|
1106
|
-
duration: 1e3,
|
|
1107
|
-
center: coords[0],
|
|
1108
|
-
...fitOptions || {}
|
|
1109
|
-
});
|
|
1110
|
-
else {
|
|
1111
|
-
const extent = boundingExtent(coords);
|
|
1112
|
-
view.fit(extent, {
|
|
1113
|
-
...DEFAULT_FIT_OPTIONS,
|
|
1114
|
-
...fitOptions || {}
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
/**
|
|
1119
|
-
* 根据 SimpleGeometry Fit
|
|
1120
|
-
* @param geom
|
|
1121
|
-
* @param mapInstance
|
|
1122
|
-
* @param fitOptions
|
|
1123
|
-
*/
|
|
1124
|
-
const setFitViewByGeom = (geom, mapInstance = instance, fitOptions) => {
|
|
1125
|
-
mapInstance.getView().fit(geom, {
|
|
1126
|
-
...DEFAULT_FIT_OPTIONS,
|
|
1127
|
-
...fitOptions || {}
|
|
1128
|
-
});
|
|
1129
|
-
};
|
|
1130
|
-
/**
|
|
1131
|
-
* 根据 VectorLayer 要素的四至信息 Fit
|
|
1132
|
-
* @param layer
|
|
1133
|
-
* @param mapInstance
|
|
1134
|
-
* @param fitOptions
|
|
1135
|
-
*/
|
|
1136
|
-
const setFitViewByLayer = (layer, mapInstance = instance, fitOptions) => {
|
|
1137
|
-
const source = layer.getSource();
|
|
1138
|
-
if (!source || source.isEmpty()) return;
|
|
1139
|
-
const extent = source.getExtent();
|
|
1140
|
-
mapInstance.getView().fit(extent, {
|
|
1141
|
-
...DEFAULT_FIT_OPTIONS,
|
|
1142
|
-
...fitOptions || {}
|
|
1143
|
-
});
|
|
1144
|
-
};
|
|
1145
|
-
/** 使用两段缩放动画飞到目标位置。 */
|
|
1146
|
-
const flyToAnimate = (center, zoom) => {
|
|
1147
|
-
const view = instance.getView();
|
|
1148
|
-
const currentZoom = view.getZoom();
|
|
1149
|
-
view.animate({
|
|
1150
|
-
center,
|
|
1151
|
-
duration: 2e3
|
|
1152
|
-
});
|
|
1153
|
-
if (!zoom) view.animate({
|
|
1154
|
-
zoom: currentZoom - 2,
|
|
1155
|
-
duration: 1e3
|
|
1156
|
-
}, {
|
|
1157
|
-
zoom: currentZoom + 2 > 12 ? 12 : currentZoom + 2,
|
|
1158
|
-
duration: 1e3
|
|
1159
|
-
});
|
|
1160
|
-
else view.animate({
|
|
1161
|
-
zoom: zoom - 2,
|
|
1162
|
-
duration: 1e3
|
|
1163
|
-
}, {
|
|
1164
|
-
zoom,
|
|
1165
|
-
duration: 1e3
|
|
1166
|
-
});
|
|
1167
|
-
};
|
|
1168
|
-
|
|
1169
|
-
//#endregion
|
|
1170
|
-
//#region src/layer/marker-layer.ts
|
|
1171
|
-
/** 创建 Marker 文本标注。 */
|
|
1172
|
-
const createText = (item) => new Text({
|
|
1173
|
-
text: item.text,
|
|
1174
|
-
offsetY: item.offsetY,
|
|
1175
|
-
offsetX: item.offsetX,
|
|
1176
|
-
justify: "center",
|
|
1177
|
-
textAlign: "center",
|
|
1178
|
-
font: "16px PingFangSC-Semibold",
|
|
1179
|
-
fill: new Fill({ color: "rgba(255, 255, 255, 0.8)" })
|
|
1180
|
-
});
|
|
1181
|
-
/** 创建 Marker 默认态或激活态样式。 */
|
|
1182
|
-
const createIconStyle = (item, active = false) => new Style({
|
|
1183
|
-
image: new Icon({
|
|
1184
|
-
scale: item.scale || 1,
|
|
1185
|
-
rotation: item.rotation || 0,
|
|
1186
|
-
anchor: item.anchor || [.5, 1],
|
|
1187
|
-
src: active ? item.activeImg || item.img : item.img
|
|
1188
|
-
}),
|
|
1189
|
-
text: item.showText ? createText(item) : void 0
|
|
1190
|
-
});
|
|
1191
|
-
/** 创建带 data 属性约定的 Marker Feature。 */
|
|
1192
|
-
const createMarker = (item, config) => {
|
|
1193
|
-
const feature = new Feature$1({
|
|
1194
|
-
geometry: new Point([+item.lng, +item.lat]),
|
|
1195
|
-
data: Object.assign({}, item || {}, { layerName: config.layerName })
|
|
1196
|
-
});
|
|
1197
|
-
feature.setStyle(createIconStyle(item));
|
|
1198
|
-
return feature;
|
|
1199
|
-
};
|
|
1200
|
-
/** 批量创建 Marker 矢量图层。 */
|
|
1201
|
-
const createMarkersLayer = (dataList, config) => {
|
|
1202
|
-
const { layerName, visible = true, zIndex = 10 } = config;
|
|
1203
|
-
const source = new VectorSource();
|
|
1204
|
-
dataList.forEach((item) => source.addFeature(createMarker(item, config)));
|
|
1205
|
-
return new VectorLayer({
|
|
1206
|
-
zIndex,
|
|
1207
|
-
visible,
|
|
1208
|
-
source,
|
|
1209
|
-
className: layerName || "marker-layer"
|
|
1210
|
-
});
|
|
1211
|
-
};
|
|
1212
|
-
/** @internal 按当前缩放级别刷新弹性 Marker 的图标。 */
|
|
1213
|
-
const setElasticMarkerStyle = (featureList, currentZoom) => {
|
|
1214
|
-
const imageKey = currentZoom >= 14 ? "largeImg" : currentZoom >= 11 ? "smallImg" : "img";
|
|
1215
|
-
featureList.forEach((feature) => {
|
|
1216
|
-
const config = feature.get("data");
|
|
1217
|
-
if (!config.isSelected) feature.setStyle(createIconStyle({
|
|
1218
|
-
...config,
|
|
1219
|
-
img: config[imageKey]
|
|
1220
|
-
}, false));
|
|
1221
|
-
});
|
|
1222
|
-
};
|
|
1223
|
-
/** @internal 同步 Marker 选中状态及对应图标样式。 */
|
|
1224
|
-
const setMarkerSelectedState = (feature, selected) => {
|
|
1225
|
-
const config = feature.get("data");
|
|
1226
|
-
const next = {
|
|
1227
|
-
...config,
|
|
1228
|
-
isSelected: selected
|
|
1229
|
-
};
|
|
1230
|
-
feature.set("data", next);
|
|
1231
|
-
if (config.isElastic) setElasticMarkerStyle([feature], instance.getView().getZoom());
|
|
1232
|
-
else feature.setStyle(createIconStyle(next, selected));
|
|
1233
|
-
};
|
|
1234
|
-
/** 清空外部选择集合并恢复 Marker 未选中样式。 */
|
|
1235
|
-
const changeUnselectStyle = (featureList) => {
|
|
1236
|
-
featureList.forEach((feature) => {
|
|
1237
|
-
setMarkerSelectedState(feature, false);
|
|
1238
|
-
});
|
|
1239
|
-
featureList.clear();
|
|
1240
|
-
};
|
|
1241
|
-
/** 创建随缩放级别切换小图/大图的 Marker 图层。 */
|
|
1242
|
-
const createElasticMarkerLayer = (dataList, config) => {
|
|
1243
|
-
const elasticData = dataList.map((item) => ({
|
|
1244
|
-
...item,
|
|
1245
|
-
isElastic: true
|
|
1246
|
-
}));
|
|
1247
|
-
const vectorLayer = createMarkersLayer(elasticData, config);
|
|
1248
|
-
const features = vectorLayer.getSource().getFeatures();
|
|
1249
|
-
setElasticMarkerStyle(features, instance.getView().getZoom());
|
|
1250
|
-
instance.getView().on("change:resolution", ({ target }) => {
|
|
1251
|
-
setElasticMarkerStyle(features, target.get("zoom"));
|
|
1252
|
-
});
|
|
1253
|
-
return vectorLayer;
|
|
1254
|
-
};
|
|
1255
|
-
/** 创建单 Marker 图层并直接加入当前地图实例。 */
|
|
1256
|
-
const createMoveMarkerLayer = (dataItem, config) => {
|
|
1257
|
-
const marker = createMarker(dataItem, config);
|
|
1258
|
-
const vectorLayer = new VectorLayer({
|
|
1259
|
-
className: config.layerName || "ol-layer",
|
|
1260
|
-
source: new VectorSource({ features: [marker] })
|
|
1261
|
-
});
|
|
1262
|
-
instance.addLayer(vectorLayer);
|
|
1263
|
-
return { vectorLayer };
|
|
1264
|
-
};
|
|
1265
|
-
|
|
1266
|
-
//#endregion
|
|
1267
|
-
//#region src/interaction/select.ts
|
|
1268
|
-
/**
|
|
1269
|
-
* 处理要素选中:切换激活样式、派发事件、按需自动居中
|
|
1270
|
-
* @param vectorLayer
|
|
1271
|
-
* @param feature
|
|
1272
|
-
*/
|
|
1273
|
-
const selectEventHandle = (vectorLayer, feature) => {
|
|
1274
|
-
const data = feature.getProperties();
|
|
1275
|
-
const { activeStyle } = data;
|
|
1276
|
-
feature.setStyle(activeStyle);
|
|
1277
|
-
emitter.emit("selected-feature", data);
|
|
1278
|
-
vectorLayer.set("_hasActive", true);
|
|
1279
|
-
if (vectorLayer.get("_isDynamic")) {
|
|
1280
|
-
const activeImage = activeStyle.getImage();
|
|
1281
|
-
const previousTimer = vectorLayer.get("_dynamicMarkerTimer");
|
|
1282
|
-
if (previousTimer !== void 0) clearTimeout(previousTimer);
|
|
1283
|
-
const timer = setTimeout(() => {
|
|
1284
|
-
vectorLayer.unset("_dynamicMarkerTimer");
|
|
1285
|
-
emitter.emit("dynamic-marker", Object.assign({}, data.rawData || {}, {
|
|
1286
|
-
lnglat: data.lnglat,
|
|
1287
|
-
style: data.style,
|
|
1288
|
-
crsForm: data.crsForm,
|
|
1289
|
-
width: activeImage.getWidth(),
|
|
1290
|
-
height: activeImage.getHeight()
|
|
1291
|
-
}));
|
|
1292
|
-
}, 1e3);
|
|
1293
|
-
vectorLayer.set("_dynamicMarkerTimer", timer);
|
|
1294
|
-
}
|
|
1295
|
-
if (vectorLayer.get("_isAutoFit")) {
|
|
1296
|
-
const view = instance.getView();
|
|
1297
|
-
view.animate({
|
|
1298
|
-
duration: 1e3,
|
|
1299
|
-
zoom: view.getZoom(),
|
|
1300
|
-
center: getCenter(feature.getGeometry().getExtent())
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
};
|
|
1304
|
-
/**
|
|
1305
|
-
* 创建点击交互
|
|
1306
|
-
* @param vectorLayer
|
|
1307
|
-
*/
|
|
1308
|
-
const createSelectByLayer = (vectorLayer) => {
|
|
1309
|
-
const interaction = new Select({
|
|
1310
|
-
style: null,
|
|
1311
|
-
multi: false,
|
|
1312
|
-
hitTolerance: 10,
|
|
1313
|
-
layers: [vectorLayer],
|
|
1314
|
-
condition: (evt) => {
|
|
1315
|
-
if (!singleClick(evt)) return false;
|
|
1316
|
-
let hasFeature = false;
|
|
1317
|
-
instance.forEachFeatureAtPixel(evt.pixel, () => {
|
|
1318
|
-
hasFeature = true;
|
|
1319
|
-
}, { layerFilter: (layer) => layer === vectorLayer });
|
|
1320
|
-
return hasFeature;
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
interaction.on("select", (evt) => {
|
|
1324
|
-
const { selected, deselected } = evt;
|
|
1325
|
-
if (selected.length) {
|
|
1326
|
-
const feature = selected[0];
|
|
1327
|
-
if (vectorLayer.get("_isClusterLayer")) {
|
|
1328
|
-
const features = feature.get("features");
|
|
1329
|
-
if (features.length === 1) selectEventHandle(vectorLayer, features[0]);
|
|
1330
|
-
} else selectEventHandle(vectorLayer, feature);
|
|
1331
|
-
}
|
|
1332
|
-
if (deselected.length) {
|
|
1333
|
-
const feature = deselected[0];
|
|
1334
|
-
const data = feature.getProperties();
|
|
1335
|
-
feature.setStyle(data.unActiveStyle);
|
|
1336
|
-
vectorLayer.set("_hasActive", false);
|
|
1337
|
-
}
|
|
1338
|
-
});
|
|
1339
|
-
instance.addInteraction(interaction);
|
|
1340
|
-
vectorLayer.set("_selectInteraction", interaction);
|
|
1341
|
-
return interaction;
|
|
1342
|
-
};
|
|
1343
|
-
/** 按多个 className 创建选择交互。 */
|
|
1344
|
-
const createLayerSelectByNames = (layerNames, selectedHandler, unSelectHandler) => {
|
|
1345
|
-
const interaction = new Select({
|
|
1346
|
-
style: null,
|
|
1347
|
-
layers: (layer) => layerNames.includes(layer.getClassName())
|
|
1348
|
-
});
|
|
1349
|
-
interaction.on("select", ({ deselected, selected }) => {
|
|
1350
|
-
if (deselected.length) {
|
|
1351
|
-
const feature = deselected[0];
|
|
1352
|
-
setMarkerSelectedState(feature, false);
|
|
1353
|
-
unSelectHandler?.(feature);
|
|
1354
|
-
}
|
|
1355
|
-
if (selected.length) {
|
|
1356
|
-
const feature = selected[0];
|
|
1357
|
-
setMarkerSelectedState(feature, true);
|
|
1358
|
-
selectedHandler?.(feature);
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
|
-
return interaction;
|
|
1362
|
-
};
|
|
1363
|
-
|
|
1364
|
-
//#endregion
|
|
1365
|
-
//#region src/interaction/draw.ts
|
|
1366
|
-
const formatArea = (polygon) => {
|
|
1367
|
-
const area = getArea(polygon, { projection: "EPSG:4326" });
|
|
1368
|
-
return area > 1e4 ? `${Math.round(area / 1e6 * 100) / 100} km\xB2` : `${Math.round(area * 100) / 100} m\xB2`;
|
|
1369
|
-
};
|
|
1370
|
-
const formatLength = (line) => {
|
|
1371
|
-
const length = getLength(line, { projection: "EPSG:4326" });
|
|
1372
|
-
return length > 100 ? `${Math.round(length / 1e3 * 100) / 100} km` : `${Math.round(length * 100) / 100} m`;
|
|
1373
|
-
};
|
|
1374
|
-
const drawDefaultStyle = new Style({
|
|
1375
|
-
fill: new Fill({ color: "rgba(255, 255, 255, 0.2)" }),
|
|
1376
|
-
stroke: new Stroke({
|
|
1377
|
-
color: "rgba(0, 0, 0, 0.5)",
|
|
1378
|
-
lineDash: [10, 10],
|
|
1379
|
-
width: 2
|
|
1380
|
-
}),
|
|
1381
|
-
image: new Circle({
|
|
1382
|
-
radius: 5,
|
|
1383
|
-
stroke: new Stroke({ color: "rgba(0, 0, 0, 0.7)" }),
|
|
1384
|
-
fill: new Fill({ color: "rgba(255, 255, 255, 0.2)" })
|
|
1385
|
-
})
|
|
1386
|
-
});
|
|
1387
|
-
const drawLabelStyleCache = /* @__PURE__ */ new WeakMap();
|
|
1388
|
-
const getOrCreateLabelStyle = (feature) => {
|
|
1389
|
-
const cached = drawLabelStyleCache.get(feature);
|
|
1390
|
-
if (cached) return cached;
|
|
1391
|
-
const style = new Style({
|
|
1392
|
-
text: new Text({
|
|
1393
|
-
font: "14px",
|
|
1394
|
-
fill: new Fill({ color: "rgba(255, 255, 255, 1)" }),
|
|
1395
|
-
backgroundFill: new Fill({ color: "rgba(0, 0, 0, 0.7)" }),
|
|
1396
|
-
padding: [
|
|
1397
|
-
3,
|
|
1398
|
-
3,
|
|
1399
|
-
3,
|
|
1400
|
-
3
|
|
1401
|
-
],
|
|
1402
|
-
textBaseline: "bottom",
|
|
1403
|
-
offsetY: -15
|
|
1404
|
-
}),
|
|
1405
|
-
image: new RegularShape({
|
|
1406
|
-
radius: 8,
|
|
1407
|
-
points: 3,
|
|
1408
|
-
angle: Math.PI,
|
|
1409
|
-
displacement: [0, 10],
|
|
1410
|
-
fill: new Fill({ color: "rgba(0, 0, 0, 0.7)" })
|
|
1411
|
-
})
|
|
1412
|
-
});
|
|
1413
|
-
drawLabelStyleCache.set(feature, style);
|
|
1414
|
-
return style;
|
|
1415
|
-
};
|
|
1416
|
-
/** 为绘制中的线和面追加长度/面积标签。 */
|
|
1417
|
-
const getDrawStyle = (feature, drawStyle = drawDefaultStyle) => {
|
|
1418
|
-
const styles = drawStyle ? [drawStyle] : [];
|
|
1419
|
-
const geometry = feature.getGeometry();
|
|
1420
|
-
let point;
|
|
1421
|
-
let label;
|
|
1422
|
-
if (geometry.getType() === "Polygon") {
|
|
1423
|
-
point = geometry.getInteriorPoint();
|
|
1424
|
-
label = formatArea(geometry);
|
|
1425
|
-
} else if (geometry.getType() === "LineString") {
|
|
1426
|
-
point = new Point(geometry.getLastCoordinate());
|
|
1427
|
-
label = formatLength(geometry);
|
|
1428
|
-
}
|
|
1429
|
-
if (point && label) {
|
|
1430
|
-
const labelStyle = getOrCreateLabelStyle(feature);
|
|
1431
|
-
labelStyle.setGeometry(point);
|
|
1432
|
-
labelStyle.getText().setText(label);
|
|
1433
|
-
styles.push(labelStyle);
|
|
1434
|
-
}
|
|
1435
|
-
return styles;
|
|
1436
|
-
};
|
|
1437
|
-
/** 创建绘制交互。 */
|
|
1438
|
-
const createDrawInteraction = (type = "Point", vectorLayer, openTip) => new Draw({
|
|
1439
|
-
source: vectorLayer.getSource(),
|
|
1440
|
-
type,
|
|
1441
|
-
style: openTip ? (feature) => getDrawStyle(feature, void 0) : void 0
|
|
1442
|
-
});
|
|
1443
|
-
|
|
1444
|
-
//#endregion
|
|
1445
|
-
//#region src/layer/geojson-layer.ts
|
|
1446
|
-
/** 直接从 GeoJSON 创建矢量图层。 */
|
|
1447
|
-
const createGeoJsonLayer = (geoJson, style, config) => {
|
|
1448
|
-
const { layerName } = config || {};
|
|
1449
|
-
const layerConfig = Object.assign({
|
|
1450
|
-
className: layerName || "ol-layer",
|
|
1451
|
-
source: new VectorSource({ features: new GeoJSON$1().readFeatures(geoJson) }),
|
|
1452
|
-
style: style || new Style({
|
|
1453
|
-
stroke: new Stroke({
|
|
1454
|
-
color: "red",
|
|
1455
|
-
lineDash: [10],
|
|
1456
|
-
width: 3
|
|
1457
|
-
}),
|
|
1458
|
-
fill: new Fill({ color: "rgba(0, 0, 255, 0.1)" })
|
|
1459
|
-
})
|
|
1460
|
-
}, config || {});
|
|
1461
|
-
return new VectorLayer(layerConfig);
|
|
1462
|
-
};
|
|
1463
|
-
|
|
1464
|
-
//#endregion
|
|
1465
|
-
//#region src/layer/vector-layer.ts
|
|
1466
|
-
/**
|
|
1467
|
-
* 按 colorKey/colorMap 解析颜色,未命中时回退到 fallback
|
|
1468
|
-
*/
|
|
1469
|
-
const resolveColor = (data, colorKey, colorMap, fallback) => colorKey ? colorMap?.get(data[colorKey]) || fallback : fallback;
|
|
1470
|
-
/**
|
|
1471
|
-
* 组装业务数据为 GeoJson(坐标统一转 WGS84)
|
|
1472
|
-
* @param dataList
|
|
1473
|
-
* @param config
|
|
1474
|
-
*/
|
|
1475
|
-
const assembleGeoJson = (dataList, config) => {
|
|
1476
|
-
const { key, crsForm } = config;
|
|
1477
|
-
const tempList = [];
|
|
1478
|
-
dataList.forEach((item) => {
|
|
1479
|
-
try {
|
|
1480
|
-
const raw = item[key];
|
|
1481
|
-
const pathArray = Array.isArray(raw) ? raw : JSON.parse(raw || "[]");
|
|
1482
|
-
if (pathArray.length) {
|
|
1483
|
-
const type = item["geoType"] || determineGeometryType(pathArray);
|
|
1484
|
-
if (type !== "Invalid") tempList.push({
|
|
1485
|
-
type: "Feature",
|
|
1486
|
-
properties: { ...item },
|
|
1487
|
-
geometry: {
|
|
1488
|
-
type,
|
|
1489
|
-
coordinates: pathArray
|
|
1490
|
-
}
|
|
1491
|
-
});
|
|
1492
|
-
}
|
|
1493
|
-
} catch (e) {
|
|
1494
|
-
console.error(`创建线状数据失败:${e}`);
|
|
1495
|
-
}
|
|
1496
|
-
});
|
|
1497
|
-
return gcoord.transform({
|
|
1498
|
-
type: "FeatureCollection",
|
|
1499
|
-
features: tempList
|
|
1500
|
-
}, crsForm || gcoord.WGS84, gcoord.WGS84);
|
|
1501
|
-
};
|
|
1502
|
-
const createVectorStyle = (feature, config) => {
|
|
1503
|
-
const { isLine, colorMap, fillColor, colorKey, strokeColor, fillOpacity, strokeWeight, activeFillColor, activeFillOpacity, activeStrokeColor, activeStrokeWeight } = config || {};
|
|
1504
|
-
const data = feature.getProperties();
|
|
1505
|
-
const activeStyle = new Style({ stroke: new Stroke({
|
|
1506
|
-
width: activeStrokeWeight ?? strokeWeight ?? 4,
|
|
1507
|
-
color: activeStrokeColor || resolveColor(data, colorKey, colorMap, strokeColor)
|
|
1508
|
-
}) });
|
|
1509
|
-
const unActiveStyle = new Style({ stroke: new Stroke({
|
|
1510
|
-
width: strokeWeight ?? 2,
|
|
1511
|
-
color: resolveColor(data, colorKey, colorMap, strokeColor)
|
|
1512
|
-
}) });
|
|
1513
|
-
if (!isLine) {
|
|
1514
|
-
activeStyle.setFill(new Fill({ color: hexToRgba(activeFillColor || fillColor || "", activeFillOpacity ?? fillOpacity ?? 1) || hexToRgba(resolveColor(data, colorKey, colorMap, fillColor) || "", fillOpacity ?? 1) }));
|
|
1515
|
-
unActiveStyle.setFill(new Fill({ color: hexToRgba(resolveColor(data, colorKey, colorMap, fillColor) || "", fillOpacity ?? 1) }));
|
|
1516
|
-
}
|
|
1517
|
-
feature.setProperties({
|
|
1518
|
-
activeStyle,
|
|
1519
|
-
unActiveStyle
|
|
1520
|
-
});
|
|
1521
|
-
return unActiveStyle;
|
|
1522
|
-
};
|
|
1523
|
-
const readBusinessFeatures = (dataList, config) => {
|
|
1524
|
-
const geoJson = assembleGeoJson(dataList, config);
|
|
1525
|
-
const features = new GeoJSON().readFeatures(geoJson);
|
|
1526
|
-
const acceptedData = features.map((feature) => {
|
|
1527
|
-
const data = { ...feature.getProperties() };
|
|
1528
|
-
delete data.geometry;
|
|
1529
|
-
return data;
|
|
1530
|
-
});
|
|
1531
|
-
features.forEach((feature) => {
|
|
1532
|
-
feature.setStyle(createVectorStyle(feature, config));
|
|
1533
|
-
});
|
|
1534
|
-
return {
|
|
1535
|
-
features,
|
|
1536
|
-
acceptedData
|
|
1537
|
-
};
|
|
1538
|
-
};
|
|
1539
|
-
/**
|
|
1540
|
-
* 创建业务矢量图层(自动推断线/面,生成默认/激活样式)
|
|
1541
|
-
* @param dataList
|
|
1542
|
-
* @param config
|
|
1543
|
-
*/
|
|
1544
|
-
const createBusinessLayer = (dataList, config) => {
|
|
1545
|
-
const { isAutoFit, layerName, openClick } = config;
|
|
1546
|
-
const { features, acceptedData } = readBusinessFeatures(dataList, config);
|
|
1547
|
-
const vectorLayer = new VectorLayer({
|
|
1548
|
-
zIndex: 5,
|
|
1549
|
-
className: layerName ? layerName : "ol-layer",
|
|
1550
|
-
source: new VectorSource({ features })
|
|
1551
|
-
});
|
|
1552
|
-
if (openClick) createSelectByLayer(vectorLayer);
|
|
1553
|
-
vectorLayer.setProperties({
|
|
1554
|
-
_isCustom: true,
|
|
1555
|
-
_isAutoFit: isAutoFit,
|
|
1556
|
-
_isVectorLayer: true,
|
|
1557
|
-
_vectorConfig: config,
|
|
1558
|
-
_layerName: layerName,
|
|
1559
|
-
_dataList: acceptedData,
|
|
1560
|
-
_uuidSet: new Set(acceptedData.map((item) => item._uuid))
|
|
1561
|
-
});
|
|
1562
|
-
return vectorLayer;
|
|
1563
|
-
};
|
|
1564
|
-
/**
|
|
1565
|
-
* 追加 Features
|
|
1566
|
-
* @param targetLayer
|
|
1567
|
-
* @param dataList
|
|
1568
|
-
* @param crsForm
|
|
1569
|
-
*/
|
|
1570
|
-
const appendFeaturesByLayer = (targetLayer, dataList, crsForm) => {
|
|
1571
|
-
const vectorSource = targetLayer.getSource();
|
|
1572
|
-
const { _vectorConfig: config } = targetLayer.getProperties() || {};
|
|
1573
|
-
const { features, acceptedData } = readBusinessFeatures(dataList, Object.assign({}, config, { crsForm }));
|
|
1574
|
-
vectorSource.addFeatures(features);
|
|
1575
|
-
const uuidSet = new Set(targetLayer.get("_uuidSet") || []);
|
|
1576
|
-
acceptedData.forEach((item) => uuidSet.add(item._uuid));
|
|
1577
|
-
targetLayer.set("_uuidSet", uuidSet);
|
|
1578
|
-
targetLayer.set("_dataList", [...targetLayer.get("_dataList") || [], ...acceptedData]);
|
|
1579
|
-
};
|
|
1580
|
-
/**
|
|
1581
|
-
* 创建面状图层
|
|
1582
|
-
* @param dataList
|
|
1583
|
-
* @param config
|
|
1584
|
-
*/
|
|
1585
|
-
const createVectorFaceLayer = (dataList = [], config) => createBusinessLayer(dataList, Object.assign({}, config, { isLine: false }));
|
|
1586
|
-
/**
|
|
1587
|
-
* 创建矢量线图层
|
|
1588
|
-
* @param dataList
|
|
1589
|
-
* @param config
|
|
1590
|
-
*/
|
|
1591
|
-
const createVectorLineLayer = (dataList = [], config) => createBusinessLayer(dataList, Object.assign({}, config, { isLine: true }));
|
|
1592
|
-
|
|
1593
|
-
//#endregion
|
|
1594
|
-
//#region src/layer/mass-marks.ts
|
|
1595
|
-
/**
|
|
1596
|
-
* 构建 MassMarks 样式数组(默认段 + 激活段,长度 statusNum * 2)
|
|
1597
|
-
*/
|
|
1598
|
-
const buildMassMarksStyles = (getImgUrlFn) => {
|
|
1599
|
-
return (iconConfig) => {
|
|
1600
|
-
const { baseUrl, markerIcon, statusNum = 5, activeScale = 1.3, markerSize = [32, 32] } = iconConfig;
|
|
1601
|
-
const getMarkerUrl = getImgUrlFn(baseUrl);
|
|
1602
|
-
const [x, y] = markerSize;
|
|
1603
|
-
const massMarksStyles = new Array(statusNum * 2);
|
|
1604
|
-
for (let i = 0; i < statusNum; i++) {
|
|
1605
|
-
const imageUrl = getMarkerUrl(`${markerIcon}-${i}`, ".webp");
|
|
1606
|
-
massMarksStyles[i] = new Style({
|
|
1607
|
-
zIndex: 88 + i,
|
|
1608
|
-
image: new Icon({
|
|
1609
|
-
width: x,
|
|
1610
|
-
height: y,
|
|
1611
|
-
src: imageUrl,
|
|
1612
|
-
anchor: [.5, 1]
|
|
1613
|
-
})
|
|
1614
|
-
});
|
|
1615
|
-
const activeImage = getMarkerUrl(`${markerIcon}-${i}-active`, ".webp");
|
|
1616
|
-
massMarksStyles[i + statusNum] = new Style({
|
|
1617
|
-
zIndex: 120,
|
|
1618
|
-
image: new Icon({
|
|
1619
|
-
width: x * activeScale,
|
|
1620
|
-
height: y * activeScale,
|
|
1621
|
-
anchor: [.5, 1],
|
|
1622
|
-
src: activeImage ? activeImage : imageUrl
|
|
1623
|
-
})
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
return massMarksStyles;
|
|
1627
|
-
};
|
|
1628
|
-
};
|
|
1629
|
-
const createFeatures = (dataList, styleList = []) => {
|
|
1630
|
-
return dataList.map((item) => {
|
|
1631
|
-
const { crsForm } = item;
|
|
1632
|
-
const coord = gcoord.transform([+item.lnglat[0], +item.lnglat[1]], crsForm || gcoord.WGS84, gcoord.WGS84);
|
|
1633
|
-
const feature = new Feature({ geometry: new Point$1(coord) });
|
|
1634
|
-
feature.setProperties({
|
|
1635
|
-
...item,
|
|
1636
|
-
unActiveStyle: styleList[item.style],
|
|
1637
|
-
activeStyle: styleList[item.style + styleList.length / 2]
|
|
1638
|
-
});
|
|
1639
|
-
feature.setStyle(styleList[item.style]);
|
|
1640
|
-
return feature;
|
|
1641
|
-
});
|
|
1642
|
-
};
|
|
1643
|
-
/**
|
|
1644
|
-
* 向海量点图层增量添加 Features
|
|
1645
|
-
* @param targetLayer
|
|
1646
|
-
* @param dataList
|
|
1647
|
-
* @param config
|
|
1648
|
-
*/
|
|
1649
|
-
const addFeaturesByLayer = (targetLayer, dataList, config = {}) => {
|
|
1650
|
-
const massMarksStyles = targetLayer.get("_massMarksStyles");
|
|
1651
|
-
const targetSource = targetLayer?.getSource();
|
|
1652
|
-
const { clear } = config;
|
|
1653
|
-
const features = createFeatures(dataList, massMarksStyles);
|
|
1654
|
-
if (clear) targetSource?.clear();
|
|
1655
|
-
targetSource?.addFeatures(features);
|
|
1656
|
-
const nextData = [...clear ? [] : targetLayer.get("_dataList") || [], ...dataList];
|
|
1657
|
-
targetLayer.set("_dataList", nextData);
|
|
1658
|
-
targetLayer.set("_uuidSet", new Set(nextData.map((item) => item._uuid)));
|
|
1659
|
-
};
|
|
1660
|
-
/**
|
|
1661
|
-
* 创建 massMarksLayer
|
|
1662
|
-
* @param dataList
|
|
1663
|
-
* @param styleList
|
|
1664
|
-
*/
|
|
1665
|
-
const createMassMarksLayer = (dataList, styleList = []) => {
|
|
1666
|
-
const vectorLayer = new VectorLayer({
|
|
1667
|
-
zIndex: 2e3,
|
|
1668
|
-
source: new VectorSource({ features: createFeatures(dataList, styleList) })
|
|
1669
|
-
});
|
|
1670
|
-
vectorLayer.setProperties({
|
|
1671
|
-
_isCustom: true,
|
|
1672
|
-
_isVectorLayer: true,
|
|
1673
|
-
_isMassMarksLayer: true,
|
|
1674
|
-
_statusNum: styleList.length / 2,
|
|
1675
|
-
_dataList: [...dataList],
|
|
1676
|
-
_uuidSet: new Set(dataList.map((item) => item._uuid) || [])
|
|
1677
|
-
});
|
|
1678
|
-
return vectorLayer;
|
|
1679
|
-
};
|
|
1680
|
-
/**
|
|
1681
|
-
* 添加 massMarksLayer(按需附带线/面图层)
|
|
1682
|
-
* @param getImgUrlFn
|
|
1683
|
-
*/
|
|
1684
|
-
const addMassMarksLayer = (getImgUrlFn) => {
|
|
1685
|
-
return (dataList = [], iconConfig, openClick = true) => {
|
|
1686
|
-
const { offset = [0, 0], baseUrl, hasLine, hasFace, layerName, isDynamic, isAutoFit, statusNum, markerIcon, marksStyles, markerSize, activeScale, vectorConfig } = iconConfig;
|
|
1687
|
-
const massMarksStyles = marksStyles ? marksStyles : buildMassMarksStyles(getImgUrlFn)({
|
|
1688
|
-
baseUrl,
|
|
1689
|
-
markerIcon,
|
|
1690
|
-
markerSize,
|
|
1691
|
-
statusNum: statusNum || 5,
|
|
1692
|
-
activeScale: activeScale || 1.3
|
|
1693
|
-
});
|
|
1694
|
-
const massMarksLayer = createMassMarksLayer(dataList, massMarksStyles);
|
|
1695
|
-
if (openClick) {
|
|
1696
|
-
massMarksLayer.setProperties({
|
|
1697
|
-
_offset: offset,
|
|
1698
|
-
_isAutoFit: isAutoFit
|
|
1699
|
-
});
|
|
1700
|
-
createSelectByLayer(massMarksLayer);
|
|
1701
|
-
}
|
|
1702
|
-
if (hasLine) {
|
|
1703
|
-
const vectorLayer = createBusinessLayer(dataList, Object.assign({}, {
|
|
1704
|
-
key: "line",
|
|
1705
|
-
isLine: true,
|
|
1706
|
-
openClick: false,
|
|
1707
|
-
strokeWeight: 4,
|
|
1708
|
-
strokeColor: "rgba(29, 235, 255, 0.8)"
|
|
1709
|
-
}, vectorConfig || {}));
|
|
1710
|
-
instance.addLayer(vectorLayer);
|
|
1711
|
-
}
|
|
1712
|
-
if (hasFace) {
|
|
1713
|
-
const vectorLayer = createBusinessLayer(dataList, Object.assign({}, {
|
|
1714
|
-
isLine: false,
|
|
1715
|
-
key: "faceList",
|
|
1716
|
-
strokeWeight: 1,
|
|
1717
|
-
openClick: false,
|
|
1718
|
-
fillColor: "#4247DF",
|
|
1719
|
-
strokeStyle: "dashed",
|
|
1720
|
-
strokeColor: "#4247DF"
|
|
1721
|
-
}, vectorConfig || {}));
|
|
1722
|
-
instance.addLayer(vectorLayer);
|
|
1723
|
-
}
|
|
1724
|
-
massMarksLayer.setProperties({
|
|
1725
|
-
_isCustom: true,
|
|
1726
|
-
_offset: offset,
|
|
1727
|
-
_hasLine: hasLine,
|
|
1728
|
-
_hasFace: hasFace,
|
|
1729
|
-
className: layerName,
|
|
1730
|
-
_layerName: layerName,
|
|
1731
|
-
_isVectorLayer: true,
|
|
1732
|
-
_isDynamic: isDynamic,
|
|
1733
|
-
_massMarksStyles: massMarksStyles
|
|
1734
|
-
});
|
|
1735
|
-
instance.addLayer(massMarksLayer);
|
|
1736
|
-
return massMarksLayer;
|
|
1737
|
-
};
|
|
1738
|
-
};
|
|
1739
|
-
/**
|
|
1740
|
-
* 按选中状态列表更新海量点显隐
|
|
1741
|
-
* @param checkedStatusList
|
|
1742
|
-
*/
|
|
1743
|
-
const updateMarksLayersByStatus = (checkedStatusList = []) => {
|
|
1744
|
-
const hideStyle = new Style({});
|
|
1745
|
-
const checkedStatuses = new Set(checkedStatusList);
|
|
1746
|
-
const tempList = [];
|
|
1747
|
-
instance.getAllLayers().filter((layer) => layer.get("_isMassMarksLayer")).forEach((layer) => {
|
|
1748
|
-
layer.getSource().getFeatures().forEach((feature) => {
|
|
1749
|
-
const data = feature.getProperties();
|
|
1750
|
-
if (!checkedStatuses.has(data.status)) feature.setStyle(hideStyle);
|
|
1751
|
-
else {
|
|
1752
|
-
tempList.push(data);
|
|
1753
|
-
feature.setStyle(data.unActiveStyle);
|
|
1754
|
-
}
|
|
1755
|
-
});
|
|
1756
|
-
});
|
|
1757
|
-
return tempList;
|
|
1758
|
-
};
|
|
1759
|
-
|
|
1760
|
-
//#endregion
|
|
1761
|
-
//#region src/layer/cluster-layer.ts
|
|
1762
|
-
/**
|
|
1763
|
-
* 创建聚合图层(半径/颜色随聚合数量动态计算)
|
|
1764
|
-
* @param dataList
|
|
1765
|
-
* @param config
|
|
1766
|
-
*/
|
|
1767
|
-
const createClusterLayer = (dataList, config) => {
|
|
1768
|
-
const count = dataList.length;
|
|
1769
|
-
const styleCache = {};
|
|
1770
|
-
const { distance, minDistance, openClick, buildStyle, singleStyles, computedRadius, layerName = "cluster-layer" } = config;
|
|
1771
|
-
const innerComputedRadius = (size) => Math.pow(size / count, 1 / 18);
|
|
1772
|
-
styleCache[1] = singleStyles;
|
|
1773
|
-
const innerBuildStyle = (feature) => {
|
|
1774
|
-
const size = feature.get("features").length;
|
|
1775
|
-
let style = styleCache[size];
|
|
1776
|
-
const tempRadius = computedRadius ? computedRadius(size) : innerComputedRadius(size);
|
|
1777
|
-
if (!style) {
|
|
1778
|
-
style = new Style({
|
|
1779
|
-
image: new CircleStyle({
|
|
1780
|
-
radius: tempRadius * 20,
|
|
1781
|
-
stroke: new Stroke({
|
|
1782
|
-
width: 2,
|
|
1783
|
-
color: `hsla(${180 - tempRadius * 180}, 100%, 40%, 1)`
|
|
1784
|
-
}),
|
|
1785
|
-
fill: new Fill({ color: `hsla(${180 - tempRadius * 180}, 100%, 50%, 1)` })
|
|
1786
|
-
}),
|
|
1787
|
-
text: new Text({
|
|
1788
|
-
text: size.toString(),
|
|
1789
|
-
fill: new Fill({ color: `hsla(${180 - tempRadius * 180}, 100%, 10%, 1)` }),
|
|
1790
|
-
textBaseline: "middle"
|
|
1791
|
-
})
|
|
1792
|
-
});
|
|
1793
|
-
styleCache[size] = style;
|
|
1794
|
-
}
|
|
1795
|
-
if (Array.isArray(style)) {
|
|
1796
|
-
const index = feature.get("features")[0].get("styleIndex") ?? 0;
|
|
1797
|
-
return style[index];
|
|
1798
|
-
}
|
|
1799
|
-
return style;
|
|
1800
|
-
};
|
|
1801
|
-
const vectorLayer = new VectorLayer({
|
|
1802
|
-
zIndex: 200,
|
|
1803
|
-
className: layerName,
|
|
1804
|
-
source: new Cluster({
|
|
1805
|
-
distance: distance || 80,
|
|
1806
|
-
minDistance: minDistance || 0,
|
|
1807
|
-
source: new VectorSource({ features: dataList.map((item) => {
|
|
1808
|
-
const feature = new Feature(new Point$1(item.lnglat));
|
|
1809
|
-
feature.setProperties(item);
|
|
1810
|
-
const index = item.style ?? 0;
|
|
1811
|
-
feature.setProperties({
|
|
1812
|
-
unActiveStyle: singleStyles[index],
|
|
1813
|
-
activeStyle: singleStyles[index + singleStyles.length / 2]
|
|
1814
|
-
});
|
|
1815
|
-
return feature;
|
|
1816
|
-
}) })
|
|
1817
|
-
}),
|
|
1818
|
-
style: (feature) => buildStyle ? buildStyle(feature) : innerBuildStyle(feature)
|
|
1819
|
-
});
|
|
1820
|
-
vectorLayer.setProperties({
|
|
1821
|
-
_isCustom: true,
|
|
1822
|
-
_isVectorLayer: true,
|
|
1823
|
-
_isClusterLayer: true
|
|
1824
|
-
});
|
|
1825
|
-
if (openClick) createSelectByLayer(vectorLayer);
|
|
1826
|
-
return vectorLayer;
|
|
1827
|
-
};
|
|
1828
|
-
|
|
1829
|
-
//#endregion
|
|
1830
|
-
export { GeometryType, TDT_VECTOR_SOURCE_ID, TDT_XOR_BYTE_INDEXES, TdtDecoderWorkerClient, activeFeatureByUid, addFeaturesByLayer, addMassMarksLayer, appendFeaturesByLayer, baseLayers, buildMassMarksStyles, changeLayer, changeSource, changeUnselectStyle, createAnimationMarker, createBaseLayer, createBusinessLayer, createClusterLayer, createDrawInteraction, createElasticMarkerLayer, createGeoJsonLayer, createIconStyle, createLayerSelectByNames, createMap, createMapInstance, createMapWorldLayer, createMarker, createMarkersLayer, createMassMarksLayer, createMoveMarkerLayer, createOverlay, createSelectByLayer, createTdtVectorSource, createTdtVtsUrl, createText, createVectorFaceLayer, createVectorLineLayer, createWmtsLayer, createZheJiangLayer, decodeMvt, decodeTdtPk, disposeInstance, disposeMap, emitter, encodeStandardMvt, encodeTdtPk, flyToAnimate, getConstantBackgroundColor, getDrawStyle, getLayerByName, getLayersDataList, getMapInstance, instance, loadAndNormalizeTdtVts, loadTdtStyleBundle, mergeTdtStyleLayers, normalizePrivateGeometry, normalizePrivateMvt, privateTypeToStandard, redactTdtToken, removeAllLayers, removeLayerByName, removeOverlays, resetActiveFeature, resetActiveVector, resetMap, resetZoomAndCenter, resolveFeatureProperties, restoreTdtBytes, setFitViewByCoords, setFitViewByGeom, setFitViewByLayer, updateMarksLayersByStatus };
|
|
1
|
+
import{TdtDecoderWorkerClient as e,createTdtVtsUrl as t,redactTdtToken as n}from"tdt-vts-core";import r from"ol/format/MVT.js";import i from"ol/layer/VectorTile.js";import a from"ol/TileState.js";import o from"ol/source/VectorTile.js";import s from"mitt";import{WMTS as c,XYZ as l}from"ol/source.js";import u from"ol/layer/Tile.js";import{GeoJSON as d,WMTSCapabilities as f}from"ol/format.js";import{optionsFromCapabilities as p}from"ol/source/WMTS.js";import{TileGrid as m}from"ol/tilegrid.js";import{Tile as h}from"ol/layer.js";import g from"ol/tilegrid/WMTS.js";import{unByKey as _}from"ol/Observable.js";import v from"ol/layer/Image.js";import ee from"ol/source/Raster.js";import te from"ol/Map.js";import ne from"ol/View.js";import{defaults as re}from"ol/control.js";import y from"gcoord";import{Feature as b,Overlay as ie}from"ol";import{Select as x,defaults as ae}from"ol/interaction.js";import{boundingExtent as oe,getCenter as se}from"ol/extent.js";import{singleClick as ce}from"ol/events/condition.js";import le from"ol/Feature.js";import ue from"ol/geom/Point.js";import S from"ol/layer/Vector.js";import C from"ol/source/Vector.js";import{Circle as de,Fill as w,Icon as T,RegularShape as fe,Stroke as E,Style as D,Text as O}from"ol/style.js";import pe from"ol/interaction/Draw.js";import{getArea as me,getLength as he}from"ol/sphere.js";import ge from"ol/format/GeoJSON.js";import{Point as k}from"ol/geom.js";import _e from"ol/source/Cluster.js";import ve from"ol/style/Circle.js";export*from"tdt-vts-core";let ye=function(e){return e.LineString=`LineString`,e.MultiLineString=`MultiLineString`,e.Polygon=`Polygon`,e.MultiPolygon=`MultiPolygon`,e.Invalid=`Invalid`,e}({});const be=async e=>{if(typeof e!=`string`)return e;if(!e.trim())throw TypeError(`style 地址不能为空`);let t=await fetch(e);if(!t.ok)throw Error(`加载 style 失败:HTTP ${t.status}`);return await t.json()},A=i=>{if(!i.hosts?.length||i.hosts.some(e=>!e.trim()))throw TypeError(`hosts 不能为空`);let s=new e(i.restoreMode,i.decoderOptions),c=new r({layerName:`mvt:layer`}),l=new o({projection:`EPSG:3857`,minZoom:i.minZoom??1,maxZoom:i.maxZoom??18,tileSize:512,wrapX:!0,format:c,attributions:`© <a href="https://www.tianditu.gov.cn/" target="_blank" rel="noopener">天地图</a>`,tileUrlFunction(e){if(!e)return;let[n,r,a]=e;if(n!==void 0&&r!==void 0&&a!==void 0)return t([n,r,a],{token:i.token,hosts:i.hosts,maxZoom:i.maxZoom??18,wrapX:!0})},tileLoadFunction(e,t){let r=e;r.setLoader(async(e,o,l)=>{let u=n(t);try{let n=await s.decode(t),a=c.readFeatures(n.buffer,{extent:e,featureProjection:l});r.setFeatures(a),i.diagnostics?.onDecoded?.(n.stats,u)}catch(e){let t=e instanceof Error?e:Error(String(e));i.diagnostics?.onError?.(t,u),console.error(`天地图私有 VTS 加载失败`,u,t),r.setState(a.ERROR)}})}}),u=l.dispose.bind(l);return l.dispose=()=>{s.dispose(),u()},{source:l,decoder:s}},xe=async e=>{let t=await be(e.style);if(t?.version!==8||!t.sources||!Array.isArray(t.layers))throw TypeError(`style 必须是 Style v8 对象或其 JSON 地址`);let{source:n}=A({token:e.token,hosts:e.hosts,restoreMode:`auto`}),r=new i({declutter:!0,source:n}),a=r.dispose.bind(r);r.dispose=()=>{n.dispose(),a()};try{let{applyBackground:e,applyStyle:n}=await import(`ol-mapbox-style`),i=structuredClone(t);for(let e of i.layers)e.type===`symbol`&&e.layout?.[`text-font`]&&(e.layout[`text-font`]=[`sans-serif`]);return await Promise.all([n(r,i,{projection:`EPSG:4326`,source:`base-tdt-vector-tile`,updateSource:!1}),e(r,i)]),r}catch(e){throw r.dispose(),e}},j=s();let M;const Se=e=>{M=e},Ce=()=>{M=void 0},N=(e,t)=>{if(e.startsWith(`#`)){let n=e.replace(`#`,``);return n.length===3&&(n=n[0]+n[0]+n[1]+n[1]+n[2]+n[2]),`rgba(${parseInt(n.substring(0,2),16)}, ${parseInt(n.substring(2,4),16)}, ${parseInt(n.substring(4,6),16)}, ${t})`}return e},we=()=>M?.getAllLayers().map(e=>e.get(`_dataList`)||[]).flat(2)||[],Te=e=>{let t=e=>!Array.isArray(e)||e.length===0?0:1+t(e[0]),n=e=>{if(!Array.isArray(e)||e.length<3)return!1;let t=e[0],n=e[e.length-1];return t[0]===n[0]&&t[1]===n[1]},r=e=>Array.isArray(e)&&e.length>=2&&typeof e[0]==`number`&&typeof e[1]==`number`,i=e=>Array.isArray(e)&&e.length>=2&&e.every(r),a=e=>Array.isArray(e)?(t(e)===2?[e]:e).every(e=>Array.isArray(e)&&e.length>=4&&n(e)&&e.every(r)):!1,o=t(e);try{return o<2||!e||e.length===0?`Invalid`:o===2&&i(e)?n(e)&&e.length>=4?`Polygon`:`LineString`:o===3&&Array.isArray(e)&&e.every(i)?a(e)?`Polygon`:e.every(e=>!n(e))?`MultiLineString`:`Invalid`:o===4&&Array.isArray(e)&&e.every(e=>a(e))?`MultiPolygon`:`Invalid`}catch(e){return console.error(`Error analyzing coordinates:`,e),`Invalid`}},Ee=new f,P=new Map,De=async e=>{let t=P.get(e);if(t)return t;let n=await fetch(e);if(!n.ok)throw Error(`WMTS capabilities 请求失败:${n.status}`);let r=Ee.read(await n.text());return P.set(e,r),r},Oe=e=>async t=>{let{style:n,format:r,version:i,layerName:a,matrixSet:o}=t,[s,l]=a.split(`:`),d=`${e}/${s}/${l}/gwc/service/wmts?request=GetCapabilities`,f=await De(d),m=p(f,{style:n||``,layer:l,version:i||`1.1.0`,format:r||`image/png`,matrixSet:o||`EPSG:4326`});if(!m)throw Error(`Unable to create TileLayer`);return new u({zIndex:3,className:a,source:new c(m)})},F=`c77eddc667c5bed016fded560baf93e7`,I=(e,t)=>{let n=t.get(`_selectInteraction`);n&&(e.removeInteraction(n),n.dispose(),t.unset(`_selectInteraction`));let r=t.get(`_resolutionChangeKey`);r&&(_(r),t.unset(`_resolutionChangeKey`));let i=t.get(`_dynamicMarkerTimer`);i!==void 0&&(clearTimeout(i),t.unset(`_dynamicMarkerTimer`)),t.getSource()?.dispose(),e.removeLayer(t),t.dispose()},L=(e=`hzsyvector_dark`)=>{let t={zIndex:-5,preload:1,minZoom:8,maxZoom:21,source:new l({tileGrid:new m({tileSize:256,resolutions:[.00549933137253187,.00274966568626595,.00137483284313297,.000687416421566485,.000343708210783242,.000171854105391621,859270526958104e-19,429635263479052e-19,214817631739526e-19,107408815869763e-19,537044079348816e-20,268522039674407e-20,134261019837204e-20],origin:[118.122911693886,31.2869311022836],extent:[116.43263049006,28.6201742126026,123.705594256347,31.9765041681924]}),projection:`EPSG:4326`,urls:[`https://ywtg.citybrain.hangzhou.gov.cn/${e}/{z}/{y}/{x}`,`https://ywtg.citybrain.hangzhou.gov.cn:9456/${e}/{z}/{y}/{x}`,`https://ywtg.citybrain.hangzhou.gov.cn:9457/${e}/{z}/{y}/{x}`,`https://ywtg.citybrain.hangzhou.gov.cn:9458/${e}/{z}/{y}/{x}`,`https://ywtg.citybrain.hangzhou.gov.cn:9459/${e}/{z}/{y}/{x}`]}),properties:{_isBaseLayer:!0,_layerName:e}};return new h(t)},ke=(e,t=F)=>{let[n,r]=e.split(`_`),i={preload:1,zIndex:-10,cacheSize:20240,properties:{_isBaseLayer:!0,_layerName:e},source:new c({crossOrigin:`anonymous`,projection:`EPSG:4326`,wrapX:!0,layer:n,style:`default`,version:`1.0.0`,format:`image/png`,matrixSet:r,tileGrid:new g({tileSize:256,origin:[-180,90],matrixIds:[`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`10`,`11`,`12`,`13`,`14`,`15`,`16`,`17`,`18`,`19`],resolutions:[.703125,.3515625,.17578125,.087890625,.0439453125,.02197265625,.010986328125,.0054931640625,.00274658203125,.001373291015625,.0006866455078125,.00034332275390625,.000171661376953125,858306884765625e-19,4291534423828125e-20,21457672119140625e-21,10728836059570312e-21,5364418029785156e-21,2682209014892578e-21]}),url:`https://t{0-7}.tianditu.gov.cn/${e}/wmts?tk=${t}`})};return new h(i)},Ae=e=>({baseImgLayer:new h({className:`img-layer`,source:new l({crossOrigin:`anonymous`,tileSize:256,url:`https://t{0-7}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${e}`}),zIndex:1}),baseCiaLayer:new h({className:`img-layer`,source:new l({crossOrigin:`anonymous`,tileSize:256,url:`https://t{0-7}.tianditu.gov.cn/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=${e}`}),zIndex:2}),baseVecLayer:new h({className:`vec-layer`,source:new l({crossOrigin:`anonymous`,tileSize:256,url:`https://t{0-7}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=${e}`}),zIndex:1}),baseCvaLayer:new h({className:`vec-layer`,source:new l({crossOrigin:`anonymous`,tileSize:256,url:`https://t{0-7}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${e}`}),zIndex:2})}),je=(e=`emap`,t=`3b0cdd02-c366-4363-b6de-081a9f12b364`)=>new h({preload:1,minZoom:4,maxZoom:20,zIndex:-10,cacheSize:20240,source:new l({wrapX:!0,projection:`EPSG:4326`,crossOrigin:`anonymous`,url:`https://ditu{0-6}.zjzwfw.gov.cn/services/wmts/${e}/default/oss/getTile/{z}/{y}/{x}?token=${t}`})}),Me=e=>M.getAllLayers().find(t=>(t.get(`_layerName`)||t.getClassName())===e),Ne=e=>{let t=Me(e);t&&I(M,t)},Pe=()=>Math.max(1,Math.min(4,globalThis.navigator?.hardwareConcurrency??2)),Fe=(e,t,n,r={})=>{let i=new ee({sources:[e],operationType:`image`,operation:(e,t)=>(((e,t)=>{let n=t.ratioArray,r=t.rgbColorArray;if(n&&r){let[t=0,i=0,a=0]=n;for(let n=0;n<e.length;n+=4){let o=e[n],s=e[n+1],c=e[n+2],l=o*t+s*i+c*a;e[n]=r[0]-l,e[n+1]=r[1]-l,e[n+2]=r[2]-l}}})(e[0].data,t),e[0]),threads:r.threads??Pe()});return i.on(`beforeoperations`,e=>{e.data.ratioArray=n,e.data.rgbColorArray=t}),i},Ie=(e,t,n,r={})=>new v({source:Fe(e,t,n,r)}),R=(e=``)=>{M&&M.getOverlays().getArray().filter(t=>t.get(e||`position`)).forEach(e=>M.removeOverlay(e))},Le=()=>R(`_isAnimationMarker`),Re=(e,t)=>{Le();let{width:n,height:r,lnglat:i,crsForm:a}=t,o=document.createElement(`div`),s=document.createElement(`img`);s.src=e,s.alt=``,n!==void 0&&(s.width=n),r!==void 0&&(s.height=r),o.append(s);let c=new ie({element:o,stopEvent:!1,positioning:`bottom-center`,position:y.transform([+i[0],+i[1]],a||y.WGS84,y.WGS84)});c.set(`_isAnimationMarker`,!0),M.addOverlay(c)},ze=(e={id:`popup-container`,positioning:`top-center`})=>new ie(Object.assign({element:document.getElementById(e.id)||void 0},e)),Be=()=>{R(),M.getAllLayers().forEach(e=>{e.get(`_selectInteraction`)?.getFeatures().clear()})},Ve=()=>{R(),M&&M.getAllLayers().forEach(e=>{e.get(`_isBaseLayer`)||I(M,e)})},He=()=>{R(),M.getAllLayers().filter(e=>e.get(`_isVectorLayer`)).forEach(e=>{let t=e.get(`_selectInteraction`);t&&(t.dispatchEvent({type:`select`,selected:[],mapBrowserEvent:void 0,deselected:t.getFeatures().getArray()}),t.getFeatures().clear())})},Ue=e=>{let t=M.getAllLayers().filter(e=>e.get(`_isVectorLayer`));if(t.length){let n=t.find(t=>t.get(`_uuidSet`).has(e)&&t.get(`_selectInteraction`));if(n){let t=(n.getSource()?.getFeatures()||[]).find(t=>t.get(`_uuid`)===e),r=n.get(`_selectInteraction`);if(r){let n=r.getFeatures().getArray();n.find(t=>t.get(`_uuid`)===e)||(r.dispatchEvent({type:`select`,deselected:n,selected:[t],mapBrowserEvent:void 0}),r.getFeatures().push(t))}}}};let z=[119.71512016971076,30.122490541963536];const We=(e={})=>{if(M)return M;let t=B(e);return Se(t),t},B=(e={})=>{let t=Object.assign({zoom:9.4,maxZoom:18,minZoom:9.4,center:z,baseLayer:`vec-layer`,projection:`EPSG:4326`,layers:[L()],constrainResolution:!1,smoothExtentConstraint:!1,tk:F},e||{}),n=new te({view:new ne(t),controls:re({zoom:!1,rotate:!1,attribution:!1}),interactions:ae({pinchRotate:!1,doubleClickZoom:!1,altShiftDragRotate:!1}),layers:t.layers||[L()]});z=t.center||z,n.activeBaseLayer=t.baseLayer;let r=e=>n.getAllLayers().find(t=>(t.get(`_layerName`)||t.getClassName())===e);n.getLayerByName=r,n.removeLayerByName=e=>{let t=r(e);t&&I(n,t)},n.changeLayersByNames=(e,t)=>{n.getAllLayers().filter(e=>t.includes(e.getClassName())).forEach(t=>t.setVisible(e?.includes(t.getClassName())))};let i,a;return n.on(`pointermove`,e=>{if(a=e.pixel,i!==void 0)return;let t=()=>{i=void 0,a&&(n.getTargetElement().style.cursor=n.hasFeatureAtPixel(a)?`pointer`:``)};i=typeof globalThis.requestAnimationFrame==`function`?globalThis.requestAnimationFrame(t):setTimeout(t,16)}),n.set(`_disposePointerMove`,()=>{i!==void 0&&(typeof globalThis.cancelAnimationFrame==`function`?globalThis.cancelAnimationFrame(i):clearTimeout(i),i=void 0)}),n},V=()=>{if(!M)return;let e=M;e.get(`_disposePointerMove`)?.(),e.unset(`_disposePointerMove`),e.getOverlays().forEach(t=>{e.removeOverlay(t)}),e.getInteractions().clear(),e.getAllLayers().forEach(t=>I(e,t)),e.setTarget(``),e.dispose(),Ce()},Ge=()=>V(),Ke=()=>M,H=(e=z)=>{M&&M.getView().animate({zoom:9.4,center:e,duration:1e3})},qe=(e={center:z,isClearAll:!0})=>{Ve(),H(e.center||z)},U={padding:[50,50,50,50],duration:1e3},Je=(e,t=M,n)=>{let r=t.getView();if(e.length===1)r.animate({zoom:14,duration:1e3,center:e[0],...n||{}});else{let t=oe(e);r.fit(t,{...U,...n||{}})}},Ye=(e,t=M,n)=>{t.getView().fit(e,{...U,...n||{}})},Xe=(e,t=M,n)=>{let r=e.getSource();if(!r||r.isEmpty())return;let i=r.getExtent();i&&t.getView().fit(i,{...U,...n||{}})},Ze=(e,t)=>{let n=M.getView(),r=n.getZoom();n.animate({center:e,duration:2e3}),t?n.animate({zoom:t-2,duration:1e3},{zoom:t,duration:1e3}):n.animate({zoom:r-2,duration:1e3},{zoom:r+2>12?12:r+2,duration:1e3})},W=e=>new O({text:e.text,offsetY:e.offsetY,offsetX:e.offsetX,justify:`center`,textAlign:`center`,font:`16px PingFangSC-Semibold`,fill:new w({color:`rgba(255, 255, 255, 0.8)`})}),G=(e,t=!1)=>new D({image:new T({scale:e.scale||1,rotation:e.rotation||0,anchor:e.anchor||[.5,1],src:t&&e.activeImg||e.img}),text:e.showText?W(e):void 0}),K=(e,t)=>{let n=new le({geometry:new ue([+e.lng,+e.lat]),data:Object.assign({},e||{},{layerName:t.layerName})});return n.setStyle(G(e)),n},q=(e,t)=>{let{layerName:n,visible:r=!0,zIndex:i=10}=t,a=new C;return e.forEach(e=>a.addFeature(K(e,t))),new S({zIndex:i,visible:r,source:a,className:n||`marker-layer`})},J=(e,t)=>{let n=t>=14?`largeImg`:t>=11?`smallImg`:`img`;e.forEach(e=>{let t=e.get(`data`);t.isSelected||e.setStyle(G({...t,img:t[n]},!1))})},Y=(e,t)=>{let n=e.get(`data`),r={...n,isSelected:t};e.set(`data`,r),n.isElastic?J([e],M.getView().getZoom()):e.setStyle(G(r,t))},Qe=e=>{e.forEach(e=>{Y(e,!1)}),e.clear()},$e=(e,t)=>{let n=e.map(e=>({...e,isElastic:!0})),r=q(n,t),i=r.getSource().getFeatures(),a=M.getView();return J(i,a.getZoom()),r.set(`_resolutionChangeKey`,a.on(`change:resolution`,()=>J(i,a.getZoom()))),r},et=(e,t)=>{let n=K(e,t),r=new S({className:t.layerName||`ol-layer`,source:new C({features:[n]})});return M.addLayer(r),{vectorLayer:r}},tt=(e,t)=>{let n=t.getProperties(),{activeStyle:r}=n;if(t.setStyle(r),j.emit(`selected-feature`,n),e.set(`_hasActive`,!0),e.get(`_isDynamic`)){let t=r.getImage(),i=e.get(`_dynamicMarkerTimer`);i!==void 0&&clearTimeout(i);let a=setTimeout(()=>{e.unset(`_dynamicMarkerTimer`),j.emit(`dynamic-marker`,Object.assign({},n.rawData||{},{lnglat:n.lnglat,style:n.style,crsForm:n.crsForm,width:t.getWidth(),height:t.getHeight()}))},1e3);e.set(`_dynamicMarkerTimer`,a)}if(e.get(`_isAutoFit`)){let e=M.getView();e.animate({duration:1e3,zoom:e.getZoom(),center:se(t.getGeometry().getExtent())})}},X=e=>{let t=new x({style:null,multi:!1,hitTolerance:10,layers:[e],condition:t=>{if(!ce(t))return!1;let n=!1;return M.forEachFeatureAtPixel(t.pixel,()=>{n=!0},{layerFilter:t=>t===e}),n}});return t.on(`select`,t=>{let{selected:n,deselected:r}=t;if(n.length){let t=n[0];if(e.get(`_isClusterLayer`)){let n=t.get(`features`);n.length===1&&tt(e,n[0])}else tt(e,t)}if(r.length){let t=r[0],n=t.getProperties();t.setStyle(n.unActiveStyle),e.set(`_hasActive`,!1)}}),M.addInteraction(t),e.set(`_selectInteraction`,t),t},nt=(e,t,n)=>{let r=new x({style:null,layers:t=>e.includes(t.getClassName())});return r.on(`select`,({deselected:e,selected:r})=>{if(e.length){let t=e[0];Y(t,!1),n?.(t)}if(r.length){let e=r[0];Y(e,!0),t?.(e)}}),r},rt=e=>{let t=me(e,{projection:`EPSG:4326`});return t>1e4?`${Math.round(t/1e6*100)/100} km\xB2`:`${Math.round(t*100)/100} m\xB2`},it=e=>{let t=he(e,{projection:`EPSG:4326`});return t>100?`${Math.round(t/1e3*100)/100} km`:`${Math.round(t*100)/100} m`},at=new D({fill:new w({color:`rgba(255, 255, 255, 0.2)`}),stroke:new E({color:`rgba(0, 0, 0, 0.5)`,lineDash:[10,10],width:2}),image:new de({radius:5,stroke:new E({color:`rgba(0, 0, 0, 0.7)`}),fill:new w({color:`rgba(255, 255, 255, 0.2)`})})}),ot=new WeakMap,st=e=>{let t=ot.get(e);if(t)return t;let n=new D({text:new O({font:`14px`,fill:new w({color:`rgba(255, 255, 255, 1)`}),backgroundFill:new w({color:`rgba(0, 0, 0, 0.7)`}),padding:[3,3,3,3],textBaseline:`bottom`,offsetY:-15}),image:new fe({radius:8,points:3,angle:Math.PI,displacement:[0,10],fill:new w({color:`rgba(0, 0, 0, 0.7)`})})});return ot.set(e,n),n},ct=(e,t=at)=>{let n=t?[t]:[],r=e.getGeometry(),i,a;if(r.getType()===`Polygon`?(i=r.getInteriorPoint(),a=rt(r)):r.getType()===`LineString`&&(i=new ue(r.getLastCoordinate()),a=it(r)),i&&a){let t=st(e);t.setGeometry(i),t.getText().setText(a),n.push(t)}return n},lt=(e=`Point`,t,n)=>new pe({source:t.getSource(),type:e,style:n?e=>ct(e,void 0):void 0}),ut=(e,t,n)=>{let{layerName:r}=n||{},i=Object.assign({className:r||`ol-layer`,source:new C({features:new ge().readFeatures(e)}),style:t||new D({stroke:new E({color:`red`,lineDash:[10],width:3}),fill:new w({color:`rgba(0, 0, 255, 0.1)`})})},n||{});return new S(i)},Z=(e,t,n,r)=>t&&n?.get(e[t])||r,dt=(e,t)=>{let{key:n,crsForm:r}=t,i=[];return e.forEach(e=>{try{let t=e[n],r=Array.isArray(t)?t:JSON.parse(t||`[]`);if(r.length){let t=e.geoType||Te(r);t!==`Invalid`&&i.push({type:`Feature`,properties:{...e},geometry:{type:t,coordinates:r}})}}catch(e){console.error(`创建线状数据失败:${e}`)}}),y.transform({type:`FeatureCollection`,features:i},r||y.WGS84,y.WGS84)},ft=(e,t)=>{let{isLine:n,colorMap:r,fillColor:i,colorKey:a,strokeColor:o,fillOpacity:s,strokeWeight:c,activeFillColor:l,activeFillOpacity:u,activeStrokeColor:d,activeStrokeWeight:f}=t||{},p=e.getProperties(),m=new D({stroke:new E({width:f??c??4,color:d||Z(p,a,r,o)})}),h=new D({stroke:new E({width:c??2,color:Z(p,a,r,o)})});return n||(m.setFill(new w({color:N(l||i||``,u??s??1)||N(Z(p,a,r,i)||``,s??1)})),h.setFill(new w({color:N(Z(p,a,r,i)||``,s??1)}))),e.setProperties({activeStyle:m,unActiveStyle:h}),h},Q=(e,t)=>{let n=dt(e,t),r=new d().readFeatures(n),i=r.map(e=>{let t={...e.getProperties()};return delete t.geometry,t});return r.forEach(e=>{e.setStyle(ft(e,t))}),{features:r,acceptedData:i}},$=(e,t)=>{let{isAutoFit:n,layerName:r,openClick:i}=t,{features:a,acceptedData:o}=Q(e,t),s=new S({zIndex:5,className:r||`ol-layer`,source:new C({features:a})});return i&&X(s),s.setProperties({_isCustom:!0,_isAutoFit:n,_isVectorLayer:!0,_vectorConfig:t,_layerName:r,_dataList:o,_uuidSet:new Set(o.map(e=>e._uuid))}),s},pt=(e,t,n)=>{let r=e.getSource(),{_vectorConfig:i}=e.getProperties()||{},{features:a,acceptedData:o}=Q(t,Object.assign({},i,{crsForm:n??i?.crsForm}));r.addFeatures(a);let s=new Set(e.get(`_uuidSet`)||[]);o.forEach(e=>s.add(e._uuid)),e.set(`_uuidSet`,s),e.set(`_dataList`,[...e.get(`_dataList`)||[],...o])},mt=(e=[],t)=>$(e,Object.assign({},t,{isLine:!1})),ht=(e=[],t)=>$(e,Object.assign({},t,{isLine:!0})),gt=e=>t=>{let{baseUrl:n,markerIcon:r,statusNum:i=5,activeScale:a=1.3,markerSize:o=[32,32]}=t,s=e(n),[c,l]=o,u=Array(i*2);for(let e=0;e<i;e++){let t=s(`${r}-${e}`,`.webp`);u[e]=new D({zIndex:88+e,image:new T({width:c,height:l,src:t,anchor:[.5,1]})});let n=s(`${r}-${e}-active`,`.webp`);u[e+i]=new D({zIndex:120,image:new T({width:c*a,height:l*a,anchor:[.5,1],src:n||t})})}return u},_t=(e,t=[])=>e.map(e=>{let{crsForm:n}=e,r=y.transform([+e.lnglat[0],+e.lnglat[1]],n||y.WGS84,y.WGS84),i=new b({geometry:new k(r)});return i.setProperties({...e,unActiveStyle:t[e.style],activeStyle:t[e.style+t.length/2]}),i.setStyle(t[e.style]),i}),vt=(e,t,n={})=>{let r=e.get(`_massMarksStyles`),i=e?.getSource(),{clear:a}=n,o=_t(t,r);a&&i?.clear(),i?.addFeatures(o);let s=[...a?[]:e.get(`_dataList`)||[],...t];e.set(`_dataList`,s),e.set(`_uuidSet`,new Set(s.map(e=>e._uuid)))},yt=(e,t=[])=>{let n=new S({zIndex:2e3,source:new C({features:_t(e,t)})});return n.setProperties({_isCustom:!0,_isVectorLayer:!0,_isMassMarksLayer:!0,_statusNum:t.length/2,_dataList:[...e],_uuidSet:new Set(e.map(e=>e._uuid)||[])}),n},bt=e=>(t=[],n,r=!0)=>{let{offset:i=[0,0],baseUrl:a,hasLine:o,hasFace:s,layerName:c,isDynamic:l,isAutoFit:u,statusNum:d,markerIcon:f,marksStyles:p,markerSize:m,activeScale:h,vectorConfig:g}=n,_=p||gt(e)({baseUrl:a,markerIcon:f,markerSize:m,statusNum:d||5,activeScale:h||1.3}),v=yt(t,_);if(r&&(v.setProperties({_offset:i,_isAutoFit:u}),X(v)),o){let e=$(t,Object.assign({},{key:`line`,isLine:!0,openClick:!1,strokeWeight:4,strokeColor:`rgba(29, 235, 255, 0.8)`},g||{}));M.addLayer(e)}if(s){let e=$(t,Object.assign({},{isLine:!1,key:`faceList`,strokeWeight:1,openClick:!1,fillColor:`#4247DF`,strokeStyle:`dashed`,strokeColor:`#4247DF`},g||{}));M.addLayer(e)}return v.setProperties({_isCustom:!0,_offset:i,_hasLine:o,_hasFace:s,className:c,_layerName:c,_isVectorLayer:!0,_isDynamic:l,_massMarksStyles:_}),M.addLayer(v),v},xt=(e=[])=>{let t=new D({}),n=new Set(e),r=[];return M.getAllLayers().filter(e=>e.get(`_isMassMarksLayer`)).forEach(e=>{e.getSource().getFeatures().forEach(e=>{let i=e.getProperties();n.has(i.status)?(r.push(i),e.setStyle(i.unActiveStyle)):e.setStyle(t)})}),r},St=(e,t)=>{let n=e.length,r={},{distance:i,minDistance:a,openClick:o,buildStyle:s,singleStyles:c,computedRadius:l,layerName:u=`cluster-layer`}=t,d=e=>(e/n)**(1/18);r[1]=c;let f=e=>{let t=e.get(`features`).length,n=r[t],i=l?l(t):d(t);if(n||(n=new D({image:new ve({radius:i*20,stroke:new E({width:2,color:`hsla(${180-i*180}, 100%, 40%, 1)`}),fill:new w({color:`hsla(${180-i*180}, 100%, 50%, 1)`})}),text:new O({text:t.toString(),fill:new w({color:`hsla(${180-i*180}, 100%, 10%, 1)`}),textBaseline:`middle`})}),r[t]=n),Array.isArray(n)){let t=e.get(`features`)[0].get(`styleIndex`)??0;return n[t]}return n},p=new S({zIndex:200,className:u,source:new _e({distance:i||80,minDistance:a||0,source:new C({features:e.map(e=>{let t=new b(new k(e.lnglat));t.setProperties(e);let n=e.style??0;return t.setProperties({unActiveStyle:c[n],activeStyle:c[n+c.length/2]}),t})})}),style:e=>s?s(e):f(e)});return p.setProperties({_isCustom:!0,_isVectorLayer:!0,_isClusterLayer:!0}),o&&X(p),p};export{ye as GeometryType,Ue as activeFeatureByUid,vt as addFeaturesByLayer,bt as addMassMarksLayer,pt as appendFeaturesByLayer,Ae as baseLayers,gt as buildMassMarksStyles,Ie as changeLayer,Fe as changeSource,Qe as changeUnselectStyle,Re as createAnimationMarker,L as createBaseLayer,$ as createBusinessLayer,St as createClusterLayer,lt as createDrawInteraction,$e as createElasticMarkerLayer,ut as createGeoJsonLayer,G as createIconStyle,nt as createLayerSelectByNames,B as createMap,We as createMapInstance,ke as createMapWorldLayer,K as createMarker,q as createMarkersLayer,yt as createMassMarksLayer,et as createMoveMarkerLayer,ze as createOverlay,X as createSelectByLayer,A as createTdtVectorSource,xe as createTdtVtsLayer,W as createText,mt as createVectorFaceLayer,ht as createVectorLineLayer,Oe as createWmtsLayer,je as createZheJiangLayer,Ge as disposeInstance,V as disposeMap,j as emitter,Ze as flyToAnimate,ct as getDrawStyle,Me as getLayerByName,we as getLayersDataList,Ke as getMapInstance,M as instance,Ve as removeAllLayers,Ne as removeLayerByName,R as removeOverlays,He as resetActiveFeature,Be as resetActiveVector,qe as resetMap,H as resetZoomAndCenter,Je as setFitViewByCoords,Ye as setFitViewByGeom,Xe as setFitViewByLayer,xt as updateMarksLayersByStatus};
|