@tellegen/svelte 0.4.2 → 0.4.4
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/README.md +20 -0
- package/dist/TellegenMap.svelte +63 -22
- package/dist/TellegenProvider.svelte +7 -4
- package/dist/TellegenShell.svelte +10 -1
- package/dist/TellegenViewer.svelte +2 -0
- package/dist/basemap.d.ts +14 -0
- package/dist/basemap.js +27 -0
- package/dist/colors.d.ts +6 -1
- package/dist/colors.js +11 -4
- package/dist/components/DemandSlider.svelte +0 -2
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +18 -3
- package/dist/components/McResults.svelte +18 -3
- package/dist/components/McWorkbench.svelte +688 -0
- package/dist/components/McWorkbench.svelte.d.ts +38 -0
- package/dist/components/MultiWire.svelte +136 -0
- package/dist/components/MultiWire.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +7 -0
- package/dist/components/OmniSearch.svelte +163 -0
- package/dist/components/OmniSearch.svelte.d.ts +14 -0
- package/dist/components/ResultsTable.svelte +259 -0
- package/dist/components/ResultsTable.svelte.d.ts +13 -0
- package/dist/components/SchematicCanvas.svelte +1896 -0
- package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
- package/dist/components/SolveCard.svelte +6 -0
- package/dist/components/schematic/PickCard.svelte +126 -0
- package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
- package/dist/components/schematic/export.d.ts +21 -0
- package/dist/components/schematic/export.js +110 -0
- package/dist/components/schematic/layers.d.ts +161 -0
- package/dist/components/schematic/layers.js +756 -0
- package/dist/components/schematic/paint.d.ts +28 -0
- package/dist/components/schematic/paint.js +69 -0
- package/dist/components/schematic/palette.d.ts +34 -0
- package/dist/components/schematic/palette.js +54 -0
- package/dist/components/schematic/text.d.ts +83 -0
- package/dist/components/schematic/text.js +190 -0
- package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
- package/dist/components/schematic/view-prefs.svelte.js +77 -0
- package/dist/components.d.ts +1 -0
- package/dist/components.js +1 -0
- package/dist/context.svelte.d.ts +2 -0
- package/dist/context.svelte.js +2 -0
- package/dist/controller.svelte.d.ts +33 -5
- package/dist/controller.svelte.js +173 -20
- package/dist/diagram/basemap.d.ts +28 -0
- package/dist/diagram/basemap.js +39 -0
- package/dist/diagram/bus-stacks.d.ts +19 -0
- package/dist/diagram/bus-stacks.js +99 -0
- package/dist/diagram/colour-modes.d.ts +18 -0
- package/dist/diagram/colour-modes.js +33 -0
- package/dist/diagram/connections.d.ts +30 -0
- package/dist/diagram/connections.js +129 -0
- package/dist/diagram/display-tree.d.ts +21 -0
- package/dist/diagram/display-tree.js +65 -0
- package/dist/diagram/energisation.d.ts +10 -0
- package/dist/diagram/energisation.js +31 -0
- package/dist/diagram/feeders.d.ts +30 -0
- package/dist/diagram/feeders.js +57 -0
- package/dist/diagram/galvanic-zones.d.ts +60 -0
- package/dist/diagram/galvanic-zones.js +210 -0
- package/dist/diagram/graph-inputs.d.ts +18 -0
- package/dist/diagram/graph-inputs.js +103 -0
- package/dist/diagram/index.d.ts +28 -0
- package/dist/diagram/index.js +28 -0
- package/dist/diagram/landmarks.d.ts +50 -0
- package/dist/diagram/landmarks.js +153 -0
- package/dist/diagram/layout-morph.d.ts +15 -0
- package/dist/diagram/layout-morph.js +32 -0
- package/dist/diagram/multi-wire.d.ts +44 -0
- package/dist/diagram/multi-wire.js +65 -0
- package/dist/diagram/phase-bars.d.ts +35 -0
- package/dist/diagram/phase-bars.js +44 -0
- package/dist/diagram/results-table.d.ts +47 -0
- package/dist/diagram/results-table.js +173 -0
- package/dist/diagram/schematic-equipment.d.ts +133 -0
- package/dist/diagram/schematic-equipment.js +645 -0
- package/dist/diagram/schematic-layout.d.ts +51 -0
- package/dist/diagram/schematic-layout.js +240 -0
- package/dist/diagram/search.d.ts +36 -0
- package/dist/diagram/search.js +48 -0
- package/dist/diagram/shared-routes.d.ts +21 -0
- package/dist/diagram/shared-routes.js +40 -0
- package/dist/diagram/short-names.d.ts +11 -0
- package/dist/diagram/short-names.js +54 -0
- package/dist/diagram/snapshot.d.ts +115 -0
- package/dist/diagram/snapshot.js +242 -0
- package/dist/diagram/summary.d.ts +65 -0
- package/dist/diagram/summary.js +144 -0
- package/dist/diagram/svg-export.d.ts +80 -0
- package/dist/diagram/svg-export.js +108 -0
- package/dist/diagram/tree-walk.d.ts +13 -0
- package/dist/diagram/tree-walk.js +83 -0
- package/dist/diagram/value-labels.d.ts +24 -0
- package/dist/diagram/value-labels.js +69 -0
- package/dist/diagram/view-state.d.ts +12 -0
- package/dist/diagram/view-state.js +22 -0
- package/dist/diagram/voltage-levels.d.ts +14 -0
- package/dist/diagram/voltage-levels.js +20 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/state.svelte.d.ts +37 -1
- package/dist/state.svelte.js +35 -0
- package/dist/transformer-icon.d.ts +22 -13
- package/dist/transformer-icon.js +23 -13
- package/package.json +2 -2
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
function segments(graph, positions, index, route) {
|
|
2
|
+
const src = [];
|
|
3
|
+
const dst = [];
|
|
4
|
+
const owner = [];
|
|
5
|
+
graph.edges.forEach((e, k) => {
|
|
6
|
+
const a = index(e.from);
|
|
7
|
+
const b = index(e.to);
|
|
8
|
+
if (a < 0 || b < 0)
|
|
9
|
+
return;
|
|
10
|
+
const points = route(k, a, b) ?? [
|
|
11
|
+
[positions[a * 3], positions[a * 3 + 1]],
|
|
12
|
+
[positions[b * 3], positions[b * 3 + 1]]
|
|
13
|
+
];
|
|
14
|
+
for (let i = 1; i < points.length; i++) {
|
|
15
|
+
src.push(points[i - 1][0], points[i - 1][1], 0);
|
|
16
|
+
dst.push(points[i][0], points[i][1], 0);
|
|
17
|
+
owner.push(k);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
sources: Float32Array.from(src),
|
|
22
|
+
targets: Float32Array.from(dst),
|
|
23
|
+
segmentEdge: Int32Array.from(owner)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function bounds(positions) {
|
|
27
|
+
let [x0, y0, x1, y1] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
28
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
29
|
+
x0 = Math.min(x0, positions[i]);
|
|
30
|
+
x1 = Math.max(x1, positions[i]);
|
|
31
|
+
y0 = Math.min(y0, positions[i + 1]);
|
|
32
|
+
y1 = Math.max(y1, positions[i + 1]);
|
|
33
|
+
}
|
|
34
|
+
return Number.isFinite(x0) ? [x0, y0, x1, y1] : [0, 0, 1, 1];
|
|
35
|
+
}
|
|
36
|
+
const indexer = (graph) => {
|
|
37
|
+
const map = new Map(graph.buses.map((b, i) => [b.id.toLowerCase(), i]));
|
|
38
|
+
return (id) => map.get(id.toLowerCase()) ?? -1;
|
|
39
|
+
};
|
|
40
|
+
/** Engine schematic layout (`mcSchematicLayout`, computed in Rust) as GPU-ready arrays. */
|
|
41
|
+
export function fromEngineLayout(layout) {
|
|
42
|
+
const n = layout.positions.length / 2;
|
|
43
|
+
const positions = new Float32Array(n * 3);
|
|
44
|
+
for (let i = 0; i < n; i++) {
|
|
45
|
+
positions[i * 3] = layout.positions[i * 2];
|
|
46
|
+
positions[i * 3 + 1] = layout.positions[i * 2 + 1];
|
|
47
|
+
}
|
|
48
|
+
const m = layout.segment_edge.length;
|
|
49
|
+
const sources = new Float32Array(m * 3);
|
|
50
|
+
const targets = new Float32Array(m * 3);
|
|
51
|
+
for (let s = 0; s < m; s++) {
|
|
52
|
+
sources[s * 3] = layout.segments[s * 4];
|
|
53
|
+
sources[s * 3 + 1] = layout.segments[s * 4 + 1];
|
|
54
|
+
targets[s * 3] = layout.segments[s * 4 + 2];
|
|
55
|
+
targets[s * 3 + 1] = layout.segments[s * 4 + 3];
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
positions,
|
|
59
|
+
sources,
|
|
60
|
+
targets,
|
|
61
|
+
segmentEdge: Int32Array.from(layout.segment_edge),
|
|
62
|
+
role: Uint8Array.from(layout.role),
|
|
63
|
+
bounds: layout.bounds,
|
|
64
|
+
version: layout.version,
|
|
65
|
+
attachmentGap: layout.attachment_gap
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** An empty drawing, before the first layout arrives. */
|
|
69
|
+
export const EMPTY_GEOMETRY = {
|
|
70
|
+
positions: new Float32Array(),
|
|
71
|
+
sources: new Float32Array(),
|
|
72
|
+
targets: new Float32Array(),
|
|
73
|
+
segmentEdge: new Int32Array(),
|
|
74
|
+
role: new Uint8Array(),
|
|
75
|
+
bounds: [0, 0, 1, 1]
|
|
76
|
+
};
|
|
77
|
+
/** Positions as placed in the source data: geographic longitude/latitude projected to Web Mercator
|
|
78
|
+
* metres (y down), or drawing coordinates as given (already y down, as `DiagramCanvas` draws them),
|
|
79
|
+
* relative to their centroid. Branches are drawn straight. */
|
|
80
|
+
export function sourceLayout(graph, placed, geographic, tree,
|
|
81
|
+
/** Routed paths from the case (`MultiView.edges`), drawn in place of straight segments. */
|
|
82
|
+
routes = []) {
|
|
83
|
+
const R = 6_378_137;
|
|
84
|
+
const byId = new Map(placed.map((b) => [b.id.toLowerCase(), b]));
|
|
85
|
+
const project = (lon, lat) => {
|
|
86
|
+
if (!geographic)
|
|
87
|
+
return [lon, lat];
|
|
88
|
+
const clamped = Math.max(-85, Math.min(85, lat));
|
|
89
|
+
return [
|
|
90
|
+
(R * lon * Math.PI) / 180,
|
|
91
|
+
-R * Math.log(Math.tan(Math.PI / 4 + (clamped * Math.PI) / 360))
|
|
92
|
+
];
|
|
93
|
+
};
|
|
94
|
+
// Project in float64 and store relative to the centroid: absolute Web Mercator metres reach 1e7,
|
|
95
|
+
// where float32 resolves only to about a metre and neighbouring LV buses would collapse.
|
|
96
|
+
const xy = graph.buses.map((b) => {
|
|
97
|
+
const p = byId.get(b.id.toLowerCase());
|
|
98
|
+
return p ? project(p.lon, p.lat) : null;
|
|
99
|
+
});
|
|
100
|
+
const known = xy.filter((p) => p !== null);
|
|
101
|
+
const cx = known.reduce((s, p) => s + p[0], 0) / Math.max(1, known.length);
|
|
102
|
+
const cy = known.reduce((s, p) => s + p[1], 0) / Math.max(1, known.length);
|
|
103
|
+
const positions = new Float32Array(graph.buses.length * 3);
|
|
104
|
+
xy.forEach((p, i) => {
|
|
105
|
+
if (!p)
|
|
106
|
+
return;
|
|
107
|
+
positions[i * 3] = p[0] - cx;
|
|
108
|
+
positions[i * 3 + 1] = p[1] - cy;
|
|
109
|
+
});
|
|
110
|
+
const index = indexer(graph);
|
|
111
|
+
// A bus without a position (an LV stub behind a pole transformer, say) is drawn next to the
|
|
112
|
+
// nearest connected bus that has one, found breadth-first, rather than at the origin, where its
|
|
113
|
+
// branches would all run to one false point. The offset is a fraction of a typical branch.
|
|
114
|
+
const inferred = new Uint8Array(graph.buses.length);
|
|
115
|
+
if (known.length < graph.buses.length) {
|
|
116
|
+
const neighbours = graph.buses.map(() => []);
|
|
117
|
+
graph.edges.forEach((e) => {
|
|
118
|
+
const a = index(e.from);
|
|
119
|
+
const b = index(e.to);
|
|
120
|
+
if (a < 0 || b < 0 || a === b)
|
|
121
|
+
return;
|
|
122
|
+
neighbours[a].push(b);
|
|
123
|
+
neighbours[b].push(a);
|
|
124
|
+
});
|
|
125
|
+
const lengths = [];
|
|
126
|
+
graph.edges.forEach((e) => {
|
|
127
|
+
const a = index(e.from);
|
|
128
|
+
const b = index(e.to);
|
|
129
|
+
if (a >= 0 && b >= 0 && xy[a] && xy[b])
|
|
130
|
+
lengths.push(Math.hypot(xy[a][0] - xy[b][0], xy[a][1] - xy[b][1]));
|
|
131
|
+
});
|
|
132
|
+
const positive = lengths.filter((l) => l > 0).sort((p, q) => p - q);
|
|
133
|
+
const step = (positive.length ? positive[positive.length >> 1] : 1) * 0.5;
|
|
134
|
+
const placedAt = xy.map((p) => p !== null);
|
|
135
|
+
const queue = xy.flatMap((p, i) => (p ? [i] : []));
|
|
136
|
+
const children = new Int32Array(graph.buses.length);
|
|
137
|
+
for (let head = 0; head < queue.length; head++) {
|
|
138
|
+
const u = queue[head];
|
|
139
|
+
for (const v of neighbours[u]) {
|
|
140
|
+
if (placedAt[v])
|
|
141
|
+
continue;
|
|
142
|
+
placedAt[v] = true;
|
|
143
|
+
inferred[v] = 1;
|
|
144
|
+
// Fan several unplaced neighbours of one bus around it.
|
|
145
|
+
const angle = Math.PI / 4 + children[u]++ * 2.399963;
|
|
146
|
+
positions[v * 3] = positions[u * 3] + step * Math.cos(angle);
|
|
147
|
+
positions[v * 3 + 1] = positions[u * 3 + 1] + step * Math.sin(angle);
|
|
148
|
+
queue.push(v);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
placedAt.forEach((ok, i) => {
|
|
152
|
+
if (!ok)
|
|
153
|
+
inferred[i] = 2;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const role = new Uint8Array(graph.edges.length);
|
|
157
|
+
graph.edges.forEach((e, k) => {
|
|
158
|
+
const a = index(e.from);
|
|
159
|
+
const b = index(e.to);
|
|
160
|
+
role[k] =
|
|
161
|
+
!e.closed || a < 0 || b < 0
|
|
162
|
+
? 2
|
|
163
|
+
: tree.parentEdge[a] === k || tree.parentEdge[b] === k
|
|
164
|
+
? 0
|
|
165
|
+
: 1;
|
|
166
|
+
});
|
|
167
|
+
// Routes by id, so each edge looks only at its namesakes: a scan of every route per edge made
|
|
168
|
+
// this quadratic in the network size.
|
|
169
|
+
const routesById = new Map();
|
|
170
|
+
for (const r of routes) {
|
|
171
|
+
const key = r.id.toLowerCase();
|
|
172
|
+
const list = routesById.get(key);
|
|
173
|
+
if (list)
|
|
174
|
+
list.push(r);
|
|
175
|
+
else
|
|
176
|
+
routesById.set(key, [r]);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
positions,
|
|
180
|
+
...segments(graph, positions, index, (k) => {
|
|
181
|
+
// A route belongs to one edge by id and endpoints; parallel edges keep their own paths.
|
|
182
|
+
const e = graph.edges[k];
|
|
183
|
+
const same = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
184
|
+
const route = routesById
|
|
185
|
+
.get(e.id.toLowerCase())
|
|
186
|
+
?.find((r) => (same(r.from, e.from) && same(r.to, e.to)) || (same(r.from, e.to) && same(r.to, e.from)));
|
|
187
|
+
if (!route || route.path.length < 2)
|
|
188
|
+
return null;
|
|
189
|
+
return route.path.map(([lon, lat]) => {
|
|
190
|
+
const [x, y] = project(lon, lat);
|
|
191
|
+
return [x - cx, y - cy];
|
|
192
|
+
});
|
|
193
|
+
}),
|
|
194
|
+
role,
|
|
195
|
+
bounds: bounds(positions),
|
|
196
|
+
...(inferred.some((v) => v) ? { inferred } : {}),
|
|
197
|
+
...(geographic ? { mercatorOrigin: [cx, cy] } : {})
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** The drawn schematic as a PowerIO diagram layer (GeoJSON with `powerio_geo.space = "diagram"`):
|
|
201
|
+
* one point per bus and one route per line, matched by line id, y down. `kind: "synthetic"` marks
|
|
202
|
+
* the positions as computed, and `tellegen_layout` records how. Load it with the case to reuse the
|
|
203
|
+
* layout, or hand it to PowerIO tooling. */
|
|
204
|
+
export function diagramLayer(graph, geometry, provenance) {
|
|
205
|
+
const round = (v) => Math.round(v * 100) / 100;
|
|
206
|
+
const features = graph.buses.map((bus, i) => ({
|
|
207
|
+
type: 'Feature',
|
|
208
|
+
properties: { bus: bus.id },
|
|
209
|
+
geometry: {
|
|
210
|
+
type: 'Point',
|
|
211
|
+
coordinates: [round(geometry.positions[i * 3]), round(geometry.positions[i * 3 + 1])]
|
|
212
|
+
}
|
|
213
|
+
}));
|
|
214
|
+
const routes = new Map();
|
|
215
|
+
for (let s = 0; s < geometry.segmentEdge.length; s++) {
|
|
216
|
+
const k = geometry.segmentEdge[s];
|
|
217
|
+
const points = routes.get(k) ?? [
|
|
218
|
+
[round(geometry.sources[s * 3]), round(geometry.sources[s * 3 + 1])]
|
|
219
|
+
];
|
|
220
|
+
points.push([round(geometry.targets[s * 3]), round(geometry.targets[s * 3 + 1])]);
|
|
221
|
+
routes.set(k, points);
|
|
222
|
+
}
|
|
223
|
+
graph.edges.forEach((edge, k) => {
|
|
224
|
+
const points = routes.get(k);
|
|
225
|
+
// Only lines carry routes in a PowerIO layer; switches and transformers follow their buses.
|
|
226
|
+
if (edge.kind !== 'line' || !points || points.length < 2)
|
|
227
|
+
return;
|
|
228
|
+
features.push({
|
|
229
|
+
type: 'Feature',
|
|
230
|
+
properties: { id: edge.id, bus_from: edge.from, bus_to: edge.to },
|
|
231
|
+
geometry: { type: 'LineString', coordinates: points }
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
return JSON.stringify({
|
|
235
|
+
type: 'FeatureCollection',
|
|
236
|
+
powerio_geo: { space: 'diagram', kind: 'synthetic' },
|
|
237
|
+
tellegen_layout: { algorithm: 'schematic', ...provenance },
|
|
238
|
+
features
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** One search over buses, branches and attachments by source identity. Linear scan, no fuzzy
|
|
2
|
+
* guessing: an engineer searching for `LV12` must not be offered `LV21`. */
|
|
3
|
+
export type EntityKind = 'bus' | 'branch' | 'attachment';
|
|
4
|
+
export interface SearchEntity {
|
|
5
|
+
kind: EntityKind;
|
|
6
|
+
index: number;
|
|
7
|
+
id: string;
|
|
8
|
+
/** `bus`, or the equipment family such as `line` or `transformer / 2w`. */
|
|
9
|
+
family: string;
|
|
10
|
+
/** Source label when a drawing combines several networks. */
|
|
11
|
+
source?: string;
|
|
12
|
+
/** `source/id` when a drawing combines several networks; equal to `id` otherwise. */
|
|
13
|
+
qualified: string;
|
|
14
|
+
}
|
|
15
|
+
export interface SearchIndex {
|
|
16
|
+
entries: (SearchEntity & {
|
|
17
|
+
idLower: string;
|
|
18
|
+
qualifiedLower: string;
|
|
19
|
+
})[];
|
|
20
|
+
byKey: Map<string, SearchEntity>;
|
|
21
|
+
}
|
|
22
|
+
export interface SearchMatch extends SearchEntity {
|
|
23
|
+
exact: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Readable URL identity, e.g. `branch:line:feeder/L1`, unique even when raw IDs repeat. */
|
|
26
|
+
export declare function entityKey(e: Pick<SearchEntity, 'kind' | 'family' | 'qualified'>): string;
|
|
27
|
+
export declare function buildSearchIndex(entities: SearchEntity[]): SearchIndex;
|
|
28
|
+
/** Rank: exact ID, exact qualified ID, ID prefix, then substring; ties prefer buses, shorter IDs,
|
|
29
|
+
* then network order. */
|
|
30
|
+
export declare function search(index: SearchIndex, query: string, { limit, kinds }?: {
|
|
31
|
+
limit?: number | undefined;
|
|
32
|
+
kinds?: EntityKind[] | null | undefined;
|
|
33
|
+
}): {
|
|
34
|
+
matches: SearchMatch[];
|
|
35
|
+
total: number;
|
|
36
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const KIND_ORDER = { bus: 0, branch: 1, attachment: 2 };
|
|
2
|
+
/** Readable URL identity, e.g. `branch:line:feeder/L1`, unique even when raw IDs repeat. */
|
|
3
|
+
export function entityKey(e) {
|
|
4
|
+
const family = e.kind === 'bus' ? '' : `${e.family.replace(' / ', '.')}:`;
|
|
5
|
+
return `${e.kind}:${family}${e.qualified}`;
|
|
6
|
+
}
|
|
7
|
+
export function buildSearchIndex(entities) {
|
|
8
|
+
const entries = entities.map((e) => ({
|
|
9
|
+
...e,
|
|
10
|
+
idLower: e.id.toLowerCase(),
|
|
11
|
+
qualifiedLower: e.qualified.toLowerCase()
|
|
12
|
+
}));
|
|
13
|
+
return { entries, byKey: new Map(entities.map((e) => [entityKey(e), e])) };
|
|
14
|
+
}
|
|
15
|
+
/** Rank: exact ID, exact qualified ID, ID prefix, then substring; ties prefer buses, shorter IDs,
|
|
16
|
+
* then network order. */
|
|
17
|
+
export function search(index, query, { limit = 8, kinds = null } = {}) {
|
|
18
|
+
const q = query.trim().toLowerCase();
|
|
19
|
+
if (!q)
|
|
20
|
+
return { matches: [], total: 0 };
|
|
21
|
+
const hits = [];
|
|
22
|
+
for (const e of index.entries) {
|
|
23
|
+
if (kinds && !kinds.includes(e.kind))
|
|
24
|
+
continue;
|
|
25
|
+
const rank = e.idLower === q
|
|
26
|
+
? 0
|
|
27
|
+
: e.qualifiedLower === q
|
|
28
|
+
? 1
|
|
29
|
+
: e.idLower.startsWith(q)
|
|
30
|
+
? 2
|
|
31
|
+
: e.idLower.includes(q) || e.qualifiedLower.includes(q)
|
|
32
|
+
? 3
|
|
33
|
+
: -1;
|
|
34
|
+
if (rank >= 0)
|
|
35
|
+
hits.push({ rank, e });
|
|
36
|
+
}
|
|
37
|
+
hits.sort((a, b) => a.rank - b.rank ||
|
|
38
|
+
KIND_ORDER[a.e.kind] - KIND_ORDER[b.e.kind] ||
|
|
39
|
+
a.e.id.length - b.e.id.length ||
|
|
40
|
+
a.e.index - b.e.index);
|
|
41
|
+
return {
|
|
42
|
+
matches: hits.slice(0, limit).map(({ rank, e }) => {
|
|
43
|
+
const { idLower: _i, qualifiedLower: _q, ...entity } = e;
|
|
44
|
+
return { ...entity, exact: rank <= 1 };
|
|
45
|
+
}),
|
|
46
|
+
total: hits.length
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { SchematicGeometry } from './schematic-layout.js';
|
|
2
|
+
/** One branch of a shared route, drawn spread out from the others. */
|
|
3
|
+
export interface FannedBranch {
|
|
4
|
+
edge: number;
|
|
5
|
+
/** Polyline in drawing units: it starts and ends on the true route's ends and runs offset
|
|
6
|
+
* between them, so every branch still visibly meets its buses. */
|
|
7
|
+
path: [number, number][];
|
|
8
|
+
/** Where the branch's marks sit (the middle of the offset run) and its direction there. */
|
|
9
|
+
mid: [number, number];
|
|
10
|
+
dir: [number, number];
|
|
11
|
+
}
|
|
12
|
+
/** Lay the members of a shared route side by side: each is offset perpendicular to the route by a
|
|
13
|
+
* fixed screen distance (`spreadPx` between neighbours), tapering to zero at both ends over at most
|
|
14
|
+
* `taperPx` or a fifth of the route. `scale` is pixels per drawing unit. Members keep their order,
|
|
15
|
+
* centred on the true route. */
|
|
16
|
+
export declare function fanRoute(members: number[], host: Int32Array, geometry: SchematicGeometry, scale: number, options?: {
|
|
17
|
+
spreadPx?: number;
|
|
18
|
+
taperPx?: number;
|
|
19
|
+
}): FannedBranch[];
|
|
20
|
+
/** The middle of a shared route, for its count badge and its member card. */
|
|
21
|
+
export declare function routeMiddle(members: number[], host: Int32Array, geometry: SchematicGeometry): [number, number] | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Lay the members of a shared route side by side: each is offset perpendicular to the route by a
|
|
2
|
+
* fixed screen distance (`spreadPx` between neighbours), tapering to zero at both ends over at most
|
|
3
|
+
* `taperPx` or a fifth of the route. `scale` is pixels per drawing unit. Members keep their order,
|
|
4
|
+
* centred on the true route. */
|
|
5
|
+
export function fanRoute(members, host, geometry, scale, options = {}) {
|
|
6
|
+
const spread = (options.spreadPx ?? 7) / scale;
|
|
7
|
+
const taperMax = (options.taperPx ?? 14) / scale;
|
|
8
|
+
const s = host[members[0]];
|
|
9
|
+
if (s < 0)
|
|
10
|
+
return [];
|
|
11
|
+
const a = [geometry.sources[s * 3], geometry.sources[s * 3 + 1]];
|
|
12
|
+
const b = [geometry.targets[s * 3], geometry.targets[s * 3 + 1]];
|
|
13
|
+
const len = Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
14
|
+
if (!(len > 0))
|
|
15
|
+
return [];
|
|
16
|
+
const [ux, uy] = [(b[0] - a[0]) / len, (b[1] - a[1]) / len];
|
|
17
|
+
const [nx, ny] = [-uy, ux];
|
|
18
|
+
const taper = Math.min(taperMax, len / 5);
|
|
19
|
+
return members.map((edge, i) => {
|
|
20
|
+
const off = (i - (members.length - 1) / 2) * spread;
|
|
21
|
+
const p1 = [a[0] + ux * taper + nx * off, a[1] + uy * taper + ny * off];
|
|
22
|
+
const p2 = [b[0] - ux * taper + nx * off, b[1] - uy * taper + ny * off];
|
|
23
|
+
return {
|
|
24
|
+
edge,
|
|
25
|
+
path: [a, p1, p2, b],
|
|
26
|
+
mid: [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2],
|
|
27
|
+
dir: [ux, uy]
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** The middle of a shared route, for its count badge and its member card. */
|
|
32
|
+
export function routeMiddle(members, host, geometry) {
|
|
33
|
+
const s = host[members[0]];
|
|
34
|
+
if (s < 0)
|
|
35
|
+
return null;
|
|
36
|
+
return [
|
|
37
|
+
(geometry.sources[s * 3] + geometry.targets[s * 3]) / 2,
|
|
38
|
+
(geometry.sources[s * 3 + 1] + geometry.targets[s * 3 + 1]) / 2
|
|
39
|
+
];
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The distinctive core of each id: parts (split at `_ - . : /` and spaces) that every id in the
|
|
2
|
+
* set shares at its start or end are dropped, so `75_MVLV060_Transformer` among other
|
|
3
|
+
* `75_…_Transformer` ids reads `MVLV060`. A single id, or a set where dropping would leave an id
|
|
4
|
+
* empty or make two equal, keeps its ids whole. */
|
|
5
|
+
export declare function distinctiveCores(ids: string[]): string[];
|
|
6
|
+
/** Short display names for long ids such as `lv_extension_02ad4cfa5ebe_0062383670bceb3c28d5`.
|
|
7
|
+
* Parts every id shares at its start or end are dropped first ({@link distinctiveCores}); cores up
|
|
8
|
+
* to `max` characters are kept, longer ones keep their head and tail around an ellipsis, with the
|
|
9
|
+
* tail lengthened past `max` only where needed to keep every short name unique. The full id stays
|
|
10
|
+
* in tooltips and panels. */
|
|
11
|
+
export declare function shortNames(ids: string[], max?: number): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The distinctive core of each id: parts (split at `_ - . : /` and spaces) that every id in the
|
|
2
|
+
* set shares at its start or end are dropped, so `75_MVLV060_Transformer` among other
|
|
3
|
+
* `75_…_Transformer` ids reads `MVLV060`. A single id, or a set where dropping would leave an id
|
|
4
|
+
* empty or make two equal, keeps its ids whole. */
|
|
5
|
+
export function distinctiveCores(ids) {
|
|
6
|
+
if (ids.length < 2)
|
|
7
|
+
return [...ids];
|
|
8
|
+
const parts = ids.map((id) => [...id.matchAll(/[^_\-.:/\s]+/g)]);
|
|
9
|
+
const shortest = Math.min(...parts.map((p) => p.length));
|
|
10
|
+
const same = (pick) => {
|
|
11
|
+
const first = pick(parts[0])?.[0];
|
|
12
|
+
return first !== undefined && parts.every((p) => pick(p)?.[0] === first);
|
|
13
|
+
};
|
|
14
|
+
let lead = 0;
|
|
15
|
+
while (lead < shortest - 1 && same((p) => p[lead]))
|
|
16
|
+
lead++;
|
|
17
|
+
let trail = 0;
|
|
18
|
+
while (lead + trail < shortest - 1 && same((p) => p[p.length - 1 - trail]))
|
|
19
|
+
trail++;
|
|
20
|
+
if (!lead && !trail)
|
|
21
|
+
return [...ids];
|
|
22
|
+
const cores = ids.map((id, i) => {
|
|
23
|
+
const p = parts[i];
|
|
24
|
+
const start = p[lead].index;
|
|
25
|
+
const last = p[p.length - 1 - trail];
|
|
26
|
+
return id.slice(start, last.index + last[0].length);
|
|
27
|
+
});
|
|
28
|
+
return new Set(cores).size === cores.length ? cores : [...ids];
|
|
29
|
+
}
|
|
30
|
+
/** Short display names for long ids such as `lv_extension_02ad4cfa5ebe_0062383670bceb3c28d5`.
|
|
31
|
+
* Parts every id shares at its start or end are dropped first ({@link distinctiveCores}); cores up
|
|
32
|
+
* to `max` characters are kept, longer ones keep their head and tail around an ellipsis, with the
|
|
33
|
+
* tail lengthened past `max` only where needed to keep every short name unique. The full id stays
|
|
34
|
+
* in tooltips and panels. */
|
|
35
|
+
export function shortNames(ids, max = 20) {
|
|
36
|
+
const cores = distinctiveCores(ids);
|
|
37
|
+
const out = [...cores];
|
|
38
|
+
const long = cores.flatMap((id, i) => (id.length > max ? [i] : []));
|
|
39
|
+
const head = max - 6;
|
|
40
|
+
let tail = 5;
|
|
41
|
+
let pending = long;
|
|
42
|
+
while (pending.length) {
|
|
43
|
+
for (const i of pending) {
|
|
44
|
+
const id = cores[i];
|
|
45
|
+
out[i] = id.length <= head + tail + 1 ? id : `${id.slice(0, head)}…${id.slice(-tail)}`;
|
|
46
|
+
}
|
|
47
|
+
const seen = new Map();
|
|
48
|
+
for (const name of out)
|
|
49
|
+
seen.set(name, (seen.get(name) ?? 0) + 1);
|
|
50
|
+
pending = pending.filter((i) => seen.get(out[i]) > 1 && out[i] !== cores[i]);
|
|
51
|
+
tail += 3;
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { McComplex, McPfResult } from '@tellegen/engine';
|
|
2
|
+
/** Which reference a pair voltage is measured against. */
|
|
3
|
+
export type PairZone = 'phase–neutral' | 'phase–phase' | 'phase–ground';
|
|
4
|
+
/** The second member of a phase–ground pair. */
|
|
5
|
+
export declare const GROUND = "ground";
|
|
6
|
+
/** One declared terminal-pair voltage magnitude and, when declared, its limits. */
|
|
7
|
+
export interface PairVoltage {
|
|
8
|
+
/** Two terminals, or a terminal and {@link GROUND}. */
|
|
9
|
+
pair: [string, string];
|
|
10
|
+
/** Set from the bus's declared neutral, whatever that terminal is named. */
|
|
11
|
+
zone: PairZone;
|
|
12
|
+
volts: number;
|
|
13
|
+
min: number | null;
|
|
14
|
+
max: number | null;
|
|
15
|
+
}
|
|
16
|
+
export interface UnbalanceValue {
|
|
17
|
+
percent: number;
|
|
18
|
+
/** `VUF` uses phasors (|V2| / |V1|); the magnitude-only fallbacks name their standard. */
|
|
19
|
+
definition: 'VUF' | 'IEEE PVUR' | 'NEMA LVUR';
|
|
20
|
+
}
|
|
21
|
+
export interface BusSnapshot {
|
|
22
|
+
pairs: PairVoltage[];
|
|
23
|
+
unbalance: UnbalanceValue | null;
|
|
24
|
+
}
|
|
25
|
+
export interface ConductorCurrent {
|
|
26
|
+
terminal: string;
|
|
27
|
+
amps: number;
|
|
28
|
+
}
|
|
29
|
+
/** End totals over every terminal of the element, positive into the equipment. */
|
|
30
|
+
export interface BranchEnd {
|
|
31
|
+
p: number;
|
|
32
|
+
q: number;
|
|
33
|
+
currents: ConductorCurrent[];
|
|
34
|
+
}
|
|
35
|
+
export interface BranchSnapshot {
|
|
36
|
+
ends: [BranchEnd | null, BranchEnd | null];
|
|
37
|
+
/** Loading of the whole piece of equipment: every edge of a multi-winding transformer shows the
|
|
38
|
+
* same value. */
|
|
39
|
+
loading: number | null;
|
|
40
|
+
basis: string | null;
|
|
41
|
+
/** The first edge of its equipment. A multi-winding transformer projects to one edge per
|
|
42
|
+
* winding pair; only its first edge counts in totals and issue lists. */
|
|
43
|
+
primary: boolean;
|
|
44
|
+
/** Active and reactive power the equipment consumes (kW, kvar), summed once over every port;
|
|
45
|
+
* on the primary edge only, and null when a port is missing. */
|
|
46
|
+
loss: {
|
|
47
|
+
p: number;
|
|
48
|
+
q: number;
|
|
49
|
+
} | null;
|
|
50
|
+
}
|
|
51
|
+
/** A result snapshot indexed like the drawing: `buses[i]` and `branches[b]` follow the network order. */
|
|
52
|
+
export interface ResultSnapshot {
|
|
53
|
+
kind: 'solved' | 'stress';
|
|
54
|
+
label: string;
|
|
55
|
+
iterations: number | null;
|
|
56
|
+
buses: BusSnapshot[];
|
|
57
|
+
branches: BranchSnapshot[];
|
|
58
|
+
}
|
|
59
|
+
/** Declared limits for one bus, by reference; a bus can declare several. Phase–neutral limits
|
|
60
|
+
* list one band per phase terminal, phase–ground limits one per bounded terminal in terminal
|
|
61
|
+
* order. A phase–phase band applies to its pair of terminals in either order, or to every pair
|
|
62
|
+
* when `pair` is null (a uniform band). */
|
|
63
|
+
export interface BusLimits {
|
|
64
|
+
phaseNeutral?: {
|
|
65
|
+
min: number[];
|
|
66
|
+
max: number[];
|
|
67
|
+
};
|
|
68
|
+
phasePhase?: {
|
|
69
|
+
pair: [string, string] | null;
|
|
70
|
+
min: number;
|
|
71
|
+
max: number;
|
|
72
|
+
}[];
|
|
73
|
+
phaseGround?: {
|
|
74
|
+
terminal: string;
|
|
75
|
+
min: number;
|
|
76
|
+
max: number;
|
|
77
|
+
}[];
|
|
78
|
+
}
|
|
79
|
+
export interface SnapshotBus {
|
|
80
|
+
id: string;
|
|
81
|
+
terminals: string[];
|
|
82
|
+
/** The declared neutral; `null` when the model declares there is none, absent when it is silent
|
|
83
|
+
* (the familiar aliases then apply). */
|
|
84
|
+
neutral?: string | null;
|
|
85
|
+
limits?: BusLimits | null;
|
|
86
|
+
}
|
|
87
|
+
/** Ampacity per conductor, or a transformer's apparent-power rating (VA) at each bus it connects,
|
|
88
|
+
* keyed by bus id. */
|
|
89
|
+
export type BranchRating = {
|
|
90
|
+
kind: 'current';
|
|
91
|
+
amps: number[];
|
|
92
|
+
} | {
|
|
93
|
+
kind: 'apparent';
|
|
94
|
+
byBus: Record<string, number>;
|
|
95
|
+
};
|
|
96
|
+
export interface SnapshotBranch {
|
|
97
|
+
id: string;
|
|
98
|
+
from: string;
|
|
99
|
+
to: string;
|
|
100
|
+
fromTerminals: string[];
|
|
101
|
+
toTerminals: string[];
|
|
102
|
+
inService: boolean;
|
|
103
|
+
/** Element kind, matched against port kinds so a line and a switch sharing a name stay apart. */
|
|
104
|
+
kind?: string;
|
|
105
|
+
rating?: BranchRating | null;
|
|
106
|
+
}
|
|
107
|
+
/** Voltage unbalance factor |V2| / |V1| in percent, from three phasors in phase order. The ratio
|
|
108
|
+
* is the same for phase–neutral and phase–phase voltages, so either reference works. */
|
|
109
|
+
export declare function sequenceUnbalance(a: McComplex, b: McComplex, c: McComplex): number | null;
|
|
110
|
+
/** Project a full multiconductor power-flow result onto the drawing's bus and branch order.
|
|
111
|
+
* Missing ports leave an end `null`; nothing is interpolated. */
|
|
112
|
+
export declare function snapshotFromMcPf(result: McPfResult, buses: SnapshotBus[], branches: SnapshotBranch[], label?: string): ResultSnapshot;
|
|
113
|
+
/** Bus results from a live session's transferred terminal arrays (`[re, im]` interleaved), for when
|
|
114
|
+
* the full result with element ports has not been fetched. Branches are left without results. */
|
|
115
|
+
export declare function snapshotBusesFromTerminals(ids: ArrayLike<[string, string]>, voltages: ArrayLike<number>, buses: SnapshotBus[]): BusSnapshot[];
|