Several marked routes running down one path drew as a single line, only the topmost color visible. maplibre-gl-lanes puts each route in its own lane; OSM relations already share exact vertices, which is all it needs. One LaneLayer across every picked country; click hit-testing and the highlight move to queryLane/setHighlight.
3558 lines
251 KiB
JavaScript
3558 lines
251 KiB
JavaScript
var __defProp = Object.defineProperty;
|
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value;
|
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key != "symbol" ? key + "" : key, value);
|
|
|
|
// src/core/graph.ts
|
|
function lngLatToMercator(lng, lat) {
|
|
let x = (lng + 180) / 360, s = Math.sin(lat * Math.PI / 180), y = 0.5 - 0.25 * Math.log((1 + s) / (1 - s)) / Math.PI;
|
|
return [x, y];
|
|
}
|
|
function mercatorToLngLat(x, y) {
|
|
let lng = x * 360 - 180, lat = Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180 / Math.PI;
|
|
return [lng, lat];
|
|
}
|
|
function buildLineGraph(features, opts = {}) {
|
|
let routeProp = opts.routeProperty ?? "route", colorProp = opts.colorProperty ?? "color", nameProp = opts.nameProperty ?? "name", snap = opts.snapDegrees ?? 1e-7, uniform = opts.uniformProperties ?? [], routes = /* @__PURE__ */ new Map(), vertexPos = /* @__PURE__ */ new Map(), elem = /* @__PURE__ */ new Map(), adjacency = /* @__PURE__ */ new Map(), keyOf = (lng, lat) => {
|
|
let kx = Math.round(lng / snap), ky = Math.round(lat / snap);
|
|
return `${kx},${ky}`;
|
|
};
|
|
for (let f of features) {
|
|
if (!f.geometry) continue;
|
|
let props = f.properties ?? {}, rid = props[routeProp];
|
|
if (rid == null) continue;
|
|
let routeId = String(rid);
|
|
if (!routes.has(routeId)) {
|
|
let override = opts.routes?.[routeId] ?? {};
|
|
routes.set(routeId, {
|
|
id: routeId,
|
|
color: override.color ?? props[colorProp] ?? "#888888",
|
|
name: override.name ?? props[nameProp] ?? void 0,
|
|
dash: override.dash,
|
|
dashColor: override.dashColor,
|
|
dashCap: override.dashCap,
|
|
casing: override.casing
|
|
});
|
|
}
|
|
let lines = f.geometry.type === "LineString" ? [f.geometry.coordinates] : f.geometry.type === "MultiLineString" ? f.geometry.coordinates : [], uniformProps = uniform.length ? Object.fromEntries(uniform.map((k) => [k, props[k]])) : null;
|
|
for (let line of lines) {
|
|
let prevKey = null;
|
|
for (let c of line) {
|
|
let k = keyOf(c[0], c[1]);
|
|
if (vertexPos.has(k) || vertexPos.set(k, lngLatToMercator(c[0], c[1])), prevKey !== null && prevKey !== k) {
|
|
let forward = prevKey < k, ek = forward ? `${prevKey}|${k}` : `${k}|${prevKey}`, e = elem.get(ek);
|
|
if (!e) {
|
|
e = { u: forward ? prevKey : k, v: forward ? k : prevKey, routes: /* @__PURE__ */ new Map(), props: uniformProps, sig: null }, elem.set(ek, e);
|
|
for (let vk of [prevKey, k]) {
|
|
let s = adjacency.get(vk);
|
|
s || adjacency.set(vk, s = /* @__PURE__ */ new Set()), s.add(ek);
|
|
}
|
|
}
|
|
let dir = forward ? 1 : -1, prev = e.routes.get(routeId);
|
|
e.routes.set(routeId, prev === void 0 || prev === dir ? dir : 0);
|
|
}
|
|
prevKey = k;
|
|
}
|
|
}
|
|
}
|
|
let sigOf = (e) => {
|
|
if (e.sig === null) {
|
|
let routeSig = [...e.routes.keys()].sort().join("\0");
|
|
e.sig = routeSig + "" + (e.props ? JSON.stringify(uniform.map((k) => e.props[k] ?? null)) : "");
|
|
}
|
|
return e.sig;
|
|
}, junction = /* @__PURE__ */ new Map(), isJunction = (vk) => {
|
|
let is = junction.get(vk);
|
|
if (is === void 0) {
|
|
let inc = adjacency.get(vk);
|
|
if (inc.size !== 2)
|
|
is = !0;
|
|
else {
|
|
let [k1, k2] = inc;
|
|
is = sigOf(elem.get(k1)) !== sigOf(elem.get(k2));
|
|
}
|
|
junction.set(vk, is);
|
|
}
|
|
return is;
|
|
}, nodeIndex = /* @__PURE__ */ new Map(), nodes = [], nodeFor = (vk) => {
|
|
let id = nodeIndex.get(vk);
|
|
if (id === void 0) {
|
|
let [x, y] = vertexPos.get(vk);
|
|
id = nodes.length, nodeIndex.set(vk, id), nodes.push({ id, x, y, ports: [], transitions: [] });
|
|
}
|
|
return id;
|
|
}, edges = [], visited = /* @__PURE__ */ new Set(), walk = (startVk, firstEk) => {
|
|
let coordsKeys = [startVk], elemKeys = [], vk = startVk, ek = firstEk;
|
|
for (; ; ) {
|
|
visited.add(ek), elemKeys.push(ek);
|
|
let e = elem.get(ek), next = e.u === vk ? e.v : e.u;
|
|
if (coordsKeys.push(next), vk = next, isJunction(vk) || vk === startVk) break;
|
|
let inc = [...adjacency.get(vk)], nk = inc[0] === ek ? inc[1] : inc[0];
|
|
if (visited.has(nk)) break;
|
|
ek = nk;
|
|
}
|
|
let first = elem.get(elemKeys[0]), a = nodeFor(coordsKeys[0]), b = nodeFor(coordsKeys[coordsKeys.length - 1]), coords = [];
|
|
for (let k of coordsKeys) {
|
|
let p = vertexPos.get(k);
|
|
coords.push(p[0], p[1]);
|
|
}
|
|
let id = edges.length, direction = /* @__PURE__ */ new Map();
|
|
for (let [r, d] of first.routes) {
|
|
let walkForward = first.u === coordsKeys[0];
|
|
direction.set(r, d === 0 ? 0 : walkForward ? d : -d);
|
|
}
|
|
let edge = { id, a, b, coords, routes: [...first.routes.keys()].sort(), direction, order: [] };
|
|
first.props && (edge.properties = first.props), edges.push(edge), nodes[a].ports.push({ edge: id, end: "a" }), nodes[b].ports.push({ edge: id, end: "b" });
|
|
};
|
|
for (let vk of adjacency.keys())
|
|
if (isJunction(vk))
|
|
for (let ek of adjacency.get(vk))
|
|
visited.has(ek) || walk(vk, ek);
|
|
for (let ek of elem.keys())
|
|
visited.has(ek) || walk(elem.get(ek).u, ek);
|
|
let chains = deriveChains(nodes, edges, routes);
|
|
return { nodes, edges, routes, chains };
|
|
}
|
|
function deriveChains(nodes, edges, routes) {
|
|
for (let n of nodes) n.transitions = [];
|
|
let chains = /* @__PURE__ */ new Map(), portIndex = (node, edge, end) => node.ports.findIndex((p) => p.edge === edge && p.end === end), outDir = (edge, end) => {
|
|
let c = edges[edge].coords, dx, dy;
|
|
if (end === "a")
|
|
dx = c[2] - c[0], dy = c[3] - c[1];
|
|
else {
|
|
let n = c.length;
|
|
dx = c[n - 4] - c[n - 2], dy = c[n - 3] - c[n - 1];
|
|
}
|
|
let l = Math.hypot(dx, dy) || 1;
|
|
return [dx / l, dy / l];
|
|
};
|
|
for (let rid of routes.keys()) {
|
|
let routeEdges = edges.filter((e) => e.routes.includes(rid));
|
|
if (!routeEdges.length) {
|
|
chains.set(rid, []);
|
|
continue;
|
|
}
|
|
let unused = new Set(routeEdges.map((e) => e.id)), degree = /* @__PURE__ */ new Map();
|
|
for (let e of routeEdges)
|
|
degree.set(e.a, (degree.get(e.a) ?? 0) + 1), degree.set(e.b, (degree.get(e.b) ?? 0) + 1);
|
|
let out = [], chainEndPorts = [];
|
|
for (; unused.size; ) {
|
|
let startEdge = null, startNode = -1;
|
|
for (let id of unused) {
|
|
let e = edges[id];
|
|
if (degree.get(e.a) % 2 === 1 && (startEdge === null || startNode !== e.a && startNode !== e.b)) {
|
|
startEdge = e, startNode = e.a;
|
|
break;
|
|
}
|
|
if (degree.get(e.b) % 2 === 1) {
|
|
startEdge = e, startNode = e.b;
|
|
break;
|
|
}
|
|
}
|
|
if (!startEdge) {
|
|
let id = Math.min(...unused);
|
|
startEdge = edges[id], startNode = startEdge.a;
|
|
}
|
|
let steps = [], node = startNode, arriving = null, nextEdge = startEdge;
|
|
for (; nextEdge; ) {
|
|
let forward = nextEdge.a === node;
|
|
steps.push({ edge: nextEdge.id, forward }), unused.delete(nextEdge.id);
|
|
let far = forward ? nextEdge.b : nextEdge.a, farEnd = forward ? "b" : "a", d = outDir(nextEdge.id, farEnd);
|
|
arriving = [-d[0], -d[1]], node = far;
|
|
let best = null, bestDot = -1 / 0;
|
|
for (let p of nodes[node].ports) {
|
|
if (!unused.has(p.edge)) continue;
|
|
let o = outDir(p.edge, p.end), dot = o[0] * arriving[0] + o[1] * arriving[1];
|
|
(dot > bestDot + 1e-9 || Math.abs(dot - bestDot) <= 1e-9 && best && p.edge < best.id) && (bestDot = dot, best = edges[p.edge]);
|
|
}
|
|
nextEdge = best;
|
|
}
|
|
let first = steps[0], last = steps[steps.length - 1], firstNode = first.forward ? edges[first.edge].a : edges[first.edge].b, lastNode = last.forward ? edges[last.edge].b : edges[last.edge].a, closed = steps.length > 1 && firstNode === lastNode, chain = { steps, closed };
|
|
out.push(chain);
|
|
let stepEnd = (s) => ({ node: s.forward ? edges[s.edge].b : edges[s.edge].a, end: s.forward ? "b" : "a" }), stepStart = (s) => ({ node: s.forward ? edges[s.edge].a : edges[s.edge].b, end: s.forward ? "a" : "b" }), pairs = steps.length - (closed ? 0 : 1);
|
|
for (let i = 0; i < pairs; i++) {
|
|
let s = steps[i], t = steps[(i + 1) % steps.length], n = nodes[stepEnd(s).node];
|
|
n.transitions.push({ route: rid, from: portIndex(n, s.edge, stepEnd(s).end), to: portIndex(n, t.edge, stepStart(t).end) });
|
|
}
|
|
closed || (chainEndPorts.push({ node: firstNode, port: portIndex(nodes[firstNode], first.edge, stepStart(first).end), chain, end: "start" }), chainEndPorts.push({ node: lastNode, port: portIndex(nodes[lastNode], last.edge, stepEnd(last).end), chain, end: "end" }));
|
|
}
|
|
let seen = /* @__PURE__ */ new Set();
|
|
for (let ce of chainEndPorts) {
|
|
let n = nodes[ce.node], own = n.ports[ce.port], arriveDir = outDir(own.edge, own.end).map((v) => -v), best = -1, bestDot = -1 / 0;
|
|
if (n.ports.forEach((p, i) => {
|
|
if (i === ce.port || !edges[p.edge].routes.includes(rid)) return;
|
|
let o = outDir(p.edge, p.end), dot = o[0] * arriveDir[0] + o[1] * arriveDir[1];
|
|
dot > bestDot && (bestDot = dot, best = i);
|
|
}), best < 0) continue;
|
|
let key = `${ce.node}:${Math.min(ce.port, best)}:${Math.max(ce.port, best)}`;
|
|
seen.has(key) || (seen.add(key), n.transitions.push({ route: rid, from: ce.port, to: best }), ce.end === "start" ? ce.chain.mergeStart = n.ports[best].edge : ce.chain.mergeEnd = n.ports[best].edge);
|
|
}
|
|
chains.set(rid, out);
|
|
}
|
|
return chains;
|
|
}
|
|
function filterGraph(g, visible) {
|
|
let keepEdge = /* @__PURE__ */ new Map(), edges = [];
|
|
for (let e of g.edges) {
|
|
let routes2 = e.routes.filter(visible);
|
|
routes2.length && (keepEdge.set(e.id, edges.length), edges.push({ ...e, id: edges.length, routes: routes2, order: [], baseline: void 0, direction: new Map([...e.direction].filter(([r]) => visible(r))) }));
|
|
}
|
|
let nodes = g.nodes.map((n) => ({
|
|
...n,
|
|
transitions: [],
|
|
ports: n.ports.filter((p) => keepEdge.has(p.edge)).map((p) => ({ edge: keepEdge.get(p.edge), end: p.end }))
|
|
})), routes = new Map([...g.routes].filter(([r]) => visible(r))), chains = deriveChains(nodes, edges, routes);
|
|
return { nodes, edges, routes, chains };
|
|
}
|
|
|
|
// src/core/geometry.ts
|
|
function polylineLength(p) {
|
|
let len = 0;
|
|
for (let i = 2; i < p.length; i += 2)
|
|
len += Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
|
|
return len;
|
|
}
|
|
function reversed(p) {
|
|
let out = new Array(p.length);
|
|
for (let i = 0, j = p.length - 2; i < p.length; i += 2, j -= 2)
|
|
out[i] = p[j], out[i + 1] = p[j + 1];
|
|
return out;
|
|
}
|
|
function startDirection(p) {
|
|
for (let i = 2; i < p.length; i += 2) {
|
|
let dx = p[i] - p[0], dy = p[i + 1] - p[1], l = Math.hypot(dx, dy);
|
|
if (l > 0) return [dx / l, dy / l];
|
|
}
|
|
return [0, 0];
|
|
}
|
|
function endDirection(p) {
|
|
let n = p.length;
|
|
for (let i = n - 4; i >= 0; i -= 2) {
|
|
let dx = p[n - 2] - p[i], dy = p[n - 1] - p[i + 1], l = Math.hypot(dx, dy);
|
|
if (l > 0) return [dx / l, dy / l];
|
|
}
|
|
return [0, 0];
|
|
}
|
|
function trimPolyline(p, fromStart, fromEnd, keepFraction = 0.2) {
|
|
let n = p.length / 2, seg = new Float64Array(Math.max(0, n - 1)), total = 0;
|
|
for (let i = 0; i < n - 1; i++)
|
|
seg[i] = Math.hypot(p[i * 2 + 2] - p[i * 2], p[i * 2 + 3] - p[i * 2 + 1]), total += seg[i];
|
|
if (total <= 0) return p.slice();
|
|
let maxCut = total * (1 - keepFraction);
|
|
if (fromStart + fromEnd > maxCut) {
|
|
let s = maxCut / (fromStart + fromEnd);
|
|
fromStart *= s, fromEnd *= s;
|
|
}
|
|
let startPt = pointAlongSegments(p, seg, fromStart), endPt = pointAlongSegments(p, seg, total - fromEnd), out = [startPt.x, startPt.y], acc = 0;
|
|
for (let i = 0; i < n - 1; i++)
|
|
acc += seg[i], acc > fromStart && acc < total - fromEnd && out.push(p[i * 2 + 2], p[i * 2 + 3]);
|
|
return out.push(endPt.x, endPt.y), out;
|
|
}
|
|
function pointAlongSegments(p, seg, dist) {
|
|
if (dist <= 0) return { x: p[0], y: p[1] };
|
|
let acc = 0;
|
|
for (let i = 0; i < seg.length; i++) {
|
|
let s = seg[i];
|
|
if (acc + s >= dist) {
|
|
let t = s > 0 ? (dist - acc) / s : 0;
|
|
return { x: p[i * 2] + (p[i * 2 + 2] - p[i * 2]) * t, y: p[i * 2 + 1] + (p[i * 2 + 3] - p[i * 2 + 1]) * t };
|
|
}
|
|
acc += s;
|
|
}
|
|
return { x: p[p.length - 2], y: p[p.length - 1] };
|
|
}
|
|
function simplify(p, tolerance) {
|
|
let n = p.length / 2;
|
|
if (n <= 2 || tolerance <= 0) return p.slice();
|
|
let keep = new Uint8Array(n);
|
|
keep[0] = 1, keep[n - 1] = 1;
|
|
let tol2 = tolerance * tolerance, stack = [0, n - 1];
|
|
for (; stack.length; ) {
|
|
let last = stack.pop(), first = stack.pop(), maxD = 0, idx = -1, ax = p[first * 2], ay = p[first * 2 + 1], bx = p[last * 2], by = p[last * 2 + 1];
|
|
for (let i = first + 1; i < last; i++) {
|
|
let d = segmentDistanceSq(p[i * 2], p[i * 2 + 1], ax, ay, bx, by);
|
|
d > maxD && (maxD = d, idx = i);
|
|
}
|
|
maxD > tol2 && idx > 0 && (keep[idx] = 1, stack.push(first, idx, idx, last));
|
|
}
|
|
let out = [];
|
|
for (let i = 0; i < n; i++) keep[i] && out.push(p[i * 2], p[i * 2 + 1]);
|
|
return out;
|
|
}
|
|
function simplifyRank(p) {
|
|
let n = p.length / 2, rank = new Float64Array(n);
|
|
if (n === 0 || (rank[0] = 1 / 0, rank[n - 1] = 1 / 0, n <= 2)) return rank;
|
|
let stack = [0, n - 1], cap = [1 / 0];
|
|
for (; stack.length; ) {
|
|
let last = stack.pop(), first = stack.pop(), above = cap.pop(), maxD = 0, idx = -1, ax = p[first * 2], ay = p[first * 2 + 1], bx = p[last * 2], by = p[last * 2 + 1];
|
|
for (let i = first + 1; i < last; i++) {
|
|
let d = segmentDistanceSq(p[i * 2], p[i * 2 + 1], ax, ay, bx, by);
|
|
d > maxD && (maxD = d, idx = i);
|
|
}
|
|
if (maxD > 0 && idx > 0) {
|
|
let r = maxD < above ? maxD : above;
|
|
rank[idx] = r, stack.push(first, idx, idx, last), cap.push(r, r);
|
|
}
|
|
}
|
|
return rank;
|
|
}
|
|
function simplifyRanked(p, rank, tolerance) {
|
|
let n = p.length / 2;
|
|
if (n <= 2 || tolerance <= 0) return p.slice();
|
|
let tol2 = tolerance * tolerance, out = [];
|
|
for (let i = 0; i < n; i++) rank[i] > tol2 && out.push(p[i * 2], p[i * 2 + 1]);
|
|
return out;
|
|
}
|
|
function segmentDistanceSq(px, py, ax, ay, bx, by) {
|
|
let dx = bx - ax, dy = by - ay, l2 = dx * dx + dy * dy;
|
|
if (l2 > 0) {
|
|
let t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / l2));
|
|
dx = ax + dx * t, dy = ay + dy * t;
|
|
} else
|
|
dx = ax, dy = ay;
|
|
return (px - dx) * (px - dx) + (py - dy) * (py - dy);
|
|
}
|
|
function dedupe(p, eps = 1e-9) {
|
|
let out = [];
|
|
for (let i = 0; i < p.length; i += 2) {
|
|
let n = out.length;
|
|
n >= 2 && Math.abs(out[n - 2] - p[i]) < eps && Math.abs(out[n - 1] - p[i + 1]) < eps || out.push(p[i], p[i + 1]);
|
|
}
|
|
return out;
|
|
}
|
|
var LOOP_WINDOW_OFFSETS = 12, INSIDE_MITER_LIMIT = 8;
|
|
function offsetPolyline(p, d, arcStepRad = Math.PI / 9) {
|
|
return offsetPolylineAnchored(p, d, arcStepRad).points;
|
|
}
|
|
function offsetPolylineAnchored(p, d, arcStepRad = Math.PI / 9) {
|
|
let src = dedupe(p), n = src.length / 2;
|
|
if (n < 2 || d === 0) return { points: src.slice(), anchors: src.slice() };
|
|
let segs = new Float64Array((n - 1) * 4);
|
|
for (let i = 0; i < n - 1; i++) {
|
|
let ax = src[i * 2], ay = src[i * 2 + 1], bx = src[i * 2 + 2], by = src[i * 2 + 3], dx = bx - ax, dy = by - ay, l = Math.hypot(dx, dy), nx = dy / l, ny = -dx / l;
|
|
segs[i * 4] = ax + nx * d, segs[i * 4 + 1] = ay + ny * d, segs[i * 4 + 2] = bx + nx * d, segs[i * 4 + 3] = by + ny * d;
|
|
}
|
|
let out = [segs[0], segs[1]], anchors = [src[0], src[1]];
|
|
for (let i = 1; i < n - 1; i++) {
|
|
let o0 = (i - 1) * 4, o1 = i * 4, s0x = segs[o0], s0y = segs[o0 + 1], s0X = segs[o0 + 2], s0Y = segs[o0 + 3], s1x = segs[o1], s1y = segs[o1 + 1], s1X = segs[o1 + 2], s1Y = segs[o1 + 3], vx = src[i * 2], vy = src[i * 2 + 1], cross = (s0X - s0x) * (s1Y - s1y) - (s0Y - s0y) * (s1X - s1x), dot = (s0X - s0x) * (s1X - s1x) + (s0Y - s0y) * (s1Y - s1y), outside = d > 0 == cross > 0;
|
|
if (Math.abs(cross) < 1e-9 * Math.max(1, Math.abs(dot))) {
|
|
out.push(s1x, s1y), anchors.push(vx, vy);
|
|
continue;
|
|
}
|
|
if (!outside) {
|
|
let ix = intersectLines(s0x, s0y, s0X, s0Y, s1x, s1y, s1X, s1Y);
|
|
ix && Math.hypot(ix[0] - vx, ix[1] - vy) <= INSIDE_MITER_LIMIT * Math.abs(d) ? (out.push(ix[0], ix[1]), anchors.push(vx, vy)) : (out.push(s0X, s0Y, s1x, s1y), anchors.push(vx, vy, vx, vy));
|
|
continue;
|
|
}
|
|
let a0 = Math.atan2(s0Y - vy, s0X - vx), da = Math.atan2(s1y - vy, s1x - vx) - a0;
|
|
for (; da > Math.PI; ) da -= 2 * Math.PI;
|
|
for (; da < -Math.PI; ) da += 2 * Math.PI;
|
|
let steps = Math.max(1, Math.ceil(Math.abs(da) / arcStepRad)), r = Math.abs(d);
|
|
out.push(s0X, s0Y), anchors.push(vx, vy);
|
|
for (let k = 1; k < steps; k++) {
|
|
let a = a0 + da * k / steps;
|
|
out.push(vx + Math.cos(a) * r, vy + Math.sin(a) * r), anchors.push(vx, vy);
|
|
}
|
|
out.push(s1x, s1y), anchors.push(vx, vy);
|
|
}
|
|
out.push(segs[segs.length - 2], segs[segs.length - 1]), anchors.push(src[src.length - 2], src[src.length - 1]);
|
|
let open = removeLoopsAnchored(out, anchors, Math.abs(d) * LOOP_WINDOW_OFFSETS);
|
|
return dropEndHook(open, src[2] - src[0], src[3] - src[1], 0, 2 * Math.abs(d)), dropEndHook(open, src[src.length - 2] - src[src.length - 4], src[src.length - 1] - src[src.length - 3], 1, 2 * Math.abs(d)), open;
|
|
}
|
|
function dropEndHook(line, dx, dy, end, reach) {
|
|
let { points, anchors } = line, e = end ? points.length - 2 : 0, step = end ? -2 : 2, hook = 0;
|
|
for (let v = e + step; v >= 0 && v < points.length; v += step) {
|
|
let ex = points[e] - points[v], ey = points[e + 1] - points[v + 1];
|
|
if (Math.hypot(ex, ey) > reach || (end ? 1 : -1) * (ex * dx + ey * dy) > 0) break;
|
|
hook++;
|
|
}
|
|
if (hook = Math.min(hook, points.length / 2 - 2), hook <= 0) return;
|
|
let from = end ? e - 2 * hook : 2;
|
|
points.splice(from, 2 * hook), anchors.splice(from, 2 * hook);
|
|
}
|
|
function offsetPolylineSlidingAnchored(p, d0, d1, stepPx = 4, via = []) {
|
|
let src = dedupe(p), n = src.length / 2;
|
|
if (n < 2) return { points: src.slice(), anchors: src.slice() };
|
|
let dense = [src[0], src[1]];
|
|
for (let i = 1; i < n; i++) {
|
|
let ax = src[i * 2 - 2], ay = src[i * 2 - 1], bx = src[i * 2], by = src[i * 2 + 1], l = Math.hypot(bx - ax, by - ay), k = Math.max(1, Math.ceil(l / stepPx));
|
|
for (let j = 1; j <= k; j++) dense.push(ax + (bx - ax) * j / k, ay + (by - ay) * j / k);
|
|
}
|
|
let m = dense.length / 2, total = polylineLength(dense), out = new Array(dense.length), s = 0;
|
|
for (let i = 0; i < m; i++) {
|
|
let x = dense[i * 2], y = dense[i * 2 + 1];
|
|
i > 0 && (s += Math.hypot(x - dense[i * 2 - 2], y - dense[i * 2 - 1]));
|
|
let d = d0;
|
|
if (total > 0) {
|
|
let s0 = 0, o0 = d0, s1 = total, o1 = d1;
|
|
for (let k = 0; k < via.length; k += 2) {
|
|
let at = via[k] * total;
|
|
if (at <= s)
|
|
s0 = at, o0 = via[k + 1];
|
|
else {
|
|
s1 = at, o1 = via[k + 1];
|
|
break;
|
|
}
|
|
}
|
|
d = s1 > s0 ? o0 + (o1 - o0) * (s - s0) / (s1 - s0) : o1;
|
|
}
|
|
let t0x = 0, t0y = 0, t1x = 0, t1y = 0;
|
|
if (i > 0) {
|
|
t0x = x - dense[i * 2 - 2], t0y = y - dense[i * 2 - 1];
|
|
let l = Math.hypot(t0x, t0y) || 1;
|
|
t0x /= l, t0y /= l;
|
|
}
|
|
if (i < m - 1) {
|
|
t1x = dense[i * 2 + 2] - x, t1y = dense[i * 2 + 3] - y;
|
|
let l = Math.hypot(t1x, t1y) || 1;
|
|
t1x /= l, t1y /= l;
|
|
}
|
|
i === 0 && ([t0x, t0y] = [t1x, t1y]), i === m - 1 && ([t1x, t1y] = [t0x, t0y]);
|
|
let n0x = t0y, n0y = -t0x, n1x = t1y, n1y = -t1x, mx = n0x + n1x, my = n0y + n1y, ml = Math.hypot(mx, my);
|
|
if (ml < 1e-9)
|
|
mx = n0x, my = n0y;
|
|
else {
|
|
let cosHalf = Math.min(1, Math.max(0.5, ml / 2));
|
|
mx = mx / ml / cosHalf, my = my / ml / cosHalf;
|
|
}
|
|
out[i * 2] = x + mx * d, out[i * 2 + 1] = y + my * d;
|
|
}
|
|
let widest = Math.max(Math.abs(d0), Math.abs(d1), 1);
|
|
for (let k = 1; k < via.length; k += 2) widest = Math.max(widest, Math.abs(via[k]));
|
|
return removeLoopsAnchored(out, dense, widest * LOOP_WINDOW_OFFSETS);
|
|
}
|
|
function selfIntersects(p) {
|
|
let n = p.length / 2;
|
|
for (let i = 0; i + 1 < n; i++)
|
|
for (let j = i + 2; j + 1 < n; j++)
|
|
if (intersectSegments(p[i * 2], p[i * 2 + 1], p[i * 2 + 2], p[i * 2 + 3], p[j * 2], p[j * 2 + 1], p[j * 2 + 2], p[j * 2 + 3])) return !0;
|
|
return !1;
|
|
}
|
|
function intersectLines(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
let r = [bx - ax, by - ay], s = [dx - cx, dy - cy], denom = r[0] * s[1] - r[1] * s[0];
|
|
if (Math.abs(denom) < 1e-12) return null;
|
|
let t = ((cx - ax) * s[1] - (cy - ay) * s[0]) / denom;
|
|
return [ax + r[0] * t, ay + r[1] * t];
|
|
}
|
|
function intersectSegments(ax, ay, bx, by, cx, cy, dx, dy) {
|
|
let rX = bx - ax, rY = by - ay, sX = dx - cx, sY = dy - cy, denom = rX * sY - rY * sX;
|
|
if (Math.abs(denom) < 1e-12) return null;
|
|
let qpX = cx - ax, qpY = cy - ay, t = (qpX * sY - qpY * sX) / denom, u = (qpX * rY - qpY * rX) / denom;
|
|
return t <= 0 || t >= 1 || u <= 0 || u >= 1 ? null : { x: ax + rX * t, y: ay + rY * t, t, u };
|
|
}
|
|
var OWN_LOOP_AREA_SHARE = 0.1;
|
|
function removeLoopsAnchored(p, anchors, window) {
|
|
let pts = p, anc = anchors, along = new Array(p.length / 2);
|
|
along[0] = 0;
|
|
for (let k = 1; k < along.length; k++)
|
|
along[k] = along[k - 1] + Math.hypot(p[k * 2] - p[k * 2 - 2], p[k * 2 + 1] - p[k * 2 - 1]);
|
|
let guard = 0;
|
|
for (; !(guard++ > 1e3); ) {
|
|
let n = pts.length / 2, found = null;
|
|
outer: for (let i = 0; i < n - 1 && !found; i++) {
|
|
let ax = pts[i * 2], ay = pts[i * 2 + 1], bx = pts[i * 2 + 2], by = pts[i * 2 + 3], minX = ax < bx ? ax : bx, maxX = ax < bx ? bx : ax, minY = ay < by ? ay : by, maxY = ay < by ? by : ay;
|
|
for (let j = i + 2; j < n - 1 && !(along[j] - along[i + 1] > window); j++) {
|
|
let cx = pts[j * 2], cy = pts[j * 2 + 1], dx = pts[j * 2 + 2], dy = pts[j * 2 + 3];
|
|
if (cx < minX && dx < minX || cx > maxX && dx > maxX || cy < minY && dy < minY || cy > maxY && dy > maxY) continue;
|
|
let hit = intersectSegments(ax, ay, bx, by, cx, cy, dx, dy);
|
|
if (hit && !ownLoop(pts, anc, i, j, hit)) {
|
|
found = { i, j, x: hit.x, y: hit.y, t: hit.t, u: hit.u };
|
|
break outer;
|
|
}
|
|
}
|
|
}
|
|
if (!found) break;
|
|
let cut = (found.i + 1) * 2, keep = (found.j + 1) * 2;
|
|
pts = pts.slice(0, cut).concat([found.x, found.y], pts.slice(keep));
|
|
let ai = found.i * 2, t = found.t;
|
|
anc = anc.slice(0, cut).concat([anc[ai] + (anc[ai + 2] - anc[ai]) * t, anc[ai + 1] + (anc[ai + 3] - anc[ai + 1]) * t], anc.slice(keep));
|
|
let alongCut = along[found.i] + (along[found.i + 1] - along[found.i]) * t;
|
|
along = along.slice(0, found.i + 1).concat([alongCut], along.slice(found.j + 1));
|
|
}
|
|
return { points: pts, anchors: anc };
|
|
}
|
|
function ownLoop(pts, anc, i, j, hit) {
|
|
let ring = (x0, y0, xs, k0, k1, x1, y1) => {
|
|
let a = 0, px = x0, py = y0;
|
|
for (let k = k0; k <= k1; k++) {
|
|
let x = xs[k * 2], y = xs[k * 2 + 1];
|
|
a += px * y - x * py, px = x, py = y;
|
|
}
|
|
return a + px * y1 - x1 * py + x1 * y0 - x0 * y1;
|
|
}, offset = ring(hit.x, hit.y, pts, i + 1, j, hit.x, hit.y), ground = ring(
|
|
anc[i * 2] + (anc[i * 2 + 2] - anc[i * 2]) * hit.t,
|
|
anc[i * 2 + 1] + (anc[i * 2 + 3] - anc[i * 2 + 1]) * hit.t,
|
|
anc,
|
|
i + 1,
|
|
j,
|
|
anc[j * 2] + (anc[j * 2 + 2] - anc[j * 2]) * hit.u,
|
|
anc[j * 2 + 1] + (anc[j * 2 + 3] - anc[j * 2 + 1]) * hit.u
|
|
);
|
|
return offset * ground > 0 && Math.abs(ground) >= OWN_LOOP_AREA_SHARE * Math.abs(offset);
|
|
}
|
|
function sampleCubicBezier(p0, p1, p2, p3, n) {
|
|
let out = [];
|
|
for (let i = 0; i <= n; i++) {
|
|
let t = i / n, mt = 1 - t, a = mt * mt * mt, b = 3 * mt * mt * t, c = 3 * mt * t * t, d = t * t * t;
|
|
out.push(a * p0[0] + b * p1[0] + c * p2[0] + d * p3[0], a * p0[1] + b * p1[1] + c * p2[1] + d * p3[1]);
|
|
}
|
|
return out;
|
|
}
|
|
function connectorCurve(P, tp, Q, tq, samples, handleScale = 1) {
|
|
let K = 0.5523 * handleScale, chord = Math.hypot(Q[0] - P[0], Q[1] - P[1]), h = K * chord, I = intersectLines(P[0], P[1], P[0] + tp[0], P[1] + tp[1], Q[0], Q[1], Q[0] + tq[0], Q[1] + tq[1]);
|
|
if (I) {
|
|
let t = (I[0] - P[0]) * tp[0] + (I[1] - P[1]) * tp[1], u = (I[0] - Q[0]) * tq[0] + (I[1] - Q[1]) * tq[1];
|
|
if (t > 0 && u > 0) {
|
|
let delta = (Math.hypot(I[0] - P[0], I[1] - P[1]) + Math.hypot(I[0] - Q[0], I[1] - Q[1])) / 2;
|
|
h = K * Math.min(delta, chord * 1.5);
|
|
}
|
|
}
|
|
let c1 = [P[0] + tp[0] * h, P[1] + tp[1] * h], c2 = [Q[0] + tq[0] * h, Q[1] + tq[1] * h];
|
|
return sampleCubicBezier(P, c1, c2, Q, samples);
|
|
}
|
|
function ccwAngle(a, b) {
|
|
let d = -(Math.atan2(b[1], b[0]) - Math.atan2(a[1], a[0]));
|
|
for (; d < 0; ) d += 2 * Math.PI;
|
|
for (; d >= 2 * Math.PI; ) d -= 2 * Math.PI;
|
|
return d;
|
|
}
|
|
function smoothCatmullRom(p, segmentPx, maxPerSegment = 10, minBendDeg = 3, maxBendDeg = 180, cornerPx = 0) {
|
|
let n = p.length / 2;
|
|
if (n < 3) return p.slice();
|
|
if (maxBendDeg < 180) {
|
|
let cosMax = Math.cos(maxBendDeg * Math.PI / 180), corners = null;
|
|
for (let i = 1; i < n - 1; i++) bendAt(p, i) < cosMax && (corners ?? (corners = [])).push(i);
|
|
if (corners) {
|
|
let out2 = [], from = 0;
|
|
for (let c of [...corners, n - 1]) {
|
|
let run = smoothCatmullRom(p.slice(from * 2, (c + 1) * 2), segmentPx, maxPerSegment, minBendDeg);
|
|
for (let k = out2.length ? 2 : 0; k < run.length; k += 2) out2.push(run[k], run[k + 1]);
|
|
from = c;
|
|
}
|
|
return cornerPx > 0 ? roundCorners(out2, p, corners, cornerPx) : out2;
|
|
}
|
|
}
|
|
let X = (i) => p[i * 2], Y = (i) => p[i * 2 + 1], bend = new Float64Array(n), cosMin = Math.cos(minBendDeg * Math.PI / 180);
|
|
for (let i = 1; i < n - 1; i++) {
|
|
let ax = X(i) - X(i - 1), ay = Y(i) - Y(i - 1), bx = X(i + 1) - X(i), by = Y(i + 1) - Y(i), la = Math.hypot(ax, ay) || 1, lb = Math.hypot(bx, by) || 1;
|
|
bend[i] = (ax * bx + ay * by) / (la * lb);
|
|
}
|
|
let out = [X(0), Y(0)];
|
|
for (let i = 0; i < n - 1; i++) {
|
|
let curved = i > 0 && bend[i] < cosMin || i + 1 < n - 1 && bend[i + 1] < cosMin, len = Math.hypot(X(i + 1) - X(i), Y(i + 1) - Y(i)), steps = curved ? Math.max(1, Math.min(maxPerSegment, Math.round(len / segmentPx))) : 1;
|
|
if (steps === 1) {
|
|
out.push(X(i + 1), Y(i + 1));
|
|
continue;
|
|
}
|
|
let p0x = i > 0 ? X(i - 1) : 2 * X(0) - X(1), p0y = i > 0 ? Y(i - 1) : 2 * Y(0) - Y(1), p1x = X(i), p1y = Y(i), p2x = X(i + 1), p2y = Y(i + 1), p3x = i + 2 < n ? X(i + 2) : 2 * X(n - 1) - X(n - 2), p3y = i + 2 < n ? Y(i + 2) : 2 * Y(n - 1) - Y(n - 2), t0 = 0, t1 = t0 + (Math.sqrt(Math.hypot(p1x - p0x, p1y - p0y)) || 1e-6), t2 = t1 + (Math.sqrt(Math.hypot(p2x - p1x, p2y - p1y)) || 1e-6), t3 = t2 + (Math.sqrt(Math.hypot(p3x - p2x, p3y - p2y)) || 1e-6);
|
|
for (let s = 1; s < steps; s++) {
|
|
let t = t1 + (t2 - t1) * s / steps, a1x = (t1 - t) / (t1 - t0) * p0x + (t - t0) / (t1 - t0) * p1x, a1y = (t1 - t) / (t1 - t0) * p0y + (t - t0) / (t1 - t0) * p1y, a2x = (t2 - t) / (t2 - t1) * p1x + (t - t1) / (t2 - t1) * p2x, a2y = (t2 - t) / (t2 - t1) * p1y + (t - t1) / (t2 - t1) * p2y, a3x = (t3 - t) / (t3 - t2) * p2x + (t - t2) / (t3 - t2) * p3x, a3y = (t3 - t) / (t3 - t2) * p2y + (t - t2) / (t3 - t2) * p3y, b1x = (t2 - t) / (t2 - t0) * a1x + (t - t0) / (t2 - t0) * a2x, b1y = (t2 - t) / (t2 - t0) * a1y + (t - t0) / (t2 - t0) * a2y, b2x = (t3 - t) / (t3 - t1) * a2x + (t - t1) / (t3 - t1) * a3x, b2y = (t3 - t) / (t3 - t1) * a2y + (t - t1) / (t3 - t1) * a3y;
|
|
out.push((t2 - t) / (t2 - t1) * b1x + (t - t1) / (t2 - t1) * b2x, (t2 - t) / (t2 - t1) * b1y + (t - t1) / (t2 - t1) * b2y);
|
|
}
|
|
out.push(p2x, p2y);
|
|
}
|
|
return out;
|
|
}
|
|
function splineStartDirection(p, segmentPx, maxPerSegment = 10, minBendDeg = 3, maxBendDeg = 180) {
|
|
if (p.length / 2 < 3 || maxBendDeg < 180 && bendAt(p, 1) < Math.cos(maxBendDeg * Math.PI / 180)) return startDirection(p);
|
|
let steps = splineSteps(p, 0, segmentPx, maxPerSegment, minBendDeg);
|
|
if (steps < 2) return startDirection(p);
|
|
let [x, y] = centripetalPoint(2 * p[0] - p[2], 2 * p[1] - p[3], p[0], p[1], p[2], p[3], p[4], p[5], 1 / steps), dx = x - p[0], dy = y - p[1], l = Math.hypot(dx, dy);
|
|
return l > 0 ? [dx / l, dy / l] : startDirection(p);
|
|
}
|
|
function splineEndDirection(p, segmentPx, maxPerSegment = 10, minBendDeg = 3, maxBendDeg = 180) {
|
|
let n = p.length / 2;
|
|
if (n < 3 || maxBendDeg < 180 && bendAt(p, n - 2) < Math.cos(maxBendDeg * Math.PI / 180)) return endDirection(p);
|
|
let steps = splineSteps(p, n - 2, segmentPx, maxPerSegment, minBendDeg);
|
|
if (steps < 2) return endDirection(p);
|
|
let o = (n - 1) * 2, [x, y] = centripetalPoint(p[o - 4], p[o - 3], p[o - 2], p[o - 1], p[o], p[o + 1], 2 * p[o] - p[o - 2], 2 * p[o + 1] - p[o - 1], (steps - 1) / steps), dx = p[o] - x, dy = p[o + 1] - y, l = Math.hypot(dx, dy);
|
|
return l > 0 ? [dx / l, dy / l] : endDirection(p);
|
|
}
|
|
function splineSteps(p, i, segmentPx, maxPerSegment, minBendDeg) {
|
|
let n = p.length / 2, cosMin = Math.cos(minBendDeg * Math.PI / 180);
|
|
if (!(i > 0 && bendAt(p, i) < cosMin || i + 1 < n - 1 && bendAt(p, i + 1) < cosMin)) return 1;
|
|
let len = Math.hypot(p[i * 2 + 2] - p[i * 2], p[i * 2 + 3] - p[i * 2 + 1]);
|
|
return Math.max(1, Math.min(maxPerSegment, Math.round(len / segmentPx)));
|
|
}
|
|
function roundCorners(out, src, corners, radiusPx) {
|
|
let n = src.length / 2, cum = new Float64Array(n);
|
|
for (let i = 1; i < n; i++)
|
|
cum[i] = cum[i - 1] + Math.hypot(src[i * 2] - src[i * 2 - 2], src[i * 2 + 1] - src[i * 2 - 1]);
|
|
let consumed = new Uint8Array(out.length), arcs = /* @__PURE__ */ new Map(), lastFwd = null;
|
|
for (let ci = 0; ci < corners.length; ci++) {
|
|
let c = corners[ci], prev = ci > 0 ? corners[ci - 1] : 0, next = ci + 1 < corners.length ? corners[ci + 1] : n - 1, r = Math.min(radiusPx, 0.4 * (cum[c] - cum[prev]), 0.4 * (cum[next] - cum[c]));
|
|
if (r < 0.05) continue;
|
|
let cx = src[c * 2], cy = src[c * 2 + 1], at = -1;
|
|
for (let i = 0; i < out.length; i += 2)
|
|
if (!consumed[i] && Math.abs(out[i] - cx) < 1e-6 && Math.abs(out[i + 1] - cy) < 1e-6) {
|
|
at = i;
|
|
break;
|
|
}
|
|
if (at <= 0 || at >= out.length - 2) continue;
|
|
let back = walkAlong(out, at, -2, r, lastFwd), fwd = walkAlong(out, at, 2, r, null);
|
|
if (r = Math.min(r, back.got, fwd.got), r < 0.05) continue;
|
|
let steps = Math.max(4, Math.min(12, Math.round(r / 2))), arc = [];
|
|
for (let i = 0; i <= steps; i++) {
|
|
let t = i / steps, m = 1 - t;
|
|
arc.push(m * m * back.x + 2 * m * t * cx + t * t * fwd.x, m * m * back.y + 2 * m * t * cy + t * t * fwd.y);
|
|
}
|
|
for (let i = back.idx + 2; i < fwd.idx; i += 2) consumed[i] = 1;
|
|
arcs.set(back.idx + 2, arc), lastFwd = { idx: fwd.idx, x: fwd.x, y: fwd.y };
|
|
}
|
|
if (!arcs.size) return out;
|
|
let done = [];
|
|
for (let i = 0; i < out.length; i += 2) {
|
|
let arc = arcs.get(i);
|
|
if (arc) for (let k = 0; k < arc.length; k++) done.push(arc[k]);
|
|
consumed[i] || done.push(out[i], out[i + 1]);
|
|
}
|
|
return done;
|
|
}
|
|
function walkAlong(p, at, step, dist, spliced) {
|
|
let acc = 0, i = at;
|
|
for (; ; ) {
|
|
let j = i + step;
|
|
if (j < 0 || j + 1 >= p.length) return { x: p[i], y: p[i + 1], idx: i, got: acc };
|
|
let stand = spliced !== null && step < 0 && i === spliced.idx, jx = stand ? spliced.x : p[j], jy = stand ? spliced.y : p[j + 1], seg = Math.hypot(jx - p[i], jy - p[i + 1]);
|
|
if (acc + seg >= dist) {
|
|
let t = seg > 1e-12 ? (dist - acc) / seg : 0;
|
|
return { x: p[i] + (jx - p[i]) * t, y: p[i + 1] + (jy - p[i + 1]) * t, idx: j, got: dist };
|
|
}
|
|
acc += seg, i = j;
|
|
}
|
|
}
|
|
function bendAt(p, k) {
|
|
let ax = p[k * 2] - p[k * 2 - 2], ay = p[k * 2 + 1] - p[k * 2 - 1], bx = p[k * 2 + 2] - p[k * 2], by = p[k * 2 + 3] - p[k * 2 + 1];
|
|
return (ax * bx + ay * by) / ((Math.hypot(ax, ay) || 1) * (Math.hypot(bx, by) || 1));
|
|
}
|
|
function centripetalPoint(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, u) {
|
|
let t1 = 0 + (Math.sqrt(Math.hypot(p1x - p0x, p1y - p0y)) || 1e-6), t2 = t1 + (Math.sqrt(Math.hypot(p2x - p1x, p2y - p1y)) || 1e-6), t3 = t2 + (Math.sqrt(Math.hypot(p3x - p2x, p3y - p2y)) || 1e-6), t = t1 + (t2 - t1) * u, a1x = (t1 - t) / (t1 - 0) * p0x + (t - 0) / (t1 - 0) * p1x, a1y = (t1 - t) / (t1 - 0) * p0y + (t - 0) / (t1 - 0) * p1y, a2x = (t2 - t) / (t2 - t1) * p1x + (t - t1) / (t2 - t1) * p2x, a2y = (t2 - t) / (t2 - t1) * p1y + (t - t1) / (t2 - t1) * p2y, a3x = (t3 - t) / (t3 - t2) * p2x + (t - t2) / (t3 - t2) * p3x, a3y = (t3 - t) / (t3 - t2) * p2y + (t - t2) / (t3 - t2) * p3y, b1x = (t2 - t) / (t2 - 0) * a1x + (t - 0) / (t2 - 0) * a2x, b1y = (t2 - t) / (t2 - 0) * a1y + (t - 0) / (t2 - 0) * a2y, b2x = (t3 - t) / (t3 - t1) * a2x + (t - t1) / (t3 - t1) * a3x, b2y = (t3 - t) / (t3 - t1) * a2y + (t - t1) / (t3 - t1) * a3y;
|
|
return [(t2 - t) / (t2 - t1) * b1x + (t - t1) / (t2 - t1) * b2x, (t2 - t) / (t2 - t1) * b1y + (t - t1) / (t2 - t1) * b2y];
|
|
}
|
|
function polylineBounds(p) {
|
|
let minX = 1 / 0, minY = 1 / 0, maxX = -1 / 0, maxY = -1 / 0;
|
|
for (let i = 0; i < p.length; i += 2)
|
|
p[i] < minX && (minX = p[i]), p[i] > maxX && (maxX = p[i]), p[i + 1] < minY && (minY = p[i + 1]), p[i + 1] > maxY && (maxY = p[i + 1]);
|
|
return { minX, minY, maxX, maxY };
|
|
}
|
|
function boundsIntersect(a, b) {
|
|
return a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
|
|
}
|
|
|
|
// src/core/order.ts
|
|
function mulberry32(seed) {
|
|
let a = seed >>> 0;
|
|
return () => {
|
|
a = a + 1831565813 >>> 0;
|
|
let t = a;
|
|
return t = Math.imul(t ^ t >>> 15, t | 1), t ^= t + Math.imul(t ^ t >>> 7, t | 61), ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
};
|
|
}
|
|
var SAME_NEXT = 0, DIFF_NEXT = 1, PERIPHERY = 2, ONE_WAY_SHARE = 0.3, ONE_WAY_STOP = 0.15, SECOND_PASS_STABILITY = 0.25, ALIGN_STABILITY = 0.1, ALIGN_DEPTHS = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 1 / 0], permTables = [];
|
|
function permTable(k) {
|
|
let t = permTables[k];
|
|
if (t) return t;
|
|
t = [];
|
|
let rec = (rest, m) => {
|
|
if (!rest.length) {
|
|
t.push(m);
|
|
return;
|
|
}
|
|
for (let i = 0; i < rest.length; i++) rec(rest.slice(0, i).concat(rest.slice(i + 1)), m.concat(rest[i]));
|
|
};
|
|
return rec(Array.from({ length: k }, (_, i) => i), []), permTables[k] = t, t;
|
|
}
|
|
var LaneOrderer = class {
|
|
constructor(g, opts = {}) {
|
|
__publicField(this, "g");
|
|
__publicField(this, "w");
|
|
__publicField(this, "opts");
|
|
/** Route indices follow sorted ids, so comparing indices breaks ties as ids would. */
|
|
__publicField(this, "routeIds");
|
|
__publicField(this, "routeIndex");
|
|
/**
|
|
* Every edge's lanes and lane positions live in two flat arrays, `lanes[e]` and `pos[e]` being
|
|
* views into them, so the cost evaluation can index by edge and route without a pointer chase.
|
|
*/
|
|
__publicField(this, "laneBuf");
|
|
__publicField(this, "laneStart");
|
|
__publicField(this, "laneCount");
|
|
__publicField(this, "posBuf");
|
|
__publicField(this, "lanes");
|
|
__publicField(this, "pos");
|
|
/** Port index of edge e at its a node (2e) and b node (2e + 1). */
|
|
__publicField(this, "portAt");
|
|
/** Per node and port, per route index: the ports the route continues to. */
|
|
__publicField(this, "next");
|
|
/**
|
|
* The same continuations flat, for the swap walk: the ports of node n start at `portBase[n]`,
|
|
* a port's edge is `portEdge`, and the ports route r continues to from port q are
|
|
* `nextList[nextStart[q * R + r] .. nextStart[q * R + r + 1])`, in the order `next` lists them.
|
|
*/
|
|
__publicField(this, "portBase");
|
|
__publicField(this, "portEdge");
|
|
__publicField(this, "nextStart");
|
|
__publicField(this, "nextList");
|
|
__publicField(this, "terms");
|
|
/** Per seeded edge: lane position of each route index in the initial order (-1 when unseeded). */
|
|
__publicField(this, "seedPos", []);
|
|
__publicField(this, "stability", 0);
|
|
__publicField(this, "stack", []);
|
|
__publicField(this, "edgeStamp", new Uint32Array(0));
|
|
__publicField(this, "nodeStamp", new Uint32Array(0));
|
|
__publicField(this, "stamp", 0);
|
|
__publicField(this, "nodeCache", new Float64Array(0));
|
|
__publicField(this, "nodeFresh", new Float64Array(0));
|
|
/** Timing of the last solve, ms, both passes together, for diagnostics. */
|
|
__publicField(this, "timings", { greedy: 0, anneal: 0, descend: 0 });
|
|
this.g = g, this.opts = opts, this.w = {
|
|
sameSegmentCrossing: opts.sameSegmentCrossing ?? 4,
|
|
diffSegmentCrossing: opts.diffSegmentCrossing ?? 1,
|
|
separation: opts.separation ?? 3,
|
|
periphery: opts.periphery ?? 0.5
|
|
};
|
|
let ids = /* @__PURE__ */ new Set();
|
|
for (let e of g.edges) for (let r of e.routes) ids.add(r);
|
|
this.routeIds = [...ids].sort(), this.routeIndex = new Map(this.routeIds.map((r, i) => [r, i]));
|
|
let R = this.routeIds.length, E = g.edges.length;
|
|
this.laneStart = new Int32Array(E + 1), this.laneCount = new Int32Array(E);
|
|
for (let e = 0; e < E; e++)
|
|
this.laneCount[e] = g.edges[e].routes.length, this.laneStart[e + 1] = this.laneStart[e] + this.laneCount[e];
|
|
this.laneBuf = new Int32Array(this.laneStart[E]), this.posBuf = new Int32Array(E * R).fill(-1), this.lanes = g.edges.map((e) => this.laneBuf.subarray(this.laneStart[e.id], this.laneStart[e.id + 1])), this.pos = g.edges.map((e) => e.routes.length > 1 ? this.posBuf.subarray(e.id * R, (e.id + 1) * R) : null), this.portAt = new Int32Array(2 * g.edges.length).fill(-1);
|
|
for (let n of g.nodes) n.ports.forEach((p, i) => this.portAt[2 * p.edge + (p.end === "a" ? 0 : 1)] = i);
|
|
this.next = g.nodes.map((node) => {
|
|
let next = node.ports.map(() => []);
|
|
for (let t of node.transitions) {
|
|
if (t.from < 0 || t.to < 0 || t.from === t.to) continue;
|
|
let r = this.routeIndex.get(t.route);
|
|
if (r !== void 0)
|
|
for (let [a, b] of [[t.from, t.to], [t.to, t.from]]) {
|
|
let l = next[a][r] ?? (next[a][r] = []);
|
|
l.includes(b) || l.push(b);
|
|
}
|
|
}
|
|
return next;
|
|
}), this.portBase = new Int32Array(g.nodes.length + 1);
|
|
for (let n = 0; n < g.nodes.length; n++) this.portBase[n + 1] = this.portBase[n] + g.nodes[n].ports.length;
|
|
let P = this.portBase[g.nodes.length];
|
|
this.portEdge = new Int32Array(P), this.nextStart = new Int32Array(P * R + 1);
|
|
let list = [];
|
|
for (let n = 0; n < g.nodes.length; n++)
|
|
for (let pi = 0; pi < g.nodes[n].ports.length; pi++) {
|
|
let q = this.portBase[n] + pi;
|
|
this.portEdge[q] = g.nodes[n].ports[pi].edge;
|
|
for (let r = 0; r < R; r++) {
|
|
let l = this.next[n][pi][r];
|
|
if (l) for (let p of l) list.push(p);
|
|
this.nextStart[q * R + r + 1] = list.length;
|
|
}
|
|
}
|
|
this.nextList = Int32Array.from(list), this.terms = this.buildTerms(), this.load();
|
|
}
|
|
buildTerms() {
|
|
let g = this.g, buf = { kind: [], edge: [], out: [], a: [], b: [], next: [], nextOut: [], aLeft: [] }, start = new Int32Array(g.nodes.length + 1);
|
|
g.nodes.forEach((node, n) => {
|
|
start[n] = buf.kind.length;
|
|
let np = node.ports.length, dirs = node.ports.map((p) => {
|
|
let e = g.edges[p.edge];
|
|
return p.end === "a" ? startDirection(e.coords) : endDirection(e.coords).map((v) => -v);
|
|
}), ccw = new Float64Array(np * np);
|
|
for (let i = 0; i < np; i++) for (let j = 0; j < np; j++) ccw[i * np + j] = ccwAngle(dirs[i], dirs[j]);
|
|
for (let pi = 0; pi < np; pi++) {
|
|
let port = node.ports[pi], e = g.edges[port.edge], k = e.routes.length;
|
|
if (k < 2) continue;
|
|
let out = port.end === "a" ? 1 : 0, nextOf = this.next[n][pi], routes = e.routes.map((r) => this.routeIndex.get(r));
|
|
for (let i = 0; i < k; i++) {
|
|
let a = routes[i], aNexts = nextOf[a];
|
|
if (!aNexts || !aNexts.length) {
|
|
push(buf, PERIPHERY, e.id, out, a, -1, -1, 0, 0);
|
|
continue;
|
|
}
|
|
for (let j = i + 1; j < k; j++) {
|
|
let b = routes[j], bNexts = nextOf[b];
|
|
if (!(!bNexts || !bNexts.length))
|
|
for (let aNext of aNexts)
|
|
for (let bNext of bNexts)
|
|
if (aNext === bNext) {
|
|
if (aNext < pi) continue;
|
|
let f = node.ports[aNext];
|
|
push(buf, SAME_NEXT, e.id, out, a, b, f.edge, f.end === "a" ? 1 : 0, 0);
|
|
} else {
|
|
let aLeft = ccw[pi * np + aNext] < ccw[pi * np + bNext] ? 1 : 0;
|
|
push(buf, DIFF_NEXT, e.id, out, a, b, -1, 0, aLeft);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}), start[g.nodes.length] = buf.kind.length;
|
|
let R = this.routeIds.length, T = buf.kind.length, posA = new Int32Array(T), posB = new Int32Array(T), nextA = new Int32Array(T), nextB = new Int32Array(T);
|
|
for (let i = 0; i < T; i++)
|
|
posA[i] = buf.edge[i] * R + buf.a[i], posB[i] = buf.edge[i] * R + Math.max(0, buf.b[i]), nextA[i] = Math.max(0, buf.next[i]) * R + buf.a[i], nextB[i] = Math.max(0, buf.next[i]) * R + Math.max(0, buf.b[i]);
|
|
return {
|
|
start,
|
|
kind: Uint8Array.from(buf.kind),
|
|
edge: Int32Array.from(buf.edge),
|
|
out: Uint8Array.from(buf.out),
|
|
a: Int32Array.from(buf.a),
|
|
b: Int32Array.from(buf.b),
|
|
next: Int32Array.from(buf.next),
|
|
nextOut: Uint8Array.from(buf.nextOut),
|
|
aLeft: Uint8Array.from(buf.aLeft),
|
|
posA,
|
|
posB,
|
|
nextA,
|
|
nextB
|
|
};
|
|
}
|
|
/** Read every edge's `order` into the solver state (sorted route ids where it is unusable). */
|
|
load() {
|
|
this.g.edges.forEach((e, i) => {
|
|
let order = e.order.length === e.routes.length && sameSet(e.order, e.routes) ? e.order : e.routes.slice().sort();
|
|
this.setLanes(i, order.map((r) => this.routeIndex.get(r)));
|
|
});
|
|
}
|
|
store() {
|
|
this.g.edges.forEach((e, i) => e.order = Array.from(this.lanes[i], (r) => this.routeIds[r]));
|
|
}
|
|
setLanes(e, routes) {
|
|
let lanes = this.lanes[e];
|
|
lanes.set(routes);
|
|
let pos = this.pos[e];
|
|
if (pos) for (let i = 0; i < lanes.length; i++) pos[lanes[i]] = i;
|
|
}
|
|
/** Cost contributed by one node under the current edge orders. */
|
|
nodeCost(nodeId) {
|
|
return this.load(), this.evalNode(nodeId);
|
|
}
|
|
totalCost() {
|
|
return this.load(), this.evalAll();
|
|
}
|
|
evalAll() {
|
|
let c = 0;
|
|
for (let i = 0; i < this.g.nodes.length; i++) c += this.evalNode(i);
|
|
return c;
|
|
}
|
|
evalNode(n) {
|
|
let t = this.terms, tEdge = t.edge, tOut = t.out, tKind = t.kind, tNext = t.next, tNextOut = t.nextOut, tALeft = t.aLeft, tPosA = t.posA, tPosB = t.posB, tNextA = t.nextA, tNextB = t.nextB, pos = this.posBuf, count = this.laneCount, wPeriphery = this.w.periphery, wDiff = this.w.diffSegmentCrossing, wSame = this.w.sameSegmentCrossing, wSep = this.w.separation, cost = 0;
|
|
for (let i = t.start[n], end = t.start[n + 1]; i < end; i++) {
|
|
let k = count[tEdge[i]], pa = pos[tPosA[i]];
|
|
tOut[i] || (pa = k - 1 - pa);
|
|
let kind = tKind[i];
|
|
if (kind === PERIPHERY) {
|
|
pa !== 0 && pa !== k - 1 && (cost += wPeriphery);
|
|
continue;
|
|
}
|
|
let pb = pos[tPosB[i]];
|
|
tOut[i] || (pb = k - 1 - pb);
|
|
let aLeft = pa < pb;
|
|
if (kind === DIFF_NEXT) {
|
|
tALeft[i] === 1 !== aLeft && (cost += wDiff);
|
|
continue;
|
|
}
|
|
let fa = pos[tNextA[i]], fb = pos[tNextB[i]];
|
|
if (!tNextOut[i]) {
|
|
let kf = count[tNext[i]];
|
|
fa = kf - 1 - fa, fb = kf - 1 - fb;
|
|
}
|
|
fa < fb === aLeft ? cost += wSame : Math.abs(pa - pb) === 1 && Math.abs(fa - fb) !== 1 && (cost += wSep);
|
|
}
|
|
return cost;
|
|
}
|
|
edgeLocalCost(e) {
|
|
let edge = this.g.edges[e];
|
|
return (edge.a === edge.b ? this.evalNode(edge.a) : this.evalNode(edge.a) + this.evalNode(edge.b)) + this.edgePenalty(e);
|
|
}
|
|
edgePenalty(e) {
|
|
let sp = this.seedPos[e];
|
|
if (!sp) return 0;
|
|
let lanes = this.lanes[e], inversions = 0;
|
|
for (let i = 0; i < lanes.length; i++) {
|
|
let sa = sp[lanes[i]];
|
|
if (!(sa < 0))
|
|
for (let j = i + 1; j < lanes.length; j++) {
|
|
let sb = sp[lanes[j]];
|
|
sb >= 0 && sb < sa && inversions++;
|
|
}
|
|
}
|
|
return inversions * this.stability;
|
|
}
|
|
/** Try every permutation (k <= 6) or pairwise swaps (larger k) of one edge; keep the best. */
|
|
optimizeEdge(e) {
|
|
let lanes = this.lanes[e], k = lanes.length;
|
|
if (k < 2) return !1;
|
|
let startCost = this.edgeLocalCost(e), best = lanes.slice(), bestCost = startCost;
|
|
if (k <= 6) {
|
|
let base = lanes.slice(), scratch = new Int32Array(k);
|
|
for (let p of permTable(k)) {
|
|
for (let i = 0; i < k; i++) scratch[i] = base[p[i]];
|
|
this.setLanes(e, scratch);
|
|
let c = this.edgeLocalCost(e);
|
|
(c < bestCost - 1e-9 || Math.abs(c - bestCost) < 1e-9 && lexLess(scratch, best)) && (bestCost = c, best = scratch.slice());
|
|
}
|
|
} else {
|
|
let improved = !0;
|
|
for (; improved; ) {
|
|
improved = !1;
|
|
for (let i = 0; i < k; i++)
|
|
for (let j = i + 1; j < k; j++) {
|
|
let p = best.slice(), tmp = p[i];
|
|
p[i] = p[j], p[j] = tmp, this.setLanes(e, p);
|
|
let c = this.edgeLocalCost(e);
|
|
c < bestCost - 1e-9 && (bestCost = c, best = p, improved = !0);
|
|
}
|
|
}
|
|
}
|
|
return this.setLanes(e, best), bestCost < startCost - 1e-9;
|
|
}
|
|
descend(comp) {
|
|
let changed = !0, rounds = 0;
|
|
for (; changed && rounds++ < 100; ) {
|
|
changed = !1;
|
|
for (let e of comp.edges) this.optimizeEdge(e) && (changed = !0);
|
|
}
|
|
return this.componentCost(comp);
|
|
}
|
|
componentCost(comp) {
|
|
let c = 0;
|
|
for (let n of comp.nodes) c += this.evalNode(n);
|
|
if (this.seedPos.length) for (let e of comp.edges) c += this.edgePenalty(e);
|
|
return c;
|
|
}
|
|
/**
|
|
* Swap along every connected edge where the pair stays adjacent, so a crossing moves as a whole.
|
|
* That alone cannot remove a pair's crossing between two edges it shares, because both sides of
|
|
* it flip together. A one-way swap (`end` 0 or 1) leaves the start edge through that end only,
|
|
* and with `stop` may halt at any further edge, so such a crossing slides along the pair's run
|
|
* until it leaves it or reaches a junction where it costs less.
|
|
*/
|
|
swapAlong(e, i, touched, stamp, end = -1, stop = null) {
|
|
let g = this.g, a = this.lanes[e][i], b = this.lanes[e][i + 1], stack = this.stack, edgeStamp = this.edgeStamp, { portBase, portEdge, nextStart, nextList } = this, R = this.routeIds.length;
|
|
for (stack.length = 0, stack.push(e); stack.length; ) {
|
|
let cur = stack.pop(), pos = this.pos[cur];
|
|
if (!pos || edgeStamp[cur] === stamp) continue;
|
|
edgeStamp[cur] = stamp;
|
|
let ia = pos[a], ib = pos[b];
|
|
if (ia < 0 || ib < 0 || Math.abs(ia - ib) !== 1) continue;
|
|
touched.push(cur), this.swapPair(cur, a, b);
|
|
let edge = g.edges[cur];
|
|
for (let side = 0; side < 2; side++) {
|
|
if (cur === e && end >= 0 && side !== end || cur !== e && stop && stop() < ONE_WAY_STOP) continue;
|
|
let nodeId = side === 0 ? edge.a : edge.b, pi = this.portAt[2 * cur + side];
|
|
if (pi < 0) continue;
|
|
let q = portBase[nodeId] + pi, a0 = nextStart[q * R + a], a1 = nextStart[q * R + a + 1], b0 = nextStart[q * R + b], b1 = nextStart[q * R + b + 1];
|
|
if (!(a0 === a1 || b0 === b1))
|
|
for (let x = a0; x < a1; x++) {
|
|
let p = nextList[x];
|
|
for (let y = b0; y < b1; y++)
|
|
if (nextList[y] === p) {
|
|
stack.push(portEdge[portBase[nodeId] + p]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Make edge `to` agree with edge `from` across `node`, so that no pair swaps sides between
|
|
* them, and carry that along `to`'s run for `depth` further edges. The routes keep the places
|
|
* they hold as a group, so the lanes around them do not move.
|
|
*/
|
|
alignRun(from, to, node, depth, touched, saved, stamp) {
|
|
let g = this.g, edgeStamp = this.edgeStamp;
|
|
edgeStamp[from] = stamp;
|
|
let frontier = [[to, from, node]];
|
|
for (let d = 0; frontier.length; d++) {
|
|
let next = [];
|
|
for (let [h, x, n] of frontier) {
|
|
let posX = this.pos[x], posH = this.pos[h];
|
|
if (edgeStamp[h] === stamp || !posX || !posH || g.edges[h].a === g.edges[h].b || g.edges[x].a === g.edges[x].b) continue;
|
|
edgeStamp[h] = stamp;
|
|
let xOut = g.edges[x].a === n, hOut = g.edges[h].a === n, portH = this.portAt[2 * h + (hOut ? 0 : 1)], nextOf = this.next[n][this.portAt[2 * x + (xOut ? 0 : 1)]], lx = this.lanes[x], lh = this.lanes[h], group = [];
|
|
for (let i = 0; i < lx.length; i++) {
|
|
let r = lx[xOut ? i : lx.length - 1 - i];
|
|
posH[r] >= 0 && nextOf[r]?.includes(portH) && group.push(r);
|
|
}
|
|
if (group.length < 2) continue;
|
|
group.reverse();
|
|
let slots = [];
|
|
for (let i = 0; i < lh.length; i++) {
|
|
let at = hOut ? i : lh.length - 1 - i;
|
|
group.includes(lh[at]) && slots.push(at);
|
|
}
|
|
if (slots.every((at, i) => lh[at] === group[i])) continue;
|
|
touched.push(h), saved.push(lh.slice());
|
|
let aligned = lh.slice();
|
|
if (slots.forEach((at, i) => aligned[at] = group[i]), this.setLanes(h, aligned), d >= depth) continue;
|
|
let far = hOut ? g.edges[h].b : g.edges[h].a;
|
|
for (let p of g.nodes[far].ports) p.edge !== h && edgeStamp[p.edge] !== stamp && next.push([p.edge, h, far]);
|
|
}
|
|
frontier = next;
|
|
}
|
|
}
|
|
/**
|
|
* Go to each node where a pair swaps sides between two edges it shares, and align one side's
|
|
* run to the other wherever that lowers the cost, best first. A search by random swaps finds
|
|
* the same orders and takes as long again as the first pass.
|
|
*/
|
|
alignSwaps(comp, changed) {
|
|
let g = this.g, t = this.terms, cache2 = this.nodeCache, nodeStamp = this.nodeStamp;
|
|
for (let n of comp.nodes) cache2[n] = this.evalNode(n);
|
|
let touched = [], saved = [], nodes = [], costs = [], attempt = (from, to, node, depth, keep) => {
|
|
let stamp = ++this.stamp;
|
|
if (touched.length = 0, saved.length = 0, this.alignRun(from, to, node, depth, touched, saved, stamp), !touched.length) return 0;
|
|
nodes.length = 0, costs.length = 0;
|
|
for (let e of touched)
|
|
for (let n of [g.edges[e].a, g.edges[e].b])
|
|
nodeStamp[n] !== stamp && (nodeStamp[n] = stamp, nodes.push(n));
|
|
let delta = 0;
|
|
for (let n of nodes) {
|
|
let c = this.evalNode(n);
|
|
costs.push(c), delta += c - cache2[n];
|
|
}
|
|
if (this.stability > 0) {
|
|
let aligned = touched.map((e) => this.lanes[e].slice());
|
|
for (let e of touched) delta += this.edgePenalty(e);
|
|
touched.forEach((e, i) => this.setLanes(e, saved[i]));
|
|
for (let e of touched) delta -= this.edgePenalty(e);
|
|
touched.forEach((e, i) => this.setLanes(e, aligned[i]));
|
|
}
|
|
return keep ? nodes.forEach((n, i) => cache2[n] = costs[i]) : touched.forEach((e, i) => this.setLanes(e, saved[i])), delta;
|
|
};
|
|
for (let round = 0; round < 400; round++) {
|
|
let best = -1e-9, move = null, tried = /* @__PURE__ */ new Set();
|
|
for (let n of comp.nodes)
|
|
for (let k = t.start[n]; k < t.start[n + 1]; k++) {
|
|
if (t.kind[k] !== SAME_NEXT) continue;
|
|
let e = t.edge[k], f = t.next[k], key = `${n}:${e}:${f}`;
|
|
if (tried.has(key)) continue;
|
|
let pe = this.pos[e], pf = this.pos[f], left = pe[t.a[k]] < pe[t.b[k]] == (t.out[k] === 1), leftNext = pf[t.a[k]] < pf[t.b[k]] == (t.nextOut[k] === 1);
|
|
if (left === leftNext) {
|
|
tried.add(key);
|
|
for (let [from, to] of [[e, f], [f, e]]) {
|
|
let reach = -1;
|
|
for (let depth of ALIGN_DEPTHS) {
|
|
let delta = attempt(from, to, n, depth, !1);
|
|
if (touched.length === reach) break;
|
|
reach = touched.length, delta < best && (best = delta, move = [from, to, n, depth]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!move) return;
|
|
attempt(move[0], move[1], move[2], move[3], !0);
|
|
for (let e of touched) changed.push(e);
|
|
}
|
|
}
|
|
/** Apply one swap and return what it changes the cost by; `touched` and `nodes` say where. */
|
|
swapDelta(e, i, end, stop, touched, nodes) {
|
|
let g = this.g, a = this.lanes[e][i], b = this.lanes[e][i + 1], stamp = ++this.stamp, nodeStamp = this.nodeStamp;
|
|
touched.length = 0, this.swapAlong(e, i, touched, stamp, end, stop), nodes.length = 0;
|
|
for (let t of touched) {
|
|
let edge = g.edges[t];
|
|
nodeStamp[edge.a] !== stamp && (nodeStamp[edge.a] = stamp, nodes.push(edge.a)), nodeStamp[edge.b] !== stamp && (nodeStamp[edge.b] = stamp, nodes.push(edge.b));
|
|
}
|
|
let delta = 0;
|
|
for (let n of nodes)
|
|
this.nodeFresh[n] = this.evalNode(n), delta += this.nodeFresh[n] - this.nodeCache[n];
|
|
if (this.stability > 0)
|
|
for (let t of touched) {
|
|
let sp = this.seedPos[t];
|
|
if (!sp || sp[a] < 0 || sp[b] < 0) continue;
|
|
let pos = this.pos[t];
|
|
delta += pos[a] < pos[b] == sp[a] < sp[b] ? -this.stability : this.stability;
|
|
}
|
|
return delta;
|
|
}
|
|
swapPair(e, a, b) {
|
|
let pos = this.pos[e], lanes = this.lanes[e], ia = pos[a], ib = pos[b];
|
|
lanes[ia] = b, lanes[ib] = a, pos[a] = ib, pos[b] = ia;
|
|
}
|
|
/**
|
|
* `oneWay` adds the one-way swaps, and keeps the best order the walk passes through rather
|
|
* than the one it ends on. The first pass of an unseeded solve runs without it.
|
|
*/
|
|
anneal(comp, moves, rand, oneWay) {
|
|
let candidates = comp.edges;
|
|
if (moves <= 0) return;
|
|
let T0 = 5, decay = Math.pow(0.3 / T0, 1 / moves), cache2 = this.nodeCache, fresh = this.nodeFresh;
|
|
for (let n of comp.nodes) cache2[n] = this.evalNode(n);
|
|
let touched = [], touchedNodes = [], running = 0, lowest = 0, kept = null, T = T0;
|
|
for (let m = 0; m < moves; m++, T *= decay) {
|
|
let e = candidates[Math.floor(rand() * candidates.length)], i = Math.floor(rand() * (this.lanes[e].length - 1)), a = this.lanes[e][i], b = this.lanes[e][i + 1], end = oneWay && rand() < ONE_WAY_SHARE ? rand() < 0.5 ? 0 : 1 : -1, delta = this.swapDelta(e, i, end, end >= 0 ? rand : null, touched, touchedNodes);
|
|
if (delta <= 0 || rand() < Math.exp(-delta / T)) {
|
|
for (let n of touchedNodes) cache2[n] = fresh[n];
|
|
if (running += delta, oneWay && running < lowest - 1e-9)
|
|
if (lowest = running, !kept) kept = candidates.map((c) => this.lanes[c].slice());
|
|
else for (let c = 0; c < candidates.length; c++) kept[c].set(this.lanes[candidates[c]]);
|
|
} else
|
|
for (let t of touched) this.swapPair(t, a, b);
|
|
}
|
|
kept && lowest < running - 1e-9 && candidates.forEach((c, k) => this.setLanes(c, kept[k]));
|
|
}
|
|
/**
|
|
* One scan of a hill climb on the annealing's own swaps, over every edge or over those marked
|
|
* in `only`. The edges it changes are added to `changed`.
|
|
*/
|
|
descendSwaps(comp, only, changed) {
|
|
let cache2 = this.nodeCache, fresh = this.nodeFresh;
|
|
for (let n of comp.nodes) cache2[n] = this.evalNode(n);
|
|
let touched = [], touchedNodes = [];
|
|
for (let e of comp.edges)
|
|
if (!(only && !only[e]))
|
|
for (let i = 0; i + 1 < this.lanes[e].length; i++)
|
|
for (let end = -1; end < 2; end++) {
|
|
let a = this.lanes[e][i], b = this.lanes[e][i + 1];
|
|
if (this.swapDelta(e, i, end, null, touched, touchedNodes) < -1e-9) {
|
|
for (let n of touchedNodes) cache2[n] = fresh[n];
|
|
for (let t of touched) changed.push(t);
|
|
} else
|
|
for (let t of touched) this.swapPair(t, a, b);
|
|
}
|
|
}
|
|
/**
|
|
* Both descents, each over the edges that share a node with one that just changed, until
|
|
* nothing changes. A change can only open a gain next to itself, so scanning the whole group
|
|
* again after each one costs several times as much and finds the same order. The first scan
|
|
* of swaps covers the whole group when `wholeFirst`, since no pass before it tried them.
|
|
*/
|
|
polish(comp, from, wholeFirst) {
|
|
let near = from ? this.beside(from) : null, whole = wholeFirst;
|
|
for (let round = 0; round < 200; round++) {
|
|
let changed = [];
|
|
for (let e of comp.edges) (!near || near[e]) && this.optimizeEdge(e) && changed.push(e);
|
|
if (this.descendSwaps(comp, whole ? null : near, changed), whole = !1, !changed.length) return;
|
|
near = this.beside(changed);
|
|
}
|
|
}
|
|
/** Marks the edges that share a node with any of `edges`, themselves included. */
|
|
beside(edges) {
|
|
let g = this.g, mark = new Uint8Array(g.edges.length);
|
|
for (let e of edges)
|
|
for (let n of [g.edges[e].a, g.edges[e].b]) for (let p of g.nodes[n].ports) mark[p.edge] = 1;
|
|
return mark;
|
|
}
|
|
/** Multi-route edges in groups that share no node, each an independent problem; sorted for determinism. */
|
|
components() {
|
|
let g = this.g, E = g.edges.length, parent = new Int32Array(E);
|
|
for (let e = 0; e < E; e++) parent[e] = e;
|
|
let find = (x) => {
|
|
for (; parent[x] !== x; ) x = parent[x] = parent[parent[x]];
|
|
return x;
|
|
};
|
|
for (let node of g.nodes) {
|
|
let first = -1;
|
|
for (let p of node.ports)
|
|
this.lanes[p.edge].length < 2 || (first < 0 ? first = p.edge : parent[find(p.edge)] = find(first));
|
|
}
|
|
let byRoot = /* @__PURE__ */ new Map();
|
|
for (let e = 0; e < E; e++) {
|
|
if (this.lanes[e].length < 2) continue;
|
|
let r = find(e), c = byRoot.get(r);
|
|
c || byRoot.set(r, c = { edges: [], nodes: [] }), c.edges.push(e);
|
|
}
|
|
let comps = [...byRoot.values()];
|
|
for (let c of comps) {
|
|
let nodes = /* @__PURE__ */ new Set();
|
|
for (let e of c.edges)
|
|
nodes.add(g.edges[e].a), nodes.add(g.edges[e].b);
|
|
c.nodes = [...nodes].sort((x, y) => x - y);
|
|
}
|
|
return comps.sort((x, y) => y.edges.length - x.edges.length || x.edges[0] - y.edges[0]);
|
|
}
|
|
/**
|
|
* Assign orders on all edges. Returns the final cost.
|
|
*
|
|
* An unseeded solve runs two passes. The first is greedy propagation and annealing with whole
|
|
* swaps. The second starts from the first's order and does no annealing: it aligns the runs
|
|
* across every pair the first left swapping sides, then descends, and charges each pair that
|
|
* leaves the first pass's order. A seeded solve is one pass from the caller's seed, annealed
|
|
* with the one-way swaps as well, at the caller's `stability`.
|
|
*/
|
|
solve() {
|
|
this.timings = { greedy: 0, anneal: 0, descend: 0 };
|
|
let seed = this.opts.initial;
|
|
if (seed && seed.size > 0) return this.pass(seed, this.opts.stability ?? 1, "seeded");
|
|
this.pass(null, 0, "first");
|
|
let first = /* @__PURE__ */ new Map();
|
|
for (let e of this.g.edges) e.routes.length > 1 && first.set(e.id, e.order.slice());
|
|
return this.pass(first, SECOND_PASS_STABILITY, "second");
|
|
}
|
|
pass(initial, stability, kind) {
|
|
let tStart = performance.now(), g = this.g, rand = mulberry32(this.opts.seed ?? 42), seeded = !!initial;
|
|
this.seedPos = g.edges.map(() => null), this.stability = seeded ? stability : 0;
|
|
for (let e of g.edges) {
|
|
let init = initial?.get(e.id)?.filter((r) => e.routes.includes(r));
|
|
if (!init || !init.length) {
|
|
e.order = e.routes.slice().sort();
|
|
continue;
|
|
}
|
|
if (e.order = init.concat(e.routes.filter((r) => !init.includes(r)).sort()), e.routes.length > 1) {
|
|
let sp = new Int32Array(this.routeIds.length).fill(-1);
|
|
init.forEach((r, i) => sp[this.routeIndex.get(r)] = i), this.seedPos[e.id] = sp;
|
|
}
|
|
}
|
|
this.load(), this.edgeStamp = new Uint32Array(g.edges.length), this.nodeStamp = new Uint32Array(g.nodes.length), this.nodeCache = new Float64Array(g.nodes.length), this.nodeFresh = new Float64Array(g.nodes.length), this.stamp = 0;
|
|
let seen = new Set(seeded ? g.edges.map((e) => e.id) : []), queue = g.edges.slice().sort((x, y) => y.routes.length - x.routes.length || x.id - y.id), frontier = [];
|
|
for (let start of queue)
|
|
if (!seen.has(start.id))
|
|
for (frontier.push(start.id); frontier.length; ) {
|
|
let id = frontier.shift();
|
|
if (seen.has(id)) continue;
|
|
seen.add(id), this.optimizeEdge(id);
|
|
let e = g.edges[id];
|
|
for (let n of [e.a, e.b]) for (let p of g.nodes[n].ports) seen.has(p.edge) || frontier.push(p.edge);
|
|
}
|
|
let timings = this.timings;
|
|
timings.greedy += performance.now() - tStart;
|
|
let comps = this.components(), candidates = comps.reduce((s, c) => s + c.edges.length, 0), totalMoves = this.opts.annealMoves ?? Math.max(1e4, 400 * candidates);
|
|
for (let comp of comps) {
|
|
let t = performance.now(), bestCost = 1 / 0, best = [];
|
|
seeded && (bestCost = kind === "second" ? this.componentCost(comp) : this.descend(comp), best = comp.edges.map((e) => this.lanes[e].slice())), timings.descend += performance.now() - t, t = performance.now();
|
|
let aligned = [];
|
|
kind === "second" ? (this.stability = ALIGN_STABILITY, this.alignSwaps(comp, aligned), this.stability = stability) : bestCost > 0 && this.anneal(comp, Math.ceil(totalMoves * comp.edges.length / candidates), rand, kind === "seeded"), timings.anneal += performance.now() - t, t = performance.now(), kind === "second" ? this.polish(comp, aligned, !0) : kind === "seeded" && this.polish(comp, null, !0);
|
|
let c = kind === "second" ? this.componentCost(comp) : this.descend(comp);
|
|
c < bestCost - 1e-9 ? (bestCost = c, best = comp.edges.map((e) => this.lanes[e].slice())) : best.forEach((l, i) => this.setLanes(comp.edges[i], l)), timings.descend += performance.now() - t;
|
|
}
|
|
return this.store(), this.evalAll();
|
|
}
|
|
};
|
|
function push(buf, kind, edge, out, a, b, next, nextOut, aLeft) {
|
|
buf.kind.push(kind), buf.edge.push(edge), buf.out.push(out), buf.a.push(a), buf.b.push(b), buf.next.push(next), buf.nextOut.push(nextOut), buf.aLeft.push(aLeft);
|
|
}
|
|
function sameSet(a, b) {
|
|
if (a.length !== b.length) return !1;
|
|
let s = new Set(a);
|
|
return b.every((x) => s.has(x));
|
|
}
|
|
function lexLess(a, b) {
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (a[i] < b[i]) return !0;
|
|
if (a[i] > b[i]) return !1;
|
|
}
|
|
return !1;
|
|
}
|
|
function orderLanes(g, opts) {
|
|
return new LaneOrderer(g, opts).solve();
|
|
}
|
|
function snapshotKey(e) {
|
|
let c = e.coords, n = c.length / 2, cmp = (i, j) => c[2 * i] - c[2 * j] || c[2 * i + 1] - c[2 * j + 1], flipped = (cmp(0, n - 1) || (n > 2 ? cmp(1, n - 2) : 0)) > 0, v = (i) => {
|
|
let k = flipped ? n - 1 - i : i;
|
|
return `${c[2 * k].toFixed(9)},${c[2 * k + 1].toFixed(9)}`;
|
|
};
|
|
return { key: `${v(0)}|${v(Math.floor(n / 2))}|${v(n - 1)}`, flipped };
|
|
}
|
|
function snapshotLaneOrders(g) {
|
|
let edges = {};
|
|
for (let e of g.edges) {
|
|
let { key, flipped } = snapshotKey(e), baseline = e.baseline === void 0 ? void 0 : flipped ? -e.baseline : e.baseline;
|
|
edges[key] = { order: flipped ? e.order.slice().reverse() : e.order.slice(), baseline };
|
|
}
|
|
return { version: 3, edges };
|
|
}
|
|
function snapshotEntry(snap, e) {
|
|
let edges = snap?.version === 3 ? snap.edges : null;
|
|
if (!edges || typeof edges != "object") return null;
|
|
let { key, flipped } = snapshotKey(e), hit = Object.hasOwn(edges, key) ? edges[key] : null, order = hit?.order;
|
|
if (!Array.isArray(order) || !order.every((r) => typeof r == "string") || new Set(order).size !== order.length) return null;
|
|
let b = hit.baseline, baseline = typeof b == "number" && Number.isFinite(b) ? flipped ? -b : b : void 0;
|
|
return { order: flipped ? order.slice().reverse() : order, baseline };
|
|
}
|
|
function applyLaneOrders(g, snap) {
|
|
let n = 0;
|
|
for (let e of g.edges) {
|
|
let hit = snapshotEntry(snap, e), kept = hit ? hit.order.filter((r) => e.routes.includes(r)) : [];
|
|
e.order = kept.concat(e.routes.filter((r) => !kept.includes(r)).sort());
|
|
let exact = kept.length === e.routes.length && kept.length === hit?.order.length;
|
|
e.baseline = exact ? hit.baseline : void 0, exact && n++;
|
|
}
|
|
return n;
|
|
}
|
|
function seedFromSnapshot(g, snap) {
|
|
let initial = /* @__PURE__ */ new Map();
|
|
for (let e of g.edges) {
|
|
let hit = snapshotEntry(snap, e);
|
|
if (!hit) continue;
|
|
let kept = hit.order.filter((r) => e.routes.includes(r));
|
|
kept.length && initial.set(e.id, kept);
|
|
}
|
|
return initial;
|
|
}
|
|
|
|
// src/core/baselines.ts
|
|
function centeredPos(order, r) {
|
|
return (order.length - 1) / 2 - order.indexOf(r);
|
|
}
|
|
function stabilizeLanes(g, opts = {}) {
|
|
let maxDrift = opts.maxDrift ?? 0.5, driftWeight = opts.driftWeight ?? 1e-3, iterations = opts.iterations ?? 60, E = g.edges.length, b = new Float64Array(E), terms = g.edges.map(() => []);
|
|
for (let node of g.nodes)
|
|
for (let t of node.transitions) {
|
|
if (t.from < 0 || t.to < 0) continue;
|
|
let pu = node.ports[t.from], pv = node.ports[t.to], u = g.edges[pu.edge], v = g.edges[pv.edge];
|
|
if (u === v) continue;
|
|
let su = pu.end === "b" ? 1 : -1, sv = pv.end === "a" ? 1 : -1, posU = centeredPos(u.order, t.route), posV = centeredPos(v.order, t.route);
|
|
terms[u.id].push({ other: v.id, coef: sv / su, constant: (sv * posV - su * posU) / su }), terms[v.id].push({ other: u.id, coef: su / sv, constant: (su * posU - sv * posV) / sv });
|
|
}
|
|
let clamp = (x) => Math.max(-maxDrift, Math.min(maxDrift, x));
|
|
for (let it = 0; it < iterations; it++) {
|
|
let moved = 0;
|
|
for (let e = 0; e < E; e++) {
|
|
let ts = terms[e];
|
|
if (!ts.length) {
|
|
b[e] = 0;
|
|
continue;
|
|
}
|
|
let targets = ts.map((t) => ({ v: t.coef * b[t.other] + t.constant, w: 1 }));
|
|
targets.push({ v: 0, w: driftWeight }), targets.sort((x, y) => x.v - y.v);
|
|
let total = targets.reduce((s, t) => s + t.w, 0), acc = 0, median = 0;
|
|
for (let t of targets)
|
|
if (acc += t.w, acc >= total / 2) {
|
|
median = t.v;
|
|
break;
|
|
}
|
|
let next = clamp(median);
|
|
moved += Math.abs(next - b[e]), b[e] = next;
|
|
}
|
|
if (moved < 1e-9) break;
|
|
}
|
|
g.edges.forEach((e, i) => e.baseline = b[i]);
|
|
}
|
|
|
|
// src/core/serialize.ts
|
|
function toTransfer(g) {
|
|
return {
|
|
nodes: g.nodes.map((n) => ({ id: n.id, x: n.x, y: n.y, ports: n.ports, transitions: n.transitions })),
|
|
edges: g.edges.map((e) => ({ id: e.id, a: e.a, b: e.b, coords: e.coords, routes: e.routes, order: e.order, direction: [...e.direction], baseline: e.baseline })),
|
|
routeIds: [...g.routes.keys()],
|
|
routes: [...g.routes.values()],
|
|
chains: [...g.chains]
|
|
};
|
|
}
|
|
function unpackPaths(t) {
|
|
let n = t.route.length, paths = new Array(n);
|
|
for (let i = 0; i < n; i++) {
|
|
let from = t.offsets[i], to = t.offsets[i + 1], coords = new Array(to - from), anchors = new Array(to - from);
|
|
for (let k = from; k < to; k++)
|
|
coords[k - from] = t.coords[k], anchors[k - from] = t.anchors[k];
|
|
paths[i] = {
|
|
route: t.routeIds[t.route[i]],
|
|
look: t.looks[t.look[i]],
|
|
coords,
|
|
anchors,
|
|
kind: t.kind[i] === 0 ? "lane" : "connector",
|
|
travel: t.travel[i],
|
|
startDistance: t.startDistance[i],
|
|
edge: t.edge[i],
|
|
node: t.node[i] >= 0 ? t.node[i] : void 0,
|
|
between: t.betweenU[i] >= 0 ? [t.betweenU[i], t.betweenV[i]] : void 0
|
|
};
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
// src/core/worker-client.ts
|
|
var workers = { order: null, layout: null }, failed = { order: !1, layout: !1 }, blobUrl = null, nextId = 1, pending = /* @__PURE__ */ new Map(), kindOf = (req) => req.kind === "layout" || req.kind === "dispose" ? "layout" : "order";
|
|
function settle(p, error) {
|
|
let { req } = p;
|
|
if (req.kind === "layout") {
|
|
let base = { kind: "layout", id: req.id, session: req.session };
|
|
p.resolve(error === null ? { ...base, needGraph: !0 } : { ...base, error });
|
|
} else if (req.kind === "dispose")
|
|
p.resolve({ kind: "dispose", id: req.id });
|
|
else {
|
|
let base = { kind: "order", id: req.id };
|
|
p.resolve(error === null ? { ...base, disposed: !0 } : { ...base, error });
|
|
}
|
|
}
|
|
function settleAll(kind, error) {
|
|
for (let [id, p] of pending)
|
|
kind !== null && p.worker !== kind || (pending.delete(id), settle(p, error));
|
|
}
|
|
function getWorker(kind) {
|
|
let have = workers[kind];
|
|
if (have) return have;
|
|
if (failed[kind] || typeof Worker > "u") return null;
|
|
let worker;
|
|
try {
|
|
blobUrl || (blobUrl = URL.createObjectURL(new Blob(['"use strict";(()=>{var Fn=Object.defineProperty;var Rn=(e,n,t)=>n in e?Fn(e,n,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[n]=t;var B=(e,n,t)=>Rn(e,typeof n!="symbol"?n+"":n,t);function Fe(e){let n=e.edges.map(o=>({...o,direction:new Map(o.direction??[]),order:o.order.slice()})),t=e.routes?new Map(e.routes.map(o=>[o.id,o])):new Map(e.routeIds.map(o=>[o,{id:o,color:"#000"}]));return{nodes:e.nodes,edges:n,routes:t,chains:new Map(e.chains)}}function Re(e){let n=e.length,t=new Uint32Array(n+1),o=0;for(let l=0;l<n;l++)o+=e[l].coords.length,t[l+1]=o;let s={coords:new Float64Array(o),anchors:new Float64Array(o),offsets:t,routeIds:[],route:new Uint32Array(n),looks:[],look:new Uint32Array(n),kind:new Uint8Array(n),travel:new Int8Array(n),startDistance:new Float64Array(n),edge:new Int32Array(n),node:new Int32Array(n),betweenU:new Int32Array(n),betweenV:new Int32Array(n)},i=new Map,d=new Map;for(let l=0;l<n;l++){let r=e[l],c=t[l];for(let f=0;f<r.coords.length;f++)s.coords[c+f]=r.coords[f],s.anchors[c+f]=r.anchors[f];let a=i.get(r.route);a===void 0&&(a=s.routeIds.push(r.route)-1,i.set(r.route,a));let u=d.get(r.look);u===void 0&&(u=s.looks.push(r.look)-1,d.set(r.look,u)),s.route[l]=a,s.look[l]=u,s.kind[l]=r.kind==="lane"?0:1,s.travel[l]=r.travel,s.startDistance[l]=r.startDistance,s.edge[l]=r.edge,s.node[l]=r.node??-1,s.betweenU[l]=r.between?r.between[0]:-1,s.betweenV[l]=r.between?r.between[1]:-1}return s}function je(e){return[e.coords.buffer,e.anchors.buffer,e.offsets.buffer,e.route.buffer,e.look.buffer,e.kind.buffer,e.travel.buffer,e.startDistance.buffer,e.edge.buffer,e.node.buffer,e.betweenU.buffer,e.betweenV.buffer]}function Q(e){let n=0;for(let t=2;t<e.length;t+=2)n+=Math.hypot(e[t]-e[t-2],e[t+1]-e[t-1]);return n}function W(e){let n=new Array(e.length);for(let t=0,o=e.length-2;t<e.length;t+=2,o-=2)n[t]=e[o],n[t+1]=e[o+1];return n}function J(e){for(let n=2;n<e.length;n+=2){let t=e[n]-e[0],o=e[n+1]-e[1],s=Math.hypot(t,o);if(s>0)return[t/s,o/s]}return[0,0]}function Y(e){let n=e.length;for(let t=n-4;t>=0;t-=2){let o=e[n-2]-e[t],s=e[n-1]-e[t+1],i=Math.hypot(o,s);if(i>0)return[o/i,s/i]}return[0,0]}function we(e,n,t,o=.2){let s=e.length/2,i=new Float64Array(Math.max(0,s-1)),d=0;for(let f=0;f<s-1;f++)i[f]=Math.hypot(e[f*2+2]-e[f*2],e[f*2+3]-e[f*2+1]),d+=i[f];if(d<=0)return e.slice();let l=d*(1-o);if(n+t>l){let f=l/(n+t);n*=f,t*=f}let r=$e(e,i,n),c=$e(e,i,d-t),a=[r.x,r.y],u=0;for(let f=0;f<s-1;f++)u+=i[f],u>n&&u<d-t&&a.push(e[f*2+2],e[f*2+3]);return a.push(c.x,c.y),a}function $e(e,n,t){if(t<=0)return{x:e[0],y:e[1]};let o=0;for(let s=0;s<n.length;s++){let i=n[s];if(o+i>=t){let d=i>0?(t-o)/i:0;return{x:e[s*2]+(e[s*2+2]-e[s*2])*d,y:e[s*2+1]+(e[s*2+3]-e[s*2+1])*d}}o+=i}return{x:e[e.length-2],y:e[e.length-1]}}function ie(e,n){let t=e.length/2;if(t<=2||n<=0)return e.slice();let o=new Uint8Array(t);o[0]=1,o[t-1]=1;let s=n*n,i=[0,t-1];for(;i.length;){let l=i.pop(),r=i.pop(),c=0,a=-1,u=e[r*2],f=e[r*2+1],m=e[l*2],h=e[l*2+1];for(let g=r+1;g<l;g++){let b=Ye(e[g*2],e[g*2+1],u,f,m,h);b>c&&(c=b,a=g)}c>s&&a>0&&(o[a]=1,i.push(r,a,a,l))}let d=[];for(let l=0;l<t;l++)o[l]&&d.push(e[l*2],e[l*2+1]);return d}function Ze(e){let n=e.length/2,t=new Float64Array(n);if(n===0||(t[0]=1/0,t[n-1]=1/0,n<=2))return t;let o=[0,n-1],s=[1/0];for(;o.length;){let i=o.pop(),d=o.pop(),l=s.pop(),r=0,c=-1,a=e[d*2],u=e[d*2+1],f=e[i*2],m=e[i*2+1];for(let h=d+1;h<i;h++){let g=Ye(e[h*2],e[h*2+1],a,u,f,m);g>r&&(r=g,c=h)}if(r>0&&c>0){let h=r<l?r:l;t[c]=h,o.push(d,c,c,i),s.push(h,h)}}return t}function Je(e,n,t){let o=e.length/2;if(o<=2||t<=0)return e.slice();let s=t*t,i=[];for(let d=0;d<o;d++)n[d]>s&&i.push(e[d*2],e[d*2+1]);return i}function Ye(e,n,t,o,s,i){let d=s-t,l=i-o,r=d*d+l*l;if(r>0){let c=Math.max(0,Math.min(1,((e-t)*d+(n-o)*l)/r));d=t+d*c,l=o+l*c}else d=t,l=o;return(e-d)*(e-d)+(n-l)*(n-l)}function He(e,n=1e-9){let t=[];for(let o=0;o<e.length;o+=2){let s=t.length;s>=2&&Math.abs(t[s-2]-e[o])<n&&Math.abs(t[s-1]-e[o+1])<n||t.push(e[o],e[o+1])}return t}var Ke=12,Tn=8;function qe(e,n,t=Math.PI/9){return q(e,n,t).points}function q(e,n,t=Math.PI/9){let o=He(e),s=o.length/2;if(s<2||n===0)return{points:o.slice(),anchors:o.slice()};let i=new Float64Array((s-1)*4);for(let c=0;c<s-1;c++){let a=o[c*2],u=o[c*2+1],f=o[c*2+2],m=o[c*2+3],h=f-a,g=m-u,b=Math.hypot(h,g),p=g/b,A=-h/b;i[c*4]=a+p*n,i[c*4+1]=u+A*n,i[c*4+2]=f+p*n,i[c*4+3]=m+A*n}let d=[i[0],i[1]],l=[o[0],o[1]];for(let c=1;c<s-1;c++){let a=(c-1)*4,u=c*4,f=i[a],m=i[a+1],h=i[a+2],g=i[a+3],b=i[u],p=i[u+1],A=i[u+2],w=i[u+3],y=o[c*2],k=o[c*2+1],v=(h-f)*(w-p)-(g-m)*(A-b),x=(h-f)*(A-b)+(g-m)*(w-p),M=n>0==v>0;if(Math.abs(v)<1e-9*Math.max(1,Math.abs(x))){d.push(b,p),l.push(y,k);continue}if(!M){let N=xe(f,m,h,g,b,p,A,w);N&&Math.hypot(N[0]-y,N[1]-k)<=Tn*Math.abs(n)?(d.push(N[0],N[1]),l.push(y,k)):(d.push(h,g,b,p),l.push(y,k,y,k));continue}let L=Math.atan2(g-k,h-y),C=Math.atan2(p-k,b-y)-L;for(;C>Math.PI;)C-=2*Math.PI;for(;C<-Math.PI;)C+=2*Math.PI;let O=Math.max(1,Math.ceil(Math.abs(C)/t)),U=Math.abs(n);d.push(h,g),l.push(y,k);for(let N=1;N<O;N++){let _=L+C*N/O;d.push(y+Math.cos(_)*U,k+Math.sin(_)*U),l.push(y,k)}d.push(b,p),l.push(y,k)}d.push(i[i.length-2],i[i.length-1]),l.push(o[o.length-2],o[o.length-1]);let r=fe(d,l,Math.abs(n)*Ke);return We(r,o[2]-o[0],o[3]-o[1],0,2*Math.abs(n)),We(r,o[o.length-2]-o[o.length-4],o[o.length-1]-o[o.length-3],1,2*Math.abs(n)),r}function We(e,n,t,o,s){let{points:i,anchors:d}=e,l=o?i.length-2:0,r=o?-2:2,c=0;for(let u=l+r;u>=0&&u<i.length;u+=r){let f=i[l]-i[u],m=i[l+1]-i[u+1];if(Math.hypot(f,m)>s||(o?1:-1)*(f*n+m*t)>0)break;c++}if(c=Math.min(c,i.length/2-2),c<=0)return;let a=o?l-2*c:2;i.splice(a,2*c),d.splice(a,2*c)}function ae(e,n,t,o=4,s=[]){let i=He(e),d=i.length/2;if(d<2)return{points:i.slice(),anchors:i.slice()};let l=[i[0],i[1]];for(let m=1;m<d;m++){let h=i[m*2-2],g=i[m*2-1],b=i[m*2],p=i[m*2+1],A=Math.hypot(b-h,p-g),w=Math.max(1,Math.ceil(A/o));for(let y=1;y<=w;y++)l.push(h+(b-h)*y/w,g+(p-g)*y/w)}let r=l.length/2,c=Q(l),a=new Array(l.length),u=0;for(let m=0;m<r;m++){let h=l[m*2],g=l[m*2+1];m>0&&(u+=Math.hypot(h-l[m*2-2],g-l[m*2-1]));let b=n;if(c>0){let C=0,O=n,U=c,N=t;for(let _=0;_<s.length;_+=2){let S=s[_]*c;if(S<=u)C=S,O=s[_+1];else{U=S,N=s[_+1];break}}b=U>C?O+(N-O)*(u-C)/(U-C):N}let p=0,A=0,w=0,y=0;if(m>0){p=h-l[m*2-2],A=g-l[m*2-1];let C=Math.hypot(p,A)||1;p/=C,A/=C}if(m<r-1){w=l[m*2+2]-h,y=l[m*2+3]-g;let C=Math.hypot(w,y)||1;w/=C,y/=C}m===0&&([p,A]=[w,y]),m===r-1&&([w,y]=[p,A]);let k=A,v=-p,x=y,M=-w,I=k+x,L=v+M,R=Math.hypot(I,L);if(R<1e-9)I=k,L=v;else{let C=Math.min(1,Math.max(.5,R/2));I=I/R/C,L=L/R/C}a[m*2]=h+I*b,a[m*2+1]=g+L*b}let f=Math.max(Math.abs(n),Math.abs(t),1);for(let m=1;m<s.length;m+=2)f=Math.max(f,Math.abs(s[m]));return fe(a,l,f*Ke)}function Qe(e){let n=e.length/2;for(let t=0;t+1<n;t++)for(let o=t+2;o+1<n;o++)if(en(e[t*2],e[t*2+1],e[t*2+2],e[t*2+3],e[o*2],e[o*2+1],e[o*2+2],e[o*2+3]))return!0;return!1}function xe(e,n,t,o,s,i,d,l){let r=[t-e,o-n],c=[d-s,l-i],a=r[0]*c[1]-r[1]*c[0];if(Math.abs(a)<1e-12)return null;let u=((s-e)*c[1]-(i-n)*c[0])/a;return[e+r[0]*u,n+r[1]*u]}function en(e,n,t,o,s,i,d,l){let r=t-e,c=o-n,a=d-s,u=l-i,f=r*u-c*a;if(Math.abs(f)<1e-12)return null;let m=s-e,h=i-n,g=(m*u-h*a)/f,b=(m*c-h*r)/f;return g<=0||g>=1||b<=0||b>=1?null:{x:e+r*g,y:n+c*g,t:g,u:b}}var Nn=.1;function fe(e,n,t){let o=e,s=n,i=new Array(e.length/2);i[0]=0;for(let l=1;l<i.length;l++)i[l]=i[l-1]+Math.hypot(e[l*2]-e[l*2-2],e[l*2+1]-e[l*2-1]);let d=0;for(;!(d++>1e3);){let l=o.length/2,r=null;e:for(let h=0;h<l-1&&!r;h++){let g=o[h*2],b=o[h*2+1],p=o[h*2+2],A=o[h*2+3],w=g<p?g:p,y=g<p?p:g,k=b<A?b:A,v=b<A?A:b;for(let x=h+2;x<l-1&&!(i[x]-i[h+1]>t);x++){let M=o[x*2],I=o[x*2+1],L=o[x*2+2],R=o[x*2+3];if(M<w&&L<w||M>y&&L>y||I<k&&R<k||I>v&&R>v)continue;let C=en(g,b,p,A,M,I,L,R);if(C&&!Un(o,s,h,x,C)){r={i:h,j:x,x:C.x,y:C.y,t:C.t,u:C.u};break e}}}if(!r)break;let c=(r.i+1)*2,a=(r.j+1)*2;o=o.slice(0,c).concat([r.x,r.y],o.slice(a));let u=r.i*2,f=r.t;s=s.slice(0,c).concat([s[u]+(s[u+2]-s[u])*f,s[u+1]+(s[u+3]-s[u+1])*f],s.slice(a));let m=i[r.i]+(i[r.i+1]-i[r.i])*f;i=i.slice(0,r.i+1).concat([m],i.slice(r.j+1))}return{points:o,anchors:s}}function Un(e,n,t,o,s){let i=(r,c,a,u,f,m,h)=>{let g=0,b=r,p=c;for(let A=u;A<=f;A++){let w=a[A*2],y=a[A*2+1];g+=b*y-w*p,b=w,p=y}return g+b*h-m*p+m*c-r*h},d=i(s.x,s.y,e,t+1,o,s.x,s.y),l=i(n[t*2]+(n[t*2+2]-n[t*2])*s.t,n[t*2+1]+(n[t*2+3]-n[t*2+1])*s.t,n,t+1,o,n[o*2]+(n[o*2+2]-n[o*2])*s.u,n[o*2+1]+(n[o*2+3]-n[o*2+1])*s.u);return d*l>0&&Math.abs(l)>=Nn*Math.abs(d)}function _n(e,n,t,o,s){let i=[];for(let d=0;d<=s;d++){let l=d/s,r=1-l,c=r*r*r,a=3*r*r*l,u=3*r*l*l,f=l*l*l;i.push(c*e[0]+a*n[0]+u*t[0]+f*o[0],c*e[1]+a*n[1]+u*t[1]+f*o[1])}return i}function Te(e,n,t,o,s,i=1){let d=.5523*i,l=Math.hypot(t[0]-e[0],t[1]-e[1]),r=d*l,c=xe(e[0],e[1],e[0]+n[0],e[1]+n[1],t[0],t[1],t[0]+o[0],t[1]+o[1]);if(c){let f=(c[0]-e[0])*n[0]+(c[1]-e[1])*n[1],m=(c[0]-t[0])*o[0]+(c[1]-t[1])*o[1];if(f>0&&m>0){let h=(Math.hypot(c[0]-e[0],c[1]-e[1])+Math.hypot(c[0]-t[0],c[1]-t[1]))/2;r=d*Math.min(h,l*1.5)}}let a=[e[0]+n[0]*r,e[1]+n[1]*r],u=[t[0]+o[0]*r,t[1]+o[1]*r];return _n(e,a,u,t,s)}function ve(e,n){let t=-(Math.atan2(n[1],n[0])-Math.atan2(e[1],e[0]));for(;t<0;)t+=2*Math.PI;for(;t>=2*Math.PI;)t-=2*Math.PI;return t}function he(e,n,t=10,o=3,s=180,i=0){let d=e.length/2;if(d<3)return e.slice();if(s<180){let f=Math.cos(s*Math.PI/180),m=null;for(let h=1;h<d-1;h++)de(e,h)<f&&(m??(m=[])).push(h);if(m){let h=[],g=0;for(let b of[...m,d-1]){let p=he(e.slice(g*2,(b+1)*2),n,t,o);for(let A=h.length?2:0;A<p.length;A+=2)h.push(p[A],p[A+1]);g=b}return i>0?Gn(h,e,m,i):h}}let l=f=>e[f*2],r=f=>e[f*2+1],c=new Float64Array(d),a=Math.cos(o*Math.PI/180);for(let f=1;f<d-1;f++){let m=l(f)-l(f-1),h=r(f)-r(f-1),g=l(f+1)-l(f),b=r(f+1)-r(f),p=Math.hypot(m,h)||1,A=Math.hypot(g,b)||1;c[f]=(m*g+h*b)/(p*A)}let u=[l(0),r(0)];for(let f=0;f<d-1;f++){let m=f>0&&c[f]<a||f+1<d-1&&c[f+1]<a,h=Math.hypot(l(f+1)-l(f),r(f+1)-r(f)),g=m?Math.max(1,Math.min(t,Math.round(h/n))):1;if(g===1){u.push(l(f+1),r(f+1));continue}let b=f>0?l(f-1):2*l(0)-l(1),p=f>0?r(f-1):2*r(0)-r(1),A=l(f),w=r(f),y=l(f+1),k=r(f+1),v=f+2<d?l(f+2):2*l(d-1)-l(d-2),x=f+2<d?r(f+2):2*r(d-1)-r(d-2),M=0,I=M+(Math.sqrt(Math.hypot(A-b,w-p))||1e-6),L=I+(Math.sqrt(Math.hypot(y-A,k-w))||1e-6),R=L+(Math.sqrt(Math.hypot(v-y,x-k))||1e-6);for(let C=1;C<g;C++){let O=I+(L-I)*C/g,U=(I-O)/(I-M)*b+(O-M)/(I-M)*A,N=(I-O)/(I-M)*p+(O-M)/(I-M)*w,_=(L-O)/(L-I)*A+(O-I)/(L-I)*y,S=(L-O)/(L-I)*w+(O-I)/(L-I)*k,P=(R-O)/(R-L)*y+(O-L)/(R-L)*v,E=(R-O)/(R-L)*k+(O-L)/(R-L)*x,F=(L-O)/(L-M)*U+(O-M)/(L-M)*_,T=(L-O)/(L-M)*N+(O-M)/(L-M)*S,G=(R-O)/(R-I)*_+(O-I)/(R-I)*P,D=(R-O)/(R-I)*S+(O-I)/(R-I)*E;u.push((L-O)/(L-I)*F+(O-I)/(L-I)*G,(L-O)/(L-I)*T+(O-I)/(L-I)*D)}u.push(y,k)}return u}function nn(e,n,t=10,o=3,s=180){if(e.length/2<3||s<180&&de(e,1)<Math.cos(s*Math.PI/180))return J(e);let d=on(e,0,n,t,o);if(d<2)return J(e);let[l,r]=sn(2*e[0]-e[2],2*e[1]-e[3],e[0],e[1],e[2],e[3],e[4],e[5],1/d),c=l-e[0],a=r-e[1],u=Math.hypot(c,a);return u>0?[c/u,a/u]:J(e)}function tn(e,n,t=10,o=3,s=180){let i=e.length/2;if(i<3||s<180&&de(e,i-2)<Math.cos(s*Math.PI/180))return Y(e);let d=on(e,i-2,n,t,o);if(d<2)return Y(e);let l=(i-1)*2,[r,c]=sn(e[l-4],e[l-3],e[l-2],e[l-1],e[l],e[l+1],2*e[l]-e[l-2],2*e[l+1]-e[l-1],(d-1)/d),a=e[l]-r,u=e[l+1]-c,f=Math.hypot(a,u);return f>0?[a/f,u/f]:Y(e)}function on(e,n,t,o,s){let i=e.length/2,d=Math.cos(s*Math.PI/180);if(!(n>0&&de(e,n)<d||n+1<i-1&&de(e,n+1)<d))return 1;let r=Math.hypot(e[n*2+2]-e[n*2],e[n*2+3]-e[n*2+1]);return Math.max(1,Math.min(o,Math.round(r/t)))}function Gn(e,n,t,o){let s=n.length/2,i=new Float64Array(s);for(let a=1;a<s;a++)i[a]=i[a-1]+Math.hypot(n[a*2]-n[a*2-2],n[a*2+1]-n[a*2-1]);let d=new Uint8Array(e.length),l=new Map,r=null;for(let a=0;a<t.length;a++){let u=t[a],f=a>0?t[a-1]:0,m=a+1<t.length?t[a+1]:s-1,h=Math.min(o,.4*(i[u]-i[f]),.4*(i[m]-i[u]));if(h<.05)continue;let g=n[u*2],b=n[u*2+1],p=-1;for(let v=0;v<e.length;v+=2)if(!d[v]&&Math.abs(e[v]-g)<1e-6&&Math.abs(e[v+1]-b)<1e-6){p=v;break}if(p<=0||p>=e.length-2)continue;let A=Xe(e,p,-2,h,r),w=Xe(e,p,2,h,null);if(h=Math.min(h,A.got,w.got),h<.05)continue;let y=Math.max(4,Math.min(12,Math.round(h/2))),k=[];for(let v=0;v<=y;v++){let x=v/y,M=1-x;k.push(M*M*A.x+2*M*x*g+x*x*w.x,M*M*A.y+2*M*x*b+x*x*w.y)}for(let v=A.idx+2;v<w.idx;v+=2)d[v]=1;l.set(A.idx+2,k),r={idx:w.idx,x:w.x,y:w.y}}if(!l.size)return e;let c=[];for(let a=0;a<e.length;a+=2){let u=l.get(a);if(u)for(let f=0;f<u.length;f++)c.push(u[f]);d[a]||c.push(e[a],e[a+1])}return c}function Xe(e,n,t,o,s){let i=0,d=n;for(;;){let l=d+t;if(l<0||l+1>=e.length)return{x:e[d],y:e[d+1],idx:d,got:i};let r=s!==null&&t<0&&d===s.idx,c=r?s.x:e[l],a=r?s.y:e[l+1],u=Math.hypot(c-e[d],a-e[d+1]);if(i+u>=o){let f=u>1e-12?(o-i)/u:0;return{x:e[d]+(c-e[d])*f,y:e[d+1]+(a-e[d+1])*f,idx:l,got:o}}i+=u,d=l}}function de(e,n){let t=e[n*2]-e[n*2-2],o=e[n*2+1]-e[n*2-1],s=e[n*2+2]-e[n*2],i=e[n*2+3]-e[n*2+1];return(t*s+o*i)/((Math.hypot(t,o)||1)*(Math.hypot(s,i)||1))}function sn(e,n,t,o,s,i,d,l,r){let a=0+(Math.sqrt(Math.hypot(t-e,o-n))||1e-6),u=a+(Math.sqrt(Math.hypot(s-t,i-o))||1e-6),f=u+(Math.sqrt(Math.hypot(d-s,l-i))||1e-6),m=a+(u-a)*r,h=(a-m)/(a-0)*e+(m-0)/(a-0)*t,g=(a-m)/(a-0)*n+(m-0)/(a-0)*o,b=(u-m)/(u-a)*t+(m-a)/(u-a)*s,p=(u-m)/(u-a)*o+(m-a)/(u-a)*i,A=(f-m)/(f-u)*s+(m-u)/(f-u)*d,w=(f-m)/(f-u)*i+(m-u)/(f-u)*l,y=(u-m)/(u-0)*h+(m-0)/(u-0)*b,k=(u-m)/(u-0)*g+(m-0)/(u-0)*p,v=(f-m)/(f-a)*b+(m-a)/(f-a)*A,x=(f-m)/(f-a)*p+(m-a)/(f-a)*w;return[(u-m)/(u-a)*y+(m-a)/(u-a)*v,(u-m)/(u-a)*k+(m-a)/(u-a)*x]}function rn(e){let n=1/0,t=1/0,o=-1/0,s=-1/0;for(let i=0;i<e.length;i+=2)e[i]<n&&(n=e[i]),e[i]>o&&(o=e[i]),e[i+1]<t&&(t=e[i+1]),e[i+1]>s&&(s=e[i+1]);return{minX:n,minY:t,maxX:o,maxY:s}}function an(e,n){return e.minX<=n.maxX&&e.maxX>=n.minX&&e.minY<=n.maxY&&e.maxY>=n.minY}function Dn(e){let n=e>>>0;return()=>{n=n+1831565813>>>0;let t=n;return t=Math.imul(t^t>>>15,t|1),t^=t+Math.imul(t^t>>>7,t|61),((t^t>>>14)>>>0)/4294967296}}var cn=0,ln=1,un=2,Bn=.3,Vn=.15,zn=.25,jn=.1,$n=[0,1,2,3,4,6,8,12,16,24,1/0],dn=[];function Wn(e){let n=dn[e];if(n)return n;n=[];let t=(o,s)=>{if(!o.length){n.push(s);return}for(let i=0;i<o.length;i++)t(o.slice(0,i).concat(o.slice(i+1)),s.concat(o[i]))};return t(Array.from({length:e},(o,s)=>s),[]),dn[e]=n,n}var Ae=class{constructor(n,t={}){B(this,"g");B(this,"w");B(this,"opts");B(this,"routeIds");B(this,"routeIndex");B(this,"laneBuf");B(this,"laneStart");B(this,"laneCount");B(this,"posBuf");B(this,"lanes");B(this,"pos");B(this,"portAt");B(this,"next");B(this,"portBase");B(this,"portEdge");B(this,"nextStart");B(this,"nextList");B(this,"terms");B(this,"seedPos",[]);B(this,"stability",0);B(this,"stack",[]);B(this,"edgeStamp",new Uint32Array(0));B(this,"nodeStamp",new Uint32Array(0));B(this,"stamp",0);B(this,"nodeCache",new Float64Array(0));B(this,"nodeFresh",new Float64Array(0));B(this,"timings",{greedy:0,anneal:0,descend:0});this.g=n,this.opts=t,this.w={sameSegmentCrossing:t.sameSegmentCrossing??4,diffSegmentCrossing:t.diffSegmentCrossing??1,separation:t.separation??3,periphery:t.periphery??.5};let o=new Set;for(let r of n.edges)for(let c of r.routes)o.add(c);this.routeIds=[...o].sort(),this.routeIndex=new Map(this.routeIds.map((r,c)=>[r,c]));let s=this.routeIds.length,i=n.edges.length;this.laneStart=new Int32Array(i+1),this.laneCount=new Int32Array(i);for(let r=0;r<i;r++)this.laneCount[r]=n.edges[r].routes.length,this.laneStart[r+1]=this.laneStart[r]+this.laneCount[r];this.laneBuf=new Int32Array(this.laneStart[i]),this.posBuf=new Int32Array(i*s).fill(-1),this.lanes=n.edges.map(r=>this.laneBuf.subarray(this.laneStart[r.id],this.laneStart[r.id+1])),this.pos=n.edges.map(r=>r.routes.length>1?this.posBuf.subarray(r.id*s,(r.id+1)*s):null),this.portAt=new Int32Array(2*n.edges.length).fill(-1);for(let r of n.nodes)r.ports.forEach((c,a)=>this.portAt[2*c.edge+(c.end==="a"?0:1)]=a);this.next=n.nodes.map(r=>{let c=r.ports.map(()=>[]);for(let a of r.transitions){if(a.from<0||a.to<0||a.from===a.to)continue;let u=this.routeIndex.get(a.route);if(u!==void 0)for(let[f,m]of[[a.from,a.to],[a.to,a.from]]){let h=c[f][u]??(c[f][u]=[]);h.includes(m)||h.push(m)}}return c}),this.portBase=new Int32Array(n.nodes.length+1);for(let r=0;r<n.nodes.length;r++)this.portBase[r+1]=this.portBase[r]+n.nodes[r].ports.length;let d=this.portBase[n.nodes.length];this.portEdge=new Int32Array(d),this.nextStart=new Int32Array(d*s+1);let l=[];for(let r=0;r<n.nodes.length;r++)for(let c=0;c<n.nodes[r].ports.length;c++){let a=this.portBase[r]+c;this.portEdge[a]=n.nodes[r].ports[c].edge;for(let u=0;u<s;u++){let f=this.next[r][c][u];if(f)for(let m of f)l.push(m);this.nextStart[a*s+u+1]=l.length}}this.nextList=Int32Array.from(l),this.terms=this.buildTerms(),this.load()}buildTerms(){let n=this.g,t={kind:[],edge:[],out:[],a:[],b:[],next:[],nextOut:[],aLeft:[]},o=new Int32Array(n.nodes.length+1);n.nodes.forEach((a,u)=>{o[u]=t.kind.length;let f=a.ports.length,m=a.ports.map(g=>{let b=n.edges[g.edge];return g.end==="a"?J(b.coords):Y(b.coords).map(p=>-p)}),h=new Float64Array(f*f);for(let g=0;g<f;g++)for(let b=0;b<f;b++)h[g*f+b]=ve(m[g],m[b]);for(let g=0;g<f;g++){let b=a.ports[g],p=n.edges[b.edge],A=p.routes.length;if(A<2)continue;let w=b.end==="a"?1:0,y=this.next[u][g],k=p.routes.map(v=>this.routeIndex.get(v));for(let v=0;v<A;v++){let x=k[v],M=y[x];if(!M||!M.length){Ne(t,un,p.id,w,x,-1,-1,0,0);continue}for(let I=v+1;I<A;I++){let L=k[I],R=y[L];if(!(!R||!R.length))for(let C of M)for(let O of R)if(C===O){if(C<g)continue;let U=a.ports[C];Ne(t,cn,p.id,w,x,L,U.edge,U.end==="a"?1:0,0)}else{let U=h[g*f+C]<h[g*f+O]?1:0;Ne(t,ln,p.id,w,x,L,-1,0,U)}}}}}),o[n.nodes.length]=t.kind.length;let s=this.routeIds.length,i=t.kind.length,d=new Int32Array(i),l=new Int32Array(i),r=new Int32Array(i),c=new Int32Array(i);for(let a=0;a<i;a++)d[a]=t.edge[a]*s+t.a[a],l[a]=t.edge[a]*s+Math.max(0,t.b[a]),r[a]=Math.max(0,t.next[a])*s+t.a[a],c[a]=Math.max(0,t.next[a])*s+Math.max(0,t.b[a]);return{start:o,kind:Uint8Array.from(t.kind),edge:Int32Array.from(t.edge),out:Uint8Array.from(t.out),a:Int32Array.from(t.a),b:Int32Array.from(t.b),next:Int32Array.from(t.next),nextOut:Uint8Array.from(t.nextOut),aLeft:Uint8Array.from(t.aLeft),posA:d,posB:l,nextA:r,nextB:c}}load(){this.g.edges.forEach((n,t)=>{let o=n.order.length===n.routes.length&&Xn(n.order,n.routes)?n.order:n.routes.slice().sort();this.setLanes(t,o.map(s=>this.routeIndex.get(s)))})}store(){this.g.edges.forEach((n,t)=>n.order=Array.from(this.lanes[t],o=>this.routeIds[o]))}setLanes(n,t){let o=this.lanes[n];o.set(t);let s=this.pos[n];if(s)for(let i=0;i<o.length;i++)s[o[i]]=i}nodeCost(n){return this.load(),this.evalNode(n)}totalCost(){return this.load(),this.evalAll()}evalAll(){let n=0;for(let t=0;t<this.g.nodes.length;t++)n+=this.evalNode(t);return n}evalNode(n){let t=this.terms,o=t.edge,s=t.out,i=t.kind,d=t.next,l=t.nextOut,r=t.aLeft,c=t.posA,a=t.posB,u=t.nextA,f=t.nextB,m=this.posBuf,h=this.laneCount,g=this.w.periphery,b=this.w.diffSegmentCrossing,p=this.w.sameSegmentCrossing,A=this.w.separation,w=0;for(let y=t.start[n],k=t.start[n+1];y<k;y++){let v=h[o[y]],x=m[c[y]];s[y]||(x=v-1-x);let M=i[y];if(M===un){x!==0&&x!==v-1&&(w+=g);continue}let I=m[a[y]];s[y]||(I=v-1-I);let L=x<I;if(M===ln){r[y]===1!==L&&(w+=b);continue}let R=m[u[y]],C=m[f[y]];if(!l[y]){let O=h[d[y]];R=O-1-R,C=O-1-C}R<C===L?w+=p:Math.abs(x-I)===1&&Math.abs(R-C)!==1&&(w+=A)}return w}edgeLocalCost(n){let t=this.g.edges[n];return(t.a===t.b?this.evalNode(t.a):this.evalNode(t.a)+this.evalNode(t.b))+this.edgePenalty(n)}edgePenalty(n){let t=this.seedPos[n];if(!t)return 0;let o=this.lanes[n],s=0;for(let i=0;i<o.length;i++){let d=t[o[i]];if(!(d<0))for(let l=i+1;l<o.length;l++){let r=t[o[l]];r>=0&&r<d&&s++}}return s*this.stability}optimizeEdge(n){let t=this.lanes[n],o=t.length;if(o<2)return!1;let s=this.edgeLocalCost(n),i=t.slice(),d=s;if(o<=6){let l=t.slice(),r=new Int32Array(o);for(let c of Wn(o)){for(let u=0;u<o;u++)r[u]=l[c[u]];this.setLanes(n,r);let a=this.edgeLocalCost(n);(a<d-1e-9||Math.abs(a-d)<1e-9&&Zn(r,i))&&(d=a,i=r.slice())}}else{let l=!0;for(;l;){l=!1;for(let r=0;r<o;r++)for(let c=r+1;c<o;c++){let a=i.slice(),u=a[r];a[r]=a[c],a[c]=u,this.setLanes(n,a);let f=this.edgeLocalCost(n);f<d-1e-9&&(d=f,i=a,l=!0)}}}return this.setLanes(n,i),d<s-1e-9}descend(n){let t=!0,o=0;for(;t&&o++<100;){t=!1;for(let s of n.edges)this.optimizeEdge(s)&&(t=!0)}return this.componentCost(n)}componentCost(n){let t=0;for(let o of n.nodes)t+=this.evalNode(o);if(this.seedPos.length)for(let o of n.edges)t+=this.edgePenalty(o);return t}swapAlong(n,t,o,s,i=-1,d=null){let l=this.g,r=this.lanes[n][t],c=this.lanes[n][t+1],a=this.stack,u=this.edgeStamp,{portBase:f,portEdge:m,nextStart:h,nextList:g}=this,b=this.routeIds.length;for(a.length=0,a.push(n);a.length;){let p=a.pop(),A=this.pos[p];if(!A||u[p]===s)continue;u[p]=s;let w=A[r],y=A[c];if(w<0||y<0||Math.abs(w-y)!==1)continue;o.push(p),this.swapPair(p,r,c);let k=l.edges[p];for(let v=0;v<2;v++){if(p===n&&i>=0&&v!==i||p!==n&&d&&d()<Vn)continue;let x=v===0?k.a:k.b,M=this.portAt[2*p+v];if(M<0)continue;let I=f[x]+M,L=h[I*b+r],R=h[I*b+r+1],C=h[I*b+c],O=h[I*b+c+1];if(!(L===R||C===O))for(let U=L;U<R;U++){let N=g[U];for(let _=C;_<O;_++)if(g[_]===N){a.push(m[f[x]+N]);break}}}}}alignRun(n,t,o,s,i,d,l){let r=this.g,c=this.edgeStamp;c[n]=l;let a=[[t,n,o]];for(let u=0;a.length;u++){let f=[];for(let[m,h,g]of a){let b=this.pos[h],p=this.pos[m];if(c[m]===l||!b||!p||r.edges[m].a===r.edges[m].b||r.edges[h].a===r.edges[h].b)continue;c[m]=l;let A=r.edges[h].a===g,w=r.edges[m].a===g,y=this.portAt[2*m+(w?0:1)],k=this.next[g][this.portAt[2*h+(A?0:1)]],v=this.lanes[h],x=this.lanes[m],M=[];for(let C=0;C<v.length;C++){let O=v[A?C:v.length-1-C];p[O]>=0&&k[O]?.includes(y)&&M.push(O)}if(M.length<2)continue;M.reverse();let I=[];for(let C=0;C<x.length;C++){let O=w?C:x.length-1-C;M.includes(x[O])&&I.push(O)}if(I.every((C,O)=>x[C]===M[O]))continue;i.push(m),d.push(x.slice());let L=x.slice();if(I.forEach((C,O)=>L[C]=M[O]),this.setLanes(m,L),u>=s)continue;let R=w?r.edges[m].b:r.edges[m].a;for(let C of r.nodes[R].ports)C.edge!==m&&c[C.edge]!==l&&f.push([C.edge,m,R])}a=f}}alignSwaps(n,t){let o=this.g,s=this.terms,i=this.nodeCache,d=this.nodeStamp;for(let f of n.nodes)i[f]=this.evalNode(f);let l=[],r=[],c=[],a=[],u=(f,m,h,g,b)=>{let p=++this.stamp;if(l.length=0,r.length=0,this.alignRun(f,m,h,g,l,r,p),!l.length)return 0;c.length=0,a.length=0;for(let w of l)for(let y of[o.edges[w].a,o.edges[w].b])d[y]!==p&&(d[y]=p,c.push(y));let A=0;for(let w of c){let y=this.evalNode(w);a.push(y),A+=y-i[w]}if(this.stability>0){let w=l.map(y=>this.lanes[y].slice());for(let y of l)A+=this.edgePenalty(y);l.forEach((y,k)=>this.setLanes(y,r[k]));for(let y of l)A-=this.edgePenalty(y);l.forEach((y,k)=>this.setLanes(y,w[k]))}return b?c.forEach((w,y)=>i[w]=a[y]):l.forEach((w,y)=>this.setLanes(w,r[y])),A};for(let f=0;f<400;f++){let m=-1e-9,h=null,g=new Set;for(let b of n.nodes)for(let p=s.start[b];p<s.start[b+1];p++){if(s.kind[p]!==cn)continue;let A=s.edge[p],w=s.next[p],y=`${b}:${A}:${w}`;if(g.has(y))continue;let k=this.pos[A],v=this.pos[w],x=k[s.a[p]]<k[s.b[p]]==(s.out[p]===1),M=v[s.a[p]]<v[s.b[p]]==(s.nextOut[p]===1);if(x===M){g.add(y);for(let[I,L]of[[A,w],[w,A]]){let R=-1;for(let C of $n){let O=u(I,L,b,C,!1);if(l.length===R)break;R=l.length,O<m&&(m=O,h=[I,L,b,C])}}}}if(!h)return;u(h[0],h[1],h[2],h[3],!0);for(let b of l)t.push(b)}}swapDelta(n,t,o,s,i,d){let l=this.g,r=this.lanes[n][t],c=this.lanes[n][t+1],a=++this.stamp,u=this.nodeStamp;i.length=0,this.swapAlong(n,t,i,a,o,s),d.length=0;for(let m of i){let h=l.edges[m];u[h.a]!==a&&(u[h.a]=a,d.push(h.a)),u[h.b]!==a&&(u[h.b]=a,d.push(h.b))}let f=0;for(let m of d)this.nodeFresh[m]=this.evalNode(m),f+=this.nodeFresh[m]-this.nodeCache[m];if(this.stability>0)for(let m of i){let h=this.seedPos[m];if(!h||h[r]<0||h[c]<0)continue;let g=this.pos[m];f+=g[r]<g[c]==h[r]<h[c]?-this.stability:this.stability}return f}swapPair(n,t,o){let s=this.pos[n],i=this.lanes[n],d=s[t],l=s[o];i[d]=o,i[l]=t,s[t]=l,s[o]=d}anneal(n,t,o,s){let i=n.edges;if(t<=0)return;let d=5,r=Math.pow(.3/d,1/t),c=this.nodeCache,a=this.nodeFresh;for(let p of n.nodes)c[p]=this.evalNode(p);let u=[],f=[],m=0,h=0,g=null,b=d;for(let p=0;p<t;p++,b*=r){let A=i[Math.floor(o()*i.length)],w=Math.floor(o()*(this.lanes[A].length-1)),y=this.lanes[A][w],k=this.lanes[A][w+1],v=s&&o()<Bn?o()<.5?0:1:-1,x=this.swapDelta(A,w,v,v>=0?o:null,u,f);if(x<=0||o()<Math.exp(-x/b)){for(let M of f)c[M]=a[M];if(m+=x,s&&m<h-1e-9)if(h=m,!g)g=i.map(M=>this.lanes[M].slice());else for(let M=0;M<i.length;M++)g[M].set(this.lanes[i[M]])}else for(let M of u)this.swapPair(M,y,k)}g&&h<m-1e-9&&i.forEach((p,A)=>this.setLanes(p,g[A]))}descendSwaps(n,t,o){let s=this.nodeCache,i=this.nodeFresh;for(let r of n.nodes)s[r]=this.evalNode(r);let d=[],l=[];for(let r of n.edges)if(!(t&&!t[r]))for(let c=0;c+1<this.lanes[r].length;c++)for(let a=-1;a<2;a++){let u=this.lanes[r][c],f=this.lanes[r][c+1];if(this.swapDelta(r,c,a,null,d,l)<-1e-9){for(let h of l)s[h]=i[h];for(let h of d)o.push(h)}else for(let h of d)this.swapPair(h,u,f)}}polish(n,t,o){let s=t?this.beside(t):null,i=o;for(let d=0;d<200;d++){let l=[];for(let r of n.edges)(!s||s[r])&&this.optimizeEdge(r)&&l.push(r);if(this.descendSwaps(n,i?null:s,l),i=!1,!l.length)return;s=this.beside(l)}}beside(n){let t=this.g,o=new Uint8Array(t.edges.length);for(let s of n)for(let i of[t.edges[s].a,t.edges[s].b])for(let d of t.nodes[i].ports)o[d.edge]=1;return o}components(){let n=this.g,t=n.edges.length,o=new Int32Array(t);for(let l=0;l<t;l++)o[l]=l;let s=l=>{for(;o[l]!==l;)l=o[l]=o[o[l]];return l};for(let l of n.nodes){let r=-1;for(let c of l.ports)this.lanes[c.edge].length<2||(r<0?r=c.edge:o[s(c.edge)]=s(r))}let i=new Map;for(let l=0;l<t;l++){if(this.lanes[l].length<2)continue;let r=s(l),c=i.get(r);c||i.set(r,c={edges:[],nodes:[]}),c.edges.push(l)}let d=[...i.values()];for(let l of d){let r=new Set;for(let c of l.edges)r.add(n.edges[c].a),r.add(n.edges[c].b);l.nodes=[...r].sort((c,a)=>c-a)}return d.sort((l,r)=>r.edges.length-l.edges.length||l.edges[0]-r.edges[0])}solve(){this.timings={greedy:0,anneal:0,descend:0};let n=this.opts.initial;if(n&&n.size>0)return this.pass(n,this.opts.stability??1,"seeded");this.pass(null,0,"first");let t=new Map;for(let o of this.g.edges)o.routes.length>1&&t.set(o.id,o.order.slice());return this.pass(t,zn,"second")}pass(n,t,o){let s=performance.now(),i=this.g,d=Dn(this.opts.seed??42),l=!!n;this.seedPos=i.edges.map(()=>null),this.stability=l?t:0;for(let g of i.edges){let b=n?.get(g.id)?.filter(p=>g.routes.includes(p));if(!b||!b.length){g.order=g.routes.slice().sort();continue}if(g.order=b.concat(g.routes.filter(p=>!b.includes(p)).sort()),g.routes.length>1){let p=new Int32Array(this.routeIds.length).fill(-1);b.forEach((A,w)=>p[this.routeIndex.get(A)]=w),this.seedPos[g.id]=p}}this.load(),this.edgeStamp=new Uint32Array(i.edges.length),this.nodeStamp=new Uint32Array(i.nodes.length),this.nodeCache=new Float64Array(i.nodes.length),this.nodeFresh=new Float64Array(i.nodes.length),this.stamp=0;let r=new Set(l?i.edges.map(g=>g.id):[]),c=i.edges.slice().sort((g,b)=>b.routes.length-g.routes.length||g.id-b.id),a=[];for(let g of c)if(!r.has(g.id))for(a.push(g.id);a.length;){let b=a.shift();if(r.has(b))continue;r.add(b),this.optimizeEdge(b);let p=i.edges[b];for(let A of[p.a,p.b])for(let w of i.nodes[A].ports)r.has(w.edge)||a.push(w.edge)}let u=this.timings;u.greedy+=performance.now()-s;let f=this.components(),m=f.reduce((g,b)=>g+b.edges.length,0),h=this.opts.annealMoves??Math.max(1e4,400*m);for(let g of f){let b=performance.now(),p=1/0,A=[];l&&(p=o==="second"?this.componentCost(g):this.descend(g),A=g.edges.map(k=>this.lanes[k].slice())),u.descend+=performance.now()-b,b=performance.now();let w=[];o==="second"?(this.stability=jn,this.alignSwaps(g,w),this.stability=t):p>0&&this.anneal(g,Math.ceil(h*g.edges.length/m),d,o==="seeded"),u.anneal+=performance.now()-b,b=performance.now(),o==="second"?this.polish(g,w,!0):o==="seeded"&&this.polish(g,null,!0);let y=o==="second"?this.componentCost(g):this.descend(g);y<p-1e-9?(p=y,A=g.edges.map(k=>this.lanes[k].slice())):A.forEach((k,v)=>this.setLanes(g.edges[v],k)),u.descend+=performance.now()-b}return this.store(),this.evalAll()}};function Ne(e,n,t,o,s,i,d,l,r){e.kind.push(n),e.edge.push(t),e.out.push(o),e.a.push(s),e.b.push(i),e.next.push(d),e.nextOut.push(l),e.aLeft.push(r)}function Xn(e,n){if(e.length!==n.length)return!1;let t=new Set(e);return n.every(o=>t.has(o))}function Zn(e,n){for(let t=0;t<e.length;t++){if(e[t]<n[t])return!0;if(e[t]>n[t])return!1}return!1}function fn(e,n){return(e.length-1)/2-e.indexOf(n)}function hn(e,n={}){let t=n.maxDrift??.5,o=n.driftWeight??.001,s=n.iterations??60,i=e.edges.length,d=new Float64Array(i),l=e.edges.map(()=>[]);for(let c of e.nodes)for(let a of c.transitions){if(a.from<0||a.to<0)continue;let u=c.ports[a.from],f=c.ports[a.to],m=e.edges[u.edge],h=e.edges[f.edge];if(m===h)continue;let g=u.end==="b"?1:-1,b=f.end==="a"?1:-1,p=fn(m.order,a.route),A=fn(h.order,a.route);l[m.id].push({other:h.id,coef:b/g,constant:(b*A-g*p)/g}),l[h.id].push({other:m.id,coef:g/b,constant:(g*p-b*A)/b})}let r=c=>Math.max(-t,Math.min(t,c));for(let c=0;c<s;c++){let a=0;for(let u=0;u<i;u++){let f=l[u];if(!f.length){d[u]=0;continue}let m=f.map(A=>({v:A.coef*d[A.other]+A.constant,w:1}));m.push({v:0,w:o}),m.sort((A,w)=>A.v-w.v);let h=m.reduce((A,w)=>A+w.w,0),g=0,b=0;for(let A of m)if(g+=A.w,g>=h/2){b=A.v;break}let p=r(b);a+=Math.abs(p-d[u]),d[u]=p}if(a<1e-9)break}e.edges.forEach((c,a)=>c.baseline=d[a])}var Jn=1/5,gn=-.5,Pe=2,Yn=.15,Hn=.1,mn=.01;function Kn(e,n,t,o,s){let i=e.length,d=16;for(;d<2*i;)d*=2;let l=(h,g)=>(Math.imul(h,73856093)^Math.imul(g,19349663))&d-1,r=new Int32Array(i),c=new Int32Array(d+1);for(let h=0;h<i;h++)r[h]=l(Math.floor(e[h]/t),Math.floor(n[h]/t)),c[r[h]+1]++;for(let h=0;h<d;h++)c[h+1]+=c[h];let a=new Int32Array(i),u=c.slice(0,d);for(let h=0;h<i;h++)a[u[r[h]]++]=h;let f=t*t,m=new Int32Array(9);for(let h=0;h<i-o;h++){let g=Math.floor(e[h]/t),b=Math.floor(n[h]/t),p=0;for(let A=b-1;A<=b+1;A++)for(let w=g-1;w<=g+1;w++){let y=l(w,A),k=!1;for(let v=0;v<p;v++)m[v]===y&&(k=!0);if(!k){m[p++]=y;for(let v=c[y+1]-1;v>=c[y];v--){let x=a[v];if(x-h<o)break;let M=e[x]-e[h],I=n[x]-n[h],L=M*M+I*I;L<f&&s(h,x,L)}}}}}var qn=1;function pn(e,n,t,o){let s=e.length/2;if(s<3||!(n>=qn))return e;let i=Math.PI/2*n,d=0;for(let S=2;S<e.length;S+=2)d+=Math.hypot(e[S]-e[S-2],e[S+1]-e[S-1]);if(d-t-o<=i)return e;{let S=0,P=0,E=0,F=Math.atan2(e[3]-e[1],e[2]-e[0]);for(let T=4;T<e.length;T+=2){let G=Math.atan2(e[T+1]-e[T-1],e[T]-e[T-2]),D=G-F;D>Math.PI?D-=2*Math.PI:D<-Math.PI&&(D+=2*Math.PI),S+=D,S<P&&(P=S),S>E&&(E=S),F=G}if(E-P<Math.acos(gn))return e}let l=Math.max(2,Math.ceil(d/(n*Jn))),r=d/l,c=l+1,a=new Float64Array(c),u=new Float64Array(c);{let S=1,P=0,E=Math.hypot(e[2]-e[0],e[3]-e[1]);for(let F=0;F<c;F++){let T=F===l?d:F*r;for(;S<s-1&&T>P+E;)P+=E,S++,E=Math.hypot(e[S*2]-e[S*2-2],e[S*2+1]-e[S*2-1]);let G=E>0?Math.min(1,(T-P)/E):0;a[F]=e[S*2-2]+(e[S*2]-e[S*2-2])*G,u[F]=e[S*2-1]+(e[S*2+1]-e[S*2-1])*G}}let f=new Uint8Array(c),m=[];if(Kn(a,u,n,Math.floor(i/r)+1,(S,P,E)=>{let F=Math.max(0,S-Pe),T=Math.min(c-1,S+Pe),G=Math.max(0,P-Pe),D=Math.min(c-1,P+Pe),V=a[T]-a[F],z=u[T]-u[F],j=a[D]-a[G],$=u[D]-u[G];if(V*j+z*$>gn*Math.hypot(V,z)*Math.hypot(j,$))return;let X=a[P]-a[S],K=u[P]-u[S];m.push(S,P,E),f[S]|=V*K-z*X>0?1:2,f[P]|=$*X-j*K>0?1:2}),!m.length)return e;let h=[];for(let S=0;S<m.length;S+=3)h.push(S);h.sort((S,P)=>m[S+2]-m[P+2]);let g=n*n,b=(S,P)=>(a[S]-a[P])*(a[S]-a[P])+(u[S]-u[P])*(u[S]-u[P]),p=new Uint8Array(c),A=[];for(let S of h){let P=m[S],E=m[S+1];if(p[P]===1||p[E]===1||p[P]&&p[E])continue;let F=P,T=E,G=-1;for(;;){if(E-P<=2){G=P+E>>1;break}let $=b(P+1,E-1),X=b(P+1,E),K=b(P,E-1),H=Math.min($,X,K);if(H>=g)break;$===H?(P++,E--):X===H?P++:E--}G>=0&&(P=G-1,E=G+1);let D={ai:[],bi:[],tip:G},V=P<0||E>c-1;for(;!V;){if(p[P]===1||p[E]===1){V=!0;break}D.ai.push(P),D.bi.push(E);let $=P>0&&E<c-1?b(P-1,E+1):1/0,X=P>0?b(P-1,E):1/0,K=E<c-1?b(P,E+1):1/0,H=Math.min($,X,K);if(H>=g)break;$===H?(P--,E++):X===H?P--:E++}if(V||!D.ai.length){p[F]||(p[F]=2),p[T]||(p[T]=2);continue}let z=D.ai[D.ai.length-1],j=D.bi[D.bi.length-1];G>=0?p.fill(1,z,j+1):(p.fill(1,z,D.ai[0]+1),p.fill(1,D.bi[0],j+1)),A.push(D)}let w=n/2,y=S=>{let P=S*r,E=Math.min((P-t)/n,(d-P-o)/n);return E<=0?0:E>=1?1:E*E*(3-2*E)},k=new Float64Array(c),v=new Float64Array(c),x=new Uint8Array(c),M=!1;for(let S of A){let P=!1;for(let F=0;F<S.ai.length&&!P;F++)P=f[S.ai[F]]===3||f[S.bi[F]]===3;if(P)continue;let E=0;if(S.tip>=0){let F=a[S.tip],T=u[S.tip],G=0,D=F,V=T,z=0;for(;G<S.ai.length;G++){let Z=(a[S.ai[G]]+a[S.bi[G]])/2,re=(u[S.ai[G]]+u[S.bi[G]])/2,Oe=Math.hypot(Z-F,re-T);if(Oe>=w){let ze=(w-z)/(Oe-z);D+=(Z-D)*ze,V+=(re-V)*ze;break}D=Z,V=re,z=Oe}if(G>=S.ai.length||y(S.ai[G])<1||y(S.bi[G])<1)continue;let j=S.ai[G],$=S.bi[G],X=Math.hypot(a[j]-a[$],u[j]-u[$])/2;if(X<1e-6)continue;let K=(a[j]-a[$])/(2*X),H=(u[j]-u[$])/(2*X),En=(K*(T-V)-H*(F-D)>0?1:-1)*Math.PI,On=Math.atan2(H,K);for(let Z=j;Z<=$;Z++){let re=On+En*(Z-j)/($-j);k[Z]=D+Math.cos(re)*w-a[Z],v[Z]=V+Math.sin(re)*w-u[Z],x[Z]=1}M=!0,E=G+1}for(let F=E;F<S.ai.length;F++){let T=S.ai[F],G=S.bi[F],D=Math.hypot(a[T]-a[G],u[T]-u[G]);if(D>=n||D<1e-6)continue;let V=(n-D)/2,z=(a[T]-a[G])/D,j=(u[T]-u[G])/D;!x[T]&&Math.hypot(k[T],v[T])<V&&(k[T]=z*V,v[T]=j*V),!x[G]&&Math.hypot(k[G],v[G])<V&&(k[G]=-z*V,v[G]=-j*V),M=!0}}if(!M)return e;let I=Math.max(1,Math.round(Yn*n/r)),L=Float64Array.from(k),R=Float64Array.from(v);for(let S of[L,R])for(let P=0;P<2;P++){let E=Float64Array.from(S),F=0;for(let T=-I;T<=I;T++)F+=E[Math.min(c-1,Math.max(0,T))];for(let T=0;T<c;T++)S[T]=F/(2*I+1),F+=E[Math.min(c-1,T+I+1)]-E[Math.max(0,T-I)]}for(let S=0;S<c;S++){if(x[S])continue;let P=Math.hypot(k[S],v[S]),E=Math.hypot(L[S],R[S]),F=E>1e-9?Math.max(P,E)/E:0;k[S]=L[S]*F,v[S]=R[S]*F}let C=new Uint8Array(c);for(let S=0;S<c;S++){let P=y(S);if(Math.abs(k[S]*P)<mn&&Math.abs(v[S]*P)<mn){k[S]=v[S]=0;continue}k[S]*=P,v[S]*=P,C.fill(1,Math.max(0,S-1),Math.min(c,S+2))}let O=[],U=0,N=0,_=S=>{for(;U<s;U++){if(U>0){let E=Math.hypot(e[U*2]-e[U*2-2],e[U*2+1]-e[U*2-1]);if(N+E>S)return;N+=E}let P=Math.min(c-1,Math.floor(N/r));!C[P]&&!C[Math.min(c-1,P+1)]&&O.push(e[U*2],e[U*2+1])}};for(let S=0;S<c;S++){if(!C[S])continue;_(S*r);let P=[];for(;S<c&&C[S];S++)P.push(a[S]+k[S],u[S]+v[S]);for(let E of ie(P,Hn))O.push(E)}return _(1/0),O}var Qn=16,et=1e3,nt=1,tt=.001,bn=new WeakMap;function yn(e,n){let t=ot(e).order;if(!t.length)return n;let o=new Set(t),s=0;return n.map(i=>o.has(i)?t[s++]:i)}function ot(e){let n="";for(let s of e.edges)s.order.length>1&&(n+=`${s.id}:${s.order.join(",")};`);let t=bn.get(e);if(t&&t.signature===n)return t;let o={signature:n,order:st(e)};return bn.set(e,o),o}function st(e){let n=[],t=[],o=new Map;for(let h of e.nodes){if(h.ports.length<2)continue;let b=h.ports.map(v=>{let x=e.edges[v.edge];return v.end==="a"?J(x.coords):Y(x.coords).map(M=>-M)}),p=(v,x)=>ve(b[v],b[x]),A=(v,x)=>{let M=h.ports[v],I=e.edges[M.edge].order,L=I.indexOf(x);return M.end==="a"?L:I.length-1-L},w=h.transitions.filter(v=>v.from>=0&&v.to>=0&&v.from!==v.to),y=new Map;for(let v of w){let x=Math.min(v.from,v.to),M=Math.max(v.from,v.to),I=`${x}:${M}`,L=y.get(I);L||y.set(I,L={u:x,v:M,routes:[]}),L.routes.includes(v.route)||L.routes.push(v.route)}for(let v of y.values())if(!(v.routes.length<2))for(let x of w){if(v.routes.includes(x.route))continue;let M=[x.from,x.to].filter(L=>L===v.u||L===v.v),I=[];if(M.length){if(M.length===1){let L=M[0],R=x.from===L?x.to:x.from,C=v.u===L?v.v:v.u,O=p(L,R)<p(L,C),U=A(L,x.route);I=v.routes.filter(N=>U<A(L,N)!==O)}}else{let L=p(v.u,v.v);p(v.u,x.from)<L!=p(v.u,x.to)<L&&(I=v.routes)}I.length>1&&n.push({group:I,across:x.route})}let k=new Set(w.map(v=>v.route));h.ports.forEach((v,x)=>{for(let M of e.edges[v.edge].routes){if(w.some(L=>L.route===M&&(L.from===x||L.to===x)))continue;let I=!1;for(let L of k)L===M||!h.ports.some((R,C)=>C!==x&&e.edges[R.edge].routes.includes(L))||(t.push([M,L]),I=!0);I&&o.set(M,(o.get(M)??0)+1)}})}if(!n.length)return[];let s=new Set;for(let h of n){s.add(h.across);for(let g of h.group)s.add(g)}if(s.size>Qn)return[];let i=[...s].sort((h,g)=>(o.get(g)??0)-(o.get(h)??0)||(h<g?-1:h>g?1:0)),d=new Map(i.map((h,g)=>[h,g])),l=i.length,r=i.map(()=>[]);for(let h of n){let g=0;for(let p of h.group)g|=1<<d.get(p);let b=r[d.get(h.across)];b.includes(g)||b.push(g)}let c=i.map(()=>0);for(let[h,g]of t){let b=d.get(h),p=d.get(g);b!==void 0&&p!==void 0&&(c[b]|=1<<p)}let a=(1<<l)-1,u=new Float64Array(a+1).fill(1/0),f=new Int8Array(a+1).fill(-1);u[0]=0;for(let h=0;h<a;h++){if(u[h]===1/0)continue;let g=0;for(let b=h;b;b&=b-1)g++;for(let b=0;b<l;b++){if(h&1<<b)continue;let p=u[h]+Math.abs(b-g)*tt;for(let w of r[b]){let y=h&w;y&&y!==w&&(p+=et)}for(let w=h&c[b];w;w&=w-1)p+=nt;let A=h|1<<b;p<u[A]-1e-12&&(u[A]=p,f[A]=b)}}let m=[];for(let h=a;h;h^=1<<f[h])m.push(i[f[h]]);return m.reverse()}var Se=.25,rt=.25,me=5,it=.6,at=.42,ct=1.5,Mn=.4,lt=12,ut=6,dt=1,ft=4,pe=60,Ie=1.5,ht=60,gt=1.5,mt=1,xn=3,be=class{constructor(){B(this,"state",null)}clear(){this.state=null}stateFor(n,t,o,s,i,d){let l=this.state;return l&&l.graph===n&&l.zoom===t&&l.spacing===o.spacing&&l.width===o.width&&l.casing===o.casingWidth&&l.smooth===s&&l.openFolds===i&&l.laneStyle===d?l:this.state=An(n,t,o,s,i,d)}};function pt(e,n){bt(e);let t=e(n),o=(s,i)=>typeof s=="number"&&Number.isFinite(s)&&s>=i;if(!t||!o(t.spacing,0)||!o(t.width,0)||!o(t.casingWidth,0)||t.spacing===0||t.width===0){let s=t&&typeof t=="object"?JSON.stringify({spacing:t.spacing,width:t.width,casingWidth:t.casingWidth},(i,d)=>d===void 0?"undefined":typeof d=="number"&&!Number.isFinite(d)?String(d):d):String(t);throw new TypeError(`maplibre-gl-lanes: \\`sizes(${n})\\` must return {spacing, width, casingWidth} as finite numbers in px, with spacing and width above 0; got ${s}`)}return t}function bt(e){if(typeof e!="function")throw new TypeError("maplibre-gl-lanes: `sizes` must be a function of the zoom that returns {spacing, width, casingWidth}")}function vn(e,n,t,o={}){let s=performance.now(),i=o.smooth??!0,d=o.openFolds??!0,l=o.laneStyle??null,r=pt(t,n),c=o.cache?o.cache.stateFor(e,n,r,i,d,l):An(e,n,r,i,d,l),a=o.bounds??null,u=o.routes?new Set(o.routes):null,f=new Uint8Array(e.edges.length);for(let p of e.edges){if(!a){f[p.id]=1;continue}p.bounds||(p.bounds=rn(p.coords)),f[p.id]=an(p.bounds,a)?1:0}let m=[],h=new Map,g=0,b=0;for(let p of e.edges)if(!(!f[p.id]||c.short[p.id])){b++;for(let A of p.routes){if(u&&!u.has(A))continue;let w=Le(c,p.id,A);w&&(h.set(te(c,p.id,A),w),g+=w.coords.length/2,m.push(w))}}for(let p=0;p<c.junctions.length;p++){if(!wn(c.junctions[p],f))continue;let A=Ue(c,p).tails;for(let w of A){let y=w.tail;if(!f[w.port.edge]&&!y.edges.some(v=>f[v.edge])||(Be(c,w),w.mode!==1||u&&!u.has(y.route)))continue;let k=w.slide;g+=k.coords.length/2,m.push(k)}for(let w of A)if(!(w.mode===1||u&&!u.has(w.tail.route)))for(let{edge:y}of w.tail.edges){let k=te(c,y,w.tail.route);if(!f[y]||h.has(k))continue;let v=Le(c,y,w.tail.route);v&&(h.set(k,v),g+=v.coords.length/2,m.push(v))}for(let w of c.junctions[p].islands)if(!(u&&!u.has(w.route)||!w.steps.some(y=>f[y.edge])))for(let y of Pn(c,w)){if(y.kind==="lane"){let k=te(c,y.edge,w.route);if(h.has(k))continue;h.set(k,y)}g+=y.coords.length/2,m.push(y)}}for(let p=0;p<c.junctions.length;p++)if(wn(c.junctions[p],f)){for(let A of Ue(c,p).groups)if(!(!f[A.edgeU]||!f[A.edgeV])){kn(c,A);for(let w of A.members){if(w.plan?.mode===1||u&&!u.has(w.route))continue;let y=c.connPaths.get(w.key);y&&(g+=y.coords.length/2,m.push(y))}}}for(let p of m)p.look.dash&&!c.phased.has(p.route)&&(Mt(c,p.route),c.phased.add(p.route));return{zoom:n,scale:c.scale,paths:m,drawOrder:c.drawOrder,bounds:a,mergedEdges:c.mergedEdges,stats:{edges:e.edges.length,edgesBuilt:b,edgesMerged:c.mergedEdges.length,nodes:e.nodes.length,vertices:g,ms:performance.now()-s}}}function An(e,n,t,o,s,i){let{spacing:d,width:l,casingWidth:r}=t,c=512*Math.pow(2,n),a=e.edges.length,u=new Map;for(let P of e.routes.keys())u.set(P,u.size);for(let P of e.edges)for(let E of P.routes)u.has(E)||u.set(E,u.size);let f=Math.max(1,u.size),m=new Float64Array(a*f);for(let P of e.edges){let E=P.order.length;for(let F=0;F<E;F++){let T=u.get(P.order[F]);T!==void 0&&(m[P.id*f+T]=((E-1)/2-F+(P.baseline??0))*d)}}let h=new Array(a),g=new Float64Array(a),b=new Array(a),p=new Array(a);for(let P of e.edges){let E=((P.routes.length-1)/2+Math.abs(P.baseline??0))*d;P.simplifyRank||(P.simplifyRank=Ze(P.coords));let F=Je(P.coords,P.simplifyRank,Math.max(Se,rt*E)/c);for(let T=0;T<F.length;T++)F[T]*=c;h[P.id]=F,g[P.id]=Q(F),b[P.id]=o?nn(F,me,10,3,pe):J(F),p[P.id]=(o?tn(F,me,10,3,pe):Y(F)).map(T=>-T)}let A=P=>(P.routes.length-1)/2*d+l/2+r,w=(P,E,F)=>{let T=m[P.id*f+u.get(E)];return F?T:-T},y=new Uint8Array(a),k=P=>e.nodes[P].ports.length,v=new Float64Array(e.nodes.length);for(let P of e.nodes){let E=0;for(let F of P.ports)E=Math.max(E,e.edges[F.edge].routes.length);v[P.id]=E*d}let x=[],M=new Float64Array(2*a),I=new Float64Array(2*a),L=new Uint8Array(2*a*f);for(let P=0;P<ft;P++){x=At(e,y,g,b,p,A),{need:M,change:I,continues:L}=Pt(e,x,w,d,u,f);let E=!1;for(let F of e.edges){if(y[F.id]||F.a===F.b||k(F.a)<2||k(F.b)<2)continue;let T=g[F.id];(T<dt*Math.max(v[F.a],v[F.b])||T<I[2*F.id]+I[2*F.id+1])&&(y[F.id]=1,E=!0)}if(!E)break}let R=new Float64Array(a),C=new Float64Array(a),O=[];for(let P of e.edges){if(y[P.id]){O.push(P.id);continue}let E=g[P.id]*at;R[P.id]=Math.min(M[2*P.id],E),C[P.id]=Math.min(M[2*P.id+1],E)}let U=new Map,N=(P,E,F)=>{for(let T of P.ports)if(T.edge!==E){for(let G of e.edges[T.edge].routes)if(G!==F)return!0}return!1};for(let P of e.edges)if(!y[P.id])for(let E of P.routes){let F=u.get(E);for(let T=0;T<2;T++){let G=e.nodes[T===0?P.a:P.b];!L[(2*P.id+T)*f+F]&&N(G,P.id,E)&&U.set(E,(U.get(E)??0)+1)}}let _=yn(e,[...e.routes.keys()].sort((P,E)=>(U.get(E)??0)-(U.get(P)??0)||(P<E?-1:P>E?1:0)));return{graph:e,zoom:n,scale:c,spacing:d,width:l,casing:r,smooth:o,openFolds:s,laneStyle:i,looks:new Map,routeIndex:u,routeCount:f,offsets:m,simplePx:h,edgeLen:g,dirA:b,dirB:p,short:y,junctions:x,groups:new Array(x.length),tails:new Array(x.length),continues:L,frontA:R,frontB:C,mergedEdges:O,drawOrder:_,preparedPx:new Array(a),lanes:new Array(a*f),lanePaths:new Array(a*f),connPaths:new Map,phased:new Set}}function wn(e,n){for(let t of e.edges)if(n[t])return!0;return!1}function Ue(e,n){let t=e.groups[n];if(t)return{groups:t,tails:e.tails[n]};let o=e.graph,s=e.junctions[n],i=new Map,d=[];for(let c of s.transitions){let a=s.ports[c.u],u=s.ports[c.v],f=oe(e,a.edge,c.route,a.end==="b"),m=oe(e,u.edge,c.route,u.end==="a"),h=ce(o.edges[a.edge].direction.get(c.route))*(a.end==="b"?1:-1),g=ce(o.edges[u.edge].direction.get(c.route))*(u.end==="a"?1:-1),b=`${a.edge}:${u.edge}:${(f-m).toFixed(3)}`,p=i.get(b);p||(p={edgeU:a.edge,edgeV:u.edge,members:[]},i.set(b,p),d.push(p)),p.members.push({route:c.route,key:ge(e,c.route,a.edge,a.end,u.edge,u.end),pu:a,pv:u,lateral:f,lateralOut:m,travel:h||g,via:c.via})}let l=new Map;for(let c of d)for(let a of c.members)for(let u of a.via){let f=`${a.route}|${u.edge}`,m=l.get(f);m?m.push(a):l.set(f,[a])}for(let c of l.values())if(c.length>1)for(let a of c)a.forked=!0;let r=[];for(let c of s.tails){let a=s.ports[c.port],u=c.edges[c.side==="end"?c.edges.length-1:0],f=c.edges[c.side==="end"?0:c.edges.length-1],m=ce(o.edges[a.edge].direction.get(c.route))*(c.side==="end"?a.end==="b"?1:-1:a.end==="a"?1:-1),h={tail:c,port:a,mode:0,key:c.side==="end"?ge(e,c.route,a.edge,a.end,u.edge,u.forward?"b":"a"):ge(e,c.route,u.edge,u.forward?"a":"b",a.edge,a.end)};r.push(h);let g=c.side==="end"?a.edge:f.edge,b=c.side==="end"?f.edge:a.edge;d.push({edgeU:g,edgeV:b,members:[{route:c.route,pu:a,pv:a,lateral:0,lateralOut:0,travel:m,via:[],plan:h,key:c.side==="end"?ge(e,c.route,g,a.end,b,f.forward?"a":"b"):ge(e,c.route,g,f.forward?"b":"a",b,a.end)}]})}return e.groups[n]=d,e.tails[n]=r,{groups:d,tails:r}}function te(e,n,t){return n*e.routeCount+e.routeIndex.get(t)}function ge(e,n,t,o,s,i){let d=2*e.graph.edges.length;return((t*2+(o==="b"?1:0))*d+s*2+(i==="b"?1:0))*e.routeCount+e.routeIndex.get(n)}function oe(e,n,t,o){let s=e.offsets[te(e,n,t)];return o?s:-s}function ke(e,n,t,o){return e.continues[(2*n+t)*e.routeCount+e.routeIndex.get(o)]!==0}function Me(e,n,t){let o=e.graph.routes.get(t),s=e.laneStyle?e.laneStyle(n,t):null,i=s?.color||o?.color||"#888",d=s?.dash??o?.dash,l=s?.dashColor??o?.dashColor,r=s?.dashCap??o?.dashCap,c=`${i}|${d?d[0]+","+d[1]:""}|${l??""}|${r??""}`,a=e.looks.get(c);return a||e.looks.set(c,a={color:i,dash:d,dashColor:l,dashCap:r}),a}function se(e,n){let t=e.preparedPx[n];if(!t){let o=e.simplePx[n];t=e.smooth?ie(he(o,me,10,3,pe,Ie*e.spacing),Se):o;let s=e.graph.edges[n];if(e.openFolds&&s.routes.length>1&&!e.short[n]){let i=((s.routes.length-1)/2+Math.abs(s.baseline??0))*e.spacing;t=pn(t,2*i+e.width+2*e.casing,e.frontA[n]+e.spacing,e.frontB[n]+e.spacing)}e.preparedPx[n]=t}return t}function De(e,n,t){let o=se(e,n);return t?o:W(o)}function le(e,n,t){let o=te(e,n,t),s=e.lanes[o];if(s!==void 0)return s;let i=e.graph,d=i.edges[n],l=se(e,n),r;if(e.short[n])r=q(l,e.offsets[o]);else{let c=ke(e,n,0,t)?e.frontA[n]:0,a=ke(e,n,1,t)?e.frontB[n]:0,u=c>0||a>0?we(l,c,a):l;r=d.order.length===1&&e.offsets[o]!==0?yt(u,e.offsets[o],ke(e,n,0,t),ke(e,n,1,t),lt*e.spacing,e.spacing):q(u,e.offsets[o]);let f=i.nodes[d.a],m=i.nodes[d.b];c>0&&(r.anchors[0]=f.x*e.scale,r.anchors[1]=f.y*e.scale),a>0&&(r.anchors[r.anchors.length-2]=m.x*e.scale,r.anchors[r.anchors.length-1]=m.y*e.scale)}return e.lanes[o]=r,r}function yt(e,n,t,o,s,i){if(!t&&!o)return q(e,0);let d=t?i:0,l=o?i:0,r=Q(e)-d-l;if(r<1)return q(e,n);let c=we(e,d,l,0),a=o?s:Math.min(s,r),u=t?s:Math.min(s,r),f=y=>1-y*y*(3-2*y),m=y=>n*Math.max(t?f(Math.min(1,y/a)):0,o?f(Math.min(1,(r-y)/u)):0),h=(y,k,v)=>{let x=Math.max(1,Math.ceil((v-k)/s*ut)),M=[];for(let I=1;I<x;I++)M.push(I/x,m(k+(v-k)*I/x));return ae(y,m(k),m(v),4,M)},g=[];d>0&&g.push(q(ye(e,d),n));let b=t?a:0,p=o?u:0;b+p>r-1?g.push(h(c,0,r)):(b>0&&g.push(h(ye(c,b),0,b)),g.push(q(we(c,b,p,0),0)),p>0&&g.push(h(ue(c,p),r-p,r))),l>0&&g.push(q(ue(e,l),n));let A=[],w=[];return g.forEach((y,k)=>{for(let v=k>0?2:0;v<y.points.length;v++)A.push(y.points[v]),w.push(y.anchors[v])}),{points:A,anchors:w}}function Le(e,n,t){let o=te(e,n,t),s=e.lanePaths[o];if(s!==void 0)return s;let i=le(e,n,t);if(i.points.length<4)return e.lanePaths[o]=null,null;let d=e.graph.edges[n],l={route:t,look:Me(e,d,t),coords:i.points,anchors:i.anchors,kind:"lane",travel:ce(d.direction.get(t)),startDistance:0,edge:n};return e.lanePaths[o]=l,l}function _e(e,n){let t=[];for(let o of n)for(let s=0;s<o.length;s+=2)t.length&&Math.abs(t[t.length-2]-o[s])<1e-6&&Math.abs(t[t.length-1]-o[s+1])<1e-6||t.push(o[s],o[s+1]);return e.smooth?ie(he(t,me,10,3,pe,Ie*e.spacing),Se):t}function Be(e,n){if(n.mode)return;n.mode=2;let t=e.graph,o=n.tail,s=n.port,i=le(e,s.edge,o.route),d=o.edges.map(w=>De(e,w.edge,w.forward)),l=o.edges[o.side==="end"?o.edges.length-1:0],r=oe(e,l.edge,o.route,l.forward),c=oe(e,s.edge,o.route,o.side==="end"?s.end==="b":s.end==="a"),a=se(e,s.edge),u=o.side==="end"?ue(s.end==="b"?a:W(a),s.end==="b"?e.frontB[s.edge]:e.frontA[s.edge]):ye(s.end==="a"?a:W(a),s.end==="a"?e.frontA[s.edge]:e.frontB[s.edge]),f=o.side==="end"?_e(e,[u,...d]):_e(e,[...d,u]),m=o.side==="end"?ae(f,c,r):ae(f,r,c);if(m.points.length<4)return;let h=o.side==="end"?s.end==="b"?i.points:W(i.points):s.end==="a"?i.points:W(i.points);o.side==="end"?(m.points[0]=h[h.length-2],m.points[1]=h[h.length-1]):(m.points[m.points.length-2]=h[0],m.points[m.points.length-1]=h[1]);let{points:g,anchors:b}=fe(m.points,m.anchors,xn*e.spacing);if(g.length<4)return;let p=ce(t.edges[s.edge].direction.get(o.route))*(o.side==="end"?s.end==="b"?1:-1:s.end==="a"?1:-1),A=o.side==="end"?[s.edge,l.edge]:[l.edge,s.edge];n.slide={route:o.route,look:Me(e,t.edges[s.edge],o.route),coords:g,anchors:b,kind:"connector",travel:p,startDistance:0,edge:A[0],node:s.node,between:A},e.connPaths.set(n.key,n.slide),n.mode=1}function Pn(e,n){if(n.paths)return n.paths;let t=e.graph,o=n.steps[0],s=n.steps[n.steps.length-1],i=_e(e,n.steps.map(a=>De(e,a.edge,a.forward))),d=oe(e,o.edge,n.route,o.forward),l=oe(e,s.edge,n.route,s.forward),r=ae(i,d,l);if(r.points.length>=4){let a=t.edges[o.edge],u=ce(a.direction.get(n.route))*(o.forward?1:-1);return n.paths=[{route:n.route,look:Me(e,a,n.route),coords:r.points,anchors:r.anchors,kind:"connector",travel:u,startDistance:0,edge:o.edge,node:o.forward?a.a:a.b,between:[o.edge,s.edge]}]}let c=[];for(let a of n.steps){let u=Le(e,a.edge,n.route);u&&c.push(u)}return n.paths=c}function Mt(e,n){let t=e.graph,o=new Map;for(let w of t.chains.get(n)??[])for(let y of w.steps)o.set(y.edge,y.forward);let s=w=>o.get(w)===!1?"a":"b",i=[],d=new Map,l=[],r=(w,y)=>{w&&!d.has(w)&&(d.set(w,i.length),i.push(w),l.push(y))},c=w=>r(Le(e,w,n),o.get(w)!==!1);for(let w of t.edges)!e.short[w.id]&&w.routes.includes(n)&&c(w.id);for(let w=0;w<e.junctions.length;w++){let{groups:y,tails:k}=Ue(e,w);for(let v of k)if(v.tail.route===n)if(Be(e,v),v.mode===1)r(v.slide,!0);else for(let{edge:x}of v.tail.edges)c(x);for(let v of y)if(v.members.some(x=>x.route===n)){kn(e,v);for(let x of v.members)x.route!==n||x.plan?.mode===1||r(e.connPaths.get(x.key),!!x.plan||x.pu.end===s(x.pu.edge))}for(let v of e.junctions[w].islands)if(v.route===n)for(let x of Pn(e,v))r(x,!0)}if(!i.length)return;let a=i.length,u=new Map,f=new Array(2*a);for(let w=0;w<a;w++){let y=i[w].coords;for(let k=0;k<2;k++){let v=k?y[y.length-2]:y[0],x=k?y[y.length-1]:y[1],M=u.get(v);M||u.set(v,M=new Map);let I=M.get(x);I||M.set(x,I=[]),I.push(w*2+k),f[w*2+k]=I}}let m=new Float64Array(a).fill(-1),h=new Float64Array(2*a),g=new Uint8Array(a),b=(w,y,k)=>{let v=m[w];v<0&&(m[w]=v=Q(i[w].coords));let x=l[w]?y?k-v:k:y?k+v:k;h[w*2]=x,h[w*2+1]=l[w]?y?k:k+v:y?k:k-v,g[w]=1},p=0;e:for(let w of t.chains.get(n)??[])for(let y of w.steps){let k=e.lanePaths[te(e,y.edge,n)];if(k){p=d.get(k);break e}}let A=[];for(let w=-1;w<a;w++){if(w>=0){if(g[w])continue;p=w}b(p,0,0),A.length=0,A.push(p);for(let k=0;k<A.length;k++){let v=A[k];for(let x=0;x<2;x++){let M=h[v*2+x];for(let I of f[v*2+x]){let L=I>>1;g[L]||(b(L,I&1,M),A.push(L))}}}let y=0;for(let k of A)y=Math.min(y,h[k*2],h[k*2+1]);for(let k of A){let v=h[k*2]-y;i[k].startDistance=l[k]?v:-v}}}function kn(e,n){let t=n.members[0].plan;if(t&&(Be(e,t),t.mode===1))return;let o=!1;for(let c of n.members)e.connPaths.has(c.key)||(o=!0);if(!o)return;let s=e.graph,i=[],d=new Map,l=!1;for(let c of n.members){if(e.connPaths.set(c.key,null),c.plan)continue;let a=le(e,c.pu.edge,c.route),u=le(e,c.pv.edge,c.route),f=c.pu.end==="b"?a.points:W(a.points),m=c.pv.end==="a"?u.points:W(u.points),h=c.via.length?null:Ge(f,m,e.spacing);d.set(c,{from:f,to:m,made:h}),h&&h.coords.length>=4&&wt(e,c,h.coords)&&(l=!0)}for(let c of n.members){if(c.plan){xt(e,c,i);continue}let{from:a,to:u,made:f}=d.get(c);if((c.via.length||l)&&vt(e,c,a,u))continue;let m=f??Ge(a,u,e.spacing);m.coords.length<4||i.push({member:c,lateral:c.lateral,own:m.coords,coords:m.coords,corner:m.corner})}let r=i.filter(c=>!c.corner);r.length>1&&kt(r,e.spacing);for(let c of i){let a=c.member,u=c.coords,f=s.nodes[a.pu.node],m=new Array(u.length);for(let h=0;h<u.length;h+=2)m[h]=f.x*e.scale,m[h+1]=f.y*e.scale;e.connPaths.set(a.key,{route:a.route,look:Me(e,s.edges[n.edgeU],a.route),coords:u,anchors:m,kind:"connector",travel:a.travel,startDistance:0,edge:n.edgeU,node:a.pu.node,between:[n.edgeU,n.edgeV]})}}function wt(e,n,t){let o=se(e,n.pu.edge),s=se(e,n.pv.edge),i=ue(n.pu.end==="b"?o:W(o),n.pu.end==="b"?e.frontB[n.pu.edge]:e.frontA[n.pu.edge]).concat(ye(n.pv.end==="a"?s:W(s),n.pv.end==="a"?e.frontA[n.pv.edge]:e.frontB[n.pv.edge])),d=Math.max(Math.abs(n.lateral),Math.abs(n.lateralOut))+mt*e.spacing;for(let l=0;l<t.length;l+=2){let r=!1;for(let c=2;c<i.length&&!r;c+=2){let a=i[c-2],u=i[c-1],f=i[c]-a,m=i[c+1]-u,h=f*f+m*m,g=h?Math.max(0,Math.min(1,((t[l]-a)*f+(t[l+1]-u)*m)/h)):0;r=Math.hypot(t[l]-a-g*f,t[l+1]-u-g*m)<=d}if(!r)return!0}return!1}function xt(e,n,t){let o=n.plan.tail,s=n.pu,i=o.edges[o.side==="end"?0:o.edges.length-1],d=le(e,s.edge,o.route),l=le(e,i.edge,o.route),r=i.forward?l.points:W(l.points),c=o.side==="end"?s.end==="b"?d.points:W(d.points):r,a=o.side==="end"?r:s.end==="a"?d.points:W(d.points),u=Ge(c,a,e.spacing);u.coords.length<4||t.push({member:n,lateral:0,own:u.coords,coords:u.coords,corner:u.corner})}function vt(e,n,t,o){let s=e.graph,i=[],d=0,l=b=>{for(let p=0;p<b.length;p+=2){let A=i.length;A&&Math.abs(i[A-2]-b[p])<1e-6&&Math.abs(i[A-1]-b[p+1])<1e-6||(A&&(d+=Math.hypot(b[p]-i[A-2],b[p+1]-i[A-1])),i.push(b[p],b[p+1]))}},r=se(e,n.pu.edge),c=se(e,n.pv.edge),a=[];l(ue(n.pu.end==="b"?r:W(r),n.pu.end==="b"?e.frontB[n.pu.edge]:e.frontA[n.pu.edge]));for(let b of n.via){let p=d;l(De(e,b.edge,b.forward)),a.push((p+d)/2)}l(ye(n.pv.end==="a"?c:W(c),n.pv.end==="a"?e.frontA[n.pv.edge]:e.frontB[n.pv.edge]));let u=[];if(n.forked){let b=d;n.via.forEach((y,k)=>u.push(a[k]/b,oe(e,y.edge,n.route,y.forward)));let p=y=>n.lateral+(n.lateralOut-n.lateral)*y,A=y=>{let k=0,v=n.lateral,x=0;for(let M=0;M<=u.length;M+=2){let I=M<u.length?u[M]:1,L=M<u.length?p(I)+(u[M+1]-p(I))*y:n.lateralOut;I>k&&(x=Math.max(x,Math.abs(L-v)/((I-k)*b))),k=I,v=L}return x},w=1;if(A(1)>Mn){let y=0,k=1;for(let v=0;v<12;v++){let x=(y+k)/2;A(x)>Mn?k=x:y=x}w=y}for(let y=0;y<u.length;y+=2)u[y+1]=p(u[y])+(u[y+1]-p(u[y]))*w}let f=e.smooth?ie(he(i,me,10,3,pe,Ie*e.spacing),Se):i,m=ae(f,n.lateral,n.lateralOut,4,u);if(m.points.length<4)return!1;m.points[0]=t[t.length-2],m.points[1]=t[t.length-1],m.points[m.points.length-2]=o[0],m.points[m.points.length-1]=o[1];let{points:h,anchors:g}=fe(m.points,m.anchors,xn*e.spacing);return h.length<4?!1:(e.connPaths.set(n.key,{route:n.route,look:Me(e,s.edges[n.pu.edge],n.route),coords:h,anchors:g,kind:"connector",travel:n.travel,startDistance:0,edge:n.pu.edge,node:n.pu.node,between:[n.pu.edge,n.pv.edge]}),!0)}function At(e,n,t,o,s,i){let d=e.nodes.length,l=new Int32Array(d);for(let x=0;x<d;x++)l[x]=x;let r=x=>{for(;l[x]!==x;)x=l[x]=l[l[x]];return x};for(let x of e.edges)n[x.id]&&(l[r(x.a)]=r(x.b));let c=new Map,a=new Array(d),u=new Int32Array(2*e.edges.length).fill(-1);for(let x=0;x<d;x++){let M=r(x),I=c.get(M);I||c.set(M,I={ports:[],transitions:[],tails:[],islands:[],edges:[]}),a[x]=I;for(let L of e.nodes[x].ports)I.edges.push(L.edge),!n[L.edge]&&(u[L.edge*2+(L.end==="a"?0:1)]=I.ports.length,I.ports.push({edge:L.edge,end:L.end,node:x,dir:L.end==="a"?o[L.edge]:s[L.edge],hw:i(e.edges[L.edge])}))}let f=[],m=new Uint8Array(e.edges.length),h=(x,M,I)=>{let L=[],R=(C,O)=>{f.push({edge:C,entryNode:O}),m[C]++;let U=e.edges[C],N=U.a===O?U.b:U.a,_=e.nodes[N],S=!1,P=null;for(let E of _.transitions){if(E.route!==x||E.from<0||E.to<0)continue;let F=_.ports[E.from],T=_.ports[E.to],G=F.edge===C&&T.edge!==C?T:T.edge===C&&F.edge!==C?F:null;G&&(n[G.edge]&&m[G.edge]||(S=!0,n[G.edge]?R(G.edge,N):L.push({edge:G.edge,end:G.end,node:N,walked:P??(P=f.slice())})))}S||L.push({edge:-1,end:"a",node:N,walked:P??(P=f.slice())}),m[C]--,f.pop()};return R(M,I),L},g=new Map,b=new Set;for(let x of e.nodes){let M=a[x.id],I=r(x.id);for(let L of x.transitions){if(L.from<0||L.to<0)continue;let R=x.ports[L.from],C=x.ports[L.to];if(!n[R.edge]&&!n[C.edge]){let _=u[R.edge*2+(R.end==="a"?0:1)],S=u[C.edge*2+(C.end==="a"?0:1)];_>=0&&S>=0&&M.transitions.push({route:L.route,u:_,v:S,via:[]});continue}if(n[R.edge]&&n[C.edge])continue;let O=!n[R.edge],U=O?R:C,N=u[U.edge*2+(U.end==="a"?0:1)];if(!(N<0))for(let _ of h(L.route,(O?C:R).edge,x.id)){let S=_.walked.map(V=>({edge:V.edge,forward:e.edges[V.edge].a===V.entryNode})),P=O?S:S.reverse().map(V=>({edge:V.edge,forward:!V.forward}));if(_.edge<0){let V=`${I}|${L.route}|${N}|${P.map(z=>z.edge).join(",")}`;if(b.has(V))continue;b.add(V),M.tails.push({route:L.route,port:N,side:O?"end":"start",edges:P});continue}let E=u[_.edge*2+(_.end==="a"?0:1)];if(E<0||a[_.node]!==M)continue;let F=`${I}|${L.route}|${Math.min(N,E)}|${Math.max(N,E)}`,T=P.reduce((V,z)=>V+t[z.edge],0),G=O?{route:L.route,u:N,v:E,via:P}:{route:L.route,u:E,v:N,via:P},D=g.get(F);if(D){T<D.length&&(M.transitions[D.index]=G,D.length=T);continue}g.set(F,{index:M.transitions.length,length:T}),M.transitions.push(G)}}}let p=new Map,A=x=>{let M=p.get(x);return M===void 0&&p.set(x,M=p.size),M};for(let x of e.routes.keys())A(x);for(let x of e.chains.keys())A(x);for(let x of c.values()){for(let M of x.transitions)A(M.route);for(let M of x.tails)A(M.route)}let w=p.size,y=new Uint8Array(e.edges.length*w);for(let x of c.values()){for(let M of x.transitions){let I=p.get(M.route);for(let L of M.via)y[L.edge*w+I]=1}for(let M of x.tails){let I=p.get(M.route);for(let L of M.edges)y[L.edge*w+I]=1}}let k=(x,M)=>x.forward===M?e.edges[x.edge].a:e.edges[x.edge].b,v=(x,M,I,L)=>{let R=new Set,C=e.nodes[I];for(let O of C.transitions){if(O.route!==M||O.from<0||O.to<0)continue;let U=C.ports[O.from],N=C.ports[O.to];U.edge===L&&n[N.edge]&&R.add(N.edge),N.edge===L&&n[U.edge]&&R.add(U.edge)}if(!R.size)return-1;for(let O of C.transitions){if(O.route!==M||O.from<0||O.to<0)continue;let U=C.ports[O.from],N=C.ports[O.to],_=R.has(N.edge)&&!n[U.edge]?U:R.has(U.edge)&&!n[N.edge]?N:null;if(_&&_.edge!==L)return u[_.edge*2+(_.end==="a"?0:1)]}return-1};for(let[x,M]of e.chains){let I=p.get(x);for(let L of M){let R=L.steps;for(let C=0;C<R.length;C++){if(!n[R[C].edge]||y[R[C].edge*w+I])continue;let O=C;for(;O<R.length&&n[R[O].edge]&&!y[R[O].edge*w+I];)O++;let U=R.slice(C,O).map(F=>({edge:F.edge,forward:F.forward}));for(let F of U)y[F.edge*w+I]=1;let N=U[0],_=U[U.length-1],S=a[k(N,!0)],P=v(S,x,k(N,!0),N.edge),E=v(S,x,k(_,!1),_.edge);P>=0&&E>=0&&P!==E?S.transitions.push({route:x,u:P,v:E,via:U}):P>=0?S.tails.push({route:x,port:P,side:"end",edges:U}):E>=0?S.tails.push({route:x,port:E,side:"start",edges:U}):S.islands.push({route:x,steps:U}),C=O-1}}}return[...c.values()]}function Pt(e,n,t,o,s,i){let d=new Float64Array(2*e.edges.length),l=new Float64Array(2*e.edges.length),r=new Uint8Array(2*e.edges.length*i),c=(a,u)=>{r[(2*a.edge+(a.end==="a"?0:1))*i+s.get(u)]=1};for(let a of n){let u=a.ports,f=new Float64Array(u.length),m=new Uint8Array(u.length);for(let g of a.transitions)m[g.u]=1,m[g.v]=1,c(u[g.u],g.route),c(u[g.v],g.route);for(let g of a.tails)m[g.port]=1,c(u[g.port],g.route);for(let g=0;g<u.length;g++)if(m[g])for(let b=0;b<u.length;b++){if(b===g||!m[b])continue;let p=u[g].dir[0]*u[b].dir[0]+u[g].dir[1]*u[b].dir[1],A=Math.sqrt(Math.max(0,1-p*p)),w;A<.001?w=p>0?6*o:0:w=(u[b].hw+u[g].hw*p)/A,w=Math.max(0,w),w>0&&(w+=it*o),f[g]=Math.max(f[g],Math.min(w,8*o))}let h=new Float64Array(u.length);for(let g of a.transitions){let b=u[g.u],p=u[g.v],A=t(e.edges[b.edge],g.route,b.end==="b"),w=t(e.edges[p.edge],g.route,p.end==="a"),y=Math.abs(A-w);if(y<1e-6)continue;let k=Math.max(2.5*y,3*o);h[g.u]=Math.max(h[g.u],k/2),h[g.v]=Math.max(h[g.v],k/2)}for(let g=0;g<u.length;g++)f[g]=Math.max(f[g],h[g]);u.forEach((g,b)=>l[2*g.edge+(g.end==="a"?0:1)]=h[b]);for(let g of a.tails){let b=u[g.port],p=g.edges[g.side==="end"?0:g.edges.length-1],A=t(e.edges[b.edge],g.route,b.end==="b"),w=t(e.edges[p.edge],g.route,p.forward),y=Math.abs(A-w);y<1e-6||(f[g.port]=Math.max(f[g.port],Math.max(2.5*y,3*o)/2))}u.forEach((g,b)=>d[2*g.edge+(g.end==="a"?0:1)]=f[b])}return{need:d,change:l,continues:r}}function ue(e,n){let t=e.length/2;if(n<=0||t<2)return[e[e.length-2],e[e.length-1]];let o=[],s=n;for(let i=t-1;i>0;i--){let d=e[i*2-2],l=e[i*2-1],r=e[i*2],c=e[i*2+1],a=Math.hypot(r-d,c-l);if(o.unshift(r,c),a>=s){let u=1-s/a;return o.unshift(d+(r-d)*u,l+(c-l)*u),o}s-=a}return o.unshift(e[0],e[1]),o}function ye(e,n){return W(ue(W(e),n))}function ce(e){return e===1?1:e===-1?-1:0}function kt(e,n){let t=e[0],o=Q(t.own);for(let i of e){let d=Q(i.own);d>o&&(t=i,o=d)}if(t.own.length<=4||o<ct*n)return;let s=Lt(t.own);for(let i of e){if(i===t)continue;let d=i.lateral-t.lateral,l=o+d*s;if(l<.25*o)continue;let r=qe(t.own,d);if(r.length<4)continue;let c=Q(r)/l;if(c>1.1||c<.9)continue;let a=Math.hypot(r[0]-i.own[0],r[1]-i.own[1]),u=Math.hypot(r[r.length-2]-i.own[i.own.length-2],r[r.length-1]-i.own[i.own.length-1]);a>n/2||u>n/2||(r[0]=i.own[0],r[1]=i.own[1],r[r.length-2]=i.own[i.own.length-2],r[r.length-1]=i.own[i.own.length-1],i.coords=r)}}function Lt(e){let n=0;for(let t=4;t<e.length;t+=2){let o=e[t-2]-e[t-4],s=e[t-1]-e[t-3],i=e[t]-e[t-2],d=e[t+1]-e[t-1];n+=Math.atan2(o*d-s*i,o*i+s*d)}return n}function Ge(e,n,t){let o=[e[e.length-2],e[e.length-1]],s=[n[0],n[1]],i=Y(e),d=J(n),l=[-d[0],-d[1]],r=Math.hypot(s[0]-o[0],s[1]-o[1]);if(r<1e-6)return{coords:[],corner:!1};if(Math.abs((s[0]-o[0])*i[1]-(s[1]-o[1])*i[0])<.05&&i[0]*d[0]+i[1]*d[1]>.9999)return{coords:[o[0],o[1],s[0],s[1]],corner:!1};let a=St(o,i,s,l,r,t);if(a)return{coords:a,corner:!0};let u=Math.max(4,Math.min(32,Math.round(r/Math.max(2,t/3)))),f=Te(o,i,s,l,u),m=(s[0]-o[0])*i[0]+(s[1]-o[1])*i[1],h=(o[0]-s[0])*l[0]+(o[1]-s[1])*l[1];if(m<0||h<0)for(let g=.5;g>.1&&Qe(f);g/=2)f=Te(o,i,s,l,u,g);return{coords:f,corner:!1}}function St(e,n,t,o,s,i){let d=xe(e[0],e[1],e[0]+n[0],e[1]+n[1],t[0],t[1],t[0]+o[0],t[1]+o[1]);if(!d)return null;let l=(d[0]-e[0])*n[0]+(d[1]-e[1])*n[1],r=(d[0]-t[0])*o[0]+(d[1]-t[1])*o[1];if(l<=.01||r<=.01||l+r>gt*s||Math.acos(Math.max(-1,Math.min(1,-(n[0]*o[0]+n[1]*o[1]))))*180/Math.PI<ht)return null;let a=Math.min(Ie*i,.45*l,.45*r),u=[d[0]-n[0]*a,d[1]-n[1]*a],f=[d[0]-o[0]*a,d[1]-o[1]*a],m=[e[0],e[1],u[0],u[1]],h=8;for(let g=1;g<h;g++){let b=g/h,p=1-b;m.push(p*p*u[0]+2*p*b*d[0]+b*b*f[0],p*p*u[1]+2*p*b*d[1]+b*b*f[1])}return m.push(f[0],f[1],t[0],t[1]),m}function Ln(e,n,t,o){if(t<=0)return;let s=e[e.length-1];s&&s[2]===o&&s[0]+s[1]===n?s[1]+=t:e.push([n,t,o])}var It=2,Ct=15,ee=9,Ce=class{constructor(n,t){B(this,"v");B(this,"c");B(this,"i");B(this,"nv",0);B(this,"ni",0);this.v=new Float32Array(n*ee),this.c=new Uint8Array(n*4),this.i=new Uint32Array(t)}reset(){this.nv=0,this.ni=0}reserve(n,t){for(;(this.nv+n)*ee>this.v.length;)this.growV();for(;this.ni+t>this.i.length;)this.growI()}growV(){let n=new Float32Array(this.v.length*2);n.set(this.v),this.v=n;let t=new Uint8Array(this.c.length*2);t.set(this.c),this.c=t}growI(){let n=new Uint32Array(this.i.length*2);n.set(this.i),this.i=n}vertex(n,t,o,s,i,d,l,r,c,a){(this.nv+1)*ee>this.v.length&&this.growV();let u=this.nv*ee;this.v[u]=n,this.v[u+1]=t,this.v[u+2]=o,this.v[u+3]=s,this.v[u+4]=i,this.v[u+5]=d,this.v[u+6]=l,this.v[u+7]=r,this.v[u+8]=c;let f=this.nv*4;return this.c[f]=a[0],this.c[f+1]=a[1],this.c[f+2]=a[2],this.c[f+3]=a[3],this.nv++}tri(n,t,o){this.ni+3>this.i.length&&this.growI(),this.i[this.ni++]=n,this.i[this.ni++]=t,this.i[this.ni++]=o}quad(n,t,o,s){this.tri(n,t,o),this.tri(t,s,o)}};function Et(e){let n=typeof OffscreenCanvas<"u"?new OffscreenCanvas(1,1).getContext("2d",{willReadFrequently:!0}):typeof document<"u"?document.createElement("canvas").getContext("2d",{willReadFrequently:!0}):null;if(!n)return null;n.fillStyle="#000000",n.fillStyle=e;let t=n.fillStyle;if(n.fillStyle="#ffffff",n.fillStyle=e,t!==n.fillStyle||typeof t!="string")return null;if(/^(#|rgba?\\()/i.test(t))return t;n.clearRect(0,0,1,1),n.fillRect(0,0,1,1);let[o,s,i,d]=n.getImageData(0,0,1,1).data;return`rgba(${o}, ${s}, ${i}, ${d/255})`}function Ve(e,n=!1){let t=new Uint8Array([136,136,136,255]),o=e.trim(),s=/^#([0-9a-f]{3,8})$/i.exec(o);if(s){let r=s[1];return r.length===3||r.length===4?(t[0]=parseInt(r[0]+r[0],16),t[1]=parseInt(r[1]+r[1],16),t[2]=parseInt(r[2]+r[2],16),t[3]=r.length===4?parseInt(r[3]+r[3],16):255):(r.length===6||r.length===8)&&(t[0]=parseInt(r.slice(0,2),16),t[1]=parseInt(r.slice(2,4),16),t[2]=parseInt(r.slice(4,6),16),t[3]=r.length===8?parseInt(r.slice(6,8),16):255),t}let i={black:"#000000",white:"#ffffff",red:"#ff0000",green:"#008000",blue:"#0000ff",yellow:"#ffff00",orange:"#ffa500",purple:"#800080",gray:"#808080",grey:"#808080",silver:"#c0c0c0",navy:"#000080",magenta:"#ff00ff",fuchsia:"#ff00ff",lime:"#00ff00",brown:"#a52a2a",teal:"#008080",maroon:"#800000"};if(i[o.toLowerCase()])return Ve(i[o.toLowerCase()]);let d=/^rgba?\\(([^)]+)\\)$/i.exec(o);if(d){let r=d[1].split(/[\\s,/]+/).filter(c=>c.length);for(let c=0;c<3&&c<r.length;c++){let a=r[c].endsWith("%")?parseFloat(r[c])*255/100:parseFloat(r[c]);t[c]=Math.round(Math.max(0,Math.min(255,a||0)))}if(r.length>3){let c=r[3].endsWith("%")?parseFloat(r[3])/100:parseFloat(r[3]);t[3]=Math.round(Math.max(0,Math.min(1,c))*255)}return t}let l=n?null:Et(o);return l?Ve(l,!0):t}var Ee=class{constructor(){B(this,"chunks",new WeakMap);B(this,"out",null);B(this,"key","")}clear(){this.chunks=new WeakMap,this.out=null,this.key=""}builderFor(n,t){let o=`${n.scale}|${n.origin[0]}|${n.origin[1]}|${n.unitsPerMercator??1}`;return o!==this.key&&(this.chunks=new WeakMap,this.key=o),this.out||(this.out=new Ce(Math.max(64,t*3),Math.max(192,t*9))),this.out.reset(),this.out}emit(n,t,o,s){let i=this.chunks.get(t);if(i&&i.startDistance===t.startDistance&&i.color===t.look.color){Ot(n,i);return}let d=n.nv,l=n.ni;if(In(n,t.coords,t.anchors,o,t.startDistance,s),i===void 0){this.chunks.set(t,null);return}let r=new Uint32Array(n.ni-l);for(let c=0;c<r.length;c++)r[c]=n.i[l+c]-d;this.chunks.set(t,{vertices:n.v.slice(d*ee,n.nv*ee),colors:n.c.slice(d*4,n.nv*4),indices:r,vertexCount:n.nv-d,startDistance:t.startDistance,color:t.look.color})}};function Ot(e,n){e.reserve(n.vertexCount,n.indices.length),e.v.set(n.vertices,e.nv*ee),e.c.set(n.colors,e.nv*4);let t=e.nv;for(let o=0;o<n.indices.length;o++)e.i[e.ni+o]=n.indices[o]+t;e.nv+=n.vertexCount,e.ni+=n.indices.length}function Sn(e,n){let t=0;for(let h of e)t+=h.coords.length;let o=n.cache??null,s=o?o.builderFor(n,t):new Ce(Math.max(64,t*3),Math.max(192,t*9)),i=new Map,d=[],l=[],r=[],c=[],a=[],u=[],f=new Map;for(let h of e){let g=f.get(h.route);g||f.set(h.route,g=new Map);let b=g.get(h.look);b||g.set(h.look,b=[]),b.push(h)}let m=n.drawOrder??[...f.keys()];for(let h of m){let g=f.get(h);if(g)for(let[b,p]of g){let A=s.ni,w=i.get(b.color);w||i.set(b.color,w=Ve(b.color));let y=[];for(let k of p){let v=s.ni;o?o.emit(s,k,w,n):In(s,k.coords,k.anchors,w,k.startDistance,n),Ln(y,v,s.ni-v,k.edge)}if(s.ni>A){d.push([A,s.ni-A]),l.push(h),r.push(b),a.push(y);let k=s.ni,v=[];if(n.width&&b.dash&&b.dash[0]===0&&b.dash[1]>0)for(let x of p){let M=s.ni;Ft(s,x.coords,x.anchors,w,x.startDistance,b.dash[1]*n.width,n),Ln(v,M,s.ni-M,x.edge)}c.push([k,s.ni-k]),u.push(v)}}}return{vertices:s.v.subarray(0,s.nv*ee),colors:s.c.subarray(0,s.nv*4),indices:s.i.subarray(0,s.ni),vertexCount:s.nv,indexCount:s.ni,groups:d,groupRoutes:l,groupLooks:r,groupDots:c,groupPieces:a,groupDotPieces:u}}function Ft(e,n,t,o,s,i,d){let l=n.length/2;if(l<2)return;let r=0;for(let y=1;y<l;y++)r+=Math.hypot(n[y*2]-n[y*2-2],n[y*2+1]-n[y*2-1]);if(r<=0)return;let c=s<0,a=Math.abs(s),u=c?a-r:a,{scale:f,origin:m}=d,h=d.unitsPerMercator??1,g=1,b=0,p=Math.hypot(n[2]-n[0],n[3]-n[1]),A=Math.ceil((u-i/2)/i),w=Math.max(0,Math.ceil((u+r-i/2)/i)-A);for(let y=0;y<w;y++){let k=i/2+(c?A+w-1-y:A+y)*i,v=c?a-k:k-a;for(;g<l-1&&v>b+p;)b+=p,g++,p=Math.hypot(n[g*2]-n[g*2-2],n[g*2+1]-n[g*2-1]);let x=p>0?Math.max(0,Math.min(1,(v-b)/p)):0,M=(g-1)*2,I=g*2,L=n[M]+(n[I]-n[M])*x,R=n[M+1]+(n[I+1]-n[M+1])*x,C=t[M]+(t[I]-t[M])*x,O=t[M+1]+(t[I+1]-t[M+1])*x,U=(C/f-m[0])*h,N=(O/f-m[1])*h,_=e.vertex(U,N,L-C,R-O,-1,-1,-1,-1,k,o),S=e.vertex(U,N,L-C,R-O,1,-1,1,-1,k,o),P=e.vertex(U,N,L-C,R-O,1,1,1,1,k,o),E=e.vertex(U,N,L-C,R-O,-1,1,-1,1,k,o);e.tri(_,S,P),e.tri(_,P,E)}}function In(e,n,t,o,s,i){let d=[],l=[];for(let M=0;M<n.length;M+=2){let I=d.length;I>=2&&Math.abs(d[I-2]-n[M])<1e-6&&Math.abs(d[I-1]-n[M+1])<1e-6||(d.push(n[M],n[M+1]),l.push(t[M],t[M+1]))}let r=d.length/2;if(r<2)return;let{scale:c,origin:a}=i,u=i.unitsPerMercator??1,f=M=>(l[M*2]/c-a[0])*u,m=M=>(l[M*2+1]/c-a[1])*u,h=M=>d[M*2]-l[M*2],g=M=>d[M*2+1]-l[M*2+1],b=new Float64Array(r);for(let M=1;M<r;M++)b[M]=b[M-1]+Math.hypot(d[M*2]-d[M*2-2],d[M*2+1]-d[M*2-1]);let p=M=>s<0?-s-b[M]:s+b[M],A=-1,w=-1,y=(M,I,L,R,C,O)=>{let U=f(M),N=m(M),_=h(M),S=g(M),P=p(M),E=e.vertex(U,N,_,S,I,L,R,C,P,o),F=e.vertex(U,N,_,S,-I,-L,-R,-C,P,o);O&&A>=0&&e.quad(A,w,E,F),A=E,w=F},k=(M,I,L,R,C,O)=>{let U=f(M),N=m(M),_=h(M),S=g(M),P=p(M),E=e.vertex(U,N,_,S,0,0,0,0,P,o),F=Math.atan2(L,I),T=Math.atan2(C,R)-F;for(;T>Math.PI;)T-=2*Math.PI;for(;T<-Math.PI;)T+=2*Math.PI;Math.abs(Math.abs(T)-Math.PI)<1e-6&&(T=O?-Math.PI:Math.PI);let G=Math.max(1,Math.ceil(Math.abs(T)*180/Math.PI/Ct)),D=e.vertex(U,N,_,S,I,L,I,L,P,o);for(let V=1;V<=G;V++){let z=F+T*V/G,j=Math.cos(z),$=Math.sin(z),X=e.vertex(U,N,_,S,j,$,j,$,P,o);e.tri(E,D,X),D=X}},v=new Float64Array(r-1),x=new Float64Array(r-1);for(let M=0;M<r-1;M++){let I=d[M*2+2]-d[M*2],L=d[M*2+3]-d[M*2+1],R=Math.hypot(I,L)||1;v[M]=I/R,x[M]=L/R}for(let M=0;M<r;M++){let I=M>0,L=M<r-1,R=I?v[M-1]:0,C=I?x[M-1]:0,O=L?v[M]:0,U=L?x[M]:0,N=C,_=-R,S=U,P=-O;if(!I){k(M,S,P,-S,-P,!0),y(M,S,P,S,P,!1);continue}if(!L){y(M,N,_,N,_,!0),k(M,N,_,-N,-_,!1);continue}let E=N+S,F=_+P,T=Math.hypot(E,F);if(T<1e-9){y(M,N,_,N,_,!0),k(M,N,_,-N,-_,!1),y(M,S,P,S,P,!1);continue}E/=T,F/=T;let G=E*S+F*P,D=G>1e-6?1/G:1/0;if(D<=It)y(M,E*D,F*D,E,F,!0);else{let z=R*U-C*O>0;y(M,N,_,N,_,!0),z?k(M,N,_,S,P,!1):k(M,-N,-_,-S,-P,!0),y(M,S,P,S,P,!1)}}}function Rt(e){let n=performance.now(),t=Fe(e.graph),o=new Ae(t,e.order).solve();return e.stabilize!==!1&&hn(t,e.stabilize??{}),{id:e.id,cost:o,orders:t.edges.map(s=>s.order),baselines:t.edges.map(s=>s.baseline),ms:performance.now()-n}}var ne=new Map,Tt=16;function Nt(e){let n=Fe(e.graph),t=new Int32Array(n.edges.length),o=0;for(let i of n.edges)t[i.id]=o,o+=i.routes.length;let s={id:e.session,graph:n,origin:e.origin??[0,0],unitsPerMercator:e.unitsPerMercator??1,layoutCache:new be,fullCache:new be,meshCache:new Ee,lookAt:t,looks:null,laneStyle:(i,d)=>{let l=s.looks;if(!l)return null;let r=i.routes.indexOf(d);return r<0?null:l[t[i.id]+r]}};return s}function Ut(e){if(e.graph)for(ne.set(e.session,Nt(e));ne.size>Tt;)ne.delete(ne.keys().next().value);let n=ne.get(e.session);if(!n)return{...Cn(e),needGraph:!0};ne.delete(e.session),ne.set(e.session,n),e.looks!==void 0&&(n.looks=e.looks,n.layoutCache.clear(),n.fullCache.clear(),n.meshCache.clear());let t=e.extent==="full",o=performance.now(),s=vn(n.graph,e.zoom,()=>e.style,{bounds:t?null:e.bounds,smooth:e.smooth,openFolds:e.openFolds,routes:e.routes??void 0,laneStyle:n.looks?n.laneStyle:void 0,cache:t?n.fullCache:n.layoutCache}),i=performance.now(),d=t?_t:Sn(s.paths,{scale:s.scale,origin:n.origin,unitsPerMercator:n.unitsPerMercator,drawOrder:s.drawOrder,width:e.style.width,cache:n.meshCache}),l=performance.now();return{kind:"layout",id:e.id,session:e.session,zoom:s.zoom,scale:s.scale,bounds:s.bounds,mergedEdges:s.mergedEdges,drawOrder:s.drawOrder,stats:s.stats,paths:Re(s.paths),vertices:d.vertices.slice(),colors:d.colors.slice(),indices:d.indices.slice(),vertexCount:d.vertexCount,indexCount:d.indexCount,groups:d.groups,groupRoutes:d.groupRoutes,groupLooks:d.groupLooks,groupDots:d.groupDots,groupPieces:d.groupPieces,groupDotPieces:d.groupDotPieces,layoutMs:i-o,meshMs:l-i}}var _t={vertices:new Float32Array(0),colors:new Uint8Array(0),indices:new Uint32Array(0),vertexCount:0,indexCount:0,groups:[],groupRoutes:[],groupLooks:[],groupDots:[],groupPieces:[],groupDotPieces:[]};function Cn(e){return{kind:"layout",id:e.id,session:e.session,zoom:e.zoom,scale:512*Math.pow(2,e.zoom),bounds:e.bounds,mergedEdges:[],drawOrder:[],stats:{edges:0,edgesBuilt:0,edgesMerged:0,nodes:0,vertices:0,ms:0},paths:Re([]),vertices:new Float32Array(0),colors:new Uint8Array(0),indices:new Uint32Array(0),vertexCount:0,indexCount:0,groups:[],groupRoutes:[],groupLooks:[],groupDots:[],groupPieces:[],groupDotPieces:[],layoutMs:0,meshMs:0}}function Gt(e){return[...je(e.paths),e.vertices.buffer,e.colors.buffer,e.indices.buffer]}typeof self<"u"&&typeof self.postMessage=="function"&&typeof document>"u"&&(self.onmessage=e=>{let n=e.data;try{if(n.kind==="dispose")ne.delete(n.session),self.postMessage({kind:"dispose",id:n.id});else if(n.kind==="layout"){let t=Ut(n);self.postMessage(t,Gt(t))}else self.postMessage(Rt(n))}catch(t){let o=String(t?.stack??t);n.kind==="layout"?self.postMessage({...Cn(n),error:o}):n.kind==="dispose"?self.postMessage({kind:"dispose",id:n.id,error:o}):self.postMessage({id:n.id,cost:NaN,orders:[],baselines:[],ms:0,error:o})}});})();\n'], { type: "text/javascript" }))), worker = new Worker(blobUrl);
|
|
} catch {
|
|
return failed[kind] = !0, null;
|
|
}
|
|
if (!worker)
|
|
return failed[kind] = !0, null;
|
|
worker.onmessage = (ev) => {
|
|
let p = pending.get(ev.data.id);
|
|
p && (pending.delete(ev.data.id), p.resolve(ev.data));
|
|
};
|
|
let broken = (reason) => {
|
|
workers[kind] === worker && (console.warn(`maplibre-gl-lanes: ${kind} worker failed, falling back to the main thread:`, reason), worker.terminate(), workers[kind] = null, failed[kind] = !0, settleAll(kind, reason));
|
|
};
|
|
return worker.onerror = (ev) => broken(ev.message || "the worker script failed"), worker.onmessageerror = () => broken("a message from the worker could not be read"), workers[kind] = worker, worker;
|
|
}
|
|
function nextRequestId() {
|
|
return nextId++;
|
|
}
|
|
function sendToWorker(req, transfer) {
|
|
let kind = kindOf(req);
|
|
if (req.kind === "dispose" && !workers.layout) return null;
|
|
let w = getWorker(kind);
|
|
return w ? new Promise((resolve) => {
|
|
pending.set(req.id, { worker: kind, req, resolve }), w.postMessage(req, transfer ?? []);
|
|
}) : null;
|
|
}
|
|
function disposeWorkers() {
|
|
for (let kind of ["order", "layout"])
|
|
workers[kind]?.terminate(), workers[kind] = null, failed[kind] = !1;
|
|
blobUrl && URL.revokeObjectURL(blobUrl), blobUrl = null, settleAll(null, null);
|
|
}
|
|
|
|
// src/core/order-async.ts
|
|
async function orderLanesAsync(g, opts = {}) {
|
|
let order = { ...opts.order };
|
|
opts.seed && (order.initial = seedFromSnapshot(g, opts.seed));
|
|
let req = { kind: "order", id: nextRequestId(), graph: toTransfer(g), order, stabilize: opts.stabilize }, sent = opts.sync ? null : sendToWorker(req);
|
|
if (!sent) {
|
|
let cost = new LaneOrderer(g, order).solve();
|
|
return opts.stabilize !== !1 && stabilizeLanes(g, opts.stabilize ?? {}), cost;
|
|
}
|
|
let res = await sent;
|
|
return res.disposed ? orderLanesAsync(g, { ...opts, sync: !0 }) : res.error ? (console.warn("maplibre-gl-lanes: ordering in worker failed, running on main thread:", res.error), orderLanesAsync(g, { ...opts, sync: !0 })) : (g.edges.forEach((e, i) => {
|
|
e.order = res.orders[i], e.baseline = res.baselines[i];
|
|
}), res.cost);
|
|
}
|
|
|
|
// src/core/folds.ts
|
|
var STEP_PER_CLEARANCE = 1 / 5, MAX_LEG_COS = -0.5, TANGENT_SAMPLES = 2, SMOOTH_PER_CLEARANCE = 0.15, SIMPLIFY_TOLERANCE_PX = 0.1, MIN_PUSH_PX = 0.01;
|
|
function scanPairs(x, y, reach, minSep, found) {
|
|
let n = x.length, size = 16;
|
|
for (; size < 2 * n; ) size *= 2;
|
|
let hash = (cx, cy) => (Math.imul(cx, 73856093) ^ Math.imul(cy, 19349663)) & size - 1, bucket = new Int32Array(n), start = new Int32Array(size + 1);
|
|
for (let i = 0; i < n; i++)
|
|
bucket[i] = hash(Math.floor(x[i] / reach), Math.floor(y[i] / reach)), start[bucket[i] + 1]++;
|
|
for (let b = 0; b < size; b++) start[b + 1] += start[b];
|
|
let items = new Int32Array(n), fill = start.slice(0, size);
|
|
for (let i = 0; i < n; i++) items[fill[bucket[i]]++] = i;
|
|
let r2 = reach * reach, seen = new Int32Array(9);
|
|
for (let i = 0; i < n - minSep; i++) {
|
|
let cx = Math.floor(x[i] / reach), cy = Math.floor(y[i] / reach), cells = 0;
|
|
for (let gy = cy - 1; gy <= cy + 1; gy++)
|
|
for (let gx = cx - 1; gx <= cx + 1; gx++) {
|
|
let b = hash(gx, gy), again = !1;
|
|
for (let q = 0; q < cells; q++) seen[q] === b && (again = !0);
|
|
if (!again) {
|
|
seen[cells++] = b;
|
|
for (let q = start[b + 1] - 1; q >= start[b]; q--) {
|
|
let j = items[q];
|
|
if (j - i < minSep) break;
|
|
let dx = x[j] - x[i], dy = y[j] - y[i], d2 = dx * dx + dy * dy;
|
|
d2 < r2 && found(i, j, d2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var MIN_CLEARANCE_PX = 1;
|
|
function openFolds(p, clearance, guardA, guardB) {
|
|
let n0 = p.length / 2;
|
|
if (n0 < 3 || !(clearance >= MIN_CLEARANCE_PX)) return p;
|
|
let arcMin = Math.PI / 2 * clearance, total = 0;
|
|
for (let i = 2; i < p.length; i += 2) total += Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
|
|
if (total - guardA - guardB <= arcMin) return p;
|
|
{
|
|
let heading = 0, lo = 0, hi = 0, last = Math.atan2(p[3] - p[1], p[2] - p[0]);
|
|
for (let i = 4; i < p.length; i += 2) {
|
|
let a = Math.atan2(p[i + 1] - p[i - 1], p[i] - p[i - 2]), turn = a - last;
|
|
turn > Math.PI ? turn -= 2 * Math.PI : turn < -Math.PI && (turn += 2 * Math.PI), heading += turn, heading < lo && (lo = heading), heading > hi && (hi = heading), last = a;
|
|
}
|
|
if (hi - lo < Math.acos(MAX_LEG_COS)) return p;
|
|
}
|
|
let count = Math.max(2, Math.ceil(total / (clearance * STEP_PER_CLEARANCE))), step = total / count, n = count + 1, x = new Float64Array(n), y = new Float64Array(n);
|
|
{
|
|
let seg = 1, segStart = 0, segLen = Math.hypot(p[2] - p[0], p[3] - p[1]);
|
|
for (let i = 0; i < n; i++) {
|
|
let s = i === count ? total : i * step;
|
|
for (; seg < n0 - 1 && s > segStart + segLen; )
|
|
segStart += segLen, seg++, segLen = Math.hypot(p[seg * 2] - p[seg * 2 - 2], p[seg * 2 + 1] - p[seg * 2 - 1]);
|
|
let t = segLen > 0 ? Math.min(1, (s - segStart) / segLen) : 0;
|
|
x[i] = p[seg * 2 - 2] + (p[seg * 2] - p[seg * 2 - 2]) * t, y[i] = p[seg * 2 - 1] + (p[seg * 2 + 1] - p[seg * 2 - 1]) * t;
|
|
}
|
|
}
|
|
let side = new Uint8Array(n), seeds = [];
|
|
if (scanPairs(x, y, clearance, Math.floor(arcMin / step) + 1, (i, j, d2) => {
|
|
let i0 = Math.max(0, i - TANGENT_SAMPLES), i1 = Math.min(n - 1, i + TANGENT_SAMPLES), j0 = Math.max(0, j - TANGENT_SAMPLES), j1 = Math.min(n - 1, j + TANGENT_SAMPLES), tix = x[i1] - x[i0], tiy = y[i1] - y[i0], tjx = x[j1] - x[j0], tjy = y[j1] - y[j0];
|
|
if (tix * tjx + tiy * tjy > MAX_LEG_COS * Math.hypot(tix, tiy) * Math.hypot(tjx, tjy)) return;
|
|
let dx2 = x[j] - x[i], dy2 = y[j] - y[i];
|
|
seeds.push(i, j, d2), side[i] |= tix * dy2 - tiy * dx2 > 0 ? 1 : 2, side[j] |= tjy * dx2 - tjx * dy2 > 0 ? 1 : 2;
|
|
}), !seeds.length) return p;
|
|
let order = [];
|
|
for (let s = 0; s < seeds.length; s += 3) order.push(s);
|
|
order.sort((a, b) => seeds[a + 2] - seeds[b + 2]);
|
|
let c2 = clearance * clearance, dist2 = (i, j) => (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]), taken = new Uint8Array(n), folds = [];
|
|
for (let s of order) {
|
|
let i = seeds[s], j = seeds[s + 1];
|
|
if (taken[i] === 1 || taken[j] === 1 || taken[i] && taken[j]) continue;
|
|
let seedI = i, seedJ = j, tip = -1;
|
|
for (; ; ) {
|
|
if (j - i <= 2) {
|
|
tip = i + j >> 1;
|
|
break;
|
|
}
|
|
let both = dist2(i + 1, j - 1), a = dist2(i + 1, j), b = dist2(i, j - 1), best = Math.min(both, a, b);
|
|
if (best >= c2) break;
|
|
both === best ? (i++, j--) : a === best ? i++ : j--;
|
|
}
|
|
tip >= 0 && (i = tip - 1, j = tip + 1);
|
|
let fold = { ai: [], bi: [], tip }, blocked = i < 0 || j > n - 1;
|
|
for (; !blocked; ) {
|
|
if (taken[i] === 1 || taken[j] === 1) {
|
|
blocked = !0;
|
|
break;
|
|
}
|
|
fold.ai.push(i), fold.bi.push(j);
|
|
let both = i > 0 && j < n - 1 ? dist2(i - 1, j + 1) : 1 / 0, a = i > 0 ? dist2(i - 1, j) : 1 / 0, b = j < n - 1 ? dist2(i, j + 1) : 1 / 0, best = Math.min(both, a, b);
|
|
if (best >= c2) break;
|
|
both === best ? (i--, j++) : a === best ? i-- : j++;
|
|
}
|
|
if (blocked || !fold.ai.length) {
|
|
taken[seedI] || (taken[seedI] = 2), taken[seedJ] || (taken[seedJ] = 2);
|
|
continue;
|
|
}
|
|
let lo = fold.ai[fold.ai.length - 1], hi = fold.bi[fold.bi.length - 1];
|
|
tip >= 0 ? taken.fill(1, lo, hi + 1) : (taken.fill(1, lo, fold.ai[0] + 1), taken.fill(1, fold.bi[0], hi + 1)), folds.push(fold);
|
|
}
|
|
let R = clearance / 2, ramp = (i) => {
|
|
let s = i * step, t = Math.min((s - guardA) / clearance, (total - s - guardB) / clearance);
|
|
return t <= 0 ? 0 : t >= 1 ? 1 : t * t * (3 - 2 * t);
|
|
}, dx = new Float64Array(n), dy = new Float64Array(n), capped = new Uint8Array(n), moved = !1;
|
|
for (let f of folds) {
|
|
let stack = !1;
|
|
for (let k = 0; k < f.ai.length && !stack; k++) stack = side[f.ai[k]] === 3 || side[f.bi[k]] === 3;
|
|
if (stack) continue;
|
|
let from = 0;
|
|
if (f.tip >= 0) {
|
|
let tx = x[f.tip], ty = y[f.tip], k = 0, mx = tx, my = ty, reached = 0;
|
|
for (; k < f.ai.length; k++) {
|
|
let qx = (x[f.ai[k]] + x[f.bi[k]]) / 2, qy = (y[f.ai[k]] + y[f.bi[k]]) / 2, far = Math.hypot(qx - tx, qy - ty);
|
|
if (far >= R) {
|
|
let t = (R - reached) / (far - reached);
|
|
mx += (qx - mx) * t, my += (qy - my) * t;
|
|
break;
|
|
}
|
|
mx = qx, my = qy, reached = far;
|
|
}
|
|
if (k >= f.ai.length || ramp(f.ai[k]) < 1 || ramp(f.bi[k]) < 1) continue;
|
|
let a = f.ai[k], b = f.bi[k], h = Math.hypot(x[a] - x[b], y[a] - y[b]) / 2;
|
|
if (h < 1e-6) continue;
|
|
let ux = (x[a] - x[b]) / (2 * h), uy = (y[a] - y[b]) / (2 * h), sweep = (ux * (ty - my) - uy * (tx - mx) > 0 ? 1 : -1) * Math.PI, a0 = Math.atan2(uy, ux);
|
|
for (let q = a; q <= b; q++) {
|
|
let ang = a0 + sweep * (q - a) / (b - a);
|
|
dx[q] = mx + Math.cos(ang) * R - x[q], dy[q] = my + Math.sin(ang) * R - y[q], capped[q] = 1;
|
|
}
|
|
moved = !0, from = k + 1;
|
|
}
|
|
for (let k = from; k < f.ai.length; k++) {
|
|
let a = f.ai[k], b = f.bi[k], d = Math.hypot(x[a] - x[b], y[a] - y[b]);
|
|
if (d >= clearance || d < 1e-6) continue;
|
|
let push2 = (clearance - d) / 2, ux = (x[a] - x[b]) / d, uy = (y[a] - y[b]) / d;
|
|
!capped[a] && Math.hypot(dx[a], dy[a]) < push2 && (dx[a] = ux * push2, dy[a] = uy * push2), !capped[b] && Math.hypot(dx[b], dy[b]) < push2 && (dx[b] = -ux * push2, dy[b] = -uy * push2), moved = !0;
|
|
}
|
|
}
|
|
if (!moved) return p;
|
|
let half = Math.max(1, Math.round(SMOOTH_PER_CLEARANCE * clearance / step)), sx = Float64Array.from(dx), sy = Float64Array.from(dy);
|
|
for (let d of [sx, sy])
|
|
for (let pass = 0; pass < 2; pass++) {
|
|
let src = Float64Array.from(d), sum = 0;
|
|
for (let i = -half; i <= half; i++) sum += src[Math.min(n - 1, Math.max(0, i))];
|
|
for (let i = 0; i < n; i++)
|
|
d[i] = sum / (2 * half + 1), sum += src[Math.min(n - 1, i + half + 1)] - src[Math.max(0, i - half)];
|
|
}
|
|
for (let i = 0; i < n; i++) {
|
|
if (capped[i]) continue;
|
|
let given = Math.hypot(dx[i], dy[i]), averaged = Math.hypot(sx[i], sy[i]), scale = averaged > 1e-9 ? Math.max(given, averaged) / averaged : 0;
|
|
dx[i] = sx[i] * scale, dy[i] = sy[i] * scale;
|
|
}
|
|
let live = new Uint8Array(n);
|
|
for (let i = 0; i < n; i++) {
|
|
let w = ramp(i);
|
|
if (Math.abs(dx[i] * w) < MIN_PUSH_PX && Math.abs(dy[i] * w) < MIN_PUSH_PX) {
|
|
dx[i] = dy[i] = 0;
|
|
continue;
|
|
}
|
|
dx[i] *= w, dy[i] *= w, live.fill(1, Math.max(0, i - 1), Math.min(n, i + 2));
|
|
}
|
|
let out = [], vertex = 0, cum = 0, flush = (upTo) => {
|
|
for (; vertex < n0; vertex++) {
|
|
if (vertex > 0) {
|
|
let l = Math.hypot(p[vertex * 2] - p[vertex * 2 - 2], p[vertex * 2 + 1] - p[vertex * 2 - 1]);
|
|
if (cum + l > upTo) return;
|
|
cum += l;
|
|
}
|
|
let at = Math.min(n - 1, Math.floor(cum / step));
|
|
!live[at] && !live[Math.min(n - 1, at + 1)] && out.push(p[vertex * 2], p[vertex * 2 + 1]);
|
|
}
|
|
};
|
|
for (let i = 0; i < n; i++) {
|
|
if (!live[i]) continue;
|
|
flush(i * step);
|
|
let run = [];
|
|
for (; i < n && live[i]; i++) run.push(x[i] + dx[i], y[i] + dy[i]);
|
|
for (let v of simplify(run, SIMPLIFY_TOLERANCE_PX)) out.push(v);
|
|
}
|
|
return flush(1 / 0), out;
|
|
}
|
|
|
|
// src/core/stacking.ts
|
|
var MAX_ROUTES = 16, WEAVE_COST = 1e3, END_OVER_COST = 1, MOVE_COST = 1e-3, cache = /* @__PURE__ */ new WeakMap();
|
|
function restack(g, base) {
|
|
let stack = stackingOf(g).order;
|
|
if (!stack.length) return base;
|
|
let involved = new Set(stack), next = 0;
|
|
return base.map((r) => involved.has(r) ? stack[next++] : r);
|
|
}
|
|
function stackingOf(g) {
|
|
let signature = "";
|
|
for (let e of g.edges) e.order.length > 1 && (signature += `${e.id}:${e.order.join(",")};`);
|
|
let known = cache.get(g);
|
|
if (known && known.signature === signature) return known;
|
|
let made = { signature, order: solve(g) };
|
|
return cache.set(g, made), made;
|
|
}
|
|
function solve(g) {
|
|
let weaves = [], endsUnder = [], endsCount = /* @__PURE__ */ new Map();
|
|
for (let node of g.nodes) {
|
|
if (node.ports.length < 2) continue;
|
|
let dirs = node.ports.map((p) => {
|
|
let e = g.edges[p.edge];
|
|
return p.end === "a" ? startDirection(e.coords) : endDirection(e.coords).map((v) => -v);
|
|
}), sweep = (from, to) => ccwAngle(dirs[from], dirs[to]), position = (port, route) => {
|
|
let p = node.ports[port], order2 = g.edges[p.edge].order, i = order2.indexOf(route);
|
|
return p.end === "a" ? i : order2.length - 1 - i;
|
|
}, turns = node.transitions.filter((t) => t.from >= 0 && t.to >= 0 && t.from !== t.to), groups = /* @__PURE__ */ new Map();
|
|
for (let t of turns) {
|
|
let u = Math.min(t.from, t.to), v = Math.max(t.from, t.to), key = `${u}:${v}`, grp = groups.get(key);
|
|
grp || groups.set(key, grp = { u, v, routes: [] }), grp.routes.includes(t.route) || grp.routes.push(t.route);
|
|
}
|
|
for (let grp of groups.values())
|
|
if (!(grp.routes.length < 2))
|
|
for (let t of turns) {
|
|
if (grp.routes.includes(t.route)) continue;
|
|
let shared = [t.from, t.to].filter((p) => p === grp.u || p === grp.v), crossed = [];
|
|
if (shared.length) {
|
|
if (shared.length === 1) {
|
|
let leg = shared[0], mine = t.from === leg ? t.to : t.from, theirs = grp.u === leg ? grp.v : grp.u, left = sweep(leg, mine) < sweep(leg, theirs), at = position(leg, t.route);
|
|
crossed = grp.routes.filter((r) => at < position(leg, r) !== left);
|
|
}
|
|
} else {
|
|
let through = sweep(grp.u, grp.v);
|
|
sweep(grp.u, t.from) < through != sweep(grp.u, t.to) < through && (crossed = grp.routes);
|
|
}
|
|
crossed.length > 1 && weaves.push({ group: crossed, across: t.route });
|
|
}
|
|
let passing = new Set(turns.map((t) => t.route));
|
|
node.ports.forEach((p, pi) => {
|
|
for (let r of g.edges[p.edge].routes) {
|
|
if (turns.some((t) => t.route === r && (t.from === pi || t.to === pi))) continue;
|
|
let under = !1;
|
|
for (let o of passing)
|
|
o === r || !node.ports.some((q, qi) => qi !== pi && g.edges[q.edge].routes.includes(o)) || (endsUnder.push([r, o]), under = !0);
|
|
under && endsCount.set(r, (endsCount.get(r) ?? 0) + 1);
|
|
}
|
|
});
|
|
}
|
|
if (!weaves.length) return [];
|
|
let ids = /* @__PURE__ */ new Set();
|
|
for (let w of weaves) {
|
|
ids.add(w.across);
|
|
for (let r of w.group) ids.add(r);
|
|
}
|
|
if (ids.size > MAX_ROUTES) return [];
|
|
let routes = [...ids].sort((x, y) => (endsCount.get(y) ?? 0) - (endsCount.get(x) ?? 0) || (x < y ? -1 : x > y ? 1 : 0)), index = new Map(routes.map((r, i) => [r, i])), n = routes.length, groupsOf = routes.map(() => []);
|
|
for (let w of weaves) {
|
|
let mask = 0;
|
|
for (let r of w.group) mask |= 1 << index.get(r);
|
|
let list = groupsOf[index.get(w.across)];
|
|
list.includes(mask) || list.push(mask);
|
|
}
|
|
let over = routes.map(() => 0);
|
|
for (let [r, o] of endsUnder) {
|
|
let a = index.get(r), b = index.get(o);
|
|
a !== void 0 && b !== void 0 && (over[a] |= 1 << b);
|
|
}
|
|
let full = (1 << n) - 1, cost = new Float64Array(full + 1).fill(1 / 0), last = new Int8Array(full + 1).fill(-1);
|
|
cost[0] = 0;
|
|
for (let drawn = 0; drawn < full; drawn++) {
|
|
if (cost[drawn] === 1 / 0) continue;
|
|
let place = 0;
|
|
for (let b = drawn; b; b &= b - 1) place++;
|
|
for (let x = 0; x < n; x++) {
|
|
if (drawn & 1 << x) continue;
|
|
let c = cost[drawn] + Math.abs(x - place) * MOVE_COST;
|
|
for (let mask of groupsOf[x]) {
|
|
let below = drawn & mask;
|
|
below && below !== mask && (c += WEAVE_COST);
|
|
}
|
|
for (let b = drawn & over[x]; b; b &= b - 1) c += END_OVER_COST;
|
|
let then = drawn | 1 << x;
|
|
c < cost[then] - 1e-12 && (cost[then] = c, last[then] = x);
|
|
}
|
|
}
|
|
let order = [];
|
|
for (let drawn = full; drawn; drawn ^= 1 << last[drawn]) order.push(routes[last[drawn]]);
|
|
return order.reverse();
|
|
}
|
|
|
|
// src/core/layout.ts
|
|
var SIMPLIFY_TOLERANCE_PX2 = 0.25, GENERALIZE_PER_OFFSET = 0.25, SMOOTH_SEGMENT_PX = 5, MIN_FRONT_MARGIN_LANES = 0.6, MAX_FRONT_FRACTION = 0.42, CLIQUE_MIN_REF_LANES = 1.5, MAX_SLIDE_SLOPE = 0.4, SOLO_EASE_LANES = 12, SOLO_EASE_STEPS = 6, MERGE_BELOW_BUNDLE_WIDTHS = 1, MAX_MERGE_PASSES = 4, CORNER_BEND_DEG = 60, CORNER_RADIUS_LANES = 1.5, CORNER_MIN_TURN_DEG = 60, CORNER_MAX_REACH = 1.5, FOLLOW_ABOVE_LANES = 1, SNAP_HOOK_LANES = 3, LayoutCache = class {
|
|
constructor() {
|
|
__publicField(this, "state", null);
|
|
}
|
|
clear() {
|
|
this.state = null;
|
|
}
|
|
/** @internal */
|
|
stateFor(g, zoom, style, smooth, openFolds2, laneStyle) {
|
|
let s = this.state;
|
|
return s && s.graph === g && s.zoom === zoom && s.spacing === style.spacing && s.width === style.width && s.casing === style.casingWidth && s.smooth === smooth && s.openFolds === openFolds2 && s.laneStyle === laneStyle ? s : this.state = zoomState(g, zoom, style, smooth, openFolds2, laneStyle);
|
|
}
|
|
};
|
|
function checkedSizes(sizesAt, zoom) {
|
|
requireSizesFunction(sizesAt);
|
|
let s = sizesAt(zoom), ok = (v, min) => typeof v == "number" && Number.isFinite(v) && v >= min;
|
|
if (!s || !ok(s.spacing, 0) || !ok(s.width, 0) || !ok(s.casingWidth, 0) || s.spacing === 0 || s.width === 0) {
|
|
let got = s && typeof s == "object" ? JSON.stringify({ spacing: s.spacing, width: s.width, casingWidth: s.casingWidth }, (_k, v) => v === void 0 ? "undefined" : typeof v == "number" && !Number.isFinite(v) ? String(v) : v) : String(s);
|
|
throw new TypeError(`maplibre-gl-lanes: \`sizes(${zoom})\` must return {spacing, width, casingWidth} as finite numbers in px, with spacing and width above 0; got ${got}`);
|
|
}
|
|
return s;
|
|
}
|
|
function requireSizesFunction(sizesAt) {
|
|
if (typeof sizesAt != "function")
|
|
throw new TypeError("maplibre-gl-lanes: `sizes` must be a function of the zoom that returns {spacing, width, casingWidth}");
|
|
}
|
|
function layoutAtZoom(g, zoom, styleAt, opts = {}) {
|
|
let t0 = performance.now(), smooth = opts.smooth ?? !0, open = opts.openFolds ?? !0, laneStyle = opts.laneStyle ?? null, style = checkedSizes(styleAt, zoom), st = opts.cache ? opts.cache.stateFor(g, zoom, style, smooth, open, laneStyle) : zoomState(g, zoom, style, smooth, open, laneStyle), bounds = opts.bounds ?? null, only = opts.routes ? new Set(opts.routes) : null, inView = new Uint8Array(g.edges.length);
|
|
for (let e of g.edges) {
|
|
if (!bounds) {
|
|
inView[e.id] = 1;
|
|
continue;
|
|
}
|
|
e.bounds || (e.bounds = polylineBounds(e.coords)), inView[e.id] = boundsIntersect(e.bounds, bounds) ? 1 : 0;
|
|
}
|
|
let paths = [], laneIndex = /* @__PURE__ */ new Map(), vertices = 0, edgesBuilt = 0;
|
|
for (let e of g.edges)
|
|
if (!(!inView[e.id] || st.short[e.id])) {
|
|
edgesBuilt++;
|
|
for (let r of e.routes) {
|
|
if (only && !only.has(r)) continue;
|
|
let path = lanePathOf(st, e.id, r);
|
|
path && (laneIndex.set(laneKey(st, e.id, r), path), vertices += path.coords.length / 2, paths.push(path));
|
|
}
|
|
}
|
|
for (let ji = 0; ji < st.junctions.length; ji++) {
|
|
if (!touchesView(st.junctions[ji], inView)) continue;
|
|
let tails = planJunction(st, ji).tails;
|
|
for (let plan of tails) {
|
|
let t = plan.tail;
|
|
if (!inView[plan.port.edge] && !t.edges.some((s) => inView[s.edge]) || (resolveTail(st, plan), plan.mode !== 1 || only && !only.has(t.route))) continue;
|
|
let path = plan.slide;
|
|
vertices += path.coords.length / 2, paths.push(path);
|
|
}
|
|
for (let plan of tails)
|
|
if (!(plan.mode === 1 || only && !only.has(plan.tail.route)))
|
|
for (let { edge } of plan.tail.edges) {
|
|
let k = laneKey(st, edge, plan.tail.route);
|
|
if (!inView[edge] || laneIndex.has(k)) continue;
|
|
let path = lanePathOf(st, edge, plan.tail.route);
|
|
path && (laneIndex.set(k, path), vertices += path.coords.length / 2, paths.push(path));
|
|
}
|
|
for (let isl of st.junctions[ji].islands)
|
|
if (!(only && !only.has(isl.route) || !isl.steps.some((s) => inView[s.edge])))
|
|
for (let path of islandPaths(st, isl)) {
|
|
if (path.kind === "lane") {
|
|
let k = laneKey(st, path.edge, isl.route);
|
|
if (laneIndex.has(k)) continue;
|
|
laneIndex.set(k, path);
|
|
}
|
|
vertices += path.coords.length / 2, paths.push(path);
|
|
}
|
|
}
|
|
for (let ji = 0; ji < st.junctions.length; ji++)
|
|
if (touchesView(st.junctions[ji], inView)) {
|
|
for (let grp of planJunction(st, ji).groups)
|
|
if (!(!inView[grp.edgeU] || !inView[grp.edgeV])) {
|
|
buildGroup(st, grp);
|
|
for (let m of grp.members) {
|
|
if (m.plan?.mode === 1 || only && !only.has(m.route)) continue;
|
|
let path = st.connPaths.get(m.key);
|
|
path && (vertices += path.coords.length / 2, paths.push(path));
|
|
}
|
|
}
|
|
}
|
|
for (let p of paths)
|
|
p.look.dash && !st.phased.has(p.route) && (assignDashPhase(st, p.route), st.phased.add(p.route));
|
|
return {
|
|
zoom,
|
|
scale: st.scale,
|
|
paths,
|
|
drawOrder: st.drawOrder,
|
|
bounds,
|
|
mergedEdges: st.mergedEdges,
|
|
stats: { edges: g.edges.length, edgesBuilt, edgesMerged: st.mergedEdges.length, nodes: g.nodes.length, vertices, ms: performance.now() - t0 }
|
|
};
|
|
}
|
|
function zoomState(g, zoom, style, smooth, openFolds2, laneStyle) {
|
|
let { spacing, width, casingWidth: casing } = style, scale = 512 * Math.pow(2, zoom), E = g.edges.length, routeIndex = /* @__PURE__ */ new Map();
|
|
for (let r of g.routes.keys()) routeIndex.set(r, routeIndex.size);
|
|
for (let e of g.edges) for (let r of e.routes) routeIndex.has(r) || routeIndex.set(r, routeIndex.size);
|
|
let routeCount = Math.max(1, routeIndex.size), offsets = new Float64Array(E * routeCount);
|
|
for (let e of g.edges) {
|
|
let k = e.order.length;
|
|
for (let p = 0; p < k; p++) {
|
|
let r = routeIndex.get(e.order[p]);
|
|
r !== void 0 && (offsets[e.id * routeCount + r] = ((k - 1) / 2 - p + (e.baseline ?? 0)) * spacing);
|
|
}
|
|
}
|
|
let simplePx = new Array(E), edgeLen = new Float64Array(E), dirA = new Array(E), dirB = new Array(E);
|
|
for (let e of g.edges) {
|
|
let widest = ((e.routes.length - 1) / 2 + Math.abs(e.baseline ?? 0)) * spacing;
|
|
e.simplifyRank || (e.simplifyRank = simplifyRank(e.coords));
|
|
let p = simplifyRanked(e.coords, e.simplifyRank, Math.max(SIMPLIFY_TOLERANCE_PX2, GENERALIZE_PER_OFFSET * widest) / scale);
|
|
for (let i = 0; i < p.length; i++) p[i] *= scale;
|
|
simplePx[e.id] = p, edgeLen[e.id] = polylineLength(p), dirA[e.id] = smooth ? splineStartDirection(p, SMOOTH_SEGMENT_PX, 10, 3, CORNER_BEND_DEG) : startDirection(p), dirB[e.id] = (smooth ? splineEndDirection(p, SMOOTH_SEGMENT_PX, 10, 3, CORNER_BEND_DEG) : endDirection(p)).map((v) => -v);
|
|
}
|
|
let halfWidth = (e) => (e.routes.length - 1) / 2 * spacing + width / 2 + casing, laneOffsetTravel = (e, r, forward) => {
|
|
let off = offsets[e.id * routeCount + routeIndex.get(r)];
|
|
return forward ? off : -off;
|
|
}, short = new Uint8Array(E), degree = (n) => g.nodes[n].ports.length, bundleWidth = new Float64Array(g.nodes.length);
|
|
for (let n of g.nodes) {
|
|
let widest = 0;
|
|
for (let p of n.ports) widest = Math.max(widest, g.edges[p.edge].routes.length);
|
|
bundleWidth[n.id] = widest * spacing;
|
|
}
|
|
let junctions = [], need = new Float64Array(2 * E), change = new Float64Array(2 * E), continues = new Uint8Array(2 * E * routeCount);
|
|
for (let pass = 0; pass < MAX_MERGE_PASSES; pass++) {
|
|
junctions = buildJunctions(g, short, edgeLen, dirA, dirB, halfWidth), { need, change, continues } = junctionFronts(g, junctions, laneOffsetTravel, spacing, routeIndex, routeCount);
|
|
let merged = !1;
|
|
for (let e of g.edges) {
|
|
if (short[e.id] || e.a === e.b || degree(e.a) < 2 || degree(e.b) < 2) continue;
|
|
let len = edgeLen[e.id];
|
|
(len < MERGE_BELOW_BUNDLE_WIDTHS * Math.max(bundleWidth[e.a], bundleWidth[e.b]) || len < change[2 * e.id] + change[2 * e.id + 1]) && (short[e.id] = 1, merged = !0);
|
|
}
|
|
if (!merged) break;
|
|
}
|
|
let frontA = new Float64Array(E), frontB = new Float64Array(E), mergedEdges = [];
|
|
for (let e of g.edges) {
|
|
if (short[e.id]) {
|
|
mergedEdges.push(e.id);
|
|
continue;
|
|
}
|
|
let cap = edgeLen[e.id] * MAX_FRONT_FRACTION;
|
|
frontA[e.id] = Math.min(need[2 * e.id], cap), frontB[e.id] = Math.min(need[2 * e.id + 1], cap);
|
|
}
|
|
let endsUnder = /* @__PURE__ */ new Map(), underOther = (node, edge, r) => {
|
|
for (let p of node.ports)
|
|
if (p.edge !== edge) {
|
|
for (let o of g.edges[p.edge].routes) if (o !== r) return !0;
|
|
}
|
|
return !1;
|
|
};
|
|
for (let e of g.edges)
|
|
if (!short[e.id])
|
|
for (let r of e.routes) {
|
|
let ri = routeIndex.get(r);
|
|
for (let end = 0; end < 2; end++) {
|
|
let node = g.nodes[end === 0 ? e.a : e.b];
|
|
!continues[(2 * e.id + end) * routeCount + ri] && underOther(node, e.id, r) && endsUnder.set(r, (endsUnder.get(r) ?? 0) + 1);
|
|
}
|
|
}
|
|
let drawOrder = restack(g, [...g.routes.keys()].sort((x, y) => (endsUnder.get(y) ?? 0) - (endsUnder.get(x) ?? 0) || (x < y ? -1 : x > y ? 1 : 0)));
|
|
return {
|
|
graph: g,
|
|
zoom,
|
|
scale,
|
|
spacing,
|
|
width,
|
|
casing,
|
|
smooth,
|
|
openFolds: openFolds2,
|
|
laneStyle,
|
|
looks: /* @__PURE__ */ new Map(),
|
|
routeIndex,
|
|
routeCount,
|
|
offsets,
|
|
simplePx,
|
|
edgeLen,
|
|
dirA,
|
|
dirB,
|
|
short,
|
|
junctions,
|
|
groups: new Array(junctions.length),
|
|
tails: new Array(junctions.length),
|
|
continues,
|
|
frontA,
|
|
frontB,
|
|
mergedEdges,
|
|
drawOrder,
|
|
preparedPx: new Array(E),
|
|
lanes: new Array(E * routeCount),
|
|
lanePaths: new Array(E * routeCount),
|
|
connPaths: /* @__PURE__ */ new Map(),
|
|
phased: /* @__PURE__ */ new Set()
|
|
};
|
|
}
|
|
function touchesView(j, inView) {
|
|
for (let edge of j.edges) if (inView[edge]) return !0;
|
|
return !1;
|
|
}
|
|
function planJunction(st, ji) {
|
|
let known = st.groups[ji];
|
|
if (known) return { groups: known, tails: st.tails[ji] };
|
|
let g = st.graph, j = st.junctions[ji], byKey = /* @__PURE__ */ new Map(), groups = [];
|
|
for (let t of j.transitions) {
|
|
let pu = j.ports[t.u], pv = j.ports[t.v], lateral = offsetTravel(st, pu.edge, t.route, pu.end === "b"), lateralOut = offsetTravel(st, pv.edge, t.route, pv.end === "a"), tu = travelSign(g.edges[pu.edge].direction.get(t.route)) * (pu.end === "b" ? 1 : -1), tv = travelSign(g.edges[pv.edge].direction.get(t.route)) * (pv.end === "a" ? 1 : -1), key = `${pu.edge}:${pv.edge}:${(lateral - lateralOut).toFixed(3)}`, grp = byKey.get(key);
|
|
grp || (grp = { edgeU: pu.edge, edgeV: pv.edge, members: [] }, byKey.set(key, grp), groups.push(grp)), grp.members.push({
|
|
route: t.route,
|
|
key: connKey(st, t.route, pu.edge, pu.end, pv.edge, pv.end),
|
|
pu,
|
|
pv,
|
|
lateral,
|
|
lateralOut,
|
|
travel: tu || tv,
|
|
via: t.via
|
|
});
|
|
}
|
|
let crossings = /* @__PURE__ */ new Map();
|
|
for (let grp of groups)
|
|
for (let m of grp.members)
|
|
for (let step of m.via) {
|
|
let k = `${m.route}|${step.edge}`, list = crossings.get(k);
|
|
list ? list.push(m) : crossings.set(k, [m]);
|
|
}
|
|
for (let list of crossings.values()) if (list.length > 1) for (let m of list) m.forked = !0;
|
|
let tails = [];
|
|
for (let t of j.tails) {
|
|
let p = j.ports[t.port], far = t.edges[t.side === "end" ? t.edges.length - 1 : 0], step = t.edges[t.side === "end" ? 0 : t.edges.length - 1], travel = travelSign(g.edges[p.edge].direction.get(t.route)) * (t.side === "end" ? p.end === "b" ? 1 : -1 : p.end === "a" ? 1 : -1), plan = {
|
|
tail: t,
|
|
port: p,
|
|
mode: 0,
|
|
key: t.side === "end" ? connKey(st, t.route, p.edge, p.end, far.edge, far.forward ? "b" : "a") : connKey(st, t.route, far.edge, far.forward ? "a" : "b", p.edge, p.end)
|
|
};
|
|
tails.push(plan);
|
|
let u = t.side === "end" ? p.edge : step.edge, v = t.side === "end" ? step.edge : p.edge;
|
|
groups.push({
|
|
edgeU: u,
|
|
edgeV: v,
|
|
members: [{
|
|
route: t.route,
|
|
pu: p,
|
|
pv: p,
|
|
lateral: 0,
|
|
lateralOut: 0,
|
|
travel,
|
|
via: [],
|
|
plan,
|
|
key: t.side === "end" ? connKey(st, t.route, u, p.end, v, step.forward ? "a" : "b") : connKey(st, t.route, u, step.forward ? "b" : "a", v, p.end)
|
|
}]
|
|
});
|
|
}
|
|
return st.groups[ji] = groups, st.tails[ji] = tails, { groups, tails };
|
|
}
|
|
function laneKey(st, edge, route) {
|
|
return edge * st.routeCount + st.routeIndex.get(route);
|
|
}
|
|
function connKey(st, route, edgeU, endU, edgeV, endV) {
|
|
let ends = 2 * st.graph.edges.length;
|
|
return ((edgeU * 2 + (endU === "b" ? 1 : 0)) * ends + edgeV * 2 + (endV === "b" ? 1 : 0)) * st.routeCount + st.routeIndex.get(route);
|
|
}
|
|
function offsetTravel(st, edge, route, forward) {
|
|
let off = st.offsets[laneKey(st, edge, route)];
|
|
return forward ? off : -off;
|
|
}
|
|
function continuesThrough(st, edge, end, route) {
|
|
return st.continues[(2 * edge + end) * st.routeCount + st.routeIndex.get(route)] !== 0;
|
|
}
|
|
function lookOf(st, e, r) {
|
|
let meta = st.graph.routes.get(r), over = st.laneStyle ? st.laneStyle(e, r) : null, color = over?.color || meta?.color || "#888", dash = over?.dash ?? meta?.dash, dashColor = over?.dashColor ?? meta?.dashColor, dashCap = over?.dashCap ?? meta?.dashCap, key = `${color}|${dash ? dash[0] + "," + dash[1] : ""}|${dashColor ?? ""}|${dashCap ?? ""}`, look = st.looks.get(key);
|
|
return look || st.looks.set(key, look = { color, dash, dashColor, dashCap }), look;
|
|
}
|
|
function preparedPx(st, edge) {
|
|
let px = st.preparedPx[edge];
|
|
if (!px) {
|
|
let simple = st.simplePx[edge];
|
|
px = st.smooth ? simplify(smoothCatmullRom(simple, SMOOTH_SEGMENT_PX, 10, 3, CORNER_BEND_DEG, CORNER_RADIUS_LANES * st.spacing), SIMPLIFY_TOLERANCE_PX2) : simple;
|
|
let e = st.graph.edges[edge];
|
|
if (st.openFolds && e.routes.length > 1 && !st.short[edge]) {
|
|
let widest = ((e.routes.length - 1) / 2 + Math.abs(e.baseline ?? 0)) * st.spacing;
|
|
px = openFolds(px, 2 * widest + st.width + 2 * st.casing, st.frontA[edge] + st.spacing, st.frontB[edge] + st.spacing);
|
|
}
|
|
st.preparedPx[edge] = px;
|
|
}
|
|
return px;
|
|
}
|
|
function orientedPx(st, edge, forward) {
|
|
let px = preparedPx(st, edge);
|
|
return forward ? px : reversed(px);
|
|
}
|
|
function laneOf(st, edge, route) {
|
|
let k = laneKey(st, edge, route), hit = st.lanes[k];
|
|
if (hit !== void 0) return hit;
|
|
let g = st.graph, e = g.edges[edge], px = preparedPx(st, edge), lane;
|
|
if (st.short[edge])
|
|
lane = offsetPolylineAnchored(px, st.offsets[k]);
|
|
else {
|
|
let cutA = continuesThrough(st, edge, 0, route) ? st.frontA[edge] : 0, cutB = continuesThrough(st, edge, 1, route) ? st.frontB[edge] : 0, trimmed = cutA > 0 || cutB > 0 ? trimPolyline(px, cutA, cutB) : px;
|
|
lane = e.order.length === 1 && st.offsets[k] !== 0 ? easedSoloLane(trimmed, st.offsets[k], continuesThrough(st, edge, 0, route), continuesThrough(st, edge, 1, route), SOLO_EASE_LANES * st.spacing, st.spacing) : offsetPolylineAnchored(trimmed, st.offsets[k]);
|
|
let na = g.nodes[e.a], nb = g.nodes[e.b];
|
|
cutA > 0 && (lane.anchors[0] = na.x * st.scale, lane.anchors[1] = na.y * st.scale), cutB > 0 && (lane.anchors[lane.anchors.length - 2] = nb.x * st.scale, lane.anchors[lane.anchors.length - 1] = nb.y * st.scale);
|
|
}
|
|
return st.lanes[k] = lane, lane;
|
|
}
|
|
function easedSoloLane(line, offset, holdA, holdB, ease, flat) {
|
|
if (!holdA && !holdB) return offsetPolylineAnchored(line, 0);
|
|
let flatA = holdA ? flat : 0, flatB = holdB ? flat : 0, len = polylineLength(line) - flatA - flatB;
|
|
if (len < 1) return offsetPolylineAnchored(line, offset);
|
|
let inner = trimPolyline(line, flatA, flatB, 0), easeA = holdB ? ease : Math.min(ease, len), easeB = holdA ? ease : Math.min(ease, len), held = (t) => 1 - t * t * (3 - 2 * t), at = (s) => offset * Math.max(
|
|
holdA ? held(Math.min(1, s / easeA)) : 0,
|
|
holdB ? held(Math.min(1, (len - s) / easeB)) : 0
|
|
), slide = (piece, s0, s1) => {
|
|
let steps = Math.max(1, Math.ceil((s1 - s0) / ease * SOLO_EASE_STEPS)), via = [];
|
|
for (let i = 1; i < steps; i++) via.push(i / steps, at(s0 + (s1 - s0) * i / steps));
|
|
return offsetPolylineSlidingAnchored(piece, at(s0), at(s1), 4, via);
|
|
}, parts = [];
|
|
flatA > 0 && parts.push(offsetPolylineAnchored(firstPortion(line, flatA), offset));
|
|
let headLen = holdA ? easeA : 0, tailLen = holdB ? easeB : 0;
|
|
headLen + tailLen > len - 1 ? parts.push(slide(inner, 0, len)) : (headLen > 0 && parts.push(slide(firstPortion(inner, headLen), 0, headLen)), parts.push(offsetPolylineAnchored(trimPolyline(inner, headLen, tailLen, 0), 0)), tailLen > 0 && parts.push(slide(lastPortion(inner, tailLen), len - tailLen, len))), flatB > 0 && parts.push(offsetPolylineAnchored(lastPortion(line, flatB), offset));
|
|
let points = [], anchors = [];
|
|
return parts.forEach((part, i) => {
|
|
for (let j = i > 0 ? 2 : 0; j < part.points.length; j++)
|
|
points.push(part.points[j]), anchors.push(part.anchors[j]);
|
|
}), { points, anchors };
|
|
}
|
|
function lanePathOf(st, edge, route) {
|
|
let k = laneKey(st, edge, route), hit = st.lanePaths[k];
|
|
if (hit !== void 0) return hit;
|
|
let lane = laneOf(st, edge, route);
|
|
if (lane.points.length < 4)
|
|
return st.lanePaths[k] = null, null;
|
|
let e = st.graph.edges[edge], path = {
|
|
route,
|
|
look: lookOf(st, e, route),
|
|
coords: lane.points,
|
|
anchors: lane.anchors,
|
|
kind: "lane",
|
|
travel: travelSign(e.direction.get(route)),
|
|
startDistance: 0,
|
|
edge
|
|
};
|
|
return st.lanePaths[k] = path, path;
|
|
}
|
|
function guideOf(st, parts) {
|
|
let out = [];
|
|
for (let p of parts)
|
|
for (let i = 0; i < p.length; i += 2)
|
|
out.length && Math.abs(out[out.length - 2] - p[i]) < 1e-6 && Math.abs(out[out.length - 1] - p[i + 1]) < 1e-6 || out.push(p[i], p[i + 1]);
|
|
return st.smooth ? simplify(smoothCatmullRom(out, SMOOTH_SEGMENT_PX, 10, 3, CORNER_BEND_DEG, CORNER_RADIUS_LANES * st.spacing), SIMPLIFY_TOLERANCE_PX2) : out;
|
|
}
|
|
function resolveTail(st, plan) {
|
|
if (plan.mode) return;
|
|
plan.mode = 2;
|
|
let g = st.graph, t = plan.tail, p = plan.port, portLane = laneOf(st, p.edge, t.route), parts = t.edges.map((s) => orientedPx(st, s.edge, s.forward)), far = t.edges[t.side === "end" ? t.edges.length - 1 : 0], farOffset = offsetTravel(st, far.edge, t.route, far.forward), portOffset = offsetTravel(st, p.edge, t.route, t.side === "end" ? p.end === "b" : p.end === "a"), portPx = preparedPx(st, p.edge), portPart = t.side === "end" ? lastPortion(p.end === "b" ? portPx : reversed(portPx), p.end === "b" ? st.frontB[p.edge] : st.frontA[p.edge]) : firstPortion(p.end === "a" ? portPx : reversed(portPx), p.end === "a" ? st.frontA[p.edge] : st.frontB[p.edge]), guide = t.side === "end" ? guideOf(st, [portPart, ...parts]) : guideOf(st, [...parts, portPart]), slide = t.side === "end" ? offsetPolylineSlidingAnchored(guide, portOffset, farOffset) : offsetPolylineSlidingAnchored(guide, farOffset, portOffset);
|
|
if (slide.points.length < 4) return;
|
|
let lanePts = t.side === "end" ? p.end === "b" ? portLane.points : reversed(portLane.points) : p.end === "a" ? portLane.points : reversed(portLane.points);
|
|
t.side === "end" ? (slide.points[0] = lanePts[lanePts.length - 2], slide.points[1] = lanePts[lanePts.length - 1]) : (slide.points[slide.points.length - 2] = lanePts[0], slide.points[slide.points.length - 1] = lanePts[1]);
|
|
let { points: coords, anchors } = removeLoopsAnchored(slide.points, slide.anchors, SNAP_HOOK_LANES * st.spacing);
|
|
if (coords.length < 4) return;
|
|
let travel = travelSign(g.edges[p.edge].direction.get(t.route)) * (t.side === "end" ? p.end === "b" ? 1 : -1 : p.end === "a" ? 1 : -1), between = t.side === "end" ? [p.edge, far.edge] : [far.edge, p.edge];
|
|
plan.slide = {
|
|
route: t.route,
|
|
look: lookOf(st, g.edges[p.edge], t.route),
|
|
coords,
|
|
anchors,
|
|
kind: "connector",
|
|
travel,
|
|
startDistance: 0,
|
|
edge: between[0],
|
|
node: p.node,
|
|
between
|
|
}, st.connPaths.set(plan.key, plan.slide), plan.mode = 1;
|
|
}
|
|
function islandPaths(st, isl) {
|
|
if (isl.paths) return isl.paths;
|
|
let g = st.graph, first = isl.steps[0], last = isl.steps[isl.steps.length - 1], guide = guideOf(st, isl.steps.map((s) => orientedPx(st, s.edge, s.forward))), d0 = offsetTravel(st, first.edge, isl.route, first.forward), d1 = offsetTravel(st, last.edge, isl.route, last.forward), slide = offsetPolylineSlidingAnchored(guide, d0, d1);
|
|
if (slide.points.length >= 4) {
|
|
let e = g.edges[first.edge], travel = travelSign(e.direction.get(isl.route)) * (first.forward ? 1 : -1);
|
|
return isl.paths = [{
|
|
route: isl.route,
|
|
look: lookOf(st, e, isl.route),
|
|
coords: slide.points,
|
|
anchors: slide.anchors,
|
|
kind: "connector",
|
|
travel,
|
|
startDistance: 0,
|
|
edge: first.edge,
|
|
node: first.forward ? e.a : e.b,
|
|
between: [first.edge, last.edge]
|
|
}];
|
|
}
|
|
let lanes = [];
|
|
for (let s of isl.steps) {
|
|
let path = lanePathOf(st, s.edge, isl.route);
|
|
path && lanes.push(path);
|
|
}
|
|
return isl.paths = lanes;
|
|
}
|
|
function assignDashPhase(st, route) {
|
|
let g = st.graph, forwardOn = /* @__PURE__ */ new Map();
|
|
for (let chain of g.chains.get(route) ?? [])
|
|
for (let s of chain.steps) forwardOn.set(s.edge, s.forward);
|
|
let travelEnd = (edge) => forwardOn.get(edge) === !1 ? "a" : "b", pieces = [], index = /* @__PURE__ */ new Map(), along = [], add = (p, withTravel) => {
|
|
p && !index.has(p) && (index.set(p, pieces.length), pieces.push(p), along.push(withTravel));
|
|
}, addLane = (edge) => add(lanePathOf(st, edge, route), forwardOn.get(edge) !== !1);
|
|
for (let e of g.edges) !st.short[e.id] && e.routes.includes(route) && addLane(e.id);
|
|
for (let ji = 0; ji < st.junctions.length; ji++) {
|
|
let { groups, tails } = planJunction(st, ji);
|
|
for (let plan of tails)
|
|
if (plan.tail.route === route)
|
|
if (resolveTail(st, plan), plan.mode === 1) add(plan.slide, !0);
|
|
else for (let { edge } of plan.tail.edges) addLane(edge);
|
|
for (let grp of groups)
|
|
if (grp.members.some((m) => m.route === route)) {
|
|
buildGroup(st, grp);
|
|
for (let m of grp.members)
|
|
m.route !== route || m.plan?.mode === 1 || add(st.connPaths.get(m.key), !!m.plan || m.pu.end === travelEnd(m.pu.edge));
|
|
}
|
|
for (let isl of st.junctions[ji].islands)
|
|
if (isl.route === route) for (let p of islandPaths(st, isl)) add(p, !0);
|
|
}
|
|
if (!pieces.length) return;
|
|
let n = pieces.length, atPoint = /* @__PURE__ */ new Map(), endsAt = new Array(2 * n);
|
|
for (let i = 0; i < n; i++) {
|
|
let c = pieces[i].coords;
|
|
for (let end = 0; end < 2; end++) {
|
|
let x = end ? c[c.length - 2] : c[0], y = end ? c[c.length - 1] : c[1], row = atPoint.get(x);
|
|
row || atPoint.set(x, row = /* @__PURE__ */ new Map());
|
|
let list = row.get(y);
|
|
list || row.set(y, list = []), list.push(i * 2 + end), endsAt[i * 2 + end] = list;
|
|
}
|
|
}
|
|
let length = new Float64Array(n).fill(-1), at = new Float64Array(2 * n), reached = new Uint8Array(n), reach = (i, end, d) => {
|
|
let l = length[i];
|
|
l < 0 && (length[i] = l = polylineLength(pieces[i].coords));
|
|
let d0 = along[i] ? end ? d - l : d : end ? d + l : d;
|
|
at[i * 2] = d0, at[i * 2 + 1] = along[i] ? end ? d : d + l : end ? d : d - l, reached[i] = 1;
|
|
}, root = 0;
|
|
chains: for (let chain of g.chains.get(route) ?? [])
|
|
for (let s of chain.steps) {
|
|
let lane = st.lanePaths[laneKey(st, s.edge, route)];
|
|
if (lane) {
|
|
root = index.get(lane);
|
|
break chains;
|
|
}
|
|
}
|
|
let queue = [];
|
|
for (let next = -1; next < n; next++) {
|
|
if (next >= 0) {
|
|
if (reached[next]) continue;
|
|
root = next;
|
|
}
|
|
reach(root, 0, 0), queue.length = 0, queue.push(root);
|
|
for (let q = 0; q < queue.length; q++) {
|
|
let i = queue[q];
|
|
for (let end = 0; end < 2; end++) {
|
|
let d = at[i * 2 + end];
|
|
for (let code of endsAt[i * 2 + end]) {
|
|
let o = code >> 1;
|
|
reached[o] || (reach(o, code & 1, d), queue.push(o));
|
|
}
|
|
}
|
|
}
|
|
let least = 0;
|
|
for (let i of queue) least = Math.min(least, at[i * 2], at[i * 2 + 1]);
|
|
for (let i of queue) {
|
|
let d0 = at[i * 2] - least;
|
|
pieces[i].startDistance = along[i] ? d0 : -d0;
|
|
}
|
|
}
|
|
}
|
|
function buildGroup(st, grp) {
|
|
let plan = grp.members[0].plan;
|
|
if (plan && (resolveTail(st, plan), plan.mode === 1))
|
|
return;
|
|
let missing = !1;
|
|
for (let m of grp.members) st.connPaths.has(m.key) || (missing = !0);
|
|
if (!missing) return;
|
|
let g = st.graph, clique = [], ends = /* @__PURE__ */ new Map(), follows = !1;
|
|
for (let m of grp.members) {
|
|
if (st.connPaths.set(m.key, null), m.plan) continue;
|
|
let laneU = laneOf(st, m.pu.edge, m.route), laneV = laneOf(st, m.pv.edge, m.route), from = m.pu.end === "b" ? laneU.points : reversed(laneU.points), to = m.pv.end === "a" ? laneV.points : reversed(laneV.points), made = m.via.length ? null : connector(from, to, st.spacing);
|
|
ends.set(m, { from, to, made }), made && made.coords.length >= 4 && leavesLine(st, m, made.coords) && (follows = !0);
|
|
}
|
|
for (let m of grp.members) {
|
|
if (m.plan) {
|
|
buildTailConnector(st, m, clique);
|
|
continue;
|
|
}
|
|
let { from, to, made } = ends.get(m);
|
|
if ((m.via.length || follows) && buildViaConnector(st, m, from, to)) continue;
|
|
let curve = made ?? connector(from, to, st.spacing);
|
|
curve.coords.length < 4 || clique.push({ member: m, lateral: m.lateral, own: curve.coords, coords: curve.coords, corner: curve.corner });
|
|
}
|
|
let arcs = clique.filter((c) => !c.corner);
|
|
arcs.length > 1 && deriveCliqueConnectors(arcs, st.spacing);
|
|
for (let c of clique) {
|
|
let m = c.member, conn = c.coords, node = g.nodes[m.pu.node], anchors = new Array(conn.length);
|
|
for (let i = 0; i < conn.length; i += 2)
|
|
anchors[i] = node.x * st.scale, anchors[i + 1] = node.y * st.scale;
|
|
st.connPaths.set(m.key, {
|
|
route: m.route,
|
|
look: lookOf(st, g.edges[grp.edgeU], m.route),
|
|
coords: conn,
|
|
anchors,
|
|
kind: "connector",
|
|
travel: m.travel,
|
|
startDistance: 0,
|
|
edge: grp.edgeU,
|
|
node: m.pu.node,
|
|
between: [grp.edgeU, grp.edgeV]
|
|
});
|
|
}
|
|
}
|
|
function leavesLine(st, m, coords) {
|
|
let uPx = preparedPx(st, m.pu.edge), vPx = preparedPx(st, m.pv.edge), line = lastPortion(m.pu.end === "b" ? uPx : reversed(uPx), m.pu.end === "b" ? st.frontB[m.pu.edge] : st.frontA[m.pu.edge]).concat(firstPortion(m.pv.end === "a" ? vPx : reversed(vPx), m.pv.end === "a" ? st.frontA[m.pv.edge] : st.frontB[m.pv.edge])), limit = Math.max(Math.abs(m.lateral), Math.abs(m.lateralOut)) + FOLLOW_ABOVE_LANES * st.spacing;
|
|
for (let i = 0; i < coords.length; i += 2) {
|
|
let within = !1;
|
|
for (let k = 2; k < line.length && !within; k += 2) {
|
|
let x1 = line[k - 2], y1 = line[k - 1], dx = line[k] - x1, dy = line[k + 1] - y1, len = dx * dx + dy * dy, t = len ? Math.max(0, Math.min(1, ((coords[i] - x1) * dx + (coords[i + 1] - y1) * dy) / len)) : 0;
|
|
within = Math.hypot(coords[i] - x1 - t * dx, coords[i + 1] - y1 - t * dy) <= limit;
|
|
}
|
|
if (!within) return !0;
|
|
}
|
|
return !1;
|
|
}
|
|
function buildTailConnector(st, m, clique) {
|
|
let t = m.plan.tail, p = m.pu, step = t.edges[t.side === "end" ? 0 : t.edges.length - 1], portLane = laneOf(st, p.edge, t.route), lane = laneOf(st, step.edge, t.route), tailPts = step.forward ? lane.points : reversed(lane.points), from = t.side === "end" ? p.end === "b" ? portLane.points : reversed(portLane.points) : tailPts, to = t.side === "end" ? tailPts : p.end === "a" ? portLane.points : reversed(portLane.points), made = connector(from, to, st.spacing);
|
|
made.coords.length < 4 || clique.push({ member: m, lateral: 0, own: made.coords, coords: made.coords, corner: made.corner });
|
|
}
|
|
function buildViaConnector(st, m, from, to) {
|
|
let g = st.graph, centerline = [], length = 0, append = (pts) => {
|
|
for (let i = 0; i < pts.length; i += 2) {
|
|
let n = centerline.length;
|
|
n && Math.abs(centerline[n - 2] - pts[i]) < 1e-6 && Math.abs(centerline[n - 1] - pts[i + 1]) < 1e-6 || (n && (length += Math.hypot(pts[i] - centerline[n - 2], pts[i + 1] - centerline[n - 1])), centerline.push(pts[i], pts[i + 1]));
|
|
}
|
|
}, uPx = preparedPx(st, m.pu.edge), vPx = preparedPx(st, m.pv.edge), middles = [];
|
|
append(lastPortion(m.pu.end === "b" ? uPx : reversed(uPx), m.pu.end === "b" ? st.frontB[m.pu.edge] : st.frontA[m.pu.edge]));
|
|
for (let step of m.via) {
|
|
let before = length;
|
|
append(orientedPx(st, step.edge, step.forward)), middles.push((before + length) / 2);
|
|
}
|
|
append(firstPortion(m.pv.end === "a" ? vPx : reversed(vPx), m.pv.end === "a" ? st.frontA[m.pv.edge] : st.frontB[m.pv.edge]));
|
|
let knots = [];
|
|
if (m.forked) {
|
|
let total = length;
|
|
m.via.forEach((step, i) => knots.push(middles[i] / total, offsetTravel(st, step.edge, m.route, step.forward)));
|
|
let straight = (at) => m.lateral + (m.lateralOut - m.lateral) * at, steepest = (share2) => {
|
|
let at = 0, offset = m.lateral, most = 0;
|
|
for (let k = 0; k <= knots.length; k += 2) {
|
|
let next = k < knots.length ? knots[k] : 1, nextOffset = k < knots.length ? straight(next) + (knots[k + 1] - straight(next)) * share2 : m.lateralOut;
|
|
next > at && (most = Math.max(most, Math.abs(nextOffset - offset) / ((next - at) * total))), at = next, offset = nextOffset;
|
|
}
|
|
return most;
|
|
}, share = 1;
|
|
if (steepest(1) > MAX_SLIDE_SLOPE) {
|
|
let lo = 0, hi = 1;
|
|
for (let i = 0; i < 12; i++) {
|
|
let mid = (lo + hi) / 2;
|
|
steepest(mid) > MAX_SLIDE_SLOPE ? hi = mid : lo = mid;
|
|
}
|
|
share = lo;
|
|
}
|
|
for (let k = 0; k < knots.length; k += 2) knots[k + 1] = straight(knots[k]) + (knots[k + 1] - straight(knots[k])) * share;
|
|
}
|
|
let guide = st.smooth ? simplify(smoothCatmullRom(centerline, SMOOTH_SEGMENT_PX, 10, 3, CORNER_BEND_DEG, CORNER_RADIUS_LANES * st.spacing), SIMPLIFY_TOLERANCE_PX2) : centerline, slide = offsetPolylineSlidingAnchored(guide, m.lateral, m.lateralOut, 4, knots);
|
|
if (slide.points.length < 4) return !1;
|
|
slide.points[0] = from[from.length - 2], slide.points[1] = from[from.length - 1], slide.points[slide.points.length - 2] = to[0], slide.points[slide.points.length - 1] = to[1];
|
|
let { points: coords, anchors } = removeLoopsAnchored(slide.points, slide.anchors, SNAP_HOOK_LANES * st.spacing);
|
|
return coords.length < 4 ? !1 : (st.connPaths.set(m.key, {
|
|
route: m.route,
|
|
look: lookOf(st, g.edges[m.pu.edge], m.route),
|
|
coords,
|
|
anchors,
|
|
kind: "connector",
|
|
travel: m.travel,
|
|
startDistance: 0,
|
|
edge: m.pu.edge,
|
|
node: m.pu.node,
|
|
between: [m.pu.edge, m.pv.edge]
|
|
}), !0);
|
|
}
|
|
function buildJunctions(g, short, edgeLen, dirA, dirB, halfWidth) {
|
|
let N = g.nodes.length, parent = new Int32Array(N);
|
|
for (let n = 0; n < N; n++) parent[n] = n;
|
|
let find = (x) => {
|
|
for (; parent[x] !== x; ) x = parent[x] = parent[parent[x]];
|
|
return x;
|
|
};
|
|
for (let e of g.edges) short[e.id] && (parent[find(e.a)] = find(e.b));
|
|
let byRoot = /* @__PURE__ */ new Map(), junctionOf = new Array(N), portIndex = new Int32Array(2 * g.edges.length).fill(-1);
|
|
for (let n = 0; n < N; n++) {
|
|
let root = find(n), j = byRoot.get(root);
|
|
j || byRoot.set(root, j = { ports: [], transitions: [], tails: [], islands: [], edges: [] }), junctionOf[n] = j;
|
|
for (let p of g.nodes[n].ports)
|
|
j.edges.push(p.edge), !short[p.edge] && (portIndex[p.edge * 2 + (p.end === "a" ? 0 : 1)] = j.ports.length, j.ports.push({ edge: p.edge, end: p.end, node: n, dir: p.end === "a" ? dirA[p.edge] : dirB[p.edge], hw: halfWidth(g.edges[p.edge]) }));
|
|
}
|
|
let stack = [], onStack = new Uint8Array(g.edges.length), follow = (route, edge, entryNode) => {
|
|
let found = [], visit = (cur, curNode) => {
|
|
stack.push({ edge: cur, entryNode: curNode }), onStack[cur]++;
|
|
let e = g.edges[cur], far = e.a === curNode ? e.b : e.a, at = g.nodes[far], onward = !1, path = null;
|
|
for (let t2 of at.transitions) {
|
|
if (t2.route !== route || t2.from < 0 || t2.to < 0) continue;
|
|
let pa = at.ports[t2.from], pb = at.ports[t2.to], next = pa.edge === cur && pb.edge !== cur ? pb : pb.edge === cur && pa.edge !== cur ? pa : null;
|
|
next && (short[next.edge] && onStack[next.edge] || (onward = !0, short[next.edge] ? visit(next.edge, far) : found.push({ edge: next.edge, end: next.end, node: far, walked: path ?? (path = stack.slice()) })));
|
|
}
|
|
onward || found.push({ edge: -1, end: "a", node: far, walked: path ?? (path = stack.slice()) }), onStack[cur]--, stack.pop();
|
|
};
|
|
return visit(edge, entryNode), found;
|
|
}, lifted = /* @__PURE__ */ new Map(), tailsSeen = /* @__PURE__ */ new Set();
|
|
for (let node of g.nodes) {
|
|
let j = junctionOf[node.id], root = find(node.id);
|
|
for (let t of node.transitions) {
|
|
if (t.from < 0 || t.to < 0) continue;
|
|
let from = node.ports[t.from], to = node.ports[t.to];
|
|
if (!short[from.edge] && !short[to.edge]) {
|
|
let u = portIndex[from.edge * 2 + (from.end === "a" ? 0 : 1)], v = portIndex[to.edge * 2 + (to.end === "a" ? 0 : 1)];
|
|
u >= 0 && v >= 0 && j.transitions.push({ route: t.route, u, v, via: [] });
|
|
continue;
|
|
}
|
|
if (short[from.edge] && short[to.edge]) continue;
|
|
let inward = !short[from.edge], drawn = inward ? from : to, here = portIndex[drawn.edge * 2 + (drawn.end === "a" ? 0 : 1)];
|
|
if (!(here < 0))
|
|
for (let out of follow(t.route, (inward ? to : from).edge, node.id)) {
|
|
let steps = out.walked.map((w) => ({ edge: w.edge, forward: g.edges[w.edge].a === w.entryNode })), via = inward ? steps : steps.reverse().map((w) => ({ edge: w.edge, forward: !w.forward }));
|
|
if (out.edge < 0) {
|
|
let key2 = `${root}|${t.route}|${here}|${via.map((w) => w.edge).join(",")}`;
|
|
if (tailsSeen.has(key2)) continue;
|
|
tailsSeen.add(key2), j.tails.push({ route: t.route, port: here, side: inward ? "end" : "start", edges: via });
|
|
continue;
|
|
}
|
|
let there = portIndex[out.edge * 2 + (out.end === "a" ? 0 : 1)];
|
|
if (there < 0 || junctionOf[out.node] !== j) continue;
|
|
let key = `${root}|${t.route}|${Math.min(here, there)}|${Math.max(here, there)}`, length = via.reduce((sum, w) => sum + edgeLen[w.edge], 0), crossing = inward ? { route: t.route, u: here, v: there, via } : { route: t.route, u: there, v: here, via }, known = lifted.get(key);
|
|
if (known) {
|
|
length < known.length && (j.transitions[known.index] = crossing, known.length = length);
|
|
continue;
|
|
}
|
|
lifted.set(key, { index: j.transitions.length, length }), j.transitions.push(crossing);
|
|
}
|
|
}
|
|
}
|
|
let routeAt = /* @__PURE__ */ new Map(), routeIdx = (r) => {
|
|
let i = routeAt.get(r);
|
|
return i === void 0 && routeAt.set(r, i = routeAt.size), i;
|
|
};
|
|
for (let r of g.routes.keys()) routeIdx(r);
|
|
for (let r of g.chains.keys()) routeIdx(r);
|
|
for (let j of byRoot.values()) {
|
|
for (let t of j.transitions) routeIdx(t.route);
|
|
for (let t of j.tails) routeIdx(t.route);
|
|
}
|
|
let R = routeAt.size, covered = new Uint8Array(g.edges.length * R);
|
|
for (let j of byRoot.values()) {
|
|
for (let t of j.transitions) {
|
|
let r = routeAt.get(t.route);
|
|
for (let v of t.via) covered[v.edge * R + r] = 1;
|
|
}
|
|
for (let t of j.tails) {
|
|
let r = routeAt.get(t.route);
|
|
for (let v of t.edges) covered[v.edge * R + r] = 1;
|
|
}
|
|
}
|
|
let nodeOf = (s, start) => s.forward === start ? g.edges[s.edge].a : g.edges[s.edge].b, portJoinedTo = (j, route, node, edge) => {
|
|
let stems = /* @__PURE__ */ new Set(), n = g.nodes[node];
|
|
for (let t of n.transitions) {
|
|
if (t.route !== route || t.from < 0 || t.to < 0) continue;
|
|
let pa = n.ports[t.from], pb = n.ports[t.to];
|
|
pa.edge === edge && short[pb.edge] && stems.add(pb.edge), pb.edge === edge && short[pa.edge] && stems.add(pa.edge);
|
|
}
|
|
if (!stems.size) return -1;
|
|
for (let t of n.transitions) {
|
|
if (t.route !== route || t.from < 0 || t.to < 0) continue;
|
|
let pa = n.ports[t.from], pb = n.ports[t.to], drawn = stems.has(pb.edge) && !short[pa.edge] ? pa : stems.has(pa.edge) && !short[pb.edge] ? pb : null;
|
|
if (drawn && drawn.edge !== edge) return portIndex[drawn.edge * 2 + (drawn.end === "a" ? 0 : 1)];
|
|
}
|
|
return -1;
|
|
};
|
|
for (let [route, chains] of g.chains) {
|
|
let r = routeAt.get(route);
|
|
for (let chain of chains) {
|
|
let steps = chain.steps;
|
|
for (let i = 0; i < steps.length; i++) {
|
|
if (!short[steps[i].edge] || covered[steps[i].edge * R + r]) continue;
|
|
let k = i;
|
|
for (; k < steps.length && short[steps[k].edge] && !covered[steps[k].edge * R + r]; ) k++;
|
|
let run = steps.slice(i, k).map((s) => ({ edge: s.edge, forward: s.forward }));
|
|
for (let s of run) covered[s.edge * R + r] = 1;
|
|
let first = run[0], last = run[run.length - 1], j = junctionOf[nodeOf(first, !0)], u = portJoinedTo(j, route, nodeOf(first, !0), first.edge), v = portJoinedTo(j, route, nodeOf(last, !1), last.edge);
|
|
u >= 0 && v >= 0 && u !== v ? j.transitions.push({ route, u, v, via: run }) : u >= 0 ? j.tails.push({ route, port: u, side: "end", edges: run }) : v >= 0 ? j.tails.push({ route, port: v, side: "start", edges: run }) : j.islands.push({ route, steps: run }), i = k - 1;
|
|
}
|
|
}
|
|
}
|
|
return [...byRoot.values()];
|
|
}
|
|
function junctionFronts(g, junctions, laneOffsetTravel, spacing, routeIndex, routeCount) {
|
|
let need = new Float64Array(2 * g.edges.length), change = new Float64Array(2 * g.edges.length), continues = new Uint8Array(2 * g.edges.length * routeCount), markContinues = (p, route) => {
|
|
continues[(2 * p.edge + (p.end === "a" ? 0 : 1)) * routeCount + routeIndex.get(route)] = 1;
|
|
};
|
|
for (let j of junctions) {
|
|
let ports = j.ports, portNeed = new Float64Array(ports.length), active = new Uint8Array(ports.length);
|
|
for (let t of j.transitions)
|
|
active[t.u] = 1, active[t.v] = 1, markContinues(ports[t.u], t.route), markContinues(ports[t.v], t.route);
|
|
for (let t of j.tails)
|
|
active[t.port] = 1, markContinues(ports[t.port], t.route);
|
|
for (let i = 0; i < ports.length; i++)
|
|
if (active[i])
|
|
for (let k = 0; k < ports.length; k++) {
|
|
if (k === i || !active[k]) continue;
|
|
let cos = ports[i].dir[0] * ports[k].dir[0] + ports[i].dir[1] * ports[k].dir[1], sin = Math.sqrt(Math.max(0, 1 - cos * cos)), t;
|
|
sin < 1e-3 ? t = cos > 0 ? 6 * spacing : 0 : t = (ports[k].hw + ports[i].hw * cos) / sin, t = Math.max(0, t), t > 0 && (t += MIN_FRONT_MARGIN_LANES * spacing), portNeed[i] = Math.max(portNeed[i], Math.min(t, 8 * spacing));
|
|
}
|
|
let portChange = new Float64Array(ports.length);
|
|
for (let t of j.transitions) {
|
|
let u = ports[t.u], v = ports[t.v], oIn = laneOffsetTravel(g.edges[u.edge], t.route, u.end === "b"), oOut = laneOffsetTravel(g.edges[v.edge], t.route, v.end === "a"), shift = Math.abs(oIn - oOut);
|
|
if (shift < 1e-6) continue;
|
|
let len = Math.max(2.5 * shift, 3 * spacing);
|
|
portChange[t.u] = Math.max(portChange[t.u], len / 2), portChange[t.v] = Math.max(portChange[t.v], len / 2);
|
|
}
|
|
for (let i = 0; i < ports.length; i++) portNeed[i] = Math.max(portNeed[i], portChange[i]);
|
|
ports.forEach((p, i) => change[2 * p.edge + (p.end === "a" ? 0 : 1)] = portChange[i]);
|
|
for (let t of j.tails) {
|
|
let p = ports[t.port], first = t.edges[t.side === "end" ? 0 : t.edges.length - 1], oIn = laneOffsetTravel(g.edges[p.edge], t.route, p.end === "b"), oOut = laneOffsetTravel(g.edges[first.edge], t.route, first.forward), shift = Math.abs(oIn - oOut);
|
|
shift < 1e-6 || (portNeed[t.port] = Math.max(portNeed[t.port], Math.max(2.5 * shift, 3 * spacing) / 2));
|
|
}
|
|
ports.forEach((p, i) => need[2 * p.edge + (p.end === "a" ? 0 : 1)] = portNeed[i]);
|
|
}
|
|
return { need, change, continues };
|
|
}
|
|
function lastPortion(p, dist) {
|
|
let n = p.length / 2;
|
|
if (dist <= 0 || n < 2) return [p[p.length - 2], p[p.length - 1]];
|
|
let out = [], remaining = dist;
|
|
for (let i = n - 1; i > 0; i--) {
|
|
let ax = p[i * 2 - 2], ay = p[i * 2 - 1], bx = p[i * 2], by = p[i * 2 + 1], l = Math.hypot(bx - ax, by - ay);
|
|
if (out.unshift(bx, by), l >= remaining) {
|
|
let f = 1 - remaining / l;
|
|
return out.unshift(ax + (bx - ax) * f, ay + (by - ay) * f), out;
|
|
}
|
|
remaining -= l;
|
|
}
|
|
return out.unshift(p[0], p[1]), out;
|
|
}
|
|
function firstPortion(p, dist) {
|
|
return reversed(lastPortion(reversed(p), dist));
|
|
}
|
|
function travelSign(d) {
|
|
return d === 1 ? 1 : d === -1 ? -1 : 0;
|
|
}
|
|
function deriveCliqueConnectors(clique, spacing) {
|
|
let ref = clique[0], refLen = polylineLength(ref.own);
|
|
for (let c of clique) {
|
|
let len = polylineLength(c.own);
|
|
len > refLen && (ref = c, refLen = len);
|
|
}
|
|
if (ref.own.length <= 4 || refLen < CLIQUE_MIN_REF_LANES * spacing) return;
|
|
let turning = signedTurning(ref.own);
|
|
for (let c of clique) {
|
|
if (c === ref) continue;
|
|
let d = c.lateral - ref.lateral, expected = refLen + d * turning;
|
|
if (expected < 0.25 * refLen) continue;
|
|
let off = offsetPolyline(ref.own, d);
|
|
if (off.length < 4) continue;
|
|
let ratio = polylineLength(off) / expected;
|
|
if (ratio > 1.1 || ratio < 0.9) continue;
|
|
let gapStart = Math.hypot(off[0] - c.own[0], off[1] - c.own[1]), gapEnd = Math.hypot(off[off.length - 2] - c.own[c.own.length - 2], off[off.length - 1] - c.own[c.own.length - 1]);
|
|
gapStart > spacing / 2 || gapEnd > spacing / 2 || (off[0] = c.own[0], off[1] = c.own[1], off[off.length - 2] = c.own[c.own.length - 2], off[off.length - 1] = c.own[c.own.length - 1], c.coords = off);
|
|
}
|
|
}
|
|
function signedTurning(p) {
|
|
let total = 0;
|
|
for (let i = 4; i < p.length; i += 2) {
|
|
let ax = p[i - 2] - p[i - 4], ay = p[i - 1] - p[i - 3], bx = p[i] - p[i - 2], by = p[i + 1] - p[i - 1];
|
|
total += Math.atan2(ax * by - ay * bx, ax * bx + ay * by);
|
|
}
|
|
return total;
|
|
}
|
|
function connector(from, to, spacing) {
|
|
let P = [from[from.length - 2], from[from.length - 1]], Q = [to[0], to[1]], tp = endDirection(from), tqIn = startDirection(to), tq = [-tqIn[0], -tqIn[1]], chord = Math.hypot(Q[0] - P[0], Q[1] - P[1]);
|
|
if (chord < 1e-6) return { coords: [], corner: !1 };
|
|
if (Math.abs((Q[0] - P[0]) * tp[1] - (Q[1] - P[1]) * tp[0]) < 0.05 && tp[0] * tqIn[0] + tp[1] * tqIn[1] > 0.9999) return { coords: [P[0], P[1], Q[0], Q[1]], corner: !1 };
|
|
let corner = cornerTurn(P, tp, Q, tq, chord, spacing);
|
|
if (corner) return { coords: corner, corner: !0 };
|
|
let samples = Math.max(4, Math.min(32, Math.round(chord / Math.max(2, spacing / 3)))), coords = connectorCurve(P, tp, Q, tq, samples), ahead = (Q[0] - P[0]) * tp[0] + (Q[1] - P[1]) * tp[1], aheadBack = (P[0] - Q[0]) * tq[0] + (P[1] - Q[1]) * tq[1];
|
|
if (ahead < 0 || aheadBack < 0)
|
|
for (let scale = 0.5; scale > 0.1 && selfIntersects(coords); scale /= 2) coords = connectorCurve(P, tp, Q, tq, samples, scale);
|
|
return { coords, corner: !1 };
|
|
}
|
|
function cornerTurn(P, tp, Q, tq, chord, spacing) {
|
|
let I = intersectLines(P[0], P[1], P[0] + tp[0], P[1] + tp[1], Q[0], Q[1], Q[0] + tq[0], Q[1] + tq[1]);
|
|
if (!I) return null;
|
|
let t = (I[0] - P[0]) * tp[0] + (I[1] - P[1]) * tp[1], u = (I[0] - Q[0]) * tq[0] + (I[1] - Q[1]) * tq[1];
|
|
if (t <= 0.01 || u <= 0.01 || t + u > CORNER_MAX_REACH * chord || Math.acos(Math.max(-1, Math.min(1, -(tp[0] * tq[0] + tp[1] * tq[1])))) * 180 / Math.PI < CORNER_MIN_TURN_DEG) return null;
|
|
let L = Math.min(CORNER_RADIUS_LANES * spacing, 0.45 * t, 0.45 * u), A = [I[0] - tp[0] * L, I[1] - tp[1] * L], B = [I[0] - tq[0] * L, I[1] - tq[1] * L], out = [P[0], P[1], A[0], A[1]], steps = 8;
|
|
for (let i = 1; i < steps; i++) {
|
|
let s = i / steps, m = 1 - s;
|
|
out.push(m * m * A[0] + 2 * m * s * I[0] + s * s * B[0], m * m * A[1] + 2 * m * s * I[1] + s * s * B[1]);
|
|
}
|
|
return out.push(B[0], B[1], Q[0], Q[1]), out;
|
|
}
|
|
|
|
// src/render/tessellate.ts
|
|
function addPiece(pieces, first, count, edge) {
|
|
if (count <= 0) return;
|
|
let last = pieces[pieces.length - 1];
|
|
last && last[2] === edge && last[0] + last[1] === first ? last[1] += count : pieces.push([first, count, edge]);
|
|
}
|
|
function splitRanges(group, pieces, bright) {
|
|
if (!pieces.some((p) => bright.has(p[2]))) return { dimmed: group[1] ? [[group[0], group[1]]] : [], bright: [] };
|
|
let out = { dimmed: [], bright: [] };
|
|
for (let [first, count, edge] of pieces) {
|
|
let side = bright.has(edge) ? out.bright : out.dimmed, last = side[side.length - 1];
|
|
last && last[0] + last[1] === first ? last[1] += count : side.push([first, count]);
|
|
}
|
|
return out;
|
|
}
|
|
var MITER_LIMIT = 2, DEG_PER_TRIANGLE = 15, FLOATS_PER_VERTEX = 9, MeshBuilder = class {
|
|
constructor(vertexCapacity, indexCapacity) {
|
|
__publicField(this, "v");
|
|
__publicField(this, "c");
|
|
__publicField(this, "i");
|
|
__publicField(this, "nv", 0);
|
|
__publicField(this, "ni", 0);
|
|
this.v = new Float32Array(vertexCapacity * FLOATS_PER_VERTEX), this.c = new Uint8Array(vertexCapacity * 4), this.i = new Uint32Array(indexCapacity);
|
|
}
|
|
reset() {
|
|
this.nv = 0, this.ni = 0;
|
|
}
|
|
reserve(vertices, indices) {
|
|
for (; (this.nv + vertices) * FLOATS_PER_VERTEX > this.v.length; ) this.growV();
|
|
for (; this.ni + indices > this.i.length; ) this.growI();
|
|
}
|
|
growV() {
|
|
let v = new Float32Array(this.v.length * 2);
|
|
v.set(this.v), this.v = v;
|
|
let c = new Uint8Array(this.c.length * 2);
|
|
c.set(this.c), this.c = c;
|
|
}
|
|
growI() {
|
|
let i = new Uint32Array(this.i.length * 2);
|
|
i.set(this.i), this.i = i;
|
|
}
|
|
vertex(ax, ay, dx, dy, ex, ey, nx, ny, s, color) {
|
|
(this.nv + 1) * FLOATS_PER_VERTEX > this.v.length && this.growV();
|
|
let o = this.nv * FLOATS_PER_VERTEX;
|
|
this.v[o] = ax, this.v[o + 1] = ay, this.v[o + 2] = dx, this.v[o + 3] = dy, this.v[o + 4] = ex, this.v[o + 5] = ey, this.v[o + 6] = nx, this.v[o + 7] = ny, this.v[o + 8] = s;
|
|
let co = this.nv * 4;
|
|
return this.c[co] = color[0], this.c[co + 1] = color[1], this.c[co + 2] = color[2], this.c[co + 3] = color[3], this.nv++;
|
|
}
|
|
tri(a, b, c) {
|
|
this.ni + 3 > this.i.length && this.growI(), this.i[this.ni++] = a, this.i[this.ni++] = b, this.i[this.ni++] = c;
|
|
}
|
|
quad(a1, a2, b1, b2) {
|
|
this.tri(a1, a2, b1), this.tri(a2, b2, b1);
|
|
}
|
|
};
|
|
function normalizeColor(css) {
|
|
let ctx = typeof OffscreenCanvas < "u" ? new OffscreenCanvas(1, 1).getContext("2d", { willReadFrequently: !0 }) : typeof document < "u" ? document.createElement("canvas").getContext("2d", { willReadFrequently: !0 }) : null;
|
|
if (!ctx) return null;
|
|
ctx.fillStyle = "#000000", ctx.fillStyle = css;
|
|
let a = ctx.fillStyle;
|
|
if (ctx.fillStyle = "#ffffff", ctx.fillStyle = css, a !== ctx.fillStyle || typeof a != "string") return null;
|
|
if (/^(#|rgba?\()/i.test(a)) return a;
|
|
ctx.clearRect(0, 0, 1, 1), ctx.fillRect(0, 0, 1, 1);
|
|
let [r, g, bl, al] = ctx.getImageData(0, 0, 1, 1).data;
|
|
return `rgba(${r}, ${g}, ${bl}, ${al / 255})`;
|
|
}
|
|
function parseColor(css, normalized = !1) {
|
|
let out = new Uint8Array([136, 136, 136, 255]), hex = css.trim(), m = /^#([0-9a-f]{3,8})$/i.exec(hex);
|
|
if (m) {
|
|
let h = m[1];
|
|
return h.length === 3 || h.length === 4 ? (out[0] = parseInt(h[0] + h[0], 16), out[1] = parseInt(h[1] + h[1], 16), out[2] = parseInt(h[2] + h[2], 16), out[3] = h.length === 4 ? parseInt(h[3] + h[3], 16) : 255) : (h.length === 6 || h.length === 8) && (out[0] = parseInt(h.slice(0, 2), 16), out[1] = parseInt(h.slice(2, 4), 16), out[2] = parseInt(h.slice(4, 6), 16), out[3] = h.length === 8 ? parseInt(h.slice(6, 8), 16) : 255), out;
|
|
}
|
|
let named = {
|
|
black: "#000000",
|
|
white: "#ffffff",
|
|
red: "#ff0000",
|
|
green: "#008000",
|
|
blue: "#0000ff",
|
|
yellow: "#ffff00",
|
|
orange: "#ffa500",
|
|
purple: "#800080",
|
|
gray: "#808080",
|
|
grey: "#808080",
|
|
silver: "#c0c0c0",
|
|
navy: "#000080",
|
|
magenta: "#ff00ff",
|
|
fuchsia: "#ff00ff",
|
|
lime: "#00ff00",
|
|
brown: "#a52a2a",
|
|
teal: "#008080",
|
|
maroon: "#800000"
|
|
};
|
|
if (named[hex.toLowerCase()]) return parseColor(named[hex.toLowerCase()]);
|
|
let rgb = /^rgba?\(([^)]+)\)$/i.exec(hex);
|
|
if (rgb) {
|
|
let parts = rgb[1].split(/[\s,/]+/).filter((s) => s.length);
|
|
for (let i = 0; i < 3 && i < parts.length; i++) {
|
|
let v = parts[i].endsWith("%") ? parseFloat(parts[i]) * 255 / 100 : parseFloat(parts[i]);
|
|
out[i] = Math.round(Math.max(0, Math.min(255, v || 0)));
|
|
}
|
|
if (parts.length > 3) {
|
|
let a = parts[3].endsWith("%") ? parseFloat(parts[3]) / 100 : parseFloat(parts[3]);
|
|
out[3] = Math.round(Math.max(0, Math.min(1, a)) * 255);
|
|
}
|
|
return out;
|
|
}
|
|
let norm = normalized ? null : normalizeColor(hex);
|
|
return norm ? parseColor(norm, !0) : out;
|
|
}
|
|
var TessellateCache = class {
|
|
constructor() {
|
|
/** null marks a path met once: its triangles are kept from the second build on. */
|
|
__publicField(this, "chunks", /* @__PURE__ */ new WeakMap());
|
|
__publicField(this, "out", null);
|
|
__publicField(this, "key", "");
|
|
}
|
|
clear() {
|
|
this.chunks = /* @__PURE__ */ new WeakMap(), this.out = null, this.key = "";
|
|
}
|
|
/** @internal */
|
|
builderFor(opts, estimate) {
|
|
let key = `${opts.scale}|${opts.origin[0]}|${opts.origin[1]}|${opts.unitsPerMercator ?? 1}`;
|
|
return key !== this.key && (this.chunks = /* @__PURE__ */ new WeakMap(), this.key = key), this.out || (this.out = new MeshBuilder(Math.max(64, estimate * 3), Math.max(192, estimate * 9))), this.out.reset(), this.out;
|
|
}
|
|
/** @internal */
|
|
emit(b, p, color, opts) {
|
|
let hit = this.chunks.get(p);
|
|
if (hit && hit.startDistance === p.startDistance && hit.color === p.look.color) {
|
|
appendChunk(b, hit);
|
|
return;
|
|
}
|
|
let nv = b.nv, ni = b.ni;
|
|
if (tessellatePath(b, p.coords, p.anchors, color, p.startDistance, opts), hit === void 0) {
|
|
this.chunks.set(p, null);
|
|
return;
|
|
}
|
|
let indices = new Uint32Array(b.ni - ni);
|
|
for (let k = 0; k < indices.length; k++) indices[k] = b.i[ni + k] - nv;
|
|
this.chunks.set(p, {
|
|
vertices: b.v.slice(nv * FLOATS_PER_VERTEX, b.nv * FLOATS_PER_VERTEX),
|
|
colors: b.c.slice(nv * 4, b.nv * 4),
|
|
indices,
|
|
vertexCount: b.nv - nv,
|
|
startDistance: p.startDistance,
|
|
color: p.look.color
|
|
});
|
|
}
|
|
};
|
|
function appendChunk(b, chunk) {
|
|
b.reserve(chunk.vertexCount, chunk.indices.length), b.v.set(chunk.vertices, b.nv * FLOATS_PER_VERTEX), b.c.set(chunk.colors, b.nv * 4);
|
|
let base = b.nv;
|
|
for (let k = 0; k < chunk.indices.length; k++) b.i[b.ni + k] = chunk.indices[k] + base;
|
|
b.nv += chunk.vertexCount, b.ni += chunk.indices.length;
|
|
}
|
|
function tessellate(paths, opts) {
|
|
let est = 0;
|
|
for (let p of paths) est += p.coords.length;
|
|
let cache2 = opts.cache ?? null, b = cache2 ? cache2.builderFor(opts, est) : new MeshBuilder(Math.max(64, est * 3), Math.max(192, est * 9)), colorCache = /* @__PURE__ */ new Map(), groups = [], groupRoutes = [], groupLooks = [], groupDots = [], groupPieces = [], groupDotPieces = [], byRoute = /* @__PURE__ */ new Map();
|
|
for (let p of paths) {
|
|
let byLook = byRoute.get(p.route);
|
|
byLook || byRoute.set(p.route, byLook = /* @__PURE__ */ new Map());
|
|
let l = byLook.get(p.look);
|
|
l || byLook.set(p.look, l = []), l.push(p);
|
|
}
|
|
let order = opts.drawOrder ?? [...byRoute.keys()];
|
|
for (let rid of order) {
|
|
let byLook = byRoute.get(rid);
|
|
if (byLook)
|
|
for (let [look, list] of byLook) {
|
|
let start = b.ni, c = colorCache.get(look.color);
|
|
c || colorCache.set(look.color, c = parseColor(look.color));
|
|
let pieces = [];
|
|
for (let p of list) {
|
|
let from = b.ni;
|
|
cache2 ? cache2.emit(b, p, c, opts) : tessellatePath(b, p.coords, p.anchors, c, p.startDistance, opts), addPiece(pieces, from, b.ni - from, p.edge);
|
|
}
|
|
if (b.ni > start) {
|
|
groups.push([start, b.ni - start]), groupRoutes.push(rid), groupLooks.push(look), groupPieces.push(pieces);
|
|
let dots = b.ni, dotPieces = [];
|
|
if (opts.width && look.dash && look.dash[0] === 0 && look.dash[1] > 0)
|
|
for (let p of list) {
|
|
let from = b.ni;
|
|
dotsOnPath(b, p.coords, p.anchors, c, p.startDistance, look.dash[1] * opts.width, opts), addPiece(dotPieces, from, b.ni - from, p.edge);
|
|
}
|
|
groupDots.push([dots, b.ni - dots]), groupDotPieces.push(dotPieces);
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
vertices: b.v.subarray(0, b.nv * FLOATS_PER_VERTEX),
|
|
colors: b.c.subarray(0, b.nv * 4),
|
|
indices: b.i.subarray(0, b.ni),
|
|
vertexCount: b.nv,
|
|
indexCount: b.ni,
|
|
groups,
|
|
groupRoutes,
|
|
groupLooks,
|
|
groupDots,
|
|
groupPieces,
|
|
groupDotPieces
|
|
};
|
|
}
|
|
function dotsOnPath(b, px, anchorsPx, color, startDistance, period, opts) {
|
|
let n = px.length / 2;
|
|
if (n < 2) return;
|
|
let total = 0;
|
|
for (let i = 1; i < n; i++) total += Math.hypot(px[i * 2] - px[i * 2 - 2], px[i * 2 + 1] - px[i * 2 - 1]);
|
|
if (total <= 0) return;
|
|
let back = startDistance < 0, s0 = Math.abs(startDistance), lo = back ? s0 - total : s0, { scale, origin } = opts, upm = opts.unitsPerMercator ?? 1, seg = 1, before = 0, len = Math.hypot(px[2] - px[0], px[3] - px[1]), first = Math.ceil((lo - period / 2) / period), count = Math.max(0, Math.ceil((lo + total - period / 2) / period) - first);
|
|
for (let k = 0; k < count; k++) {
|
|
let center = period / 2 + (back ? first + count - 1 - k : first + k) * period, at = back ? s0 - center : center - s0;
|
|
for (; seg < n - 1 && at > before + len; )
|
|
before += len, seg++, len = Math.hypot(px[seg * 2] - px[seg * 2 - 2], px[seg * 2 + 1] - px[seg * 2 - 1]);
|
|
let t = len > 0 ? Math.max(0, Math.min(1, (at - before) / len)) : 0, i = (seg - 1) * 2, j = seg * 2, x = px[i] + (px[j] - px[i]) * t, y = px[i + 1] + (px[j + 1] - px[i + 1]) * t, gx = anchorsPx[i] + (anchorsPx[j] - anchorsPx[i]) * t, gy = anchorsPx[i + 1] + (anchorsPx[j + 1] - anchorsPx[i + 1]) * t, ax = (gx / scale - origin[0]) * upm, ay = (gy / scale - origin[1]) * upm, v0 = b.vertex(ax, ay, x - gx, y - gy, -1, -1, -1, -1, center, color), v1 = b.vertex(ax, ay, x - gx, y - gy, 1, -1, 1, -1, center, color), v2 = b.vertex(ax, ay, x - gx, y - gy, 1, 1, 1, 1, center, color), v3 = b.vertex(ax, ay, x - gx, y - gy, -1, 1, -1, 1, center, color);
|
|
b.tri(v0, v1, v2), b.tri(v0, v2, v3);
|
|
}
|
|
}
|
|
function tessellatePath(b, px, anchorsPx, color, startDistance, opts) {
|
|
let pts = [], anc = [];
|
|
for (let i = 0; i < px.length; i += 2) {
|
|
let n2 = pts.length;
|
|
n2 >= 2 && Math.abs(pts[n2 - 2] - px[i]) < 1e-6 && Math.abs(pts[n2 - 1] - px[i + 1]) < 1e-6 || (pts.push(px[i], px[i + 1]), anc.push(anchorsPx[i], anchorsPx[i + 1]));
|
|
}
|
|
let n = pts.length / 2;
|
|
if (n < 2) return;
|
|
let { scale, origin } = opts, upm = opts.unitsPerMercator ?? 1, AX = (i) => (anc[i * 2] / scale - origin[0]) * upm, AY = (i) => (anc[i * 2 + 1] / scale - origin[1]) * upm, DX = (i) => pts[i * 2] - anc[i * 2], DY = (i) => pts[i * 2 + 1] - anc[i * 2 + 1], along = new Float64Array(n);
|
|
for (let i = 1; i < n; i++) along[i] = along[i - 1] + Math.hypot(pts[i * 2] - pts[i * 2 - 2], pts[i * 2 + 1] - pts[i * 2 - 1]);
|
|
let S = (i) => startDistance < 0 ? -startDistance - along[i] : startDistance + along[i], lastL = -1, lastR = -1, pair = (i, ex, ey, nx, ny, connect) => {
|
|
let ax = AX(i), ay = AY(i), dx = DX(i), dy = DY(i), s = S(i), l = b.vertex(ax, ay, dx, dy, ex, ey, nx, ny, s, color), r = b.vertex(ax, ay, dx, dy, -ex, -ey, -nx, -ny, s, color);
|
|
connect && lastL >= 0 && b.quad(lastL, lastR, l, r), lastL = l, lastR = r;
|
|
}, fan = (i, fromX, fromY, toX, toY, ccwOnScreen) => {
|
|
let ax = AX(i), ay = AY(i), dx = DX(i), dy = DY(i), s = S(i), c = b.vertex(ax, ay, dx, dy, 0, 0, 0, 0, s, color), a0 = Math.atan2(fromY, fromX), da = Math.atan2(toY, toX) - a0;
|
|
for (; da > Math.PI; ) da -= 2 * Math.PI;
|
|
for (; da < -Math.PI; ) da += 2 * Math.PI;
|
|
Math.abs(Math.abs(da) - Math.PI) < 1e-6 && (da = ccwOnScreen ? -Math.PI : Math.PI);
|
|
let steps = Math.max(1, Math.ceil(Math.abs(da) * 180 / Math.PI / DEG_PER_TRIANGLE)), prev = b.vertex(ax, ay, dx, dy, fromX, fromY, fromX, fromY, s, color);
|
|
for (let k = 1; k <= steps; k++) {
|
|
let a = a0 + da * k / steps, vx = Math.cos(a), vy = Math.sin(a), cur = b.vertex(ax, ay, dx, dy, vx, vy, vx, vy, s, color);
|
|
b.tri(c, prev, cur), prev = cur;
|
|
}
|
|
}, sdx = new Float64Array(n - 1), sdy = new Float64Array(n - 1);
|
|
for (let i = 0; i < n - 1; i++) {
|
|
let dx = pts[i * 2 + 2] - pts[i * 2], dy = pts[i * 2 + 3] - pts[i * 2 + 1], l = Math.hypot(dx, dy) || 1;
|
|
sdx[i] = dx / l, sdy[i] = dy / l;
|
|
}
|
|
for (let i = 0; i < n; i++) {
|
|
let hasPrev = i > 0, hasNext = i < n - 1, dPrevX = hasPrev ? sdx[i - 1] : 0, dPrevY = hasPrev ? sdy[i - 1] : 0, dNextX = hasNext ? sdx[i] : 0, dNextY = hasNext ? sdy[i] : 0, nPrevX = dPrevY, nPrevY = -dPrevX, nNextX = dNextY, nNextY = -dNextX;
|
|
if (!hasPrev) {
|
|
fan(i, nNextX, nNextY, -nNextX, -nNextY, !0), pair(i, nNextX, nNextY, nNextX, nNextY, !1);
|
|
continue;
|
|
}
|
|
if (!hasNext) {
|
|
pair(i, nPrevX, nPrevY, nPrevX, nPrevY, !0), fan(i, nPrevX, nPrevY, -nPrevX, -nPrevY, !1);
|
|
continue;
|
|
}
|
|
let jx = nPrevX + nNextX, jy = nPrevY + nNextY, jl = Math.hypot(jx, jy);
|
|
if (jl < 1e-9) {
|
|
pair(i, nPrevX, nPrevY, nPrevX, nPrevY, !0), fan(i, nPrevX, nPrevY, -nPrevX, -nPrevY, !1), pair(i, nNextX, nNextY, nNextX, nNextY, !1);
|
|
continue;
|
|
}
|
|
jx /= jl, jy /= jl;
|
|
let cosHalf = jx * nNextX + jy * nNextY, miterLen = cosHalf > 1e-6 ? 1 / cosHalf : 1 / 0;
|
|
if (miterLen <= MITER_LIMIT)
|
|
pair(i, jx * miterLen, jy * miterLen, jx, jy, !0);
|
|
else {
|
|
let outsideLeft = dPrevX * dNextY - dPrevY * dNextX > 0;
|
|
pair(i, nPrevX, nPrevY, nPrevX, nPrevY, !0), outsideLeft ? fan(i, nPrevX, nPrevY, nNextX, nNextY, !1) : fan(i, -nPrevX, -nPrevY, -nNextX, -nNextY, !0), pair(i, nNextX, nNextY, nNextX, nNextY, !1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// src/render/layer.ts
|
|
var VERTEX_SHADER_BODY = `
|
|
in vec2 a_anchor;
|
|
in vec2 a_delta;
|
|
in vec2 a_extrude;
|
|
in vec2 a_normal;
|
|
in float a_along;
|
|
in vec4 a_color;
|
|
uniform float u_units_per_px;
|
|
uniform float u_outset;
|
|
out vec2 v_normal;
|
|
out vec4 v_color;
|
|
out float v_along;
|
|
out float v_depth;
|
|
void main() {
|
|
v_along = a_along;
|
|
float thickness = projectLineThickness(a_anchor.y);
|
|
vec2 p = a_anchor + (a_delta + a_extrude * u_outset) * (u_units_per_px * thickness);
|
|
gl_Position = projectTile(p);
|
|
v_depth = gl_Position.z / gl_Position.w;
|
|
v_normal = a_normal;
|
|
v_color = a_color;
|
|
}`, FRAGMENT_SHADER_BODY = `
|
|
in vec2 v_normal;
|
|
in vec4 v_color;
|
|
in float v_along;
|
|
in float v_depth;
|
|
uniform float u_outset;
|
|
uniform float u_blur;
|
|
uniform vec4 u_override;
|
|
uniform float u_opacity;
|
|
uniform vec2 u_dash;
|
|
uniform float u_cap;
|
|
uniform float u_along_scale;
|
|
uniform float u_cover;
|
|
out vec4 fragColor;
|
|
void main() {
|
|
#ifdef GLOBE
|
|
if (v_depth > 1.0) discard;
|
|
#endif
|
|
float dist = length(v_normal) * u_outset;
|
|
float alpha = clamp((u_outset - dist) / u_blur, 0.0, 1.0);
|
|
if (u_dash.y > 0.0) {
|
|
if (u_dash.x > 0.0) {
|
|
float period = u_dash.x + u_dash.y;
|
|
float t = mod(v_along, period);
|
|
if (u_cap < 0.5) {
|
|
float edge = min(t, u_dash.x - t) * u_along_scale;
|
|
alpha *= clamp(edge / u_blur + 0.5, 0.0, 1.0);
|
|
} else {
|
|
float r = u_outset - 0.5 * u_blur;
|
|
float best = 1e9;
|
|
for (int k = -1; k <= 1; k++) {
|
|
float tk = t - float(k) * period;
|
|
float c = clamp(tk, 0.0, u_dash.x);
|
|
best = min(best, length(vec2((tk - c) * u_along_scale, dist)) - r);
|
|
}
|
|
alpha *= clamp(-best / u_blur + 0.5, 0.0, 1.0);
|
|
}
|
|
} else {
|
|
float period = u_dash.y;
|
|
float t = mod(v_along, period) - period * 0.5;
|
|
float r = u_outset - 0.5 * u_blur;
|
|
float d = length(vec2(t * u_along_scale, dist));
|
|
alpha = clamp((r - d) / u_blur + 0.5, 0.0, 1.0);
|
|
}
|
|
}
|
|
if (u_cover > 0.5) {
|
|
if (u_cover < 1.5) {
|
|
if (alpha < 1.0) discard;
|
|
} else if (u_cover < 2.5) {
|
|
if (alpha <= 0.0 || alpha >= 1.0) discard;
|
|
} else if (alpha < 0.5) discard;
|
|
}
|
|
vec4 c = u_override.a < 0.0 ? v_color : u_override;
|
|
fragColor = vec4(c.rgb, 1.0) * (c.a * alpha * u_opacity);
|
|
}`, DEPTH_GAP = 1 / 65536, DEPTH_CLEAR = 4 / 8, DEPTH_MARK = 3 / 8, DEPTH_FILL = 2 / 8, DEPTH_FILL_OVER = 1 / 8, offscreens = /* @__PURE__ */ new WeakMap(), COMPOSITE_VERTEX_SHADER = `#version 300 es
|
|
void main() {
|
|
vec2 p = vec2(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0);
|
|
gl_Position = vec4(p, 0.0, 1.0);
|
|
}`, COMPOSITE_FRAGMENT_SHADER = `#version 300 es
|
|
precision highp float;
|
|
uniform sampler2D u_color;
|
|
out vec4 fragColor;
|
|
void main() {
|
|
vec4 c = texelFetch(u_color, ivec2(gl_FragCoord.xy), 0);
|
|
if (c.a == 0.0) discard;
|
|
fragColor = c;
|
|
}`, COVER_ALL = 0, COVER_FULL = 1, COVER_PARTIAL = 2, COVER_HALF = 3;
|
|
function widthAt(w, zoom) {
|
|
return typeof w == "function" ? w(zoom) : w;
|
|
}
|
|
var LaneLayer = class {
|
|
constructor(opts) {
|
|
__publicField(this, "id");
|
|
__publicField(this, "type", "custom");
|
|
__publicField(this, "renderingMode", "2d");
|
|
__publicField(this, "map", null);
|
|
__publicField(this, "gl", null);
|
|
/** Per projection variant; null for one that failed to build, so it is not tried every frame. */
|
|
__publicField(this, "programs", /* @__PURE__ */ new Map());
|
|
__publicField(this, "vbo", null);
|
|
__publicField(this, "cbo", null);
|
|
__publicField(this, "ibo", null);
|
|
__publicField(this, "mesh", null);
|
|
/** Materialized from `packed` on demand. */
|
|
__publicField(this, "layout", null);
|
|
/** A worker's answer, kept as typed arrays until something asks for paths. */
|
|
__publicField(this, "packed", null);
|
|
__publicField(this, "built", null);
|
|
__publicField(this, "inFlight", null);
|
|
__publicField(this, "session", nextSession++);
|
|
__publicField(this, "sentGraph", !1);
|
|
__publicField(this, "looksDirty", !0);
|
|
__publicField(this, "layoutCache", new LayoutCache());
|
|
__publicField(this, "meshCache", new TessellateCache());
|
|
/**
|
|
* A tile the size of the graph, at its northwest corner. Anchors are in
|
|
* its local units, so float32 keeps sub-pixel precision at high zoom.
|
|
*/
|
|
__publicField(this, "tile", { z: 0, x: 0, y: 0 });
|
|
__publicField(this, "origin", [0, 0]);
|
|
__publicField(this, "unitsPerMercator", EXTENT);
|
|
/**
|
|
* What the uploaded mesh and the last build were made from. After `setGraph` the old
|
|
* mesh keeps drawing until the new build lands, so it needs its own tile, and queries
|
|
* on the old layout need the old graph's edges.
|
|
*/
|
|
__publicField(this, "builtWith", null);
|
|
__publicField(this, "dirty", !0);
|
|
__publicField(this, "graph");
|
|
__publicField(this, "sizes");
|
|
__publicField(this, "casing");
|
|
__publicField(this, "opacity");
|
|
__publicField(this, "zoomEpsilon");
|
|
__publicField(this, "smooth");
|
|
__publicField(this, "openFolds");
|
|
__publicField(this, "laneStyle");
|
|
__publicField(this, "cull");
|
|
__publicField(this, "cullMargin");
|
|
__publicField(this, "useWorker");
|
|
__publicField(this, "onBuild");
|
|
__publicField(this, "builds", 0);
|
|
__publicField(this, "fullFeatures", null);
|
|
/** By cache key, so repeat calls share one request. */
|
|
__publicField(this, "fullPending", /* @__PURE__ */ new Map());
|
|
/** Bumped by every setter a full-extent answer depends on, so one asked before it is not kept. */
|
|
__publicField(this, "generation", 0);
|
|
__publicField(this, "highlight", []);
|
|
__publicField(this, "highlightStyle", null);
|
|
/** Each group's bright split, kept until the mesh or the highlight changes. */
|
|
__publicField(this, "brightSplit", null);
|
|
__publicField(this, "timings", { layoutMs: 0, meshMs: 0, renderThreadMs: 0 });
|
|
/** The depth MapLibre gave this layer for the frame being drawn. */
|
|
__publicField(this, "depthBase", 1 - DEPTH_GAP);
|
|
/** The layer's index in the style when `depthBase` was read, or -1. */
|
|
__publicField(this, "depthPlace", -1);
|
|
if (this.id = opts.id, this.graph = opts.graph, !opts.graph || !Array.isArray(opts.graph.edges)) throw new TypeError("maplibre-gl-lanes: `graph` must be a line graph from `buildLineGraph`");
|
|
requireSizesFunction(opts.sizes), this.sizes = opts.sizes, this.opacity = Math.max(0, Math.min(1, opts.opacity ?? 1)), this.zoomEpsilon = opts.zoomEpsilon ?? 0.25, this.smooth = opts.smooth ?? !0, this.openFolds = opts.openFolds ?? !0, this.laneStyle = opts.laneStyle ?? null, this.cull = opts.cull ?? !0, this.cullMargin = opts.cullMargin ?? 0.5, this.useWorker = opts.worker ?? !0, this.onBuild = opts.onBuild ?? null;
|
|
let casingCss = opts.casingColor === void 0 ? "#333333" : opts.casingColor;
|
|
this.casing = casingCss === null ? null : rgba(parseColor(casingCss)), this.computeOrigin();
|
|
}
|
|
/** Replace the graph (e.g. after re-ordering or filtering routes). */
|
|
setGraph(graph) {
|
|
this.graph = graph, this.generation++, this.layoutCache.clear(), this.meshCache.clear(), this.fullFeatures = null, this.fullPending.clear(), this.closeSession(), this.session = nextSession++, this.sentGraph = !1, this.looksDirty = !0, this.computeOrigin(), this.dirty = !0, this.map?.triggerRepaint();
|
|
}
|
|
/** Change the sizes per zoom (see `LaneLayerOptions.sizes`). */
|
|
setSizes(sizes) {
|
|
requireSizesFunction(sizes), this.sizes = sizes, this.generation++, this.fullFeatures = null, this.fullPending.clear(), this.dirty = !0, this.map?.triggerRepaint();
|
|
}
|
|
/** Change or remove the per-edge styling (see `LaneLayerOptions.laneStyle`). */
|
|
setLaneStyle(laneStyle) {
|
|
this.laneStyle = laneStyle, this.generation++, this.layoutCache.clear(), this.meshCache.clear(), this.fullFeatures = null, this.fullPending.clear(), this.looksDirty = !0, this.dirty = !0, this.map?.triggerRepaint();
|
|
}
|
|
/** Change the opacity of the whole layer, 0 to 1, without a rebuild. */
|
|
setOpacity(opacity) {
|
|
this.opacity = Math.max(0, Math.min(1, opacity)), this.map?.triggerRepaint();
|
|
}
|
|
/** Change the casing color (see `LaneLayerOptions.casingColor`); null removes the casing. */
|
|
setCasingColor(css) {
|
|
this.casing = css === null ? null : rgba(parseColor(css)), this.map?.triggerRepaint();
|
|
}
|
|
/**
|
|
* Lift one route, or several: their lanes are drawn last, with a halo
|
|
* and an outline, and the rest can be dimmed. It draws inside this
|
|
* layer, so layers above it cover it. Pass null, or an empty list, to
|
|
* clear.
|
|
*/
|
|
setHighlight(route, style = {}) {
|
|
this.highlight = route === null ? [] : typeof route == "string" ? [route] : [...route], this.highlightStyle = this.highlight.length === 0 ? null : {
|
|
halo: style.halo === null ? null : rgba(parseColor(style.halo ?? "#ffb700")),
|
|
haloWidth: style.haloWidth ?? 4,
|
|
haloBlur: style.haloBlur ?? 3,
|
|
outline: style.outline === null ? null : rgba(parseColor(style.outline ?? "#000000")),
|
|
outlineWidth: style.outlineWidth ?? 1.5,
|
|
dim: style.dim ?? 1,
|
|
bright: new Set(style.bright ?? [])
|
|
}, this.map?.triggerRepaint();
|
|
}
|
|
/** The highlighted routes, in the order they were given; empty when none. */
|
|
getHighlight() {
|
|
return [...this.highlight];
|
|
}
|
|
/**
|
|
* What the last build covers, without its geometry. Cheap enough to call
|
|
* every frame, unlike `getLayout`.
|
|
*/
|
|
getBuildInfo() {
|
|
let l = this.layout ?? this.packed;
|
|
return l ? { build: this.builds, zoom: l.zoom, bounds: l.bounds, stats: l.stats, timings: this.timings } : null;
|
|
}
|
|
/**
|
|
* Listen for finished builds; refresh sources derived from
|
|
* `laneFeatures` here, not on `idle`, which can arrive before a worker
|
|
* build. One listener, which this replaces; null clears it.
|
|
*/
|
|
setOnBuild(listener) {
|
|
this.onBuild = listener;
|
|
}
|
|
/**
|
|
* The lane geometry of the last build, in world pixels at `layout.zoom`.
|
|
* The first call after a worker build unpacks it, so do not call it
|
|
* every frame.
|
|
*/
|
|
getLayout() {
|
|
if (this.layout) return this.layout;
|
|
let p = this.packed;
|
|
return p ? (this.layout = {
|
|
zoom: p.zoom,
|
|
scale: p.scale,
|
|
paths: unpackPaths(p.paths),
|
|
drawOrder: p.drawOrder,
|
|
bounds: p.bounds,
|
|
mergedEdges: p.mergedEdges,
|
|
stats: p.stats
|
|
}, this.layout) : null;
|
|
}
|
|
/**
|
|
* Lane geometry as GeoJSON LineStrings, one per lane piece, with
|
|
* properties `route`, `name`, `color`, `direction`, `kind`, `edge`,
|
|
* `lanes`, `routes` and the graph's `uniformProperties` (see
|
|
* docs/api.md). Lane positions depend on the zoom and on the build,
|
|
* so refresh on `moveend` and from `setOnBuild`.
|
|
*/
|
|
laneFeatures(opts = {}) {
|
|
if (opts.extent === "full") return this.fullLaneFeatures(opts);
|
|
let layout = this.getLayout(), features = [];
|
|
if (!layout) return { type: "FeatureCollection", features };
|
|
let z = opts.zoom ?? this.map?.getZoom() ?? layout.zoom, only = opts.routes ? new Set(opts.routes) : null, { scale } = layout, nowScale = 512 * Math.pow(2, z);
|
|
for (let p of layout.paths) {
|
|
if (only && !only.has(p.route)) continue;
|
|
let coords = [];
|
|
for (let i = 0; i < p.coords.length; i += 2) {
|
|
let x = (p.anchors[i] + (p.coords[i] - p.anchors[i]) * (scale / nowScale)) / scale, y = (p.anchors[i + 1] + (p.coords[i + 1] - p.anchors[i + 1]) * (scale / nowScale)) / scale;
|
|
coords.push([x * 360 - 180, Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180 / Math.PI]);
|
|
}
|
|
coords.length < 2 || features.push(this.laneFeature(p, coords, this.builtWith?.graph ?? this.graph));
|
|
}
|
|
return { type: "FeatureCollection", features };
|
|
}
|
|
/**
|
|
* `laneFeatures` with `extent: 'full'` laid out in the worker. Without a
|
|
* worker, or for the built extent, it resolves with `laneFeatures`.
|
|
*/
|
|
laneFeaturesAsync(opts = {}) {
|
|
if (opts.extent !== "full" || !this.useWorker) return Promise.resolve(this.laneFeatures(opts));
|
|
let z = opts.zoom ?? this.map?.getZoom() ?? this.built?.zoom ?? 0, routes = opts.routes ? [...opts.routes].sort() : null, key = `${z}|${routes ? routes.join("\0") : "*"}`, cached = this.fullFeatures;
|
|
if (cached && cached.graph === this.graph && cached.key === key) return Promise.resolve(cached.fc);
|
|
let waiting = this.fullPending.get(key);
|
|
if (waiting) return waiting;
|
|
let wanted = this.requestFullFeatures(z, routes, key);
|
|
this.fullPending.set(key, wanted);
|
|
let settle2 = () => {
|
|
this.fullPending.get(key) === wanted && this.fullPending.delete(key);
|
|
};
|
|
return wanted.then(settle2, settle2), wanted;
|
|
}
|
|
async requestFullFeatures(z, routes, key) {
|
|
let graph = this.graph, generation = this.generation;
|
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
let req = this.layoutRequest(z, null, "full", routes), sent = sendToWorker(req);
|
|
if (!sent) {
|
|
this.unsent(req);
|
|
break;
|
|
}
|
|
let res = await sent;
|
|
if (res.needGraph) {
|
|
this.sentGraph = !1;
|
|
continue;
|
|
}
|
|
if (res.error) {
|
|
console.warn("maplibre-gl-lanes: lane features in the worker failed, building on this thread:", res.error);
|
|
break;
|
|
}
|
|
if (res.session !== this.session || graph !== this.graph || generation !== this.generation) {
|
|
graph = this.graph, generation = this.generation;
|
|
continue;
|
|
}
|
|
let fc = this.featureCollection(unpackPaths(res.paths), res.scale, graph);
|
|
return this.fullFeatures = { graph, key, fc }, fc;
|
|
}
|
|
return this.fullLaneFeatures({ zoom: z, routes: routes ?? void 0, extent: "full" });
|
|
}
|
|
laneFeature(p, coords, graph) {
|
|
let meta = graph.routes.get(p.route), edge = p.edge, e = graph.edges[edge];
|
|
p.travel === -1 && coords.reverse();
|
|
let properties = {
|
|
...e?.properties,
|
|
route: p.route,
|
|
name: meta?.name ?? p.route,
|
|
color: p.look.color,
|
|
direction: p.travel === 0 ? 0 : 1,
|
|
kind: p.kind,
|
|
edge,
|
|
lanes: e?.routes.length ?? 1,
|
|
routes: e ? (e.order.length === e.routes.length ? e.order : e.routes).slice() : [p.route]
|
|
};
|
|
return { type: "Feature", geometry: { type: "LineString", coordinates: coords }, properties };
|
|
}
|
|
fullLaneFeatures(opts) {
|
|
let z = opts.zoom ?? this.map?.getZoom() ?? this.built?.zoom ?? 0, routes = opts.routes ? [...opts.routes].sort() : null, key = `${z}|${routes ? routes.join("\0") : "*"}`, cached = this.fullFeatures;
|
|
if (cached && cached.graph === this.graph && cached.key === key) return cached.fc;
|
|
let layout = layoutAtZoom(this.graph, z, this.sizes, { smooth: this.smooth, openFolds: this.openFolds, routes: routes ?? void 0, laneStyle: this.laneStyle ?? void 0 }), fc = this.featureCollection(layout.paths, layout.scale, this.graph);
|
|
return this.fullFeatures = { graph: this.graph, key, fc }, fc;
|
|
}
|
|
featureCollection(paths, scale, graph) {
|
|
let features = [];
|
|
for (let p of paths) {
|
|
let coords = [];
|
|
for (let i = 0; i < p.coords.length; i += 2) {
|
|
let x = p.coords[i] / scale, y = p.coords[i + 1] / scale;
|
|
coords.push([x * 360 - 180, Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180 / Math.PI]);
|
|
}
|
|
coords.length < 2 || features.push(this.laneFeature(p, coords, graph));
|
|
}
|
|
return { type: "FeatureCollection", features };
|
|
}
|
|
/**
|
|
* The lane nearest to a screen point, within `tolerancePx` (default 6),
|
|
* or null. Only the last build is searched.
|
|
*/
|
|
queryLane(point, tolerancePx = 6) {
|
|
if (!this.map) return null;
|
|
let lngLat = this.map.unproject([point.x, point.y]);
|
|
return this.queryLaneAt([lngLat.lng, lngLat.lat], this.map.getZoom(), tolerancePx);
|
|
}
|
|
/** `queryLane` for a geographic point, with the zoom that sets the pixel tolerance. */
|
|
queryLaneAt(lngLat, zoom, tolerancePx = 6) {
|
|
let layout = this.getLayout();
|
|
if (!layout) return null;
|
|
let merc = lngLatToMercator(lngLat[0], lngLat[1]), nowScale = 512 * Math.pow(2, zoom), px = [merc[0] * nowScale, merc[1] * nowScale], ratio = layout.scale / nowScale, best = null, bestD2 = tolerancePx * tolerancePx, bestX = 0, bestY = 0;
|
|
for (let p of layout.paths) {
|
|
let c = p.coords, a = p.anchors, prevX = 0, prevY = 0;
|
|
for (let i = 0; i < c.length; i += 2) {
|
|
let x = a[i] / ratio + (c[i] - a[i]), y = a[i + 1] / ratio + (c[i + 1] - a[i + 1]);
|
|
if (i > 0) {
|
|
let [d2, cx, cy] = segNearest(px[0], px[1], prevX, prevY, x, y);
|
|
d2 < bestD2 && (bestD2 = d2, best = p, bestX = cx, bestY = cy);
|
|
}
|
|
prevX = x, prevY = y;
|
|
}
|
|
}
|
|
if (!best) return null;
|
|
let edgeId = best.edge, graph = this.builtWith?.graph ?? this.graph, e = graph.edges[edgeId];
|
|
return {
|
|
route: best.route,
|
|
name: graph.routes.get(best.route)?.name,
|
|
distancePx: Math.sqrt(bestD2),
|
|
lngLat: mercatorToLngLat(bestX / nowScale, bestY / nowScale),
|
|
kind: best.kind,
|
|
edge: edgeId,
|
|
routes: e ? (e.order.length === e.routes.length ? e.order : e.routes).slice() : [best.route],
|
|
properties: e?.properties ?? {}
|
|
};
|
|
}
|
|
dashColor(css) {
|
|
let c = dashColorCache.get(css);
|
|
return c || dashColorCache.set(css, c = rgba(parseColor(css))), c;
|
|
}
|
|
computeOrigin() {
|
|
let minX = 1 / 0, minY = 1 / 0, maxX = -1 / 0, maxY = -1 / 0;
|
|
for (let node of this.graph.nodes)
|
|
node.x < minX && (minX = node.x), node.x > maxX && (maxX = node.x), node.y < minY && (minY = node.y), node.y > maxY && (maxY = node.y);
|
|
if (!Number.isFinite(minX)) {
|
|
this.tile = { z: 0, x: 0, y: 0 }, this.origin = [0, 0], this.unitsPerMercator = EXTENT;
|
|
return;
|
|
}
|
|
let span = Math.max(maxX - minX, maxY - minY), z = span > 0 ? Math.max(0, Math.min(22, Math.floor(-Math.log2(span)))) : 22, n = Math.pow(2, z);
|
|
this.tile = { z, x: Math.min(n - 1, Math.floor(minX * n)), y: Math.min(n - 1, Math.floor(minY * n)) }, this.origin = [this.tile.x / n, this.tile.y / n], this.unitsPerMercator = EXTENT * n;
|
|
}
|
|
onAdd(map, anyGl) {
|
|
if (!isWebGL2(anyGl)) {
|
|
console.error("maplibre-gl-lanes: this map has a WebGL1 context; the lane layer needs WebGL2 and will not draw."), this.map = map;
|
|
return;
|
|
}
|
|
let gl = anyGl;
|
|
this.map = map, this.gl = gl;
|
|
let share = offscreens.get(gl);
|
|
if (share) share.users++;
|
|
else {
|
|
let made = { users: 1, target: null, composite: void 0, onLost: () => {
|
|
made.target = null, made.composite = void 0;
|
|
} };
|
|
offscreens.set(gl, made), gl.canvas.addEventListener?.("webglcontextlost", made.onLost);
|
|
}
|
|
map.getTerrain() && console.warn("maplibre-gl-lanes: 3D terrain is enabled; lanes are drawn at sea level and will be hidden under raised terrain."), this.vbo = gl.createBuffer(), this.cbo = gl.createBuffer(), this.ibo = gl.createBuffer(), this.dirty = !0;
|
|
}
|
|
onRemove(_map, anyGl) {
|
|
let gl = this.gl;
|
|
if (!gl) {
|
|
this.map = null;
|
|
return;
|
|
}
|
|
for (let p of this.programs.values())
|
|
p && (gl.deleteProgram(p.program), gl.deleteVertexArray(p.vao));
|
|
this.programs.clear();
|
|
for (let b of [this.vbo, this.cbo, this.ibo]) b && gl.deleteBuffer(b);
|
|
this.vbo = this.cbo = this.ibo = null;
|
|
let share = offscreens.get(gl);
|
|
share && --share.users <= 0 && (share.target && releaseOffscreen(gl, share.target), share.composite && (gl.deleteProgram(share.composite.program), gl.deleteVertexArray(share.composite.vao)), gl.canvas.removeEventListener?.("webglcontextlost", share.onLost), offscreens.delete(gl)), this.layoutCache.clear(), this.meshCache.clear(), this.closeSession(), this.mesh = null, this.map = null, this.gl = null;
|
|
}
|
|
/** A failure is logged once and draws nothing, rather than throwing every frame. */
|
|
programFor(gl, shaderData) {
|
|
let name = shaderData.variantName;
|
|
if (this.programs.has(name)) return this.programs.get(name);
|
|
let entry = null;
|
|
try {
|
|
entry = this.buildProgram(gl, shaderData);
|
|
} catch (err) {
|
|
console.error(`maplibre-gl-lanes: lanes will not draw in the ${name} projection:`, err);
|
|
}
|
|
return this.programs.set(name, entry), entry;
|
|
}
|
|
buildProgram(gl, shaderData) {
|
|
let vsSource = `#version 300 es
|
|
precision highp float;
|
|
${shaderData.vertexShaderPrelude}
|
|
${shaderData.define}
|
|
${VERTEX_SHADER_BODY}`, fsSource = `#version 300 es
|
|
precision highp float;
|
|
${shaderData.define}
|
|
${FRAGMENT_SHADER_BODY}`, vs = compile(gl, gl.VERTEX_SHADER, vsSource), fs = compile(gl, gl.FRAGMENT_SHADER, fsSource), program = gl.createProgram();
|
|
if (gl.attachShader(program, vs), gl.attachShader(program, fs), gl.linkProgram(program), gl.deleteShader(vs), gl.deleteShader(fs), !gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
let log = gl.getProgramInfoLog(program);
|
|
throw gl.deleteProgram(program), new Error(`program link failed: ${log}`);
|
|
}
|
|
let uniforms = {};
|
|
for (let u of [
|
|
"u_units_per_px",
|
|
"u_outset",
|
|
"u_blur",
|
|
"u_override",
|
|
"u_opacity",
|
|
"u_dash",
|
|
"u_cap",
|
|
"u_along_scale",
|
|
"u_cover",
|
|
"u_projection_matrix",
|
|
"u_projection_tile_mercator_coords",
|
|
"u_projection_clipping_plane",
|
|
"u_projection_transition",
|
|
"u_projection_fallback_matrix"
|
|
])
|
|
uniforms[u] = gl.getUniformLocation(program, u);
|
|
let vao = gl.createVertexArray();
|
|
gl.bindVertexArray(vao), gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
|
|
let stride = FLOATS_PER_VERTEX * 4, attribs = [["a_anchor", 0, 2], ["a_delta", 8, 2], ["a_extrude", 16, 2], ["a_normal", 24, 2], ["a_along", 32, 1]];
|
|
for (let [name, offset, size] of attribs) {
|
|
let loc = gl.getAttribLocation(program, name);
|
|
loc < 0 || (gl.enableVertexAttribArray(loc), gl.vertexAttribPointer(loc, size, gl.FLOAT, !1, stride, offset));
|
|
}
|
|
let aCol = gl.getAttribLocation(program, "a_color");
|
|
return gl.bindBuffer(gl.ARRAY_BUFFER, this.cbo), gl.enableVertexAttribArray(aCol), gl.vertexAttribPointer(aCol, 4, gl.UNSIGNED_BYTE, !0, 4, 0), gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo), gl.bindVertexArray(null), { program, vao, uniforms };
|
|
}
|
|
viewBounds() {
|
|
if (!this.cull || !this.map) return null;
|
|
let b = this.map.getBounds(), sw = lngLatToMercator(b.getWest(), b.getSouth()), ne = lngLatToMercator(b.getEast(), b.getNorth()), minX = Math.min(sw[0], ne[0]), maxX = Math.max(sw[0], ne[0]), minY = Math.min(sw[1], ne[1]), maxY = Math.max(sw[1], ne[1]), mx = (maxX - minX) * this.cullMargin, my = (maxY - minY) * this.cullMargin;
|
|
return { minX: minX - mx, minY: minY - my, maxX: maxX + mx, maxY: maxY + my };
|
|
}
|
|
viewInside(built) {
|
|
if (!built) return !0;
|
|
let b = this.map.getBounds(), sw = lngLatToMercator(b.getWest(), b.getSouth()), ne = lngLatToMercator(b.getEast(), b.getNorth());
|
|
return Math.min(sw[0], ne[0]) >= built.minX && Math.max(sw[0], ne[0]) <= built.maxX && Math.min(sw[1], ne[1]) >= built.minY && Math.max(sw[1], ne[1]) <= built.maxY;
|
|
}
|
|
serves(build, zoom) {
|
|
let dz = zoom - build.zoom;
|
|
return dz >= -this.zoomEpsilon && dz <= 2 * this.zoomEpsilon && this.viewInside(build.bounds);
|
|
}
|
|
/**
|
|
* Only one build is ever in flight: a pan that outruns the worker asks
|
|
* again once its answer lands, rather than queueing a request per frame.
|
|
*/
|
|
rebuild(zoom) {
|
|
if (this.inFlight) return;
|
|
let bounds = this.viewBounds();
|
|
if (this.useWorker && this.sendLayout(zoom, bounds)) {
|
|
this.dirty = !1;
|
|
return;
|
|
}
|
|
let t0 = performance.now(), layout = layoutAtZoom(this.graph, zoom, this.sizes, { bounds, smooth: this.smooth, openFolds: this.openFolds, laneStyle: this.laneStyle ?? void 0, cache: this.layoutCache }), t1 = performance.now(), mesh = tessellate(layout.paths, { scale: layout.scale, origin: this.origin, unitsPerMercator: this.unitsPerMercator, drawOrder: layout.drawOrder, width: checkedSizes(this.sizes, zoom).width, cache: this.meshCache }), t2 = performance.now();
|
|
this.upload(mesh), this.layout = layout, this.packed = null, this.built = { zoom, bounds }, this.dirty = !1, this.timings = { layoutMs: t1 - t0, meshMs: t2 - t1, renderThreadMs: performance.now() - t0 }, this.finished();
|
|
}
|
|
layoutRequest(zoom, bounds, extent, routes) {
|
|
let req = {
|
|
kind: "layout",
|
|
id: nextRequestId(),
|
|
session: this.session,
|
|
zoom,
|
|
style: checkedSizes(this.sizes, zoom),
|
|
smooth: this.smooth,
|
|
openFolds: this.openFolds,
|
|
bounds,
|
|
extent
|
|
};
|
|
return routes && (req.routes = routes), this.sentGraph || (req.graph = toTransfer(this.graph), req.origin = this.origin, req.unitsPerMercator = this.unitsPerMercator, this.looksDirty = !0, this.sentGraph = !0), this.looksDirty && (req.looks = this.lookTable(), this.looksDirty = !1), req;
|
|
}
|
|
unsent(req) {
|
|
req.graph && (this.sentGraph = !1), req.looks !== void 0 && (this.looksDirty = !0);
|
|
}
|
|
sendLayout(zoom, bounds) {
|
|
let req = this.layoutRequest(zoom, bounds, "built"), sent = sendToWorker(req);
|
|
return sent ? (this.inFlight = { id: req.id }, sent.then((res) => this.onLayout(res)), !0) : (this.unsent(req), this.useWorker = !1, !1);
|
|
}
|
|
onLayout(res) {
|
|
if (!this.inFlight || res.id !== this.inFlight.id) return;
|
|
if (this.inFlight = null, res.session !== this.session) {
|
|
this.dirty = !0, this.map?.triggerRepaint();
|
|
return;
|
|
}
|
|
if (res.needGraph) {
|
|
this.sentGraph = !1, this.dirty = !0, this.map?.triggerRepaint();
|
|
return;
|
|
}
|
|
if (res.error) {
|
|
console.warn("maplibre-gl-lanes: layout in the worker failed, building on the main thread:", res.error), this.useWorker = !1, this.dirty = !0, this.map?.triggerRepaint();
|
|
return;
|
|
}
|
|
if (this.dirty) {
|
|
this.map?.triggerRepaint();
|
|
return;
|
|
}
|
|
if (!this.gl) return;
|
|
let t0 = performance.now();
|
|
this.upload(res), this.packed = res, this.layout = null, this.built = { zoom: res.zoom, bounds: res.bounds }, this.timings = { layoutMs: res.layoutMs, meshMs: res.meshMs, renderThreadMs: performance.now() - t0 }, this.finished(), this.map?.triggerRepaint();
|
|
}
|
|
/** The listener runs in a microtask, never inside the render call. */
|
|
finished() {
|
|
this.builds++;
|
|
let listener = this.onBuild;
|
|
if (!listener) return;
|
|
let info = this.getBuildInfo();
|
|
queueMicrotask(() => {
|
|
this.map && listener(info);
|
|
});
|
|
}
|
|
/** In the worker's reading order: edges, then each edge's routes. */
|
|
lookTable() {
|
|
let laneStyle = this.laneStyle;
|
|
if (!laneStyle) return null;
|
|
let out = [];
|
|
for (let e of this.graph.edges) for (let r of e.routes) out.push(laneStyle(e, r) ?? null);
|
|
return out;
|
|
}
|
|
closeSession() {
|
|
this.fullPending.clear(), this.inFlight = null, this.sentGraph && (this.sentGraph = !1, sendToWorker({ kind: "dispose", id: nextRequestId(), session: this.session }));
|
|
}
|
|
upload(mesh) {
|
|
let gl = this.gl;
|
|
gl.bindVertexArray(null), gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo), gl.bufferData(gl.ARRAY_BUFFER, mesh.vertices, gl.DYNAMIC_DRAW), gl.bindBuffer(gl.ARRAY_BUFFER, this.cbo), gl.bufferData(gl.ARRAY_BUFFER, mesh.colors, gl.DYNAMIC_DRAW), gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ibo), gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, mesh.indices, gl.DYNAMIC_DRAW), this.mesh = mesh, this.builtWith = { graph: this.graph, tile: this.tile, unitsPerMercator: this.unitsPerMercator };
|
|
}
|
|
render(anyGl, args) {
|
|
let gl = this.gl;
|
|
if (!this.map || !this.vbo || !gl) return;
|
|
let zoom = this.map.getZoom();
|
|
(this.dirty || !this.built || !this.serves(this.built, zoom)) && this.rebuild(zoom);
|
|
let mesh = this.mesh, frame = this.builtWith;
|
|
if (!mesh || !mesh.indexCount || !frame) return;
|
|
let entry = this.programFor(gl, args.shaderData);
|
|
if (!entry) return;
|
|
let { program, vao, uniforms } = entry, tileID = { canonical: frame.tile, wrap: 0 }, pd = args.getProjectionData ? args.getProjectionData({ tileID, applyGlobeMatrix: !0 }) : this.map.transform.getProjectionData({ overscaledTileID: tileID, applyGlobeMatrix: !0 }), dpr = this.map.getPixelRatio(), style = checkedSizes(this.sizes, zoom), built = this.built ? this.built.zoom : zoom, dashStyle = built === zoom ? style : checkedSizes(this.sizes, built), alongScale = Math.pow(2, zoom - built), unitsPerPx = frame.unitsPerMercator / (512 * Math.pow(2, zoom));
|
|
gl.useProgram(program), gl.bindVertexArray(vao), gl.uniformMatrix4fv(uniforms.u_projection_matrix, !1, pd.mainMatrix), gl.uniformMatrix4fv(uniforms.u_projection_fallback_matrix, !1, pd.fallbackMatrix), gl.uniform4f(uniforms.u_projection_tile_mercator_coords, ...pd.tileMercatorCoords), gl.uniform4f(uniforms.u_projection_clipping_plane, ...pd.clippingPlane), gl.uniform1f(uniforms.u_projection_transition, pd.projectionTransition), gl.uniform1f(uniforms.u_units_per_px, unitsPerPx), gl.uniform1f(uniforms.u_along_scale, alongScale), gl.uniform1f(uniforms.u_blur, 1 / dpr), gl.uniform1f(uniforms.u_opacity, this.opacity), gl.uniform1f(uniforms.u_cover, COVER_ALL);
|
|
let place = this.map.getLayersOrder ? this.map.getLayersOrder().indexOf(this.id) : -1;
|
|
if (place < 0 || place !== this.depthPlace) {
|
|
let range = gl.getParameter(gl.DEPTH_RANGE);
|
|
this.depthBase = range ? range[0] : 1 - DEPTH_GAP, this.depthPlace = place;
|
|
}
|
|
this.depthAt(gl, 0), gl.enable(gl.DEPTH_TEST), gl.depthFunc(gl.LEQUAL), gl.depthMask(!1), gl.disable(gl.STENCIL_TEST), gl.disable(gl.CULL_FACE), gl.enable(gl.BLEND), gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
let aa = 0.5 / dpr;
|
|
gl.uniform2f(uniforms.u_dash, 0, 0), gl.uniform1f(uniforms.u_cap, 0);
|
|
let runs = [];
|
|
for (let gi = 0; gi < mesh.groupRoutes.length; ) {
|
|
let route = mesh.groupRoutes[gi], run = [];
|
|
for (; gi < mesh.groupRoutes.length && mesh.groupRoutes[gi] === route; )
|
|
mesh.groups[gi][1] && run.push(gi), gi++;
|
|
run.length && runs.push(run);
|
|
}
|
|
let hl = this.highlightStyle, liftedRuns = [], lifted = /* @__PURE__ */ new Set();
|
|
if (hl)
|
|
for (let route of this.highlight) {
|
|
let run = runs.find((r) => mesh.groupRoutes[r[0]] === route);
|
|
if (run) {
|
|
liftedRuns.push(run);
|
|
for (let gi of run) lifted.add(gi);
|
|
}
|
|
}
|
|
let dimmed = !!hl && hl.dim < 1, translucent = this.opacity < 1 || dimmed, once = !!this.casing && (this.casing[3] < 1 || translucent), marks = once || translucent, offscreen = marks ? acquireOffscreen(gl) : null;
|
|
offscreen || (once = marks = !1);
|
|
let mapFramebuffer = null;
|
|
offscreen && (mapFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING), gl.bindFramebuffer(gl.FRAMEBUFFER, offscreen.target.framebuffer), gl.clearColor(0, 0, 0, 0), gl.clearDepth(1), gl.depthMask(!0), gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT), gl.depthMask(!1));
|
|
let split = dimmed && hl.bright.size > 0 ? this.splitFor(mesh, hl) : null, restOpacity = dimmed ? this.opacity * hl.dim : this.opacity, opacities = split ? { dimmed: restOpacity, bright: this.opacity, split } : null;
|
|
dimmed && gl.uniform1f(uniforms.u_opacity, restOpacity);
|
|
for (let run of runs)
|
|
lifted.has(run[0]) || this.drawRoute(gl, uniforms, run, style, dashStyle, aa, once, marks, restOpacity, opacities);
|
|
if (hl && liftedRuns.length) {
|
|
gl.uniform1f(uniforms.u_opacity, this.opacity);
|
|
let outlineEdge = style.width / 2 + style.casingWidth + widthAt(hl.outlineWidth, zoom), lift = () => {
|
|
for (let gi of lifted) gl.drawElements(gl.TRIANGLES, mesh.groups[gi][1], gl.UNSIGNED_INT, mesh.groups[gi][0] * 4);
|
|
if (once) {
|
|
gl.depthFunc(gl.ALWAYS), gl.depthMask(!0), this.depthAt(gl, DEPTH_CLEAR), gl.colorMask(!1, !1, !1, !1);
|
|
for (let gi of lifted) gl.drawElements(gl.TRIANGLES, mesh.groups[gi][1], gl.UNSIGNED_INT, mesh.groups[gi][0] * 4);
|
|
gl.colorMask(!0, !0, !0, !0), gl.depthMask(!1), gl.depthFunc(gl.LEQUAL), this.depthAt(gl, 0);
|
|
}
|
|
};
|
|
hl.halo && (gl.uniform1f(uniforms.u_blur, Math.max(1 / dpr, widthAt(hl.haloBlur, zoom))), gl.uniform1f(uniforms.u_outset, outlineEdge + widthAt(hl.haloWidth, zoom) + aa), gl.uniform4fv(uniforms.u_override, hl.halo), lift(), gl.uniform1f(uniforms.u_blur, 1 / dpr)), hl.outline && (gl.uniform1f(uniforms.u_outset, outlineEdge + aa), gl.uniform4fv(uniforms.u_override, hl.outline), lift());
|
|
for (let run of liftedRuns) this.drawRoute(gl, uniforms, run, style, dashStyle, aa, once, marks, this.opacity);
|
|
}
|
|
offscreen && (gl.invalidateFramebuffer(gl.FRAMEBUFFER, [gl.DEPTH_ATTACHMENT]), gl.bindFramebuffer(gl.FRAMEBUFFER, mapFramebuffer), gl.useProgram(offscreen.composite.program), gl.bindVertexArray(offscreen.composite.vao), gl.activeTexture(gl.TEXTURE0), gl.bindTexture(gl.TEXTURE_2D, offscreen.target.color), gl.uniform1i(offscreen.composite.color, 0), gl.depthFunc(gl.LEQUAL), gl.depthMask(!1), this.depthAt(gl, 0), gl.drawArrays(gl.TRIANGLES, 0, 3)), gl.depthFunc(gl.LEQUAL), gl.depthMask(!1), gl.depthRange(this.depthBase, this.depthBase), gl.bindVertexArray(null);
|
|
}
|
|
/** Draw at an offset behind this layer's depth, in units of the gap (see `DEPTH_GAP`). */
|
|
depthAt(gl, offset) {
|
|
let d = this.depthBase + offset * DEPTH_GAP;
|
|
gl.depthRange(d, d);
|
|
}
|
|
/** The bright split of every group, worked out once per mesh and highlight. */
|
|
splitFor(mesh, hl) {
|
|
let cached = this.brightSplit;
|
|
if (cached && cached.mesh === mesh && cached.style === hl) return cached.groups;
|
|
let groups = [];
|
|
for (let gi = 0; gi < mesh.groups.length; gi++) {
|
|
let ribbon = splitRanges(mesh.groups[gi], mesh.groupPieces[gi], hl.bright), dots = splitRanges(mesh.groupDots[gi], mesh.groupDotPieces[gi], hl.bright);
|
|
groups.push(ribbon.bright.length || dots.bright.length ? { dimmed: ribbon.dimmed, bright: ribbon.bright, dotsDimmed: dots.dimmed, dotsBright: dots.bright } : null);
|
|
}
|
|
return this.brightSplit = { mesh, style: hl, groups }, groups;
|
|
}
|
|
/**
|
|
* Every casing before any fill, or a group's casing would seam over its
|
|
* neighbor's fill. `dashStyle` is in the mesh's build pixels.
|
|
*
|
|
* With `opacities`, the pieces on bright edges draw at full opacity in
|
|
* the same passes as the rest of the route, so they keep its place in
|
|
* the drawing order and its once-only casing. Their casings go after
|
|
* the dimmed ones: where a bright cap reaches into a dimmed neighbor,
|
|
* the neighbor's own casing already holds the mark, so a full-strength
|
|
* casing never shows through the neighbor's translucent fill. Their
|
|
* fills go last, so they cover the dimmed caps that reach into them.
|
|
*
|
|
* A translucent fill paints each pixel once (`DEPTH_FILL`), over all of
|
|
* one side's groups, and the mark is cleared only after them, so a
|
|
* connector meeting a lane of another look counts as one too. The groups
|
|
* go last first, since the first full fragment keeps its pixel, so the
|
|
* one on top stays the one on top.
|
|
*/
|
|
drawRoute(gl, uniforms, run, style, dashStyle, aa, once, marks, opacity, opacities = null) {
|
|
let mesh = this.mesh, meta = (this.builtWith?.graph ?? this.graph).routes.get(mesh.groupRoutes[run[0]]), periodOf = (look) => look.dash ? [look.dash[0] * dashStyle.width, look.dash[1] * dashStyle.width] : [0, 0], split = opacities ? opacities.split : null, ribbon = (gi, pass) => {
|
|
let sp = split && split[gi];
|
|
return sp ? pass === "bright" ? sp.bright : sp.dimmed : pass === "bright" ? [] : [mesh.groups[gi]];
|
|
}, dotRanges = (gi, pass) => {
|
|
let sp = split && split[gi];
|
|
return sp ? pass === "bright" ? sp.dotsBright : sp.dotsDimmed : pass === "bright" ? [] : [mesh.groupDots[gi]];
|
|
}, drawAll = (ranges) => {
|
|
for (let [first, count] of ranges) count && gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_INT, first * 4);
|
|
}, draw = (gi, pass) => drawAll(ribbon(gi, pass)), drawDashes = (gi, look, pass) => {
|
|
let dots = mesh.groupDots[gi];
|
|
if (look.dash[0] === 0 && dots && dots[1]) {
|
|
drawAll(dotRanges(gi, pass));
|
|
return;
|
|
}
|
|
let period = periodOf(look);
|
|
gl.uniform2f(uniforms.u_dash, period[0], period[1]), draw(gi, pass), gl.uniform2f(uniforms.u_dash, 0, 0);
|
|
}, lit = !!split && run.some((gi) => split[gi]), passes = lit ? ["dimmed", "bright"] : ["all"], setOpacity = (pass) => {
|
|
lit && gl.uniform1f(uniforms.u_opacity, pass === "bright" ? opacities.bright : opacities.dimmed);
|
|
};
|
|
if (this.casing && meta?.casing !== !1) {
|
|
gl.uniform1f(uniforms.u_outset, style.width / 2 + style.casingWidth + aa), gl.uniform4fv(uniforms.u_override, this.casing);
|
|
for (let pass of passes) for (let gi of run) {
|
|
if (pass === "bright" && !split[gi]) continue;
|
|
setOpacity(pass);
|
|
let look = mesh.groupLooks[gi], drawCasing = !!look.dash && look.dash[0] === 0 && !look.dashColor ? () => drawDashes(gi, look, pass) : () => draw(gi, pass);
|
|
once ? (gl.depthFunc(gl.LESS), this.depthAt(gl, DEPTH_MARK), gl.depthMask(!0), gl.uniform1f(uniforms.u_cover, COVER_FULL), drawCasing(), gl.depthMask(!1), gl.uniform1f(uniforms.u_cover, COVER_PARTIAL), drawCasing(), gl.uniform1f(uniforms.u_cover, COVER_ALL)) : drawCasing();
|
|
}
|
|
}
|
|
once && (gl.depthFunc(gl.LEQUAL), this.depthAt(gl, 0)), gl.uniform1f(uniforms.u_outset, style.width / 2 + aa);
|
|
let fillPasses = lit ? ["dimmed", "bright"] : ["all"];
|
|
for (let pass of fillPasses) {
|
|
let groups = pass === "bright" ? run.filter((gi) => split[gi]) : run, paintOnce = marks && (pass === "all" ? opacity : pass === "bright" ? opacities.bright : opacities.dimmed) < 1, fill = (paint, depth) => {
|
|
if (!paintOnce) {
|
|
paint();
|
|
return;
|
|
}
|
|
gl.depthFunc(gl.LESS), this.depthAt(gl, depth), gl.depthMask(!0), gl.uniform1f(uniforms.u_cover, COVER_FULL), paint(), gl.depthMask(!1), gl.uniform1f(uniforms.u_cover, COVER_PARTIAL), paint(), gl.uniform1f(uniforms.u_cover, COVER_ALL);
|
|
};
|
|
setOpacity(pass);
|
|
for (let gi of paintOnce ? [...groups].reverse() : groups) {
|
|
let look = mesh.groupLooks[gi], dash = look.dash;
|
|
gl.uniform1f(uniforms.u_cap, look.dashCap === "round" ? 1 : 0), dash && look.dashColor && paintOnce ? (gl.depthFunc(gl.LESS), this.depthAt(gl, DEPTH_FILL), gl.depthMask(!0), gl.uniform1f(uniforms.u_cover, COVER_FULL), gl.uniform4f(uniforms.u_override, 0, 0, 0, -1), drawDashes(gi, look, pass), gl.uniform4fv(uniforms.u_override, this.dashColor(look.dashColor)), fill(() => draw(gi, pass), DEPTH_FILL), this.depthAt(gl, DEPTH_FILL_OVER), gl.uniform1f(uniforms.u_cover, COVER_PARTIAL), gl.uniform4f(uniforms.u_override, 0, 0, 0, -1), drawDashes(gi, look, pass), gl.uniform1f(uniforms.u_cover, COVER_ALL)) : dash && look.dashColor ? (gl.uniform4fv(uniforms.u_override, this.dashColor(look.dashColor)), draw(gi, pass), gl.uniform4f(uniforms.u_override, 0, 0, 0, -1), drawDashes(gi, look, pass)) : dash ? (gl.uniform4f(uniforms.u_override, 0, 0, 0, -1), fill(() => drawDashes(gi, look, pass), DEPTH_FILL)) : (gl.uniform4f(uniforms.u_override, 0, 0, 0, -1), fill(() => draw(gi, pass), DEPTH_FILL));
|
|
}
|
|
if (once || paintOnce) {
|
|
gl.depthFunc(gl.ALWAYS), gl.colorMask(!1, !1, !1, !1), gl.depthMask(!0), this.depthAt(gl, DEPTH_CLEAR), gl.uniform1f(uniforms.u_cover, COVER_HALF);
|
|
for (let gi of groups) {
|
|
let look = mesh.groupLooks[gi];
|
|
look.dash && !look.dashColor ? drawDashes(gi, look, pass) : draw(gi, pass);
|
|
}
|
|
gl.uniform1f(uniforms.u_cover, COVER_ALL), gl.depthMask(!1), gl.colorMask(!0, !0, !0, !0), gl.depthFunc(gl.LEQUAL), this.depthAt(gl, 0);
|
|
}
|
|
}
|
|
lit && gl.uniform1f(uniforms.u_opacity, opacities.dimmed);
|
|
}
|
|
}, EXTENT = 8192, nextSession = 1, dashColorCache = /* @__PURE__ */ new Map();
|
|
function segNearest(px, py, ax, ay, bx, by) {
|
|
let dx = bx - ax, dy = by - ay, l2 = dx * dx + dy * dy, t = l2 > 0 ? Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / l2)) : 0, x = ax + dx * t, y = ay + dy * t;
|
|
return [(px - x) * (px - x) + (py - y) * (py - y), x, y];
|
|
}
|
|
function isWebGL2(gl) {
|
|
return typeof gl.createVertexArray == "function";
|
|
}
|
|
function acquireOffscreen(gl) {
|
|
let share = offscreens.get(gl);
|
|
if (!share) return null;
|
|
let width = gl.drawingBufferWidth, height = gl.drawingBufferHeight, target = share.target, stale = target && (target.width !== width || target.height !== height);
|
|
return target && stale && (releaseOffscreen(gl, target), target = share.target = null), target || (target = share.target = createOffscreen(gl, width, height)), share.composite === void 0 && (share.composite = buildComposite(gl)), target && share.composite ? { target, composite: share.composite } : null;
|
|
}
|
|
function createOffscreen(gl, width, height) {
|
|
let color = gl.createTexture(), depth = gl.createRenderbuffer(), framebuffer = gl.createFramebuffer();
|
|
if (!color || !depth || !framebuffer) return null;
|
|
gl.bindTexture(gl.TEXTURE_2D, color), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE), gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE), gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null), gl.bindRenderbuffer(gl.RENDERBUFFER, depth), gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT24, width, height);
|
|
let bound = gl.getParameter(gl.FRAMEBUFFER_BINDING);
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer), gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, color, 0), gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depth);
|
|
let complete = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
|
|
gl.bindFramebuffer(gl.FRAMEBUFFER, bound);
|
|
let target = { framebuffer, color, depth, width, height };
|
|
return complete ? target : (releaseOffscreen(gl, target), null);
|
|
}
|
|
function releaseOffscreen(gl, target) {
|
|
gl.deleteFramebuffer(target.framebuffer), gl.deleteTexture(target.color), gl.deleteRenderbuffer(target.depth);
|
|
}
|
|
function buildComposite(gl) {
|
|
try {
|
|
let program = gl.createProgram();
|
|
if (gl.attachShader(program, compile(gl, gl.VERTEX_SHADER, COMPOSITE_VERTEX_SHADER)), gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, COMPOSITE_FRAGMENT_SHADER)), gl.linkProgram(program), !gl.getProgramParameter(program, gl.LINK_STATUS))
|
|
return gl.deleteProgram(program), null;
|
|
let vao = gl.createVertexArray();
|
|
return { program, vao, color: gl.getUniformLocation(program, "u_color") };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function rgba(c) {
|
|
return new Float32Array([c[0] / 255, c[1] / 255, c[2] / 255, c[3] / 255]);
|
|
}
|
|
function compile(gl, type, src) {
|
|
let s = gl.createShader(type);
|
|
if (gl.shaderSource(s, src), gl.compileShader(s), !gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
let log = gl.getShaderInfoLog(s);
|
|
throw gl.deleteShader(s), new Error(`shader compile failed: ${log}`);
|
|
}
|
|
return s;
|
|
}
|
|
export {
|
|
LaneLayer,
|
|
LayoutCache,
|
|
applyLaneOrders,
|
|
buildLineGraph,
|
|
disposeWorkers,
|
|
filterGraph,
|
|
layoutAtZoom,
|
|
lngLatToMercator,
|
|
mercatorToLngLat,
|
|
orderLanes,
|
|
orderLanesAsync,
|
|
seedFromSnapshot,
|
|
snapshotLaneOrders,
|
|
stabilizeLanes
|
|
};
|