@expofp/floorplan
Version:
Interactive floor plan library for expos and events
2 lines (1 loc) • 8.29 kB
JavaScript
import{lineAngle as fe,pointDistance as he,Rect as be}from"@expofp/geometry";import{update as z}from"@expofp/renderer";import{useEffect as ve,useRef as S}from"react";import{attachExactRoll as Ce}from"../../../engine/utils/rotation.js";import _e from"../../../tools/base-runtime-url";import{loadImage as Fe}from"../../../utils/loadImage";import{mapCamera as Pe}from"../camera";import{createCanvas as ge,createImageCanvas as Ne,getBounds as B,imageCanvasCssSize as De}from"../drawing/config/canvases";import{getRouteLayerRegex as Oe,getRouteStopLayerRegex as He,TRAFFIC_VEHICLES as ce}from"./trafficLayers";import{UpdateQueue as Ve}from"./UpdateQueue";const se=2,ze=se*3.5,J=se*12,K=J*.7,We=8,ie=2,me=.6,pe=8,ke="transport_locations",Ge=150,Ye="#a4aab6",we=1e3,$e=2e4,Ie=500;export function useManageTraffic({permission:e,scene:n,dataJsonUrl:o,websocketUrl:a}){const t=S(1),r=S(new Map),c=S(new Map),s=S(null),f=S(null),d=S(0),w=S(1),b=S(new Ve),R=S(new Map),E=S(we),m=S(new Map),x=S(void 0);ve(()=>{if(!e||!n)return;let l=!1,_;const T=n.rootLayer.children,p=c.current,I=r.current,y=b.current;async function Re(){try{let u;try{const i=await fetch(o,{method:"GET",headers:{"Content-Type":"application/json"}});if(!i.ok||i.status===204)return;u=await i.json()}catch(i){console.warn("useManageTraffic failed to get JSON",i)}if(l||!u||u.disabled||u.error)return;t.current=Ee(Pe()?.state().pixelSize??1);const W=new Map(u.routes.map(({id:i,color:h})=>[i,h])),v=new Map;W.forEach(i=>{v.set(i,qe({radius:ze,color:i,scale:window.devicePixelRatio}))});const Y=Oe(),$=T.filter(i=>Y.test(i.name)),X=He(),U=T.filter(i=>X.test(i.name));u.routes.slice(0,$.length).forEach(({id:i,color:h,path:g,stops:O,hiddenPoints:L},k)=>{R.current.set(i,g);const M=$[k];if(M){const H=new Map(L?.map(P=>[ue(P),P])??[]);for(let P=1;P<g.length;P++){const A=g[P-1],V=g[P];H.has(ue(A))||H.has(ue(V))||M.children.push({points:{p0:A,p1:V},color:h,width:se})}M.children.length&&y.add(()=>z(M))}const C=U[k],F=v.get(h);C&&F&&O?.length&&(O.forEach(H=>{C.children.push({source:F,bounds:B(H.x,H.y,...Q(F),t.current)})}),C.children.length&&y.add(()=>z(C)))});const D=T.find(i=>i.name===ce);if(!D){console.warn(`useManageTraffic: the scene has no "${ce}" layer`);return}const j=new Map;D.onScale=({pixelSize:i})=>{t.current=Ee(i);const h=[];for(const g of U){const O=g.children;for(const L of O){const k=L.source;L.bounds=B(L.bounds.center.x,L.bounds.center.y,...Q(k),t.current,L.bounds.rotation),h.push(L)}}for(const g of r.current.values()){const O=g.source;g.bounds=B(g.bounds.center.x,g.bounds.center.y,...Q(O),t.current,g.bounds.rotation),h.push(g)}return h},y.add(()=>z(D)),W.forEach((i,h)=>{j.set(h,xe({id:h,width:J,height:J,color:i,scale:window.devicePixelRatio}))});const Le=xe({id:"orphan",width:J,height:J,color:Ye,scale:window.devicePixelRatio});let q=null;try{const i=await Fe(`${_e}icons/bus-white.svg`);if(l)return;q=Ne(i,K,K,window.devicePixelRatio);const{width:h,height:g}=De(i,K,K);ne.set(q,[h,g])}catch(i){console.error(i)}f.current=i=>{try{const h=JSON.parse(i.data);if(h.type!==ke)return;const g=i.timeStamp||performance.now();w.current=Math.round(g-d.current),d.current=g;const O=Math.max(w.current??Ie,Ie),L=D.children;let k=!1;h.data.forEach(({id:M,x:C,y:F,route_id:H,hidden:P})=>{p.has(M)&&(p.get(M)?.(),p.delete(M));const A=I.get(M),V=I.get(le(M))??null;if(P&&A&&V){A.hidden=!0,V.hidden=!0,y.add(()=>z(A,V));return}if(m.current.set(M,performance.now()),!A){const G=j.get(H)??Le,N={source:G,bounds:B(C,F,...Q(G),t.current,0)};if(I.set(M,N),q){const Z=le(M),re={source:q,bounds:B(C,F,...Q(q),t.current,0)};Ce(re),q.setAttribute("id",Z),I.set(Z,re),L.push(N,re)}else L.push(N);k=!0;return}const Ae=A.bounds.center,oe=Qe(Ae,{x:C,y:F},R.current.get(H));if(oe.reduce((G,N,Z)=>Z===0?G:G+ae(oe[Z-1],N),0)>=Ge||document.hidden)Me({updateQueue:y,vehicle:A,vehicleFinery:V,x:C,y:F,scale:t.current,angle:je(A.bounds.center,{x:C,y:F})});else{const G=Xe(oe,O,N=>{Me({updateQueue:y,vehicle:A,vehicleFinery:V,x:N.x,y:N.y,scale:t.current,angle:N.angle})});p.set(M,G)}}),k&&y.add(()=>z(D))}catch(h){console.error("handleSocketMessage",h)}},d.current=performance.now(),te(),window.addEventListener("online",de),Te(u.config?(Number(u.config.vehicle_inactive_interval)||20)*1e3:$e)}catch(u){console.warn(u)}}Re();function te(){l||s.current?.readyState===WebSocket.OPEN||s.current?.readyState===WebSocket.CONNECTING||(s.current=new WebSocket(a),s.current.onmessage=f.current,s.current.onopen=()=>{E.current=we},s.current.onclose=()=>{if(l||!navigator.onLine)return;_=window.setTimeout(te,E.current);const u=3e4;E.current=Math.min(E.current*2,u)})}function de(){te()}function Te(u){clearInterval(x.current),!l&&(x.current=window.setInterval(()=>{const W=performance.now(),v=[],Y=[];m.current.forEach(($,X)=>{if(W-$<u)return;const U=I.get(X);U&&(U.hidden=!0,Y.push(U),v.push(X));const D=le(X),j=I.get(D);j&&(j.hidden=!0,Y.push(j),v.push(D))}),Y.length&&y.add(()=>z(...Y)),v.forEach($=>m.current.delete($))},u))}return()=>{l=!0,clearTimeout(_);const u=s.current;u&&(u.onopen=null,u.onmessage=null,u.onclose=null,u.close(),s.current=null),f.current=null;const W=T.find(v=>v.name===ce);W&&(W.onScale=void 0),p.forEach(v=>v?.()),p.clear(),I.clear(),y.destroy(),window.removeEventListener("online",de),clearInterval(x.current)}},[e,n,o,a])}function Q(e){const n=ne.get(e);if(!n)throw new Error("useManageTraffic: an icon was drawn without a recorded CSS size");return n}function ye(e){return e*2}function Ue(e,n){const o=n*me,a=pe*2;return[n+ie+o/2+a,e+o+ie+a]}function Ee(e){return Math.min(e||1,We)}function je(e,n){return fe(e,n)}function qe({radius:e,scale:n,color:o}){const a=Math.ceil(e*2*n),t=ge(a,a,n);ne.set(t,[ye(e),ye(e)]);const r=t.getContext("2d");r.imageSmoothingEnabled=!1;const c=a/2,s=e*.78,f=e*.33;return r.translate(c,c),r.scale(n,n),r.beginPath(),r.arc(0,0,e,0,2*Math.PI),r.fillStyle="#FFFFFF",r.fill(),r.beginPath(),r.arc(0,0,s,0,2*Math.PI),r.fillStyle=o,r.fill(),r.beginPath(),r.arc(0,0,f,0,2*Math.PI),r.fillStyle="#FFFFFF",r.fill(),t}function xe({id:e,width:n,height:o,color:a,scale:t}){const r=ie*t,c=n/2*t,s=o*t,f=me,d=.01,w=s*f,b=s,R=pe*t,E=b+r+w/2+R*2,m=c*2+w+r+R*2,x=ge(E,m,t);ne.set(x,Ue(n,o)),x.setAttribute("id",e);const l=x.getContext("2d"),_=E/2,T=m/2;l.save(),l.translate(_,T),l.rotate(-Math.PI/2),l.translate(-_,-T);const p=_,I=T-b/2+c,y=I+w;return l.shadowColor="rgba(16, 24, 40, 0.2)",l.shadowBlur=R/2,l.beginPath(),l.arc(p,I,c,Math.PI,0,!1),l.bezierCurveTo(p+c,I+s*.3,p+d*c,y,p,y),l.bezierCurveTo(p-d*c,y,p-c,I+s*.3,p-c,I),l.closePath(),l.fillStyle=a,l.fill(),l.strokeStyle="#FFFFFF",l.lineWidth=r,l.stroke(),l.restore(),x}function ae(e,n){return he(e,n)}function le(e){return`${e}_finery`}function Me({updateQueue:e,vehicle:n,vehicleFinery:o,x:a,y:t,scale:r,angle:c}){const s=n.source;if(n.bounds=B(a,t,...Q(s),r,c),n.hidden=!1,o){const{size:f,rotation:d,elevation:w}=o.bounds;o.bounds=new be({x:a,y:t},f,d,w),o.hidden=!1,e.add(()=>z(o,n))}else e.add(()=>z(n))}function ee(e,n,o){return{x:e.x+(n.x-e.x)*o,y:e.y+(n.y-e.y)*o}}function Se(e,n){let o=1/0,a={index:0,t:0};for(let t=0;t<n.length-1;t++){const r=n[t],c=n[t+1],s=c.x-r.x,f=c.y-r.y,d=s*s+f*f,w=d?((e.x-r.x)*s+(e.y-r.y)*f)/d:0,b=Math.max(0,Math.min(1,w)),R=ee(r,c,b),E=ae(e,R);E<o&&(o=E,a={index:t,t:b})}return a}function Be(e,n,o){const a=ae(e,n),t=Math.max(2,Math.ceil(a/o)+1),r=[];for(let c=0;c<t;c++){const s=c/(t-1),f=ee(e,n,s);r.push(f)}return r}function Qe(e,n,o){if(!o||!o.length)return Be(e,n,10);const a=Se(e,o),t=Se(n,o),r=[],c=s=>r.push(s);a.index+1<o.length?c(ee(o[a.index],o[a.index+1],a.t)):c(o[a.index]);for(let s=a.index+1;s<=t.index&&s<o.length;s++)c(o[s]);return t.index+1<o.length?c(ee(o[t.index],o[t.index+1],t.t)):c(o[t.index]),r}function Xe(e,n,o,a){if(e.length<2)return()=>{};let t=null,r;const c=[];let s=0;for(let d=0;d<e.length-1;d++){const w=he(e[d],e[d+1]);c.push(w),s+=w}const f=d=>{t||(t=d);const w=d-t,b=Math.min(w/n,1),R=b*s;let E=0,m=0;for(;m<c.length&&E+c[m]<R;)E+=c[m],m++;m>=c.length&&(m=c.length-1);const x=e[m],l=e[Math.min(m+1,e.length-1)],_=c[m]||0,T=_===0?0:(R-E)/_,p=x.x+(l.x-x.x)*T,I=x.y+(l.y-x.y)*T,y=fe(x,l);o({x:p,y:I,angle:y}),b<1?r=requestAnimationFrame(f):a&&a()};return r=requestAnimationFrame(f),()=>{r&&cancelAnimationFrame(r)}}function ue(e){return`${e.x},${e.y}`}const ne=new WeakMap;