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<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;pf[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-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{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)||(PE?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;I0&&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;vDe(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;wx.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{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=0){if(g[w])continue;p=w}b(p,0,0),A.length=0,A.push(p);for(let k=0;k>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{for(let p=0;pu.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=Mk&&(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{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{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{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({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;g0?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;gl[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||o1.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.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.PIthis.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&&c3){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;cA){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;yb+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=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;Ms<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;M0,L=M1e-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
+};