@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.
- package/dist/burst/index.cjs +708 -0
- package/dist/burst/index.cjs.map +1 -0
- package/dist/burst/index.d.cts +74 -0
- package/dist/burst/index.d.ts +74 -0
- package/dist/burst/index.js +4 -0
- package/dist/burst/index.js.map +1 -0
- package/dist/burstClass-CGMjcAYy.d.cts +161 -0
- package/dist/burstClass-Cf2p71LE.d.ts +161 -0
- package/dist/chunk-CDEYJ67B.js +80 -0
- package/dist/chunk-CDEYJ67B.js.map +1 -0
- package/dist/chunk-GCDA7KJM.js +289 -0
- package/dist/chunk-GCDA7KJM.js.map +1 -0
- package/dist/chunk-GEJRE2OR.js +351 -0
- package/dist/chunk-GEJRE2OR.js.map +1 -0
- package/dist/chunk-HCT6NSNS.js +532 -0
- package/dist/chunk-HCT6NSNS.js.map +1 -0
- package/dist/chunk-HIRNJVCV.js +13 -0
- package/dist/chunk-HIRNJVCV.js.map +1 -0
- package/dist/chunk-J3AEVXT4.js +164 -0
- package/dist/chunk-J3AEVXT4.js.map +1 -0
- package/dist/chunk-QVY4WJLM.js +256 -0
- package/dist/chunk-QVY4WJLM.js.map +1 -0
- package/dist/chunk-VA2TQWL3.js +340 -0
- package/dist/chunk-VA2TQWL3.js.map +1 -0
- package/dist/chunk-YBLESEN4.js +133 -0
- package/dist/chunk-YBLESEN4.js.map +1 -0
- package/dist/chunk-YS2ICFVK.js +101 -0
- package/dist/chunk-YS2ICFVK.js.map +1 -0
- package/dist/graph/index.cjs +382 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +211 -0
- package/dist/graph/index.d.ts +211 -0
- package/dist/graph/index.js +4 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/health/index.cjs +348 -0
- package/dist/health/index.cjs.map +1 -0
- package/dist/health/index.d.cts +210 -0
- package/dist/health/index.d.ts +210 -0
- package/dist/health/index.js +3 -0
- package/dist/health/index.js.map +1 -0
- package/dist/index.cjs +2295 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/leakage/index.cjs +358 -0
- package/dist/leakage/index.cjs.map +1 -0
- package/dist/leakage/index.d.cts +212 -0
- package/dist/leakage/index.d.ts +212 -0
- package/dist/leakage/index.js +3 -0
- package/dist/leakage/index.js.map +1 -0
- package/dist/nlpg/index.cjs +136 -0
- package/dist/nlpg/index.cjs.map +1 -0
- package/dist/nlpg/index.d.cts +51 -0
- package/dist/nlpg/index.d.ts +51 -0
- package/dist/nlpg/index.js +3 -0
- package/dist/nlpg/index.js.map +1 -0
- package/dist/style/index.cjs +379 -0
- package/dist/style/index.cjs.map +1 -0
- package/dist/style/index.d.cts +119 -0
- package/dist/style/index.d.ts +119 -0
- package/dist/style/index.js +4 -0
- package/dist/style/index.js.map +1 -0
- package/dist/topology/index.cjs +317 -0
- package/dist/topology/index.cjs.map +1 -0
- package/dist/topology/index.d.cts +69 -0
- package/dist/topology/index.d.ts +69 -0
- package/dist/topology/index.js +4 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/types/index.cjs +15 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +214 -0
- package/dist/types/index.d.ts +214 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { buildAdjacency, bfs, haversine, dijkstra, findPaths } from './chunk-GCDA7KJM.js';
|
|
2
|
+
|
|
3
|
+
// src/burst/burstCore.ts
|
|
4
|
+
function simulateBurst(dataset, pipeId, opts = {}) {
|
|
5
|
+
const t0 = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
6
|
+
const scenario = opts.scenario ?? "gas";
|
|
7
|
+
const skipClosed = opts.skipClosedValves ?? true;
|
|
8
|
+
const maxAlt = opts.maxAlternatives ?? 2;
|
|
9
|
+
const pipe = dataset.pipes.find((p) => p.id === pipeId);
|
|
10
|
+
if (!pipe) {
|
|
11
|
+
throw new Error(`BurstSimulator: pipe '${pipeId}' not found in dataset`);
|
|
12
|
+
}
|
|
13
|
+
const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
|
|
14
|
+
const adj = buildAdjacency(dataset);
|
|
15
|
+
const upstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.fromNode, skipClosed);
|
|
16
|
+
const downstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.toNode, skipClosed);
|
|
17
|
+
const candidateClose = mergeCandidateValves(upstreamValves, downstreamValves);
|
|
18
|
+
const closedSet = new Set(candidateClose.map((v) => v.id));
|
|
19
|
+
const downstream = bfs(adj, pipe.toNode, {
|
|
20
|
+
allowEdge: (_e, _f, to) => {
|
|
21
|
+
if (closedSet.has(to)) return false;
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
maxDepth: 1e3
|
|
25
|
+
});
|
|
26
|
+
const affectedSet = new Set(downstream.visited);
|
|
27
|
+
affectedSet.add(pipe.fromNode);
|
|
28
|
+
const affectedNodes = [];
|
|
29
|
+
for (const id of affectedSet) {
|
|
30
|
+
const n = nodeById.get(id);
|
|
31
|
+
if (n) affectedNodes.push(n);
|
|
32
|
+
}
|
|
33
|
+
const affectedPipes = dataset.pipes.filter(
|
|
34
|
+
(p) => affectedSet.has(p.fromNode) || affectedSet.has(p.toNode)
|
|
35
|
+
);
|
|
36
|
+
const users = dataset.users ?? [];
|
|
37
|
+
const affectedUsers = users.filter((u) => {
|
|
38
|
+
if (!u.nodeId) return false;
|
|
39
|
+
return affectedSet.has(u.nodeId);
|
|
40
|
+
});
|
|
41
|
+
const importantUsers = affectedUsers.filter((u) => u.kind === "important");
|
|
42
|
+
const impactArea = computeImpactArea(affectedNodes, affectedPipes);
|
|
43
|
+
const valvePlan = buildValvePlan(
|
|
44
|
+
adj,
|
|
45
|
+
dataset,
|
|
46
|
+
pipe,
|
|
47
|
+
candidateClose,
|
|
48
|
+
affectedNodes,
|
|
49
|
+
maxAlt,
|
|
50
|
+
scenario
|
|
51
|
+
);
|
|
52
|
+
const durationMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - t0;
|
|
53
|
+
return {
|
|
54
|
+
pipe,
|
|
55
|
+
affectedNodes,
|
|
56
|
+
affectedPipes,
|
|
57
|
+
affectedUsers,
|
|
58
|
+
affectedUserCount: affectedUsers.length,
|
|
59
|
+
importantUsers,
|
|
60
|
+
impactArea,
|
|
61
|
+
valvePlan,
|
|
62
|
+
durationMs
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function findValvesFromEndpoint(adj, pipe, dataset, startId, skipClosed) {
|
|
66
|
+
const faultPipeId = pipe.id;
|
|
67
|
+
const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
|
|
68
|
+
const out = [];
|
|
69
|
+
const endpoint = startId === pipe.fromNode ? "from" : "to";
|
|
70
|
+
const startNode = nodeById.get(startId);
|
|
71
|
+
if (!startNode) return [];
|
|
72
|
+
if (startNode.kind === "valve") {
|
|
73
|
+
if (!skipClosed || startNode.properties?.valveStatus !== "closed") {
|
|
74
|
+
out.push({ node: startNode, hops: 0, distance: 0, endpoint });
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
const visited = /* @__PURE__ */ new Set();
|
|
79
|
+
const queue = [
|
|
80
|
+
{ id: startId, hops: 0, dist: 0 }
|
|
81
|
+
];
|
|
82
|
+
while (queue.length > 0) {
|
|
83
|
+
const cur = queue.shift();
|
|
84
|
+
if (visited.has(cur.id)) continue;
|
|
85
|
+
visited.add(cur.id);
|
|
86
|
+
const n = nodeById.get(cur.id);
|
|
87
|
+
if (!n) continue;
|
|
88
|
+
if (n.kind === "valve" && (!skipClosed || n.properties?.valveStatus !== "closed")) {
|
|
89
|
+
out.push({ node: n, hops: cur.hops, distance: cur.dist, endpoint });
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
const edges = adj.get(cur.id) ?? [];
|
|
93
|
+
for (const e of edges) {
|
|
94
|
+
if (e.pipeId === faultPipeId) continue;
|
|
95
|
+
if (visited.has(e.to)) continue;
|
|
96
|
+
queue.push({
|
|
97
|
+
id: e.to,
|
|
98
|
+
hops: cur.hops + 1,
|
|
99
|
+
dist: cur.dist + (e.length || 0)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function mergeCandidateValves(upstream, downstream) {
|
|
106
|
+
const seen = /* @__PURE__ */ new Set();
|
|
107
|
+
const all = [...upstream, ...downstream].sort((a, b) => a.distance - b.distance);
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const c of all) {
|
|
110
|
+
if (seen.has(c.node.id)) continue;
|
|
111
|
+
seen.add(c.node.id);
|
|
112
|
+
out.push(c.node);
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function computeImpactArea(nodes, pipes) {
|
|
117
|
+
const features = [];
|
|
118
|
+
const nodeById = new Map(nodes.map((n) => [n.id, n]));
|
|
119
|
+
for (const n of nodes) {
|
|
120
|
+
features.push({
|
|
121
|
+
type: "Feature",
|
|
122
|
+
geometry: { type: "Point", coordinates: [n.lng, n.lat] },
|
|
123
|
+
properties: { kind: "node" }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
for (const p of pipes) {
|
|
127
|
+
let coords = [];
|
|
128
|
+
if (p.geometry && p.geometry.length >= 2) {
|
|
129
|
+
coords = p.geometry;
|
|
130
|
+
} else {
|
|
131
|
+
const from = nodeById.get(p.fromNode);
|
|
132
|
+
const to = nodeById.get(p.toNode);
|
|
133
|
+
if (from && to) coords = [[from.lng, from.lat], [to.lng, to.lat]];
|
|
134
|
+
}
|
|
135
|
+
if (coords.length >= 2) {
|
|
136
|
+
features.push({
|
|
137
|
+
type: "Feature",
|
|
138
|
+
geometry: { type: "LineString", coordinates: coords },
|
|
139
|
+
properties: { kind: "pipe" }
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const hull = convexHull(nodes.map((n) => [n.lng, n.lat]));
|
|
144
|
+
return { type: "FeatureCollection", features, hull };
|
|
145
|
+
}
|
|
146
|
+
function buildValvePlan(adj, dataset, pipe, closeValves, _affectedNodes, maxAlt, scenario) {
|
|
147
|
+
const closeSet = new Set(closeValves.map((v) => v.id));
|
|
148
|
+
const openValves = [];
|
|
149
|
+
for (const n of _affectedNodes) {
|
|
150
|
+
if (n.kind !== "valve") continue;
|
|
151
|
+
if (closeSet.has(n.id)) continue;
|
|
152
|
+
if (n.properties?.valveStatus !== "open") continue;
|
|
153
|
+
const from = dataset.nodes.find((m) => m.id === pipe.fromNode);
|
|
154
|
+
const to = dataset.nodes.find((m) => m.id === pipe.toNode);
|
|
155
|
+
if (!from || !to) continue;
|
|
156
|
+
const d1 = haversine(n.lng, n.lat, from.lng, from.lat);
|
|
157
|
+
const d2 = haversine(n.lng, n.lat, to.lng, to.lat);
|
|
158
|
+
if (Math.min(d1, d2) < 1e3) openValves.push(n);
|
|
159
|
+
}
|
|
160
|
+
const alternativePaths = [];
|
|
161
|
+
if (closeValves.length) {
|
|
162
|
+
const sources = dataset.nodes.filter((n) => n.kind === "source");
|
|
163
|
+
for (const s of sources) {
|
|
164
|
+
const r = dijkstra(adj, closeValves[0].id, s.id, {
|
|
165
|
+
weight: (e) => closeSet.has(e.to) ? Infinity : e.length || 1
|
|
166
|
+
});
|
|
167
|
+
if (r.found) {
|
|
168
|
+
const paths = findPaths(adj, closeValves[0].id, s.id, {
|
|
169
|
+
maxPaths: maxAlt,
|
|
170
|
+
maxDepth: 20
|
|
171
|
+
});
|
|
172
|
+
for (const p of paths) alternativePaths.push(p);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const userCount = (dataset.users ?? []).filter(
|
|
178
|
+
(u) => _affectedNodes.some((n) => n.id === u.nodeId)
|
|
179
|
+
).length;
|
|
180
|
+
const baseHours = userCount / 200;
|
|
181
|
+
const scenarioFactor = scenario === "water" ? 1.5 : scenario === "heating" ? 2 : 1;
|
|
182
|
+
const hours = Math.max(0.5, Math.min(8, baseHours * scenarioFactor));
|
|
183
|
+
const estimatedShutdownTime = hours >= 1 ? `${hours.toFixed(1)}h` : `${Math.round(hours * 60)}min`;
|
|
184
|
+
const summary = buildSummary(closeValves, openValves, userCount, scenario);
|
|
185
|
+
return {
|
|
186
|
+
closeValves,
|
|
187
|
+
openValves,
|
|
188
|
+
alternativePaths,
|
|
189
|
+
estimatedShutdownTime,
|
|
190
|
+
summary
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function buildSummary(closeValves, openValves, userCount, scenario) {
|
|
194
|
+
const labels = {
|
|
195
|
+
gas: "\u505C\u6C14",
|
|
196
|
+
water: "\u505C\u6C34",
|
|
197
|
+
drainage: "\u6392\u6C34\u4E2D\u65AD",
|
|
198
|
+
heating: "\u505C\u70ED"
|
|
199
|
+
};
|
|
200
|
+
const label = labels[scenario] ?? "\u53D7\u5F71\u54CD";
|
|
201
|
+
const parts = [];
|
|
202
|
+
parts.push(`\u9884\u8BA1${label}${userCount}\u6237`);
|
|
203
|
+
if (closeValves.length) {
|
|
204
|
+
parts.push(`\u5173\u95ED\u9600\u95E8 ${closeValves.map((v) => v.properties?.code ?? v.id).join(", ")}`);
|
|
205
|
+
} else {
|
|
206
|
+
parts.push("\u672A\u627E\u5230\u4E0A\u6E38\u9694\u79BB\u9600\u95E8\uFF08\u5EFA\u8BAE\u4EBA\u5DE5\u6307\u5B9A\uFF09");
|
|
207
|
+
}
|
|
208
|
+
if (openValves.length) {
|
|
209
|
+
parts.push(`\u6253\u5F00\u9600\u95E8 ${openValves.map((v) => v.properties?.code ?? v.id).join(", ")}\uFF08\u6CC4\u538B\uFF09`);
|
|
210
|
+
}
|
|
211
|
+
return parts.join("\uFF1B");
|
|
212
|
+
}
|
|
213
|
+
function convexHull(points) {
|
|
214
|
+
if (points.length < 3) return [...points];
|
|
215
|
+
const pts = [...points];
|
|
216
|
+
pts.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
217
|
+
const cross = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
218
|
+
const lower = [];
|
|
219
|
+
for (const p of pts) {
|
|
220
|
+
while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {
|
|
221
|
+
lower.pop();
|
|
222
|
+
}
|
|
223
|
+
lower.push(p);
|
|
224
|
+
}
|
|
225
|
+
const upper = [];
|
|
226
|
+
for (let i = pts.length - 1; i >= 0; i--) {
|
|
227
|
+
const p = pts[i];
|
|
228
|
+
while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {
|
|
229
|
+
upper.pop();
|
|
230
|
+
}
|
|
231
|
+
upper.push(p);
|
|
232
|
+
}
|
|
233
|
+
upper.pop();
|
|
234
|
+
lower.pop();
|
|
235
|
+
return lower.concat(upper);
|
|
236
|
+
}
|
|
237
|
+
function userSeverity(kind, scale = 1) {
|
|
238
|
+
const base = {
|
|
239
|
+
important: 100,
|
|
240
|
+
industrial: 50,
|
|
241
|
+
commercial: 20,
|
|
242
|
+
residential: 1
|
|
243
|
+
};
|
|
244
|
+
return (base[kind] ?? 1) * (scale ?? 1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/burst/valvePlanner.ts
|
|
248
|
+
function listCandidateValves(dataset, pipeId, filter) {
|
|
249
|
+
const pipe = dataset.pipes.find((p) => p.id === pipeId);
|
|
250
|
+
if (!pipe) return [];
|
|
251
|
+
const nodeById = new Map(dataset.nodes.map((n) => [n.id, n]));
|
|
252
|
+
const adj = buildAdjacency(dataset);
|
|
253
|
+
const visited = /* @__PURE__ */ new Set();
|
|
254
|
+
const valveCandidates = [];
|
|
255
|
+
for (const startId of [pipe.fromNode, pipe.toNode]) {
|
|
256
|
+
const queue = [{ id: startId, d: 0 }];
|
|
257
|
+
while (queue.length > 0) {
|
|
258
|
+
const { id, d } = queue.shift();
|
|
259
|
+
if (visited.has(id)) continue;
|
|
260
|
+
visited.add(id);
|
|
261
|
+
const n = nodeById.get(id);
|
|
262
|
+
if (!n) continue;
|
|
263
|
+
if (n.kind === "valve" && (!filter || filter(n))) {
|
|
264
|
+
valveCandidates.push({ n, dist: d });
|
|
265
|
+
if (d > 0) continue;
|
|
266
|
+
}
|
|
267
|
+
const edges = adj.get(id) ?? [];
|
|
268
|
+
for (const e of edges) {
|
|
269
|
+
if (e.pipeId === pipeId) continue;
|
|
270
|
+
if (visited.has(e.to)) continue;
|
|
271
|
+
queue.push({ id: e.to, d: d + 1 });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
valveCandidates.sort((a, b) => a.dist - b.dist);
|
|
276
|
+
return valveCandidates.map((v) => v.n);
|
|
277
|
+
}
|
|
278
|
+
function evaluateValvePlan(dataset, pipeId, valvesToClose) {
|
|
279
|
+
const pipe = dataset.pipes.find((p) => p.id === pipeId);
|
|
280
|
+
if (!pipe) {
|
|
281
|
+
return {
|
|
282
|
+
valves: valvesToClose,
|
|
283
|
+
isolationCompleteness: Infinity,
|
|
284
|
+
impactUserCount: 0,
|
|
285
|
+
operationOrder: []
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const adj = buildAdjacency(dataset);
|
|
289
|
+
const closedSet = new Set(valvesToClose.map((v) => v.id));
|
|
290
|
+
new Map(dataset.pipes.map((p) => [p.id, p]));
|
|
291
|
+
const affectedNodes = bfs(adj, pipe.toNode, {
|
|
292
|
+
allowEdge: (_e, _f, to) => !closedSet.has(to),
|
|
293
|
+
maxDepth: 1e3
|
|
294
|
+
}).visited;
|
|
295
|
+
const users = (dataset.users ?? []).filter((u) => {
|
|
296
|
+
if (!u.nodeId) return false;
|
|
297
|
+
return affectedNodes.includes(u.nodeId);
|
|
298
|
+
});
|
|
299
|
+
const sources = dataset.nodes.filter((n) => n.kind === "source");
|
|
300
|
+
let completeness = Infinity;
|
|
301
|
+
for (const s of sources) {
|
|
302
|
+
const rTo = dijkstra(adj, s.id, pipe.toNode, {
|
|
303
|
+
weight: (e) => closedSet.has(e.to) ? Infinity : e.length || 1
|
|
304
|
+
});
|
|
305
|
+
const rFrom = dijkstra(adj, s.id, pipe.fromNode, {
|
|
306
|
+
weight: (e) => closedSet.has(e.to) ? Infinity : e.length || 1
|
|
307
|
+
});
|
|
308
|
+
const reachable = rTo.found ? rTo.distance : Infinity;
|
|
309
|
+
const reachableFrom = rFrom.found ? rFrom.distance : Infinity;
|
|
310
|
+
const nearest = Math.min(reachable, reachableFrom);
|
|
311
|
+
if (nearest < completeness) completeness = nearest;
|
|
312
|
+
}
|
|
313
|
+
const order = valvesToClose.map((v) => v.properties?.code ?? v.id).sort();
|
|
314
|
+
return {
|
|
315
|
+
valves: valvesToClose,
|
|
316
|
+
isolationCompleteness: completeness,
|
|
317
|
+
impactUserCount: users.length,
|
|
318
|
+
operationOrder: order
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/burst/impactArea.ts
|
|
323
|
+
function toMapLayerData(result) {
|
|
324
|
+
const hull = result.impactArea.hull;
|
|
325
|
+
const hullPolygon = hull.length >= 3 ? {
|
|
326
|
+
type: "Feature",
|
|
327
|
+
geometry: { type: "Polygon", coordinates: [hull] },
|
|
328
|
+
properties: { score: 1, affected: result.affectedUserCount }
|
|
329
|
+
} : null;
|
|
330
|
+
const nodeCoords = /* @__PURE__ */ new Map();
|
|
331
|
+
for (const n of result.affectedNodes) nodeCoords.set(n.id, n);
|
|
332
|
+
const affectedPipes = {
|
|
333
|
+
type: "FeatureCollection",
|
|
334
|
+
features: result.affectedPipes.map((p) => ({
|
|
335
|
+
type: "Feature",
|
|
336
|
+
geometry: pipeToLineString(p, nodeCoords),
|
|
337
|
+
properties: { pipeId: p.id, scenario: "affected" }
|
|
338
|
+
})).filter((f) => f.geometry.coordinates.length >= 2)
|
|
339
|
+
};
|
|
340
|
+
const affectedNodes = {
|
|
341
|
+
type: "FeatureCollection",
|
|
342
|
+
features: result.affectedNodes.map((n) => ({
|
|
343
|
+
type: "Feature",
|
|
344
|
+
geometry: { type: "Point", coordinates: [n.lng, n.lat] },
|
|
345
|
+
properties: { nodeId: n.id, kind: n.kind }
|
|
346
|
+
}))
|
|
347
|
+
};
|
|
348
|
+
return { hullPolygon, affectedPipes, affectedNodes };
|
|
349
|
+
}
|
|
350
|
+
function pipeToLineString(p, nodes) {
|
|
351
|
+
if (p.geometry && p.geometry.length >= 2) {
|
|
352
|
+
return { type: "LineString", coordinates: p.geometry };
|
|
353
|
+
}
|
|
354
|
+
const from = nodes.get(p.fromNode);
|
|
355
|
+
const to = nodes.get(p.toNode);
|
|
356
|
+
if (from && to) {
|
|
357
|
+
return {
|
|
358
|
+
type: "LineString",
|
|
359
|
+
coordinates: [
|
|
360
|
+
[from.lng, from.lat],
|
|
361
|
+
[to.lng, to.lat]
|
|
362
|
+
]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return { type: "LineString", coordinates: [] };
|
|
366
|
+
}
|
|
367
|
+
function impactAreaSquareMeters(hull) {
|
|
368
|
+
if (hull.length < 3) return 0;
|
|
369
|
+
const refLat = hull[0][1];
|
|
370
|
+
const mPerDegLat = 110540;
|
|
371
|
+
const mPerDegLng = 111320 * Math.cos(refLat * Math.PI / 180);
|
|
372
|
+
const pts = hull.map(([lng, lat]) => [
|
|
373
|
+
(lng - hull[0][0]) * mPerDegLng,
|
|
374
|
+
(lat - hull[0][1]) * mPerDegLat
|
|
375
|
+
]);
|
|
376
|
+
let sum = 0;
|
|
377
|
+
for (let i = 0; i < pts.length; i++) {
|
|
378
|
+
const [x1, y1] = pts[i];
|
|
379
|
+
const [x2, y2] = pts[(i + 1) % pts.length];
|
|
380
|
+
sum += x1 * y2 - x2 * y1;
|
|
381
|
+
}
|
|
382
|
+
return Math.abs(sum) / 2;
|
|
383
|
+
}
|
|
384
|
+
function impactCenter(nodes) {
|
|
385
|
+
if (!nodes.length) return null;
|
|
386
|
+
let lng = 0;
|
|
387
|
+
let lat = 0;
|
|
388
|
+
for (const n of nodes) {
|
|
389
|
+
lng += n.lng;
|
|
390
|
+
lat += n.lat;
|
|
391
|
+
}
|
|
392
|
+
return [lng / nodes.length, lat / nodes.length];
|
|
393
|
+
}
|
|
394
|
+
function nearestNodeOnPipe(pipe, nodes, lng, lat) {
|
|
395
|
+
const a = nodes.find((n) => n.id === pipe.fromNode);
|
|
396
|
+
const b = nodes.find((n) => n.id === pipe.toNode);
|
|
397
|
+
if (!a || !b) return null;
|
|
398
|
+
const da = haversineApprox(a.lng, a.lat, lng, lat);
|
|
399
|
+
const db = haversineApprox(b.lng, b.lat, lng, lat);
|
|
400
|
+
return da < db ? a : b;
|
|
401
|
+
}
|
|
402
|
+
function haversineApprox(lng1, lat1, lng2, lat2) {
|
|
403
|
+
const dx = (lng2 - lng1) * 111320 * Math.cos(lat1 * Math.PI / 180);
|
|
404
|
+
const dy = (lat2 - lat1) * 110540;
|
|
405
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/burst/burstClass.ts
|
|
409
|
+
var BurstSimulator = class {
|
|
410
|
+
map;
|
|
411
|
+
dataset;
|
|
412
|
+
scenario;
|
|
413
|
+
affectedPipePaint;
|
|
414
|
+
affectedNodePaint;
|
|
415
|
+
valvePaint;
|
|
416
|
+
hullPaint;
|
|
417
|
+
layerPrefix;
|
|
418
|
+
listeners = /* @__PURE__ */ new Set();
|
|
419
|
+
lastResult = null;
|
|
420
|
+
layerIds = [];
|
|
421
|
+
constructor(options) {
|
|
422
|
+
this.map = options.map;
|
|
423
|
+
this.dataset = options.dataset;
|
|
424
|
+
this.scenario = options.scenario ?? "gas";
|
|
425
|
+
this.affectedPipePaint = options.affectedPipePaint ?? {
|
|
426
|
+
"line-color": "#ef4444",
|
|
427
|
+
"line-width": 4,
|
|
428
|
+
"line-opacity": 0.85,
|
|
429
|
+
"line-blur": 1.5
|
|
430
|
+
};
|
|
431
|
+
this.affectedNodePaint = options.affectedNodePaint ?? {
|
|
432
|
+
"circle-radius": 6,
|
|
433
|
+
"circle-color": "#ef4444",
|
|
434
|
+
"circle-stroke-width": 2,
|
|
435
|
+
"circle-stroke-color": "#ffffff"
|
|
436
|
+
};
|
|
437
|
+
this.valvePaint = options.valvePaint ?? {
|
|
438
|
+
"circle-radius": 8,
|
|
439
|
+
"circle-color": "#fbbf24",
|
|
440
|
+
"circle-stroke-width": 2,
|
|
441
|
+
"circle-stroke-color": "#000000"
|
|
442
|
+
};
|
|
443
|
+
this.hullPaint = options.hullPaint ?? {
|
|
444
|
+
"fill-color": "#ef4444",
|
|
445
|
+
"fill-opacity": 0.15,
|
|
446
|
+
"fill-outline-color": "#ef4444"
|
|
447
|
+
};
|
|
448
|
+
this.layerPrefix = options.layerPrefix ?? "cg-burst";
|
|
449
|
+
}
|
|
450
|
+
/** 触发爆管推演 */
|
|
451
|
+
simulate(pipeId, overrideOpts) {
|
|
452
|
+
const result = simulateBurst(this.dataset, pipeId, {
|
|
453
|
+
scenario: overrideOpts?.scenario ?? this.scenario,
|
|
454
|
+
skipClosedValves: overrideOpts?.skipClosedValves ?? true,
|
|
455
|
+
maxAlternatives: overrideOpts?.maxAlternatives ?? 2
|
|
456
|
+
});
|
|
457
|
+
this.lastResult = result;
|
|
458
|
+
this.render(result);
|
|
459
|
+
for (const l of this.listeners) l(result);
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
/** 清空所有受影响区域图层 */
|
|
463
|
+
clear() {
|
|
464
|
+
for (const id of this.layerIds) {
|
|
465
|
+
this.map.removeLayer(id);
|
|
466
|
+
}
|
|
467
|
+
this.layerIds = [];
|
|
468
|
+
}
|
|
469
|
+
/** 销毁并清空所有资源 */
|
|
470
|
+
destroy() {
|
|
471
|
+
this.clear();
|
|
472
|
+
this.listeners.clear();
|
|
473
|
+
this.lastResult = null;
|
|
474
|
+
}
|
|
475
|
+
/** 订阅推演结果 */
|
|
476
|
+
onResult(fn) {
|
|
477
|
+
this.listeners.add(fn);
|
|
478
|
+
return () => this.listeners.delete(fn);
|
|
479
|
+
}
|
|
480
|
+
/** 取最后一次结果 */
|
|
481
|
+
getLastResult() {
|
|
482
|
+
return this.lastResult;
|
|
483
|
+
}
|
|
484
|
+
// ------------------------------------------------------
|
|
485
|
+
// 内部:把结果渲染为 MapLibre 图层
|
|
486
|
+
// ------------------------------------------------------
|
|
487
|
+
render(result) {
|
|
488
|
+
this.clear();
|
|
489
|
+
const layerData = toMapLayerData(result);
|
|
490
|
+
const mlMap = this.map.instance;
|
|
491
|
+
const prefix = this.layerPrefix;
|
|
492
|
+
const sourcesToAdd = [];
|
|
493
|
+
const layersToAdd = [];
|
|
494
|
+
if (layerData.hullPolygon) {
|
|
495
|
+
sourcesToAdd.push({ id: `${prefix}-hull-src`, data: layerData.hullPolygon });
|
|
496
|
+
layersToAdd.push({
|
|
497
|
+
id: `${prefix}-hull-fill`,
|
|
498
|
+
type: "fill",
|
|
499
|
+
source: `${prefix}-hull-src`,
|
|
500
|
+
paint: this.hullPaint
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
sourcesToAdd.push({ id: `${prefix}-pipes-src`, data: layerData.affectedPipes });
|
|
504
|
+
layersToAdd.push({
|
|
505
|
+
id: `${prefix}-pipes-line`,
|
|
506
|
+
type: "line",
|
|
507
|
+
source: `${prefix}-pipes-src`,
|
|
508
|
+
paint: this.affectedPipePaint
|
|
509
|
+
});
|
|
510
|
+
sourcesToAdd.push({ id: `${prefix}-nodes-src`, data: layerData.affectedNodes });
|
|
511
|
+
layersToAdd.push({
|
|
512
|
+
id: `${prefix}-nodes-pt`,
|
|
513
|
+
type: "circle",
|
|
514
|
+
source: `${prefix}-nodes-src`,
|
|
515
|
+
paint: this.affectedNodePaint
|
|
516
|
+
});
|
|
517
|
+
for (const src of sourcesToAdd) {
|
|
518
|
+
if (!mlMap.getSource(src.id)) mlMap.addSource(src.id, src.data);
|
|
519
|
+
}
|
|
520
|
+
for (const layer of layersToAdd) {
|
|
521
|
+
try {
|
|
522
|
+
mlMap.addLayer(layer);
|
|
523
|
+
this.layerIds.push(layer.id);
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
export { BurstSimulator, convexHull, evaluateValvePlan, impactAreaSquareMeters, impactCenter, listCandidateValves, nearestNodeOnPipe, simulateBurst, toMapLayerData, userSeverity };
|
|
531
|
+
//# sourceMappingURL=chunk-HCT6NSNS.js.map
|
|
532
|
+
//# sourceMappingURL=chunk-HCT6NSNS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/burst/burstCore.ts","../src/burst/valvePlanner.ts","../src/burst/impactArea.ts","../src/burst/burstClass.ts"],"names":[],"mappings":"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","file":"chunk-HCT6NSNS.js","sourcesContent":["/**\n * BurstSimulator 核心算法(纯函数)\n *\n * 模拟\"故障管段\"后:\n * 1. 找所有隔离候选阀门(沿故障 pipe 任意一端 BFS,候选上、下游最近阀门)\n * 2. 至少保留:上游最近阀门 + 下游最近阀门,确保\"故障段完全与气源/水源分离\"\n * 3. 下游 BFS(同时绕过被关闭阀门)找受影响节点\n * 4. 关联受影响用户(居民/商业/工业)\n * 5. 重要用户识别(医院/学校/工厂)\n * 6. 备选供气/供水路径(Dijkstra)\n *\n * 全部为纯函数,接收数据集返回结果,可在 Node/浏览器两侧运行。\n */\n\nimport type {\n PipelineTopologyDataset,\n PipelineNode,\n PipelinePipe,\n PipelineUser,\n UserKind,\n} from '../types';\nimport {\n buildAdjacency,\n type AdjacencyList,\n haversine,\n} from '../graph/adjacency';\nimport { bfs } from '../graph/bfs';\nimport { dijkstra } from '../graph/shortestPath';\nimport { findPaths } from '../graph/connectivity';\n\n/** 阀门隔离方案 */\nexport interface ValvePlan {\n /** 需关闭的阀门(隔离故障段) */\n closeValves: PipelineNode[];\n /** 需打开的阀门(泄压/排水) */\n openValves: PipelineNode[];\n /** 备选供气/供水路径(多源时才有) */\n alternativePaths: string[][];\n /** 预计停气/停水时间字符串(如 \"2h\"、\"45min\") */\n estimatedShutdownTime: string;\n /** 隔离方案的可读摘要 */\n summary: string;\n}\n\n/** 影响区域(受影响管段的几何范围) */\nexport interface ImpactArea {\n type: 'FeatureCollection';\n features: Array<\n | { type: 'Feature'; geometry: { type: 'Point'; coordinates: [number, number] }; properties: { kind: 'node' } }\n | { type: 'Feature'; geometry: { type: 'LineString'; coordinates: [number, number][] }; properties: { kind: 'pipe' } }\n >;\n /** 凸包多边形(受影响区域外轮廓) */\n hull: [number, number][];\n}\n\nexport interface BurstSimulateOptions {\n /** 风机等扩展参数:默认按气体泄漏 */\n scenario?: 'gas' | 'water' | 'drainage' | 'heating';\n /** 是否同时禁用\"已关闭阀门\"作为隔离点(推荐 true) */\n skipClosedValves?: boolean;\n /** 最大 alternative path 数 */\n maxAlternatives?: number;\n}\n\nexport interface BurstSimulateResult {\n /** 故障管段 */\n pipe: PipelinePipe;\n /** 受影响节点列表(含故障管段两端) */\n affectedNodes: PipelineNode[];\n /** 受影响管段列表 */\n affectedPipes: PipelinePipe[];\n /** 受影响用户列表 */\n affectedUsers: PipelineUser[];\n /** 受影响用户总数 */\n affectedUserCount: number;\n /** 重要用户(医院/学校/政府) */\n importantUsers: PipelineUser[];\n /** 影响区域几何(GeoJSON FeatureCollection + 凸包) */\n impactArea: ImpactArea;\n /** 阀门隔离方案 */\n valvePlan: ValvePlan;\n /** 计算耗时(ms) */\n durationMs: number;\n}\n\n/**\n * 主入口:执行爆管推演\n */\nexport function simulateBurst(\n dataset: PipelineTopologyDataset,\n pipeId: string,\n opts: BurstSimulateOptions = {}\n): BurstSimulateResult {\n const t0 = (typeof performance !== 'undefined' ? performance.now() : Date.now());\n const scenario = opts.scenario ?? 'gas';\n const skipClosed = opts.skipClosedValves ?? true;\n const maxAlt = opts.maxAlternatives ?? 2;\n\n const pipe = dataset.pipes.find((p) => p.id === pipeId);\n if (!pipe) {\n throw new Error(`BurstSimulator: pipe '${pipeId}' not found in dataset`);\n }\n const nodeById = new Map(dataset.nodes.map((n) => [n.id, n] as const));\n const adj = buildAdjacency(dataset);\n\n // 1) 沿故障 pipe 两端都搜索最近阀门(上下游)\n const upstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.fromNode, skipClosed);\n const downstreamValves = findValvesFromEndpoint(adj, pipe, dataset, pipe.toNode, skipClosed);\n // 合并(按距离排序去重)\n const candidateClose = mergeCandidateValves(upstreamValves, downstreamValves);\n\n // 2) 下游 BFS(PRD step 2):跳过关闭的阀门\n const closedSet = new Set(candidateClose.map((v) => v.id));\n const downstream = bfs(adj, pipe.toNode, {\n allowEdge: (_e, _f, to) => {\n if (closedSet.has(to)) return false;\n return true;\n },\n maxDepth: 1_000,\n });\n\n const affectedSet = new Set(downstream.visited);\n // 故障 pipe from/to 节点也要纳入受影响范围(事件源)\n affectedSet.add(pipe.fromNode);\n\n const affectedNodes: PipelineNode[] = [];\n for (const id of affectedSet) {\n const n = nodeById.get(id);\n if (n) affectedNodes.push(n);\n }\n\n // 受影响管段:所有至少一端在 affectedNodes 的管段\n const affectedPipes: PipelinePipe[] = dataset.pipes.filter(\n (p) => affectedSet.has(p.fromNode) || affectedSet.has(p.toNode)\n );\n\n // 3) 查询受影响用户\n const users = dataset.users ?? [];\n const affectedUsers = users.filter((u) => {\n if (!u.nodeId) return false;\n return affectedSet.has(u.nodeId);\n });\n const importantUsers = affectedUsers.filter((u) => u.kind === 'important');\n\n // 4) 影响区域几何\n const impactArea = computeImpactArea(affectedNodes, affectedPipes);\n\n // 5) 阀门隔离方案\n const valvePlan = buildValvePlan(\n adj,\n dataset,\n pipe,\n candidateClose,\n affectedNodes,\n maxAlt,\n scenario\n );\n\n const durationMs = (typeof performance !== 'undefined' ? performance.now() : Date.now()) - t0;\n return {\n pipe,\n affectedNodes,\n affectedPipes,\n affectedUsers,\n affectedUserCount: affectedUsers.length,\n importantUsers,\n impactArea,\n valvePlan,\n durationMs,\n };\n}\n\n// ============================================================\n// 内部辅助\n// ============================================================\n\ninterface ValveCandidate {\n node: PipelineNode;\n /** 距起点跳数 */\n hops: number;\n /** 距起点物理距离 */\n distance: number;\n /** 来源端 fromNode/toNode */\n endpoint: 'from' | 'to';\n}\n\n/** 从指定端点出发 BFS,沿拓扑找最近的阀门\n *\n * 关键规则:\n * - 端点本身如果是阀门 → 直接作为最近阀门返回,不再扩展\n * - 否则 BFS 找最近的阀门(找到立即停止)\n */\nfunction findValvesFromEndpoint(\n adj: AdjacencyList,\n pipe: PipelinePipe,\n dataset: PipelineTopologyDataset,\n startId: string,\n skipClosed: boolean\n): ValveCandidate[] {\n const faultPipeId = pipe.id;\n const nodeById = new Map(dataset.nodes.map((n) => [n.id, n] as const));\n const out: ValveCandidate[] = [];\n const endpoint = startId === pipe.fromNode ? 'from' : 'to';\n\n const startNode = nodeById.get(startId);\n if (!startNode) return [];\n\n // 0) 端点本身就是阀门 → 立即返回\n if (startNode.kind === 'valve') {\n if (!skipClosed || startNode.properties?.valveStatus !== 'closed') {\n out.push({ node: startNode, hops: 0, distance: 0, endpoint });\n }\n return out;\n }\n\n // 1) 否则 BFS 找最近的阀门\n const visited = new Set<string>();\n const queue: Array<{ id: string; hops: number; dist: number }> = [\n { id: startId, hops: 0, dist: 0 },\n ];\n\n while (queue.length > 0) {\n const cur = queue.shift()!;\n if (visited.has(cur.id)) continue;\n visited.add(cur.id);\n\n const n = nodeById.get(cur.id);\n if (!n) continue;\n\n if (n.kind === 'valve' && (!skipClosed || n.properties?.valveStatus !== 'closed')) {\n out.push({ node: n, hops: cur.hops, distance: cur.dist, endpoint });\n return out; // 找最近的,返回\n }\n\n const edges = adj.get(cur.id) ?? [];\n for (const e of edges) {\n if (e.pipeId === faultPipeId) continue;\n if (visited.has(e.to)) continue;\n queue.push({\n id: e.to,\n hops: cur.hops + 1,\n dist: cur.dist + (e.length || 0),\n });\n }\n }\n\n return out;\n}\n\n/** 合并上下游阀门候选(按距离排重) */\nfunction mergeCandidateValves(\n upstream: ValveCandidate[],\n downstream: ValveCandidate[]\n): PipelineNode[] {\n const seen = new Set<string>();\n const all = [...upstream, ...downstream].sort((a, b) => a.distance - b.distance);\n const out: PipelineNode[] = [];\n for (const c of all) {\n if (seen.has(c.node.id)) continue;\n seen.add(c.node.id);\n out.push(c.node);\n }\n return out;\n}\n\n/**\n * 构造影响区域\n * - features: 受影响节点(Point) + 受影响管段(LineString)\n * - hull: 节点集的凸包(用于地图高亮\"受影响地理范围\")\n */\nfunction computeImpactArea(\n nodes: PipelineNode[],\n pipes: PipelinePipe[]\n): ImpactArea {\n const features: ImpactArea['features'] = [];\n const nodeById = new Map(nodes.map((n) => [n.id, n] as const));\n\n for (const n of nodes) {\n features.push({\n type: 'Feature',\n geometry: { type: 'Point', coordinates: [n.lng, n.lat] },\n properties: { kind: 'node' },\n });\n }\n for (const p of pipes) {\n let coords: [number, number][] = [];\n if (p.geometry && p.geometry.length >= 2) {\n coords = p.geometry as [number, number][];\n } else {\n const from = nodeById.get(p.fromNode);\n const to = nodeById.get(p.toNode);\n if (from && to) coords = [[from.lng, from.lat], [to.lng, to.lat]];\n }\n if (coords.length >= 2) {\n features.push({\n type: 'Feature',\n geometry: { type: 'LineString', coordinates: coords },\n properties: { kind: 'pipe' },\n });\n }\n }\n\n const hull = convexHull(nodes.map((n) => [n.lng, n.lat] as [number, number]));\n\n return { type: 'FeatureCollection', features, hull };\n}\n\n/**\n * 构造阀门隔离方案\n */\nfunction buildValvePlan(\n adj: AdjacencyList,\n dataset: PipelineTopologyDataset,\n pipe: PipelinePipe,\n closeValves: PipelineNode[],\n _affectedNodes: PipelineNode[],\n maxAlt: number,\n scenario: string\n): ValvePlan {\n const closeSet = new Set(closeValves.map((v) => v.id));\n\n // 找泄压点:受影响区域内\"开放\"且\"不在关闭列表\"的阀门\n const openValves: PipelineNode[] = [];\n for (const n of _affectedNodes) {\n if (n.kind !== 'valve') continue;\n if (closeSet.has(n.id)) continue;\n if (n.properties?.valveStatus !== 'open') continue;\n const from = dataset.nodes.find((m) => m.id === pipe.fromNode);\n const to = dataset.nodes.find((m) => m.id === pipe.toNode);\n if (!from || !to) continue;\n const d1 = haversine(n.lng, n.lat, from.lng, from.lat);\n const d2 = haversine(n.lng, n.lat, to.lng, to.lat);\n if (Math.min(d1, d2) < 1000) openValves.push(n);\n }\n\n // 备选路径:从关闭阀门出发 → 其它 source(隔离后备用气源/水源)\n const alternativePaths: string[][] = [];\n if (closeValves.length) {\n const sources = dataset.nodes.filter((n) => n.kind === 'source');\n for (const s of sources) {\n // 选最接近关闭阀门的 source 寻路(确保目标非 closed 阀门)\n const r = dijkstra(adj, closeValves[0].id, s.id, {\n weight: (e) => (closeSet.has(e.to) ? Infinity : e.length || 1),\n });\n if (r.found) {\n const paths = findPaths(adj, closeValves[0].id, s.id, {\n maxPaths: maxAlt,\n maxDepth: 20,\n });\n for (const p of paths) alternativePaths.push(p);\n break;\n }\n }\n }\n\n // 估算停气/停水时间(基于受影响用户数和场景)\n const userCount = (dataset.users ?? []).filter((u) =>\n _affectedNodes.some((n) => n.id === u.nodeId)\n ).length;\n const baseHours = userCount / 200;\n const scenarioFactor = scenario === 'water' ? 1.5 : scenario === 'heating' ? 2 : 1;\n const hours = Math.max(0.5, Math.min(8, baseHours * scenarioFactor));\n const estimatedShutdownTime = hours >= 1 ? `${hours.toFixed(1)}h` : `${Math.round(hours * 60)}min`;\n\n const summary = buildSummary(closeValves, openValves, userCount, scenario);\n\n return {\n closeValves,\n openValves,\n alternativePaths,\n estimatedShutdownTime,\n summary,\n };\n}\n\nfunction buildSummary(\n closeValves: PipelineNode[],\n openValves: PipelineNode[],\n userCount: number,\n scenario: string\n): string {\n const labels: Record<string, string> = {\n gas: '停气',\n water: '停水',\n drainage: '排水中断',\n heating: '停热',\n };\n const label = labels[scenario] ?? '受影响';\n const parts: string[] = [];\n parts.push(`预计${label}${userCount}户`);\n if (closeValves.length) {\n parts.push(`关闭阀门 ${closeValves.map((v) => v.properties?.code ?? v.id).join(', ')}`);\n } else {\n parts.push('未找到上游隔离阀门(建议人工指定)');\n }\n if (openValves.length) {\n parts.push(`打开阀门 ${openValves.map((v) => v.properties?.code ?? v.id).join(', ')}(泄压)`);\n }\n return parts.join(';');\n}\n\n// ============================================================\n// 凸包(Andrew's monotone chain,O(n log n))\n// ============================================================\n\n/** Andrew's monotone chain,返回逆时针凸包顶点(首尾不重复) */\nexport function convexHull(points: [number, number][]): [number, number][] {\n if (points.length < 3) return [...points];\n const pts = [...points];\n pts.sort((a, b) => (a[0] - b[0]) || (a[1] - b[1]));\n\n const cross = (\n o: [number, number],\n a: [number, number],\n b: [number, number]\n ): number => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);\n\n const lower: [number, number][] = [];\n for (const p of pts) {\n while (lower.length >= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0) {\n lower.pop();\n }\n lower.push(p);\n }\n const upper: [number, number][] = [];\n for (let i = pts.length - 1; i >= 0; i--) {\n const p = pts[i];\n while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0) {\n upper.pop();\n }\n upper.push(p);\n }\n upper.pop();\n lower.pop();\n return lower.concat(upper);\n}\n\n/** 用户严重程度打分(用于\"优先通告\"列表) */\nexport function userSeverity(kind: UserKind, scale = 1): number {\n const base: Record<UserKind, number> = {\n important: 100,\n industrial: 50,\n commercial: 20,\n residential: 1,\n };\n return (base[kind] ?? 1) * (scale ?? 1);\n}\n","/**\n * 阀门规划(valve planner)\n *\n * 在已知故障管段的前提下,候选出最优的\"关闭+打开\"组合。\n * 评估指标:\n * - 隔离完整性:关闭阀门后故障段必须与气源/水源完全分离\n * - 影响最小:关闭数量最小,受影响用户最少\n * - 操作便利:按操作顺序列出阀门编号\n */\n\nimport type { PipelineNode, PipelineTopologyDataset, PipelinePipe } from '../types';\nimport { buildAdjacency, type AdjacencyList } from '../graph/adjacency';\nimport { bfs } from '../graph/bfs';\nimport { dijkstra } from '../graph/shortestPath';\n\nexport interface CandidateValveSet {\n valves: PipelineNode[];\n /** 完整性:关闭后故障段到最近 source 的最短距离(Infinity = 完全隔离) */\n isolationCompleteness: number;\n /** 受影响用户数(关闭阀门后的下游覆盖) */\n impactUserCount: number;\n /** 阀门操作顺序(按节点编号字典序,仅辅助) */\n operationOrder: string[];\n}\n\nexport interface PlannerOptions {\n /** 最大候选组合数(指数爆炸时截断) */\n maxCombinations?: number;\n /** 阀门过滤:仅考虑状态/类型匹配的 */\n filter?: (valve: PipelineNode) => boolean;\n}\n\n/**\n * 列出上游候选阀门(沿故障 pipe 两端 BFS 找到的所有阀门,按距离排序)\n */\nexport function listCandidateValves(\n dataset: PipelineTopologyDataset,\n pipeId: string,\n filter?: (valve: PipelineNode) => boolean\n): PipelineNode[] {\n const pipe = dataset.pipes.find((p) => p.id === pipeId);\n if (!pipe) return [];\n const nodeById = new Map(dataset.nodes.map((n) => [n.id, n] as const));\n const adj = buildAdjacency(dataset);\n\n const visited = new Set<string>();\n const valveCandidates: Array<{ n: PipelineNode; dist: number }> = [];\n\n for (const startId of [pipe.fromNode, pipe.toNode]) {\n const queue: Array<{ id: string; d: number }> = [{ id: startId, d: 0 }];\n while (queue.length > 0) {\n const { id, d } = queue.shift()!;\n if (visited.has(id)) continue;\n visited.add(id);\n\n const n = nodeById.get(id);\n if (!n) continue;\n // 端点本身如果是阀门, 也算候选\n if (n.kind === 'valve' && (!filter || filter(n))) {\n valveCandidates.push({ n, dist: d });\n // 第一个阀门已找到, 不必继续扩展 (但允许从端点继续找其它)\n if (d > 0) continue;\n }\n\n const edges = adj.get(id) ?? [];\n for (const e of edges) {\n if (e.pipeId === pipeId) continue; // 不穿过故障 pipe\n if (visited.has(e.to)) continue;\n queue.push({ id: e.to, d: d + 1 });\n }\n }\n }\n\n valveCandidates.sort((a, b) => a.dist - b.dist);\n return valveCandidates.map((v) => v.n);\n}\n\n/**\n * 评估某组阀门关闭后的隔离完整性与受影响用户数\n */\nexport function evaluateValvePlan(\n dataset: PipelineTopologyDataset,\n pipeId: string,\n valvesToClose: PipelineNode[]\n): CandidateValveSet {\n const pipe = dataset.pipes.find((p) => p.id === pipeId);\n if (!pipe) {\n return {\n valves: valvesToClose,\n isolationCompleteness: Infinity,\n impactUserCount: 0,\n operationOrder: [],\n };\n }\n\n // 模拟\"关闭阀门 = 在邻接表中标记为不可通过\",重新计算下游覆盖\n const adj = buildAdjacency(dataset);\n const closedSet = new Set(valvesToClose.map((v) => v.id));\n const pipeById = new Map(dataset.pipes.map((p) => [p.id, p] as const));\n\n const affectedNodes = bfs(adj, pipe.toNode, {\n allowEdge: (_e, _f, to) => !closedSet.has(to),\n maxDepth: 1000,\n }).visited;\n\n // 受影响用户数\n const users = (dataset.users ?? []).filter((u) => {\n if (!u.nodeId) return false;\n return affectedNodes.includes(u.nodeId);\n });\n\n // 隔离完整性:source → 故障段 任一端 的可达性\n// 语义:所有 source 都不能绕过 closedSet 抵达 fromNode/toNode → Infinity(完全隔离)\n// 否则:completeness = source 到故障段最近的可达距离(米)\n const sources = dataset.nodes.filter((n) => n.kind === 'source');\n let completeness = Infinity;\n for (const s of sources) {\n const rTo = dijkstra(adj, s.id, pipe.toNode, {\n weight: (e) => (closedSet.has(e.to) ? Infinity : e.length || 1),\n });\n const rFrom = dijkstra(adj, s.id, pipe.fromNode, {\n weight: (e) => (closedSet.has(e.to) ? Infinity : e.length || 1),\n });\n const reachable =\n rTo.found ? rTo.distance : Infinity;\n const reachableFrom =\n rFrom.found ? rFrom.distance : Infinity;\n const nearest = Math.min(reachable, reachableFrom);\n if (nearest < completeness) completeness = nearest;\n }\n\n const order = valvesToClose\n .map((v) => v.properties?.code ?? v.id)\n .sort();\n\n return {\n valves: valvesToClose,\n isolationCompleteness: completeness,\n impactUserCount: users.length,\n operationOrder: order,\n };\n}\n","/**\n * 影响区域 helpers\n *\n * 把 BurstSimulator 输出的受影响节点+管段渲染成 MapLibre 图层用的 GeoJSON。\n */\n\nimport type { BurstSimulateResult } from './burstCore';\nimport type { PipelineNode, PipelinePipe } from '../types';\n\nexport interface MapLayerData {\n /** 受影响区域凸包多边形(用于高亮) */\n hullPolygon: GeoJSON.Feature<\n GeoJSON.Polygon,\n { score: number; affected: number }\n > | null;\n /** 受影响管段 LineString 集合(用于粗化红色线) */\n affectedPipes: GeoJSON.FeatureCollection<\n GeoJSON.LineString,\n { pipeId: string; scenario: string }\n >;\n /** 受影响节点 Point 集合 */\n affectedNodes: GeoJSON.FeatureCollection<\n GeoJSON.Point,\n { nodeId: string; kind: string }\n >;\n}\n\n/**\n * 把 BurstSimulateResult 映射为地图图层数据\n */\nexport function toMapLayerData(result: BurstSimulateResult): MapLayerData {\n const hull = result.impactArea.hull;\n const hullPolygon: MapLayerData['hullPolygon'] = hull.length >= 3\n ? {\n type: 'Feature',\n geometry: { type: 'Polygon', coordinates: [hull] },\n properties: { score: 1, affected: result.affectedUserCount },\n }\n : null;\n\n // 构造 node 坐标索引以便退化几何\n const nodeCoords = new Map<string, { lng: number; lat: number }>();\n for (const n of result.affectedNodes) nodeCoords.set(n.id, n);\n\n const affectedPipes: GeoJSON.FeatureCollection<GeoJSON.LineString, { pipeId: string; scenario: string }> = {\n type: 'FeatureCollection',\n features: result.affectedPipes\n .map((p) => ({\n type: 'Feature' as const,\n geometry: pipeToLineString(p, nodeCoords),\n properties: { pipeId: p.id, scenario: 'affected' },\n }))\n .filter((f) => f.geometry.coordinates.length >= 2),\n };\n\n const affectedNodes: GeoJSON.FeatureCollection<GeoJSON.Point, { nodeId: string; kind: string }> = {\n type: 'FeatureCollection',\n features: result.affectedNodes.map((n) => ({\n type: 'Feature' as const,\n geometry: { type: 'Point', coordinates: [n.lng, n.lat] },\n properties: { nodeId: n.id, kind: n.kind },\n })),\n };\n\n return { hullPolygon, affectedPipes, affectedNodes };\n}\n\nfunction pipeToLineString(p: PipelinePipe, nodes: Map<string, { lng: number; lat: number }>): GeoJSON.LineString {\n if (p.geometry && p.geometry.length >= 2) {\n return { type: 'LineString', coordinates: p.geometry as [number, number][] };\n }\n // 退化:从端点拉直线\n const from = nodes.get(p.fromNode);\n const to = nodes.get(p.toNode);\n if (from && to) {\n return {\n type: 'LineString',\n coordinates: [\n [from.lng, from.lat],\n [to.lng, to.lat],\n ],\n };\n }\n return { type: 'LineString', coordinates: [] };\n}\n\n/** 计算受影响区域总面积(使用 shoelace 公式) */\nexport function impactAreaSquareMeters(hull: [number, number][]): number {\n if (hull.length < 3) return 0;\n // 把经纬度局部转米(以第一个点为原点,使用等距近似)\n const refLat = hull[0][1];\n const mPerDegLat = 110_540;\n const mPerDegLng = 111_320 * Math.cos((refLat * Math.PI) / 180);\n\n const pts = hull.map(([lng, lat]) => [\n (lng - hull[0][0]) * mPerDegLng,\n (lat - hull[0][1]) * mPerDegLat,\n ]);\n\n let sum = 0;\n for (let i = 0; i < pts.length; i++) {\n const [x1, y1] = pts[i];\n const [x2, y2] = pts[(i + 1) % pts.length];\n sum += x1 * y2 - x2 * y1;\n }\n return Math.abs(sum) / 2;\n}\n\n/** 估算受影响节点的\"中心点\"(用于地图 flyTo) */\nexport function impactCenter(nodes: PipelineNode[]): [number, number] | null {\n if (!nodes.length) return null;\n let lng = 0;\n let lat = 0;\n for (const n of nodes) {\n lng += n.lng;\n lat += n.lat;\n }\n return [lng / nodes.length, lat / nodes.length];\n}\n\n/** 从管段上取最近的节点(用于点击管线后弹出该节点信息卡) */\nexport function nearestNodeOnPipe(\n pipe: PipelinePipe,\n nodes: PipelineNode[],\n lng: number,\n lat: number\n): PipelineNode | null {\n const a = nodes.find((n) => n.id === pipe.fromNode);\n const b = nodes.find((n) => n.id === pipe.toNode);\n if (!a || !b) return null;\n const da = haversineApprox(a.lng, a.lat, lng, lat);\n const db = haversineApprox(b.lng, b.lat, lng, lat);\n return da < db ? a : b;\n}\n\nfunction haversineApprox(lng1: number, lat1: number, lng2: number, lat2: number): number {\n const dx = (lng2 - lng1) * 111_320 * Math.cos((lat1 * Math.PI) / 180);\n const dy = (lat2 - lat1) * 110_540;\n return Math.sqrt(dx * dx + dy * dy);\n}\n","/**\n * BurstSimulator 组件类(地图集成层)\n *\n * 包装 simulateBurst 纯函数 + 维护 MapLibre 图层状态。\n * 组件层职责:\n * - 接收地图实例,添加受影响区域/管段/阀门、方案侧栏\n * - 暴露 simulate()、clear() 等命令式 API\n * - 可订阅结果事件(affectedNodes/valvePlan/impactArea)\n *\n * 注意:此组件不直接依赖 Vue/React(业务方可在任意框架中实例化)\n */\n\nimport type { Map as CaoguoMap } from '@caoguo/maplibre';\nimport {\n simulateBurst,\n type BurstSimulateOptions,\n type BurstSimulateResult,\n} from './burstCore';\nimport { toMapLayerData } from './impactArea';\nimport type { PipelineTopologyDataset, PipelineType } from '../types';\n\nexport interface BurstSimulatorOptions {\n map: CaoguoMap;\n dataset: PipelineTopologyDataset;\n /** 受影响管段样式(maplibre paint props) */\n affectedPipePaint?: Record<string, unknown>;\n /** 阀门隔离方案样式 */\n valvePaint?: Record<string, unknown>;\n /** 影响区域凸包样式 */\n hullPaint?: Record<string, unknown>;\n /** 受影响节点图标样式 */\n affectedNodePaint?: Record<string, unknown>;\n /** 推演情景 */\n scenario?: BurstSimulateOptions['scenario'];\n /** 该实例只处理指定管线类型(用于多管类地图) */\n pipelineType?: PipelineType;\n /** 层 ID 前缀(避免多实例冲突) */\n layerPrefix?: string;\n}\n\nexport type BurstResult = BurstSimulateResult;\n\ntype Listener = (r: BurstSimulateResult) => void;\n\n/**\n * 爆管推演组件(前端类)\n *\n * 用法:\n * const sim = new BurstSimulator({ map, dataset });\n * const result = sim.simulate('pipe-A12');\n * // 受影响区域已添加到 map\n * sim.clear();\n * sim.destroy();\n */\nexport class BurstSimulator {\n private map: CaoguoMap;\n private dataset: PipelineTopologyDataset;\n private scenario: 'gas' | 'water' | 'drainage' | 'heating';\n private affectedPipePaint: Record<string, unknown>;\n private affectedNodePaint: Record<string, unknown>;\n private valvePaint: Record<string, unknown>;\n private hullPaint: Record<string, unknown>;\n private layerPrefix: string;\n private listeners = new Set<Listener>();\n private lastResult: BurstSimulateResult | null = null;\n private layerIds: string[] = [];\n\n constructor(options: BurstSimulatorOptions) {\n this.map = options.map;\n this.dataset = options.dataset;\n this.scenario = options.scenario ?? 'gas';\n this.affectedPipePaint = options.affectedPipePaint ?? {\n 'line-color': '#ef4444',\n 'line-width': 4,\n 'line-opacity': 0.85,\n 'line-blur': 1.5,\n };\n this.affectedNodePaint = options.affectedNodePaint ?? {\n 'circle-radius': 6,\n 'circle-color': '#ef4444',\n 'circle-stroke-width': 2,\n 'circle-stroke-color': '#ffffff',\n };\n this.valvePaint = options.valvePaint ?? {\n 'circle-radius': 8,\n 'circle-color': '#fbbf24',\n 'circle-stroke-width': 2,\n 'circle-stroke-color': '#000000',\n };\n this.hullPaint = options.hullPaint ?? {\n 'fill-color': '#ef4444',\n 'fill-opacity': 0.15,\n 'fill-outline-color': '#ef4444',\n };\n this.layerPrefix = options.layerPrefix ?? 'cg-burst';\n }\n\n /** 触发爆管推演 */\n simulate(pipeId: string, overrideOpts?: BurstSimulateOptions): BurstSimulateResult {\n const result = simulateBurst(this.dataset, pipeId, {\n scenario: overrideOpts?.scenario ?? this.scenario,\n skipClosedValves: overrideOpts?.skipClosedValves ?? true,\n maxAlternatives: overrideOpts?.maxAlternatives ?? 2,\n });\n this.lastResult = result;\n this.render(result);\n for (const l of this.listeners) l(result);\n return result;\n }\n\n /** 清空所有受影响区域图层 */\n clear(): void {\n for (const id of this.layerIds) {\n this.map.removeLayer(id);\n }\n this.layerIds = [];\n }\n\n /** 销毁并清空所有资源 */\n destroy(): void {\n this.clear();\n this.listeners.clear();\n this.lastResult = null;\n }\n\n /** 订阅推演结果 */\n onResult(fn: Listener): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n /** 取最后一次结果 */\n getLastResult(): BurstSimulateResult | null {\n return this.lastResult;\n }\n\n // ------------------------------------------------------\n // 内部:把结果渲染为 MapLibre 图层\n // ------------------------------------------------------\n private render(result: BurstSimulateResult): void {\n this.clear();\n const layerData = toMapLayerData(result);\n const mlMap = (this.map as unknown as {\n instance: {\n addSource: (id: string, source: unknown) => void;\n addLayer: (layer: unknown) => void;\n getSource: (id: string) => unknown;\n };\n }).instance;\n\n const prefix = this.layerPrefix;\n const sourcesToAdd: Array<{ id: string; data: unknown }> = [];\n const layersToAdd: Array<Record<string, unknown>> = [];\n\n if (layerData.hullPolygon) {\n sourcesToAdd.push({ id: `${prefix}-hull-src`, data: layerData.hullPolygon });\n layersToAdd.push({\n id: `${prefix}-hull-fill`,\n type: 'fill',\n source: `${prefix}-hull-src`,\n paint: this.hullPaint,\n });\n }\n\n sourcesToAdd.push({ id: `${prefix}-pipes-src`, data: layerData.affectedPipes });\n layersToAdd.push({\n id: `${prefix}-pipes-line`,\n type: 'line',\n source: `${prefix}-pipes-src`,\n paint: this.affectedPipePaint,\n });\n\n sourcesToAdd.push({ id: `${prefix}-nodes-src`, data: layerData.affectedNodes });\n layersToAdd.push({\n id: `${prefix}-nodes-pt`,\n type: 'circle',\n source: `${prefix}-nodes-src`,\n paint: this.affectedNodePaint,\n });\n\n for (const src of sourcesToAdd) {\n if (!mlMap.getSource(src.id)) mlMap.addSource(src.id, src.data as never);\n }\n for (const layer of layersToAdd) {\n try {\n mlMap.addLayer(layer);\n this.layerIds.push(layer.id as string);\n } catch {\n // ignore duplicate\n }\n }\n }\n}\n"]}
|