@caoguo/maplibre-pipeline 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/burst/index.cjs +708 -0
  2. package/dist/burst/index.cjs.map +1 -0
  3. package/dist/burst/index.d.cts +74 -0
  4. package/dist/burst/index.d.ts +74 -0
  5. package/dist/burst/index.js +4 -0
  6. package/dist/burst/index.js.map +1 -0
  7. package/dist/burstClass-CGMjcAYy.d.cts +161 -0
  8. package/dist/burstClass-Cf2p71LE.d.ts +161 -0
  9. package/dist/chunk-CDEYJ67B.js +80 -0
  10. package/dist/chunk-CDEYJ67B.js.map +1 -0
  11. package/dist/chunk-GCDA7KJM.js +289 -0
  12. package/dist/chunk-GCDA7KJM.js.map +1 -0
  13. package/dist/chunk-GEJRE2OR.js +351 -0
  14. package/dist/chunk-GEJRE2OR.js.map +1 -0
  15. package/dist/chunk-HCT6NSNS.js +532 -0
  16. package/dist/chunk-HCT6NSNS.js.map +1 -0
  17. package/dist/chunk-HIRNJVCV.js +13 -0
  18. package/dist/chunk-HIRNJVCV.js.map +1 -0
  19. package/dist/chunk-J3AEVXT4.js +164 -0
  20. package/dist/chunk-J3AEVXT4.js.map +1 -0
  21. package/dist/chunk-QVY4WJLM.js +256 -0
  22. package/dist/chunk-QVY4WJLM.js.map +1 -0
  23. package/dist/chunk-VA2TQWL3.js +340 -0
  24. package/dist/chunk-VA2TQWL3.js.map +1 -0
  25. package/dist/chunk-YBLESEN4.js +133 -0
  26. package/dist/chunk-YBLESEN4.js.map +1 -0
  27. package/dist/chunk-YS2ICFVK.js +101 -0
  28. package/dist/chunk-YS2ICFVK.js.map +1 -0
  29. package/dist/graph/index.cjs +382 -0
  30. package/dist/graph/index.cjs.map +1 -0
  31. package/dist/graph/index.d.cts +211 -0
  32. package/dist/graph/index.d.ts +211 -0
  33. package/dist/graph/index.js +4 -0
  34. package/dist/graph/index.js.map +1 -0
  35. package/dist/health/index.cjs +348 -0
  36. package/dist/health/index.cjs.map +1 -0
  37. package/dist/health/index.d.cts +210 -0
  38. package/dist/health/index.d.ts +210 -0
  39. package/dist/health/index.js +3 -0
  40. package/dist/health/index.js.map +1 -0
  41. package/dist/index.cjs +2295 -0
  42. package/dist/index.cjs.map +1 -0
  43. package/dist/index.d.cts +10 -0
  44. package/dist/index.d.ts +10 -0
  45. package/dist/index.js +12 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/leakage/index.cjs +358 -0
  48. package/dist/leakage/index.cjs.map +1 -0
  49. package/dist/leakage/index.d.cts +212 -0
  50. package/dist/leakage/index.d.ts +212 -0
  51. package/dist/leakage/index.js +3 -0
  52. package/dist/leakage/index.js.map +1 -0
  53. package/dist/nlpg/index.cjs +136 -0
  54. package/dist/nlpg/index.cjs.map +1 -0
  55. package/dist/nlpg/index.d.cts +51 -0
  56. package/dist/nlpg/index.d.ts +51 -0
  57. package/dist/nlpg/index.js +3 -0
  58. package/dist/nlpg/index.js.map +1 -0
  59. package/dist/style/index.cjs +379 -0
  60. package/dist/style/index.cjs.map +1 -0
  61. package/dist/style/index.d.cts +119 -0
  62. package/dist/style/index.d.ts +119 -0
  63. package/dist/style/index.js +4 -0
  64. package/dist/style/index.js.map +1 -0
  65. package/dist/topology/index.cjs +317 -0
  66. package/dist/topology/index.cjs.map +1 -0
  67. package/dist/topology/index.d.cts +69 -0
  68. package/dist/topology/index.d.ts +69 -0
  69. package/dist/topology/index.js +4 -0
  70. package/dist/topology/index.js.map +1 -0
  71. package/dist/types/index.cjs +15 -0
  72. package/dist/types/index.cjs.map +1 -0
  73. package/dist/types/index.d.cts +214 -0
  74. package/dist/types/index.d.ts +214 -0
  75. package/dist/types/index.js +3 -0
  76. package/dist/types/index.js.map +1 -0
  77. package/package.json +81 -0
@@ -0,0 +1,708 @@
1
+ 'use strict';
2
+
3
+ // src/graph/adjacency.ts
4
+ function buildAdjacency(dataset) {
5
+ const adj = /* @__PURE__ */ new Map();
6
+ for (const node of dataset.nodes) {
7
+ if (!adj.has(node.id)) adj.set(node.id, []);
8
+ }
9
+ for (const pipe of dataset.pipes) {
10
+ const len = pipe.length ?? pipeLengthFromGeometry(pipe, dataset.nodes) ?? 0;
11
+ const fromList = adj.get(pipe.fromNode) ?? [];
12
+ fromList.push({
13
+ pipeId: pipe.id,
14
+ to: pipe.toNode,
15
+ length: len
16
+ });
17
+ adj.set(pipe.fromNode, fromList);
18
+ const toList = adj.get(pipe.toNode) ?? [];
19
+ toList.push({
20
+ pipeId: pipe.id,
21
+ to: pipe.fromNode,
22
+ length: len
23
+ });
24
+ adj.set(pipe.toNode, toList);
25
+ }
26
+ return adj;
27
+ }
28
+ function pipeLengthFromGeometry(pipe, nodes) {
29
+ if (pipe.length && pipe.length > 0) return pipe.length;
30
+ if (pipe.geometry && pipe.geometry.length >= 2) {
31
+ let total = 0;
32
+ for (let i = 1; i < pipe.geometry.length; i++) {
33
+ total += haversine(
34
+ pipe.geometry[i - 1][0],
35
+ pipe.geometry[i - 1][1],
36
+ pipe.geometry[i][0],
37
+ pipe.geometry[i][1]
38
+ );
39
+ }
40
+ return total;
41
+ }
42
+ const from = nodes.find((n) => n.id === pipe.fromNode);
43
+ const to = nodes.find((n) => n.id === pipe.toNode);
44
+ if (!from || !to) return 0;
45
+ return haversine(from.lng, from.lat, to.lng, to.lat);
46
+ }
47
+ function haversine(lng1, lat1, lng2, lat2) {
48
+ const R = 6371e3;
49
+ const toRad = (d) => d * Math.PI / 180;
50
+ const dLat = toRad(lat2 - lat1);
51
+ const dLng = toRad(lng2 - lng1);
52
+ const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) ** 2;
53
+ return 2 * R * Math.asin(Math.sqrt(a));
54
+ }
55
+
56
+ // src/graph/bfs.ts
57
+ function bfs(adj, start, opts = {}) {
58
+ const visited = [];
59
+ const parents = /* @__PURE__ */ new Map();
60
+ const visitedSet = /* @__PURE__ */ new Set();
61
+ if (!adj.has(start)) {
62
+ parents.set(start, null);
63
+ return { visited, parents, start };
64
+ }
65
+ if (opts.allowStart && !opts.allowStart(start)) {
66
+ return { visited: [], parents, start };
67
+ }
68
+ const queue = [{ id: start, depth: 0 }];
69
+ parents.set(start, null);
70
+ visitedSet.add(start);
71
+ visited.push(start);
72
+ let foundEnd;
73
+ while (queue.length > 0) {
74
+ const { id, depth } = queue.shift();
75
+ if (opts.until && opts.until(id)) {
76
+ foundEnd = id;
77
+ break;
78
+ }
79
+ if (opts.maxDepth !== void 0 && depth >= opts.maxDepth) continue;
80
+ const edges = adj.get(id) ?? [];
81
+ for (const e of edges) {
82
+ if (opts.allowEdge && !opts.allowEdge(e, id, e.to)) continue;
83
+ if (visitedSet.has(e.to)) continue;
84
+ visitedSet.add(e.to);
85
+ parents.set(e.to, id);
86
+ visited.push(e.to);
87
+ queue.push({ id: e.to, depth: depth + 1 });
88
+ }
89
+ }
90
+ return { visited, parents, start, end: foundEnd };
91
+ }
92
+
93
+ // src/graph/shortestPath.ts
94
+ function dijkstra(adj, start, end, opts = {}) {
95
+ if (!adj.has(start) || !adj.has(end)) {
96
+ return { distance: Infinity, path: [], found: false };
97
+ }
98
+ const weight = opts.weight ?? ((e) => e.length || 1);
99
+ const dist = /* @__PURE__ */ new Map();
100
+ const parents = /* @__PURE__ */ new Map();
101
+ const visited = /* @__PURE__ */ new Set();
102
+ const pq = [{ id: start, d: 0 }];
103
+ for (const n of adj.keys()) {
104
+ dist.set(n, Infinity);
105
+ parents.set(n, null);
106
+ }
107
+ dist.set(start, 0);
108
+ while (pq.length > 0) {
109
+ pq.sort((a, b) => a.d - b.d);
110
+ const cur2 = pq.shift();
111
+ if (visited.has(cur2.id)) continue;
112
+ visited.add(cur2.id);
113
+ if (cur2.id === end) break;
114
+ const edges = adj.get(cur2.id) ?? [];
115
+ for (const e of edges) {
116
+ if (visited.has(e.to)) continue;
117
+ const w = weight(e);
118
+ const alt = (dist.get(cur2.id) ?? Infinity) + w;
119
+ if (alt < (dist.get(e.to) ?? Infinity)) {
120
+ dist.set(e.to, alt);
121
+ parents.set(e.to, cur2.id);
122
+ pq.push({ id: e.to, d: alt });
123
+ }
124
+ }
125
+ if (opts.until && opts.until(cur2.id, cur2.d)) break;
126
+ }
127
+ if ((dist.get(end) ?? Infinity) === Infinity) {
128
+ return { distance: Infinity, path: [], found: false };
129
+ }
130
+ const path = [];
131
+ let cur = end;
132
+ while (cur) {
133
+ path.unshift(cur);
134
+ const p = parents.get(cur);
135
+ if (p === void 0) break;
136
+ cur = p;
137
+ }
138
+ return { distance: dist.get(end) ?? 0, path, found: true };
139
+ }
140
+
141
+ // src/graph/connectivity.ts
142
+ function findPaths(adj, start, end, opts = {}) {
143
+ const maxPaths = opts.maxPaths ?? 3;
144
+ const maxDepth = opts.maxDepth ?? 20;
145
+ const all = [];
146
+ const path = [];
147
+ const onPath = /* @__PURE__ */ new Set();
148
+ const dfsLocal = (cur) => {
149
+ if (all.length >= maxPaths) return;
150
+ if (cur === end) {
151
+ all.push([...path]);
152
+ return;
153
+ }
154
+ if (path.length >= maxDepth) return;
155
+ onPath.add(cur);
156
+ path.push(cur);
157
+ const edges = adj.get(cur) ?? [];
158
+ for (const e of edges) {
159
+ if (onPath.has(e.to)) continue;
160
+ dfsLocal(e.to);
161
+ if (all.length >= maxPaths) return;
162
+ }
163
+ path.pop();
164
+ onPath.delete(cur);
165
+ };
166
+ dfsLocal(start);
167
+ return all;
168
+ }
169
+
170
+ // src/burst/burstCore.ts
171
+ function simulateBurst(dataset, pipeId, opts = {}) {
172
+ const t0 = typeof performance !== "undefined" ? performance.now() : Date.now();
173
+ const scenario = opts.scenario ?? "gas";
174
+ const skipClosed = opts.skipClosedValves ?? true;
175
+ const maxAlt = opts.maxAlternatives ?? 2;
176
+ const pipe = dataset.pipes.find((p) => p.id === pipeId);
177
+ if (!pipe) {
178
+ throw new Error(`BurstSimulator: pipe '${pipeId}' not found in dataset`);
179
+ }
180
+ const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
181
+ const adj = buildAdjacency(dataset);
182
+ const upstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.fromNode, skipClosed);
183
+ const downstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.toNode, skipClosed);
184
+ const candidateClose = mergeCandidateValves(upstreamValves, downstreamValves);
185
+ const closedSet = new Set(candidateClose.map((v) => v.id));
186
+ const downstream = bfs(adj, pipe.toNode, {
187
+ allowEdge: (_e, _f, to) => {
188
+ if (closedSet.has(to)) return false;
189
+ return true;
190
+ },
191
+ maxDepth: 1e3
192
+ });
193
+ const affectedSet = new Set(downstream.visited);
194
+ affectedSet.add(pipe.fromNode);
195
+ const affectedNodes = [];
196
+ for (const id of affectedSet) {
197
+ const n = nodeById.get(id);
198
+ if (n) affectedNodes.push(n);
199
+ }
200
+ const affectedPipes = dataset.pipes.filter(
201
+ (p) => affectedSet.has(p.fromNode) || affectedSet.has(p.toNode)
202
+ );
203
+ const users = dataset.users ?? [];
204
+ const affectedUsers = users.filter((u) => {
205
+ if (!u.nodeId) return false;
206
+ return affectedSet.has(u.nodeId);
207
+ });
208
+ const importantUsers = affectedUsers.filter((u) => u.kind === "important");
209
+ const impactArea = computeImpactArea(affectedNodes, affectedPipes);
210
+ const valvePlan = buildValvePlan(
211
+ adj,
212
+ dataset,
213
+ pipe,
214
+ candidateClose,
215
+ affectedNodes,
216
+ maxAlt,
217
+ scenario
218
+ );
219
+ const durationMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - t0;
220
+ return {
221
+ pipe,
222
+ affectedNodes,
223
+ affectedPipes,
224
+ affectedUsers,
225
+ affectedUserCount: affectedUsers.length,
226
+ importantUsers,
227
+ impactArea,
228
+ valvePlan,
229
+ durationMs
230
+ };
231
+ }
232
+ function findValvesFromEndpoint(adj, pipe, dataset, startId, skipClosed) {
233
+ const faultPipeId = pipe.id;
234
+ const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
235
+ const out = [];
236
+ const endpoint = startId === pipe.fromNode ? "from" : "to";
237
+ const startNode = nodeById.get(startId);
238
+ if (!startNode) return [];
239
+ if (startNode.kind === "valve") {
240
+ if (!skipClosed || startNode.properties?.valveStatus !== "closed") {
241
+ out.push({ node: startNode, hops: 0, distance: 0, endpoint });
242
+ }
243
+ return out;
244
+ }
245
+ const visited = /* @__PURE__ */ new Set();
246
+ const queue = [
247
+ { id: startId, hops: 0, dist: 0 }
248
+ ];
249
+ while (queue.length > 0) {
250
+ const cur = queue.shift();
251
+ if (visited.has(cur.id)) continue;
252
+ visited.add(cur.id);
253
+ const n = nodeById.get(cur.id);
254
+ if (!n) continue;
255
+ if (n.kind === "valve" && (!skipClosed || n.properties?.valveStatus !== "closed")) {
256
+ out.push({ node: n, hops: cur.hops, distance: cur.dist, endpoint });
257
+ return out;
258
+ }
259
+ const edges = adj.get(cur.id) ?? [];
260
+ for (const e of edges) {
261
+ if (e.pipeId === faultPipeId) continue;
262
+ if (visited.has(e.to)) continue;
263
+ queue.push({
264
+ id: e.to,
265
+ hops: cur.hops + 1,
266
+ dist: cur.dist + (e.length || 0)
267
+ });
268
+ }
269
+ }
270
+ return out;
271
+ }
272
+ function mergeCandidateValves(upstream, downstream) {
273
+ const seen = /* @__PURE__ */ new Set();
274
+ const all = [...upstream, ...downstream].sort((a, b) => a.distance - b.distance);
275
+ const out = [];
276
+ for (const c of all) {
277
+ if (seen.has(c.node.id)) continue;
278
+ seen.add(c.node.id);
279
+ out.push(c.node);
280
+ }
281
+ return out;
282
+ }
283
+ function computeImpactArea(nodes, pipes) {
284
+ const features = [];
285
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
286
+ for (const n of nodes) {
287
+ features.push({
288
+ type: "Feature",
289
+ geometry: { type: "Point", coordinates: [n.lng, n.lat] },
290
+ properties: { kind: "node" }
291
+ });
292
+ }
293
+ for (const p of pipes) {
294
+ let coords = [];
295
+ if (p.geometry && p.geometry.length >= 2) {
296
+ coords = p.geometry;
297
+ } else {
298
+ const from = nodeById.get(p.fromNode);
299
+ const to = nodeById.get(p.toNode);
300
+ if (from && to) coords = [[from.lng, from.lat], [to.lng, to.lat]];
301
+ }
302
+ if (coords.length >= 2) {
303
+ features.push({
304
+ type: "Feature",
305
+ geometry: { type: "LineString", coordinates: coords },
306
+ properties: { kind: "pipe" }
307
+ });
308
+ }
309
+ }
310
+ const hull = convexHull(nodes.map((n) => [n.lng, n.lat]));
311
+ return { type: "FeatureCollection", features, hull };
312
+ }
313
+ function buildValvePlan(adj, dataset, pipe, closeValves, _affectedNodes, maxAlt, scenario) {
314
+ const closeSet = new Set(closeValves.map((v) => v.id));
315
+ const openValves = [];
316
+ for (const n of _affectedNodes) {
317
+ if (n.kind !== "valve") continue;
318
+ if (closeSet.has(n.id)) continue;
319
+ if (n.properties?.valveStatus !== "open") continue;
320
+ const from = dataset.nodes.find((m) => m.id === pipe.fromNode);
321
+ const to = dataset.nodes.find((m) => m.id === pipe.toNode);
322
+ if (!from || !to) continue;
323
+ const d1 = haversine(n.lng, n.lat, from.lng, from.lat);
324
+ const d2 = haversine(n.lng, n.lat, to.lng, to.lat);
325
+ if (Math.min(d1, d2) < 1e3) openValves.push(n);
326
+ }
327
+ const alternativePaths = [];
328
+ if (closeValves.length) {
329
+ const sources = dataset.nodes.filter((n) => n.kind === "source");
330
+ for (const s of sources) {
331
+ const r = dijkstra(adj, closeValves[0].id, s.id, {
332
+ weight: (e) => closeSet.has(e.to) ? Infinity : e.length || 1
333
+ });
334
+ if (r.found) {
335
+ const paths = findPaths(adj, closeValves[0].id, s.id, {
336
+ maxPaths: maxAlt,
337
+ maxDepth: 20
338
+ });
339
+ for (const p of paths) alternativePaths.push(p);
340
+ break;
341
+ }
342
+ }
343
+ }
344
+ const userCount = (dataset.users ?? []).filter(
345
+ (u) => _affectedNodes.some((n) => n.id === u.nodeId)
346
+ ).length;
347
+ const baseHours = userCount / 200;
348
+ const scenarioFactor = scenario === "water" ? 1.5 : scenario === "heating" ? 2 : 1;
349
+ const hours = Math.max(0.5, Math.min(8, baseHours * scenarioFactor));
350
+ const estimatedShutdownTime = hours >= 1 ? `${hours.toFixed(1)}h` : `${Math.round(hours * 60)}min`;
351
+ const summary = buildSummary(closeValves, openValves, userCount, scenario);
352
+ return {
353
+ closeValves,
354
+ openValves,
355
+ alternativePaths,
356
+ estimatedShutdownTime,
357
+ summary
358
+ };
359
+ }
360
+ function buildSummary(closeValves, openValves, userCount, scenario) {
361
+ const labels = {
362
+ gas: "\u505C\u6C14",
363
+ water: "\u505C\u6C34",
364
+ drainage: "\u6392\u6C34\u4E2D\u65AD",
365
+ heating: "\u505C\u70ED"
366
+ };
367
+ const label = labels[scenario] ?? "\u53D7\u5F71\u54CD";
368
+ const parts = [];
369
+ parts.push(`\u9884\u8BA1${label}${userCount}\u6237`);
370
+ if (closeValves.length) {
371
+ parts.push(`\u5173\u95ED\u9600\u95E8 ${closeValves.map((v) => v.properties?.code ?? v.id).join(", ")}`);
372
+ } else {
373
+ parts.push("\u672A\u627E\u5230\u4E0A\u6E38\u9694\u79BB\u9600\u95E8\uFF08\u5EFA\u8BAE\u4EBA\u5DE5\u6307\u5B9A\uFF09");
374
+ }
375
+ if (openValves.length) {
376
+ parts.push(`\u6253\u5F00\u9600\u95E8 ${openValves.map((v) => v.properties?.code ?? v.id).join(", ")}\uFF08\u6CC4\u538B\uFF09`);
377
+ }
378
+ return parts.join("\uFF1B");
379
+ }
380
+ function convexHull(points) {
381
+ if (points.length < 3) return [...points];
382
+ const pts = [...points];
383
+ pts.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
384
+ const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
385
+ const lower = [];
386
+ for (const p of pts) {
387
+ while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
388
+ lower.pop();
389
+ }
390
+ lower.push(p);
391
+ }
392
+ const upper = [];
393
+ for (let i = pts.length - 1; i >= 0; i--) {
394
+ const p = pts[i];
395
+ while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
396
+ upper.pop();
397
+ }
398
+ upper.push(p);
399
+ }
400
+ upper.pop();
401
+ lower.pop();
402
+ return lower.concat(upper);
403
+ }
404
+ function userSeverity(kind, scale = 1) {
405
+ const base = {
406
+ important: 100,
407
+ industrial: 50,
408
+ commercial: 20,
409
+ residential: 1
410
+ };
411
+ return (base[kind] ?? 1) * (scale ?? 1);
412
+ }
413
+
414
+ // src/burst/valvePlanner.ts
415
+ function listCandidateValves(dataset, pipeId, filter) {
416
+ const pipe = dataset.pipes.find((p) => p.id === pipeId);
417
+ if (!pipe) return [];
418
+ const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
419
+ const adj = buildAdjacency(dataset);
420
+ const visited = /* @__PURE__ */ new Set();
421
+ const valveCandidates = [];
422
+ for (const startId of [pipe.fromNode, pipe.toNode]) {
423
+ const queue = [{ id: startId, d: 0 }];
424
+ while (queue.length > 0) {
425
+ const { id, d } = queue.shift();
426
+ if (visited.has(id)) continue;
427
+ visited.add(id);
428
+ const n = nodeById.get(id);
429
+ if (!n) continue;
430
+ if (n.kind === "valve" && (!filter || filter(n))) {
431
+ valveCandidates.push({ n, dist: d });
432
+ if (d > 0) continue;
433
+ }
434
+ const edges = adj.get(id) ?? [];
435
+ for (const e of edges) {
436
+ if (e.pipeId === pipeId) continue;
437
+ if (visited.has(e.to)) continue;
438
+ queue.push({ id: e.to, d: d + 1 });
439
+ }
440
+ }
441
+ }
442
+ valveCandidates.sort((a, b) => a.dist - b.dist);
443
+ return valveCandidates.map((v) => v.n);
444
+ }
445
+ function evaluateValvePlan(dataset, pipeId, valvesToClose) {
446
+ const pipe = dataset.pipes.find((p) => p.id === pipeId);
447
+ if (!pipe) {
448
+ return {
449
+ valves: valvesToClose,
450
+ isolationCompleteness: Infinity,
451
+ impactUserCount: 0,
452
+ operationOrder: []
453
+ };
454
+ }
455
+ const adj = buildAdjacency(dataset);
456
+ const closedSet = new Set(valvesToClose.map((v) => v.id));
457
+ new Map(dataset.pipes.map((p) => [p.id, p]));
458
+ const affectedNodes = bfs(adj, pipe.toNode, {
459
+ allowEdge: (_e, _f, to) => !closedSet.has(to),
460
+ maxDepth: 1e3
461
+ }).visited;
462
+ const users = (dataset.users ?? []).filter((u) => {
463
+ if (!u.nodeId) return false;
464
+ return affectedNodes.includes(u.nodeId);
465
+ });
466
+ const sources = dataset.nodes.filter((n) => n.kind === "source");
467
+ let completeness = Infinity;
468
+ for (const s of sources) {
469
+ const rTo = dijkstra(adj, s.id, pipe.toNode, {
470
+ weight: (e) => closedSet.has(e.to) ? Infinity : e.length || 1
471
+ });
472
+ const rFrom = dijkstra(adj, s.id, pipe.fromNode, {
473
+ weight: (e) => closedSet.has(e.to) ? Infinity : e.length || 1
474
+ });
475
+ const reachable = rTo.found ? rTo.distance : Infinity;
476
+ const reachableFrom = rFrom.found ? rFrom.distance : Infinity;
477
+ const nearest = Math.min(reachable, reachableFrom);
478
+ if (nearest < completeness) completeness = nearest;
479
+ }
480
+ const order = valvesToClose.map((v) => v.properties?.code ?? v.id).sort();
481
+ return {
482
+ valves: valvesToClose,
483
+ isolationCompleteness: completeness,
484
+ impactUserCount: users.length,
485
+ operationOrder: order
486
+ };
487
+ }
488
+
489
+ // src/burst/impactArea.ts
490
+ function toMapLayerData(result) {
491
+ const hull = result.impactArea.hull;
492
+ const hullPolygon = hull.length >= 3 ? {
493
+ type: "Feature",
494
+ geometry: { type: "Polygon", coordinates: [hull] },
495
+ properties: { score: 1, affected: result.affectedUserCount }
496
+ } : null;
497
+ const nodeCoords = /* @__PURE__ */ new Map();
498
+ for (const n of result.affectedNodes) nodeCoords.set(n.id, n);
499
+ const affectedPipes = {
500
+ type: "FeatureCollection",
501
+ features: result.affectedPipes.map((p) => ({
502
+ type: "Feature",
503
+ geometry: pipeToLineString(p, nodeCoords),
504
+ properties: { pipeId: p.id, scenario: "affected" }
505
+ })).filter((f) => f.geometry.coordinates.length >= 2)
506
+ };
507
+ const affectedNodes = {
508
+ type: "FeatureCollection",
509
+ features: result.affectedNodes.map((n) => ({
510
+ type: "Feature",
511
+ geometry: { type: "Point", coordinates: [n.lng, n.lat] },
512
+ properties: { nodeId: n.id, kind: n.kind }
513
+ }))
514
+ };
515
+ return { hullPolygon, affectedPipes, affectedNodes };
516
+ }
517
+ function pipeToLineString(p, nodes) {
518
+ if (p.geometry && p.geometry.length >= 2) {
519
+ return { type: "LineString", coordinates: p.geometry };
520
+ }
521
+ const from = nodes.get(p.fromNode);
522
+ const to = nodes.get(p.toNode);
523
+ if (from && to) {
524
+ return {
525
+ type: "LineString",
526
+ coordinates: [
527
+ [from.lng, from.lat],
528
+ [to.lng, to.lat]
529
+ ]
530
+ };
531
+ }
532
+ return { type: "LineString", coordinates: [] };
533
+ }
534
+ function impactAreaSquareMeters(hull) {
535
+ if (hull.length < 3) return 0;
536
+ const refLat = hull[0][1];
537
+ const mPerDegLat = 110540;
538
+ const mPerDegLng = 111320 * Math.cos(refLat * Math.PI / 180);
539
+ const pts = hull.map(([lng, lat]) => [
540
+ (lng - hull[0][0]) * mPerDegLng,
541
+ (lat - hull[0][1]) * mPerDegLat
542
+ ]);
543
+ let sum = 0;
544
+ for (let i = 0; i < pts.length; i++) {
545
+ const [x1, y1] = pts[i];
546
+ const [x2, y2] = pts[(i + 1) % pts.length];
547
+ sum += x1 * y2 - x2 * y1;
548
+ }
549
+ return Math.abs(sum) / 2;
550
+ }
551
+ function impactCenter(nodes) {
552
+ if (!nodes.length) return null;
553
+ let lng = 0;
554
+ let lat = 0;
555
+ for (const n of nodes) {
556
+ lng += n.lng;
557
+ lat += n.lat;
558
+ }
559
+ return [lng / nodes.length, lat / nodes.length];
560
+ }
561
+ function nearestNodeOnPipe(pipe, nodes, lng, lat) {
562
+ const a = nodes.find((n) => n.id === pipe.fromNode);
563
+ const b = nodes.find((n) => n.id === pipe.toNode);
564
+ if (!a || !b) return null;
565
+ const da = haversineApprox(a.lng, a.lat, lng, lat);
566
+ const db = haversineApprox(b.lng, b.lat, lng, lat);
567
+ return da < db ? a : b;
568
+ }
569
+ function haversineApprox(lng1, lat1, lng2, lat2) {
570
+ const dx = (lng2 - lng1) * 111320 * Math.cos(lat1 * Math.PI / 180);
571
+ const dy = (lat2 - lat1) * 110540;
572
+ return Math.sqrt(dx * dx + dy * dy);
573
+ }
574
+
575
+ // src/burst/burstClass.ts
576
+ var BurstSimulator = class {
577
+ map;
578
+ dataset;
579
+ scenario;
580
+ affectedPipePaint;
581
+ affectedNodePaint;
582
+ valvePaint;
583
+ hullPaint;
584
+ layerPrefix;
585
+ listeners = /* @__PURE__ */ new Set();
586
+ lastResult = null;
587
+ layerIds = [];
588
+ constructor(options) {
589
+ this.map = options.map;
590
+ this.dataset = options.dataset;
591
+ this.scenario = options.scenario ?? "gas";
592
+ this.affectedPipePaint = options.affectedPipePaint ?? {
593
+ "line-color": "#ef4444",
594
+ "line-width": 4,
595
+ "line-opacity": 0.85,
596
+ "line-blur": 1.5
597
+ };
598
+ this.affectedNodePaint = options.affectedNodePaint ?? {
599
+ "circle-radius": 6,
600
+ "circle-color": "#ef4444",
601
+ "circle-stroke-width": 2,
602
+ "circle-stroke-color": "#ffffff"
603
+ };
604
+ this.valvePaint = options.valvePaint ?? {
605
+ "circle-radius": 8,
606
+ "circle-color": "#fbbf24",
607
+ "circle-stroke-width": 2,
608
+ "circle-stroke-color": "#000000"
609
+ };
610
+ this.hullPaint = options.hullPaint ?? {
611
+ "fill-color": "#ef4444",
612
+ "fill-opacity": 0.15,
613
+ "fill-outline-color": "#ef4444"
614
+ };
615
+ this.layerPrefix = options.layerPrefix ?? "cg-burst";
616
+ }
617
+ /** 触发爆管推演 */
618
+ simulate(pipeId, overrideOpts) {
619
+ const result = simulateBurst(this.dataset, pipeId, {
620
+ scenario: overrideOpts?.scenario ?? this.scenario,
621
+ skipClosedValves: overrideOpts?.skipClosedValves ?? true,
622
+ maxAlternatives: overrideOpts?.maxAlternatives ?? 2
623
+ });
624
+ this.lastResult = result;
625
+ this.render(result);
626
+ for (const l of this.listeners) l(result);
627
+ return result;
628
+ }
629
+ /** 清空所有受影响区域图层 */
630
+ clear() {
631
+ for (const id of this.layerIds) {
632
+ this.map.removeLayer(id);
633
+ }
634
+ this.layerIds = [];
635
+ }
636
+ /** 销毁并清空所有资源 */
637
+ destroy() {
638
+ this.clear();
639
+ this.listeners.clear();
640
+ this.lastResult = null;
641
+ }
642
+ /** 订阅推演结果 */
643
+ onResult(fn) {
644
+ this.listeners.add(fn);
645
+ return () => this.listeners.delete(fn);
646
+ }
647
+ /** 取最后一次结果 */
648
+ getLastResult() {
649
+ return this.lastResult;
650
+ }
651
+ // ------------------------------------------------------
652
+ // 内部:把结果渲染为 MapLibre 图层
653
+ // ------------------------------------------------------
654
+ render(result) {
655
+ this.clear();
656
+ const layerData = toMapLayerData(result);
657
+ const mlMap = this.map.instance;
658
+ const prefix = this.layerPrefix;
659
+ const sourcesToAdd = [];
660
+ const layersToAdd = [];
661
+ if (layerData.hullPolygon) {
662
+ sourcesToAdd.push({ id: `${prefix}-hull-src`, data: layerData.hullPolygon });
663
+ layersToAdd.push({
664
+ id: `${prefix}-hull-fill`,
665
+ type: "fill",
666
+ source: `${prefix}-hull-src`,
667
+ paint: this.hullPaint
668
+ });
669
+ }
670
+ sourcesToAdd.push({ id: `${prefix}-pipes-src`, data: layerData.affectedPipes });
671
+ layersToAdd.push({
672
+ id: `${prefix}-pipes-line`,
673
+ type: "line",
674
+ source: `${prefix}-pipes-src`,
675
+ paint: this.affectedPipePaint
676
+ });
677
+ sourcesToAdd.push({ id: `${prefix}-nodes-src`, data: layerData.affectedNodes });
678
+ layersToAdd.push({
679
+ id: `${prefix}-nodes-pt`,
680
+ type: "circle",
681
+ source: `${prefix}-nodes-src`,
682
+ paint: this.affectedNodePaint
683
+ });
684
+ for (const src of sourcesToAdd) {
685
+ if (!mlMap.getSource(src.id)) mlMap.addSource(src.id, src.data);
686
+ }
687
+ for (const layer of layersToAdd) {
688
+ try {
689
+ mlMap.addLayer(layer);
690
+ this.layerIds.push(layer.id);
691
+ } catch {
692
+ }
693
+ }
694
+ }
695
+ };
696
+
697
+ exports.BurstSimulator = BurstSimulator;
698
+ exports.convexHull = convexHull;
699
+ exports.evaluateValvePlan = evaluateValvePlan;
700
+ exports.impactAreaSquareMeters = impactAreaSquareMeters;
701
+ exports.impactCenter = impactCenter;
702
+ exports.listCandidateValves = listCandidateValves;
703
+ exports.nearestNodeOnPipe = nearestNodeOnPipe;
704
+ exports.simulateBurst = simulateBurst;
705
+ exports.toMapLayerData = toMapLayerData;
706
+ exports.userSeverity = userSeverity;
707
+ //# sourceMappingURL=index.cjs.map
708
+ //# sourceMappingURL=index.cjs.map