@rr0/ufoathome 0.123.0 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-embed-interview/rr0-interview.mjs +10 -10
- package/dist-embed-scene/rr0-scene.mjs +3 -3
- package/dist-embed-sighting/rr0-sighting.mjs +4 -4
- package/dist-embed-sighting-editor/{AdsbLolArchiveProvider-CDtWnd9O.js → AdsbLolArchiveProvider-CFgVABcu.js} +1 -1
- package/dist-embed-sighting-editor/{BodyEditor-ChwDW_Ws.js → BodyEditor-Dg97ZrRa.js} +1 -1
- package/dist-embed-sighting-editor/{ContrailSystem-C1ugmbHe.js → ContrailSystem-DSdLswxr.js} +1 -1
- package/dist-embed-sighting-editor/{CoverageAssessor-CmEo7iZm.js → CoverageAssessor-8zrC-ZY0.js} +1 -1
- package/dist-embed-sighting-editor/{GLTFLoader-C8s-rYg8.js → GLTFLoader-RPFGUAaO.js} +1 -1
- package/dist-embed-sighting-editor/{HeadlightSystem-NhpS6K9T.js → HeadlightSystem-DbRok2GO.js} +1 -1
- package/dist-embed-sighting-editor/{HynekAssessor-BzyMfAGm.js → HynekAssessor-9gYrshkH.js} +1 -1
- package/dist-embed-sighting-editor/{InterviewEditorElement-CT26rBmp.js → InterviewEditorElement-DRjEJwEk.js} +11 -12
- package/dist-embed-sighting-editor/{LaunchEditor-BCbplrLt.js → LaunchEditor-byu9U1W7.js} +1 -1
- package/dist-embed-sighting-editor/{ModelSource-C3dP1aBw.js → ModelSource-f1GhdDou.js} +1 -1
- package/dist-embed-sighting-editor/{ModelThumbnails-B2MXsKrk.js → ModelThumbnails-hmPAkRbd.js} +1 -1
- package/dist-embed-sighting-editor/{ModelViewer-BltV6g37.js → ModelViewer-Mvug9CHB.js} +1 -1
- package/dist-embed-sighting-editor/{RoadMarkings-B3u94pIv.js → RoadMarkings-BKfyWjdC.js} +1 -1
- package/dist-embed-sighting-editor/{SightingEditorMessages_es-DkPZhcr_.js → SightingEditorMessages_es-DvIK6dPp.js} +1 -1
- package/dist-embed-sighting-editor/{SightingEditorMessages_fr-Dhw6ob5Y.js → SightingEditorMessages_fr-Ds1GsY_1.js} +1 -1
- package/dist-embed-sighting-editor/{SightingEditorMessages_it-7r9D-wed.js → SightingEditorMessages_it-Y2D8aYCY.js} +1 -1
- package/dist-embed-sighting-editor/{TraceSystem-CZFR0soj.js → TraceSystem-BMYhHoF0.js} +2 -2
- package/dist-embed-sighting-editor/rr0-sighting-editor.mjs +298 -297
- package/dist-embed-sighting-editor/{surroundingsSources-CvhFXPJx.js → surroundingsSources-BtDVuj-U.js} +1 -1
- package/dist-embed-sighting-editor/{trafficRuntime-XVgt3vom.js → trafficRuntime-BtljEizX.js} +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{d as S,O as k,e as R,f as F}from"./rr0-sighting-editor.mjs";class K{constructor(t){if(this.candidates=t,t.length===0)throw new Error("A survey needs at least one candidate");this.current=t[0]}current;get id(){return this.current.id}get name(){return this.current.name}get attribution(){return this.current.attribution}get creditUrl(){return this.current.creditUrl}get contemporary(){return this.current.contemporary}covers(t,e){return this.candidates.some(o=>o.covers(t,e))}async getSurroundings(t,e){let o;for(const n of this.candidates)if(n.covers(t.lat,t.lng))try{const s=await n.getSurroundings(t,e);return this.current=n,s}catch(s){if(o??=s,!(s instanceof S))throw s}throw o??new S("no-coverage","No survey reaches this place")}}class w{constructor(t,e,o,n){this.heights=t,this.building=e,this.width=o,this.height=n}static ROOF_PERCENTILE=.9;static STEP_M=.9;static RECTANGLE_FILL=.72;static MIN_DEPTH_M=2.5;static MAX_PARTS=24;static MAX_CELLS=640;buildings(t){const e=t.metresPerPixel,o=t.minAreaM2??12,n=new Uint8Array(this.width*this.height),s=[];for(let r=0;r<this.building.length;r++){if(!this.building[r]||n[r])continue;const a=this.patchFrom(r,n);if(!(a.length*e*e<o))for(const i of this.buildingsOf(a,e,o))Math.hypot(i.eastM,i.northM)<=t.radiusM&&s.push(i)}return s.sort((r,a)=>Math.hypot(r.eastM,r.northM)-Math.hypot(a.eastM,a.northM)),s.slice(0,t.maxBuildings??60)}patchFrom(t,e){const o=[],n=[t];for(e[t]=1;n.length>0;){const s=n.pop();o.push(s);const r=s%this.width,a=Math.floor(s/this.width);for(const[i,c]of[[1,0],[-1,0],[0,1],[0,-1]]){const l=r+i,h=a+c;if(l<0||h<0||l>=this.width||h>=this.height)continue;const u=h*this.width+l;this.building[u]&&!e[u]&&Math.abs(this.heights[u]-this.heights[s])<=w.STEP_M&&(e[u]=1,n.push(u))}}return o}buildingsOf(t,e,o){const n=this.width/2,s=this.height/2,r=[],a=[],i=[];for(const c of t){const l=c%this.width,h=Math.floor(c/this.width);r.push((l+.5-n)*e),a.push((s-h-.5)*e),i.push(this.heights[c])}return w.ofPoints(r,a,i,e,o)}static ofPoints(t,e,o,n,s,r=w.RECTANGLE_FILL,a=[]){const i=[];for(let m=0;m<t.length;m++)for(const[E,P]of[[-.5,-.5],[.5,-.5],[-.5,.5],[.5,.5]])i.push([t[m]+E*n,e[m]+P*n]);const c=w.hull(i),{angle:l,rectangle:h}=w.smallestRectangle(c),u=t.length*n*n,M=(h.maxU-h.minU)*(h.maxV-h.minV);if(u/M>=r||u<4*s)return[w.building(h,l,w.roof(o))];const f=c.map((m,E)=>({from:{east:m[0],north:m[1]},to:{east:c[(E+1)%c.length][0],north:c[(E+1)%c.length][1]}})),d=w.dominantAngles([...a,...f]);return w.parts(t,e,o,d.length>0?d:[l],n,s)}static dominantAngles(t,e=3){const o=new Map;for(const n of t){const s=Math.hypot(n.to.east-n.from.east,n.to.north-n.from.north);if(s<1e-6)continue;const r=(Math.atan2(n.to.north-n.from.north,n.to.east-n.from.east)%(Math.PI/2)+Math.PI/2)%(Math.PI/2),a=Math.round(r/(Math.PI/60));o.set(a,(o.get(a)??0)+s)}return[...o.entries()].sort((n,s)=>s[1]-n[1]).slice(0,e).map(([n])=>n*Math.PI/60)}static parts(t,e,o,n,s,r){let a=t.map((c,l)=>l);const i=[];for(let c=0;c<w.MAX_PARTS&&a.length>0;c++){let l;for(const M of n){const f=w.largestIn(t,e,a,M,s);f&&(l===void 0||f.area>l.area)&&(l=f)}if(l===void 0||l.area<r)break;const h=l.covered;Math.min(l.rectangle.maxU-l.rectangle.minU,l.rectangle.maxV-l.rectangle.minV)>=w.MIN_DEPTH_M&&i.push(w.building(l.rectangle,l.angle,w.roof([...h].map(M=>o[M])))),a=a.filter(M=>!h.has(M))}return i}static largestIn(t,e,o,n,s){const r=Math.cos(n),a=Math.sin(n);let i=1/0,c=-1/0,l=1/0,h=-1/0;const u=[],M=[];for(const D of o){const y=t[D]*r+e[D]*a,b=-t[D]*a+e[D]*r;u.push(y),M.push(b),i=Math.min(i,y),c=Math.max(c,y),l=Math.min(l,b),h=Math.max(h,b)}const f=Math.max(s*Math.SQRT2,Math.max(c-i,h-l)/w.MAX_CELLS),d=Math.max(1,Math.ceil((c-i)/f+1e-9)),m=Math.max(1,Math.ceil((h-l)/f+1e-9)),E=new Uint8Array(d*m),P=o.map((D,y)=>{const b=Math.min(d-1,Math.max(0,Math.floor((u[y]-i)/f))),T=Math.min(m-1,Math.max(0,Math.floor((M[y]-l)/f)));return E[T*d+b]=1,T*d+b}),p=w.largestRectangle(E,d,m);if(p===void 0)return;const O=new Set;return o.forEach((D,y)=>{const b=P[y]%d,T=Math.floor(P[y]/d);b>=p.column&&b<p.column+p.columns&&T>=p.row&&T<p.row+p.rows&&O.add(D)}),{area:p.area*f*f,angle:n,covered:O,rectangle:{minU:i+p.column*f,maxU:i+(p.column+p.columns)*f,minV:l+p.row*f,maxV:l+(p.row+p.rows)*f}}}static largestRectangle(t,e,o){const n=new Array(e).fill(0);let s;for(let r=0;r<o;r++){for(let i=0;i<e;i++)n[i]=t[r*e+i]?n[i]+1:0;const a=[];for(let i=0;i<=e;i++){const c=i===e?0:n[i];for(;a.length>0&&n[a[a.length-1]]>=c;){const l=a.pop(),h=a.length===0?i:i-a[a.length-1]-1,u=n[l]*h;(s===void 0||u>s.area)&&(s={column:i-h,row:r-n[l]+1,columns:h,rows:n[l],area:u})}a.push(i)}}return s}static roof(t){const e=[...t].sort((o,n)=>o-n);return Math.max(2,e[Math.min(e.length-1,Math.floor(e.length*w.ROOF_PERCENTILE))])}static building(t,e,o){const n=Math.cos(e),s=Math.sin(e),r=t.maxU-t.minU,a=t.maxV-t.minV,i=(t.minU+t.maxU)/2,c=(t.minV+t.maxV)/2,h=((90-(r<a?e:e+Math.PI/2)*180/Math.PI)%180+180)%180;return{eastM:w.tenth(i*n-c*s),northM:w.tenth(i*s+c*n),widthM:w.tenth(Math.max(r,a)),lengthM:w.tenth(Math.min(r,a)),heightM:w.tenth(o),headingDeg:Math.round(h*10)/10}}static smallestRectangle(t){let e={area:1/0,angle:0,rectangle:{minU:0,maxU:0,minV:0,maxV:0}};for(let o=0;o<Math.max(1,t.length);o++){const[n,s]=t[o]??[0,0],[r,a]=t[(o+1)%t.length]??[1,0],i=Math.atan2(a-s,r-n),c=Math.cos(i),l=Math.sin(i);let h=1/0,u=-1/0,M=1/0,f=-1/0;for(const[m,E]of t){const P=m*c+E*l,p=-m*l+E*c;h=Math.min(h,P),u=Math.max(u,P),M=Math.min(M,p),f=Math.max(f,p)}const d=(u-h)*(f-M);d<e.area&&(e={area:d,angle:i,rectangle:{minU:h,maxU:u,minV:M,maxV:f}})}return e}static hull(t){const e=[...t].sort((a,i)=>a[0]-i[0]||a[1]-i[1]),o=e.filter((a,i)=>i===0||a[0]!==e[i-1][0]||a[1]!==e[i-1][1]);if(o.length<3)return o;const n=(a,i,c)=>(i[0]-a[0])*(c[1]-a[1])-(i[1]-a[1])*(c[0]-a[0]),s=[];for(const a of o){for(;s.length>=2&&n(s[s.length-2],s[s.length-1],a)<=0;)s.pop();s.push(a)}const r=[];for(const a of[...o].reverse()){for(;r.length>=2&&n(r[r.length-2],r[r.length-1],a)<=0;)r.pop();r.push(a)}return[...s.slice(0,-1),...r.slice(0,-1)]}static tenth(t){return Math.round(t*10)/10}}class x{constructor(t,e,o,n){this.heights=t,this.vegetation=e,this.width=o,this.height=n}static SUMMIT_RADIUS_M=2.5;static CROWN_REACH_M=8;static MAX_WIDTH_M=16;static MIN_WIDTH_M=2;static tenth(t){return Math.round(t*10)/10}trees(t){const e=t.metresPerPixel,o=t.minHeightM??4,n=t.eyeM??1.6,s=t.minElevationDeg??2,r=Math.max(1,Math.round(x.SUMMIT_RADIUS_M/e)),a=Math.round(x.CROWN_REACH_M/e),i=this.width/2,c=this.height/2,l=[];for(let h=0;h<this.height;h++)for(let u=0;u<this.width;u++){const M=this.canopy(u,h);if(M<o)continue;const f=(u+.5-i)*e,d=(c-h-.5)*e,m=Math.hypot(f,d);if(m>t.radiusM||!this.isSummit(u,h,M,r))continue;const E=Math.atan2(M-n,Math.max(m,1))*180/Math.PI;E<s||l.push({tree:{eastM:x.tenth(f),northM:x.tenth(d),heightM:x.tenth(M),widthM:x.tenth(this.crownWidth(u,h,M,e,a))},elevation:E})}return l.sort((h,u)=>u.elevation-h.elevation),l.slice(0,t.maxTrees??150).map(h=>h.tree)}canopy(t,e){if(t<0||e<0||t>=this.width||e>=this.height)return 0;const o=e*this.width+t;return this.vegetation[o]?this.heights[o]:0}isSummit(t,e,o,n){for(let s=-n;s<=n;s++)for(let r=-n;r<=n;r++){if(r===0&&s===0)continue;const a=this.canopy(t+r,e+s);if(a>o||a===o&&(s<0||s===0&&r<0))return!1}return!0}crownWidth(t,e,o,n,s){let r=0;for(let i=-s;i<=s;i++)for(let c=-s;c<=s;c++)Math.hypot(c,i)*n<=x.CROWN_REACH_M&&this.canopy(t+c,e+i)>=o*.5&&r++;const a=2*Math.sqrt(r*n*n/Math.PI);return Math.max(x.MIN_WIDTH_M,Math.min(a,x.MAX_WIDTH_M))}}class A{static WALLS={1:"stone",2:"meuliere",3:"concrete",4:"brick",5:"block",6:"wood",9:"other"};static ROOFS={1:"tile",2:"slate",3:"zinc",4:"concrete",9:"other"};static WALL_COLOURS={stone:"#b9ae9b",meuliere:"#b8a78a",concrete:"#b4b2ac",brick:"#a65f48",block:"#d0c6b2",wood:"#7d5c3e",other:"#c9c0ae"};static PLASTER="#d3cab8";static ROOF_COLOURS={tile:"#a0583f",slate:"#4a5059",zinc:"#8d969c",concrete:"#8f8e8a",other:"#7a7872"};static VARIATION=.12;static of(t,e){const o=String(t??"").trim();if(!/^\d{1,2}$/.test(o))return;const n=Number(o),s=n%10!==0?n%10:Math.floor(n/10);return(e==="wall"?A.WALLS:A.ROOFS)[String(s)]}static roofColor(t){return t?A.ROOF_COLOURS[t]:void 0}static fit(t){return t.map(e=>{if(e.facade)return e;const o={color:A.wallColor(e)};return{...e,facade:o}})}static wallColor(t){const e=t.wallMaterial?A.WALL_COLOURS[t.wallMaterial]:A.PLASTER,o=Math.sin(t.eastM*12.9898+t.northM*78.233)*43758.5453,n=1+(o-Math.floor(o)-.5)*2*A.VARIATION,s=r=>Math.max(0,Math.min(255,Math.round(parseInt(e.slice(r,r+2),16)*n))).toString(16).padStart(2,"0");return`#${s(1)}${s(3)}${s(5)}`}}class g{static MIN_RISE_M=1.2;static MIN_EAVES_M=2.5;static MIN_SEEN=.35;static NO_DATA=-9999;static BANDS=7;static INSET_M=.9;static LIFT=1.18;static PEAK=.3;static LEVEL=.45;static fit(t,e,o){return!e&&!o&&!t.some(n=>n.roofMaterial)?t:t.map(n=>{const s=e?g.shapeOf(n,e):void 0,r=(o?g.colorOf(n,o):void 0)??A.roofColor(n.roofMaterial);if(!s&&!r)return n;const a={shape:s?.shape??"flat",...s?.ridge==="depth"?{ridge:"depth"}:{},riseM:s?.riseM??0,...r?{color:r}:{}},i=s&&s.shape!=="flat"?s.topM:n.heightM;return{...n,heightM:i,roof:a}})}static world(t,e,o){const n=t.headingDeg*Math.PI/180;return{east:t.eastM+e*Math.cos(n)+o*Math.sin(n),north:t.northM-e*Math.sin(n)+o*Math.cos(n)}}static shapeOf(t,e){const o=g.BANDS,n=Math.max(e.metresPerPixel,.5),s=t.widthM/2-g.INSET_M,r=t.lengthM/2-g.INSET_M;if(s<=0||r<=0)return;const a=[],i=Array.from({length:o},()=>({sum:0,n:0})),c=Array.from({length:o},()=>({sum:0,n:0}));let l=0;for(let y=-s;y<=s;y+=n)for(let b=-r;b<=r;b+=n){l++;const{east:T,north:X}=g.world(t,y,b),C=Math.floor(T/e.metresPerPixel+e.size/2),z=Math.floor(e.size/2-X/e.metresPerPixel);if(C<0||z<0||C>=e.size||z>=e.size)continue;const V=z*e.size+C,U=e.heights[V];if(e.vegetation[V]||U===g.NO_DATA||!(U>0))continue;a.push(U);const W=i[Math.min(o-1,Math.floor((y+s)/(2*s)*o))],$=c[Math.min(o-1,Math.floor((b+r)/(2*r)*o))];W.sum+=U,W.n++,$.sum+=U,$.n++}if(l===0||a.length/l<g.MIN_SEEN)return;a.sort((y,b)=>y-b);const h=g.percentile(a,.08),u=g.percentile(a,.92),M=u-h;if(h<g.MIN_EAVES_M||M>Math.min(t.widthM,t.lengthM))return;if(M<g.MIN_RISE_M)return{shape:"flat",riseM:0,topM:u};const f=g.profile(i),d=g.profile(c);if(!f||!d)return;const m=g.peak(d)/M,E=g.peak(f)/M,P=g.spread(f)/M,p=g.spread(d)/M,O=m>=g.PEAK&&P<=g.LEVEL,D=E>=g.PEAK&&p<=g.LEVEL;if(O&&(!D||m>=E))return{shape:"gable",ridge:"width",riseM:M,topM:u};if(D)return{shape:"gable",ridge:"depth",riseM:M,topM:u};if(m>=g.PEAK&&E>=g.PEAK/2)return{shape:"hip",ridge:"width",riseM:M,topM:u}}static profile(t){const e=t.map(o=>o.n>0?o.sum/o.n:NaN);return e.filter(Number.isFinite).length>=t.length-1?e.map((o,n)=>Number.isFinite(o)?o:g.neighbour(e,n)):void 0}static neighbour(t,e){return Number.isFinite(t[e-1])?t[e-1]:t[e+1]}static peak(t){const e=Math.max(1,Math.floor(t.length/3)),o=s=>s.reduce((r,a)=>r+a,0)/s.length,n=[...t.slice(0,e),...t.slice(t.length-e)];return o(t.slice(e,t.length-e))-o(n)}static spread(t){return Math.max(...t)-Math.min(...t)}static percentile(t,e){return t[Math.min(t.length-1,Math.floor(t.length*e))]}static colorOf(t,e){const o=Math.max(e.metresPerPixel,.5),n=Math.max(t.widthM/2-g.INSET_M,o),s=Math.max(t.lengthM/2-g.INSET_M,o),r=[],a=[],i=[];for(let h=-n;h<=n;h+=o)for(let u=-s;u<=s;u+=o){const{east:M,north:f}=g.world(t,h,u),d=Math.floor(M/e.metresPerPixel+e.size/2),m=Math.floor(e.size/2-f/e.metresPerPixel);if(d<0||m<0||d>=e.size||m>=e.size)continue;const E=(m*e.size+d)*e.samples,P=e.values[E],p=e.values[E+1],O=e.values[E+2];p>P&&p>O||(r.push(P),a.push(p),i.push(O))}if(r.length===0)return;const c=h=>h.sort((u,M)=>u-M)[Math.floor(h.length/2)],l=h=>Math.min(255,Math.round(h*g.LIFT)).toString(16).padStart(2,"0");return`#${l(c(r))}${l(c(a))}${l(c(i))}`}}class v{static DEFAULT_HEIGHT_M=6;static METRES_PER_DEGREE=111320;static STREET_SPACING_M=35;static KERB_OFFSET_M=1;static DEFAULT_WIDTH_M=5.5;static PATIENCE_MS=15e3;static MAX=150;client;constructor(t={}){this.client=new k(t)}static today(){return new Date().toISOString().slice(0,10)}async near(t,e){const o=e/v.METRES_PER_DEGREE,n=e/(v.METRES_PER_DEGREE*Math.cos(t.lat*Math.PI/180)),s=[t.lat-o,t.lng-n,t.lat+o,t.lng+n].map(i=>i.toFixed(6)).join(",");let r;const a=new Promise(i=>{r=setTimeout(()=>i(void 0),v.PATIENCE_MS)});try{const i=await Promise.race([this.client.query(`[out:json][timeout:25];(node["highway"="street_lamp"](${s});way["highway"]["lit"="yes"](${s}););out geom;`),a]);return i?v.lampsFrom(i,t,e):[]}catch{return[]}finally{clearTimeout(r)}}static lampsFrom(t,e,o){const n=[];for(const r of t.elements??[]){if(r.type==="way"){v.alongStreet(r,e,n);continue}if(typeof r.lat!="number"||typeof r.lon!="number")continue;const a=(r.lon-e.lng)*v.METRES_PER_DEGREE*Math.cos(e.lat*Math.PI/180),i=(r.lat-e.lat)*v.METRES_PER_DEGREE;if(Math.hypot(a,i)>o)continue;const c=Number.parseFloat(r.tags?.height??"");n.push({eastM:Math.round(a*10)/10,northM:Math.round(i*10)/10,...Number.isFinite(c)&&c>=2&&c<=20?{heightM:c}:{}})}const s=n.filter(r=>Math.hypot(r.eastM,r.northM)<=o);return n.length=0,n.push(...s),n.sort((r,a)=>Math.hypot(r.eastM,r.northM)-Math.hypot(a.eastM,a.northM)),n.slice(0,v.MAX)}static alongStreet(t,e,o){const n=t.geometry;if(!n||n.length<2)return;const s=f=>[(f.lon-e.lng)*v.METRES_PER_DEGREE*Math.cos(e.lat*Math.PI/180),(f.lat-e.lat)*v.METRES_PER_DEGREE],r=n.map(s),a=Number.parseFloat(t.tags?.width??""),i=Number.parseInt(t.tags?.lanes??"",10),l=(Number.isFinite(a)&&a>0?a:Number.isFinite(i)&&i>0?i*3.5:v.DEFAULT_WIDTH_M)/2+v.KERB_OFFSET_M;let h=1,u=v.STREET_SPACING_M/2,M=0;for(let f=1;f<r.length;f++){const[d,m]=r[f-1],[E,P]=r[f],p=Math.hypot(E-d,P-m);if(p===0)continue;const O=(E-d)/p,D=(P-m)/p;for(;u<=M+p;u+=v.STREET_SPACING_M){const y=u-M;o.push({eastM:Math.round((d+O*y+D*l*h)*10)/10,northM:Math.round((m+D*y-O*l*h)*10)/10}),h=-h}M+=p}}}class j{id="ign-lidar-hd";name="IGN LiDAR HD";attribution="Canopy and roofs © IGN, LiDAR HD (Licence Ouverte 2.0), as surveyed on a day the service does not give";creditUrl="https://geoservices.ign.fr/lidarhd";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","The LiDAR HD survey covers France only");const o=R.lidarRadius(e),[n,s,r,a,i]=await Promise.all([this.platform.lidar(t,o),this.platform.photo(t,o).catch(()=>{}),this.lamps?.near(t,e),this.platform.laserDate(t),this.platform.photoDate(t)]);return{trees:new x(n.heights,n.vegetation,n.size,n.size).trees({metresPerPixel:n.metresPerPixel,radiusM:o}),buildings:A.fit(g.fit(new w(n.heights,n.building,n.size,n.size).buildings({metresPerPixel:n.metresPerPixel,radiusM:o,maxBuildings:300}),n,s)),bridges:[],lines:[],...r?{lamps:r}:{},dates:{...a?{laser:a}:{},...s&&i?{photo:i}:{},...r?.length?{lamps:v.today()}:{}}}}}class I{static METRES_PER_DEGREE=111320;static NAMED_NATURES={Silo:"Silo"};static DEFAULT_HEIGHT_M=6;static FLOOR_HEIGHT_M=3;static FINE_M=.5;static COARSE_M=1;static MAX_CELLS=4e4;static RECTANGLE_FILL=.9;static buildings(t,e,o,n=12,s=300){const r=[];for(const i of t){const c=I.rings(i,e);if(c.length===0)continue;const l=Number(i.properties.nombre_d_etages),h=Number(i.properties.hauteur),u=Number.isFinite(h)&&h>0?h:Number.isFinite(l)&&l>0?l*I.FLOOR_HEIGHT_M:I.DEFAULT_HEIGHT_M;for(const M of I.rectanglesOf(c,u,n)){if(Math.hypot(M.eastM,M.northM)>o)continue;const f=A.of(i.properties.materiaux_des_murs,"wall"),d=A.of(i.properties.materiaux_de_la_toiture,"roof"),m=I.NAMED_NATURES[String(i.properties.nature??"")];r.push({...M,...m?{name:m}:{},...Number.isFinite(l)&&l>0?{floors:l}:{},...f?{wallMaterial:f}:{},...d?{roofMaterial:d}:{}})}}const a=i=>Math.hypot(i.eastM,i.northM);if(r.length>s){const i=c=>(c.heightM+6)*Math.sqrt(c.widthM*c.lengthM)/Math.max(a(c),50);r.sort((c,l)=>i(l)-i(c)),r.length=s}return r.sort((i,c)=>a(i)-a(c)),r}static named(t,e,o){const n=e.map(s=>({name:typeof s.properties.toponyme=="string"?s.properties.toponyme:"",rings:I.rings(s,o)})).filter(s=>s.name.length>0&&s.rings.length>0);return n.length===0?t:t.map(s=>{const r=n.find(a=>I.inside(s.eastM,s.northM,a.rings));return r?{...s,name:r.name}:s})}static inside(t,e,o){let n=0;for(const s of o)for(let r=0;r+1<s.length;r++){const a=s[r],i=s[r+1];a.north<=e!=i.north<=e&&a.east+(e-a.north)/(i.north-a.north)*(i.east-a.east)>t&&n++}return n%2===1}static rings(t,e){const o=t.geometry,n=o.type==="Polygon"?[o.coordinates]:o.type==="MultiPolygon"?o.coordinates:[],s=Math.cos(e.lat*Math.PI/180),r=[];for(const a of n)for(const i of a)r.push(i.map(([c,l])=>({east:(c-e.lng)*I.METRES_PER_DEGREE*s,north:(l-e.lat)*I.METRES_PER_DEGREE})));return r}static rectanglesOf(t,e,o){let n=1/0,s=-1/0,r=1/0,a=-1/0;for(const u of t)for(const M of u)n=Math.min(n,M.east),s=Math.max(s,M.east),r=Math.min(r,M.north),a=Math.max(a,M.north);let i=I.FINE_M;(s-n)*(a-r)/(i*i)>I.MAX_CELLS&&(i=I.COARSE_M);const c=[],l=[];for(let u=r+i/2;u<a;u+=i){const M=[];for(const f of t)for(let d=0;d+1<f.length;d++){const m=f[d],E=f[d+1];m.north<=u!=E.north<=u&&M.push(m.east+(u-m.north)/(E.north-m.north)*(E.east-m.east))}M.sort((f,d)=>f-d);for(let f=0;f+1<M.length;f+=2)for(let d=Math.ceil((M[f]-i/2)/i)*i+i/2;d<M[f+1];d+=i)c.push(d),l.push(u)}if(c.length*i*i<o)return[];const h=t.flatMap(u=>u.slice(0,-1).map((M,f)=>({from:M,to:u[f+1]})));return w.ofPoints(c,l,new Array(c.length).fill(e),i,o,I.RECTANGLE_FILL,h)}}class q{id="ign-lidar-topo";name="IGN LiDAR HD + BD TOPO";attribution="Canopy © IGN, LiDAR HD; buildings, bridges and lines © IGN, BD TOPO (Licence Ouverte 2.0), as surveyed on days the services do not give";creditUrl="https://geoservices.ign.fr/lidarhd";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","The IGN's surveys cover France only");const o=R.radius(e),[n,s,r,a,i,c]=await Promise.allSettled([this.platform.lidar(t,o),this.platform.vectors(t,o,!0),this.platform.photo(t,o),this.lamps?.near(t,e)??Promise.resolve(void 0),this.platform.laserDate(t),this.platform.photoDate(t)]),l=R.lidarRadius(o);if(n.status==="rejected"&&s.status==="rejected")throw n.reason;const h=n.status==="fulfilled"?n.value:void 0,u=s.status==="fulfilled"?s.value:void 0,M=u!==void 0&&u.buildings.length===0&&u.bridges.length===0&&u.lines.length===0;if(n.status==="rejected"&&n.reason instanceof S&&n.reason.reason==="no-coverage"&&M)throw n.reason;return{trees:h?new x(h.heights,h.vegetation,h.size,h.size).trees({metresPerPixel:h.metresPerPixel,radiusM:l}):[],buildings:this.roofed(this.buildings(h,u,t,o),h,r.status==="fulfilled"?r.value:void 0),bridges:u?F.bridges(u,t,o,h?.structures):[],lines:u?F.lines(u,t,o,h?.structures):[],...a.status==="fulfilled"&&a.value?{lamps:a.value}:{},dates:{...n.status==="fulfilled"&&i.status==="fulfilled"&&i.value?{laser:i.value}:{},...r.status==="fulfilled"&&c.status==="fulfilled"&&c.value?{photo:c.value}:{},...u?.mapDate?{map:u.mapDate}:{},...a.status==="fulfilled"&&a.value?.length?{lamps:v.today()}:{}}}}roofed(t,e,o){return A.fit(g.fit(t,e,o))}buildings(t,e,o,n){const s=e?I.buildings(e.buildings,o,n):[],r=s.length>0||t===void 0?s:new w(t.heights,t.building,t.size,t.size).buildings({metresPerPixel:t.metresPerPixel,radiusM:R.lidarRadius(n),maxBuildings:300});return e?I.named(r,e.zones,o):r}}class Y{id="ign-bd-topo";name="IGN BD TOPO";attribution="Buildings, bridges and lines © IGN, BD TOPO (Licence Ouverte 2.0), as drawn on a day the service does not give";creditUrl="https://geoservices.ign.fr/bdtopo";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","BD TOPO covers France only");const o=R.radius(e),[n,s,r,a]=await Promise.all([this.platform.vectors(t,o,!0),this.platform.photo(t,o).catch(()=>{}),this.lamps?.near(t,e),this.platform.photoDate(t)]);return{trees:[],buildings:A.fit(g.fit(I.named(I.buildings(n.buildings,t,o),n.zones,t),void 0,s)),bridges:F.bridges(n,t,o,void 0),lines:F.lines(n,t,o,void 0),...r?{lamps:r}:{},dates:{...n.mapDate?{map:n.mapDate}:{},...s&&a?{photo:a}:{},...r?.length?{lamps:v.today()}:{}}}}}class N{static ROOF_SHAPES={flat:"flat",gabled:"gable",gambrel:"gable",saltbox:"gable","half-hipped":"hip",hipped:"hip",pyramidal:"hip",mansard:"hip",round:"gable"};static WALL_MATERIALS={brick:"brick",stone:"stone",granite:"stone",sandstone:"stone",limestone:"stone",concrete:"concrete",cement_block:"block",block:"block",wood:"wood",timber_framing:"wood"};static ROOF_MATERIALS={roof_tiles:"tile",tiles:"tile",tile:"tile",slate:"slate",metal:"zinc",zinc:"zinc",tin:"zinc",copper:"zinc",concrete:"concrete",eternit:"other"};static COLOURS={white:"#e8e6e0",grey:"#8c8c8a",gray:"#8c8c8a",black:"#3a3a3a",red:"#a8483a",brown:"#6e4f38",beige:"#d6c9a8",yellow:"#d8c070",orange:"#c9773a",green:"#5f7a52",blue:"#506a8a",tan:"#c0a37a",cream:"#e6dcc0",pink:"#c99a96",silver:"#b4b6b6"};static height(t){const e=Number.parseFloat((t??"").replace(",","."));return Number.isFinite(e)&&e>=2&&e<=500?e:void 0}static levels(t){const e=Number.parseInt(t??"",10);return Number.isFinite(e)&&e>=1&&e<=150?e:void 0}static roofShape(t){return N.ROOF_SHAPES[(t??"").trim().toLowerCase()]}static wallMaterial(t){return N.WALL_MATERIALS[(t??"").trim().toLowerCase()]}static roofMaterial(t){return N.ROOF_MATERIALS[(t??"").trim().toLowerCase()]}static colour(t){const e=(t??"").trim().toLowerCase();return/^#[0-9a-f]{6}$/.test(e)?e:/^#[0-9a-f]{3}$/.test(e)?`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`:N.COLOURS[e]}}class L{id="osm-buildings";name="OpenStreetMap buildings";attribution="Buildings © OpenStreetMap contributors (ODbL), as drawn on the day of the data, which a recording is not";creditUrl="https://www.openstreetmap.org/copyright";contemporary=!0;static MAX_RADIUS_M=1500;static MIN_AREA_M2=12;static MAX_BUILDINGS=300;client;lamps;constructor(t={},e){this.client=new k(t),this.lamps=e}covers(){return!0}async getSurroundings(t,e){const o=Math.min(e,L.MAX_RADIUS_M);let n;try{n=await this.client.query(`[out:json][timeout:30];(way["building"](around:${o},${t.lat},${t.lng});relation["building"]["type"="multipolygon"](around:${o},${t.lat},${t.lng}););out geom;`)}catch(a){throw new S("unreachable",a instanceof Error?a.message:String(a))}const s=await this.lamps?.near(t,e),r=(n.osm3s?.timestamp_osm_base??"").slice(0,10);return{trees:[],buildings:A.fit(L.buildings(n,t,o)),bridges:[],lines:[],...s?{lamps:s}:{},dates:{.../^\d{4}-\d{2}-\d{2}$/.test(r)?{map:r}:{},...s?.length?{lamps:v.today()}:{}}}}static buildings(t,e,o){const n=[];for(const s of t.elements??[]){const r=L.ringsOf(s);if(r.length===0)continue;const a=s.tags??{},i=N.levels(a["building:levels"]),c=N.height(a.height),l={geometry:{type:"Polygon",coordinates:r},properties:{...c?{hauteur:c}:{},...i?{nombre_d_etages:i}:{}}};for(const h of I.buildings([l],e,o,L.MIN_AREA_M2,50))n.push(L.described(h,a,c!==void 0))}return n.sort((s,r)=>Math.hypot(s.eastM,s.northM)-Math.hypot(r.eastM,r.northM)),n.slice(0,L.MAX_BUILDINGS)}static ringsOf(t){const e=n=>{if(!n||n.length<4)return;const s=n.map(i=>[i.lon,i.lat]),r=s[0],a=s[s.length-1];return r[0]===a[0]&&r[1]===a[1]?s:void 0};if(t.type==="way"){const n=e(t.geometry);return n?[n]:[]}return(t.members??[]).filter(n=>n.role==="outer").map(n=>e(n.geometry)).filter(n=>n!==void 0).slice(0,1)}static described(t,e,o){const n=N.wallMaterial(e["building:material"]),s=N.roofMaterial(e["roof:material"]),r=N.colour(e["building:colour"]),a=N.colour(e["roof:colour"])??A.roofColor(s),i=N.roofShape(e["roof:shape"]),c=r?{color:r}:void 0;let l,h=t.heightM;if(i==="gable"||i==="hip"){const u=N.height(e["roof:height"]),M=Math.round((u??Math.min(5,Math.max(1.5,.29*Math.min(t.widthM,t.lengthM))))*10)/10;o||(h=Math.round((t.heightM+M)*10)/10),l={shape:i,riseM:M,...e["roof:orientation"]==="across"?{ridge:"depth"}:{},...a?{color:a}:{}}}else(i==="flat"||a)&&(l={shape:"flat",riseM:0,...a?{color:a}:{}});return{...t,heightM:h,...e.name?{name:e.name}:{},...n?{wallMaterial:n}:{},...s?{roofMaterial:s}:{},...c?{facade:c}:{},...l?{roof:l}:{}}}}const _=[{id:"ign-lidar-topo",name:"IGN LiDAR HD + BD TOPO",credit:"© IGN, LiDAR HD and BD TOPO, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/lidarhd",create:()=>new q({},new v)},{id:"ign-lidar-hd",name:"IGN LiDAR HD",credit:"© IGN, LiDAR HD, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/lidarhd",create:()=>new j({},new v)},{id:"ign-bd-topo",name:"IGN BD TOPO",credit:"© IGN, BD TOPO, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/bdtopo",create:()=>new Y({},new v)},{id:"osm-buildings",name:"OpenStreetMap buildings",credit:"Buildings and street lamps © OpenStreetMap contributors (ODbL)",creditUrl:"https://www.openstreetmap.org/copyright",create:()=>new L({},new v)}];class G{static DEFAULT_ID=_[0].id;static WORLD_ID="osm-buildings";static NONE="none";static byId(t){return t===G.NONE?void 0:_.find(e=>e.id===t)??_[0]}static providerFor(t,e,o){const n=G.byId(t)?.create();if(t!==void 0&&t!==G.DEFAULT_ID)return n?.covers(e,o)?n:void 0;const s=_.find(a=>a.id===G.WORLD_ID).create(),r=[n,s].filter(a=>a!==void 0&&a.covers(e,o));return r.length>1?new K(r):r[0]}}export{_ as SURROUNDINGS_SOURCES,G as SurroundingsSources};
|
|
1
|
+
import{e as S,O as k,f as R,g as F}from"./rr0-sighting-editor.mjs";class K{constructor(t){if(this.candidates=t,t.length===0)throw new Error("A survey needs at least one candidate");this.current=t[0]}current;get id(){return this.current.id}get name(){return this.current.name}get attribution(){return this.current.attribution}get creditUrl(){return this.current.creditUrl}get contemporary(){return this.current.contemporary}covers(t,e){return this.candidates.some(o=>o.covers(t,e))}async getSurroundings(t,e){let o;for(const n of this.candidates)if(n.covers(t.lat,t.lng))try{const s=await n.getSurroundings(t,e);return this.current=n,s}catch(s){if(o??=s,!(s instanceof S))throw s}throw o??new S("no-coverage","No survey reaches this place")}}class w{constructor(t,e,o,n){this.heights=t,this.building=e,this.width=o,this.height=n}static ROOF_PERCENTILE=.9;static STEP_M=.9;static RECTANGLE_FILL=.72;static MIN_DEPTH_M=2.5;static MAX_PARTS=24;static MAX_CELLS=640;buildings(t){const e=t.metresPerPixel,o=t.minAreaM2??12,n=new Uint8Array(this.width*this.height),s=[];for(let r=0;r<this.building.length;r++){if(!this.building[r]||n[r])continue;const a=this.patchFrom(r,n);if(!(a.length*e*e<o))for(const i of this.buildingsOf(a,e,o))Math.hypot(i.eastM,i.northM)<=t.radiusM&&s.push(i)}return s.sort((r,a)=>Math.hypot(r.eastM,r.northM)-Math.hypot(a.eastM,a.northM)),s.slice(0,t.maxBuildings??60)}patchFrom(t,e){const o=[],n=[t];for(e[t]=1;n.length>0;){const s=n.pop();o.push(s);const r=s%this.width,a=Math.floor(s/this.width);for(const[i,c]of[[1,0],[-1,0],[0,1],[0,-1]]){const l=r+i,h=a+c;if(l<0||h<0||l>=this.width||h>=this.height)continue;const u=h*this.width+l;this.building[u]&&!e[u]&&Math.abs(this.heights[u]-this.heights[s])<=w.STEP_M&&(e[u]=1,n.push(u))}}return o}buildingsOf(t,e,o){const n=this.width/2,s=this.height/2,r=[],a=[],i=[];for(const c of t){const l=c%this.width,h=Math.floor(c/this.width);r.push((l+.5-n)*e),a.push((s-h-.5)*e),i.push(this.heights[c])}return w.ofPoints(r,a,i,e,o)}static ofPoints(t,e,o,n,s,r=w.RECTANGLE_FILL,a=[]){const i=[];for(let m=0;m<t.length;m++)for(const[E,P]of[[-.5,-.5],[.5,-.5],[-.5,.5],[.5,.5]])i.push([t[m]+E*n,e[m]+P*n]);const c=w.hull(i),{angle:l,rectangle:h}=w.smallestRectangle(c),u=t.length*n*n,M=(h.maxU-h.minU)*(h.maxV-h.minV);if(u/M>=r||u<4*s)return[w.building(h,l,w.roof(o))];const f=c.map((m,E)=>({from:{east:m[0],north:m[1]},to:{east:c[(E+1)%c.length][0],north:c[(E+1)%c.length][1]}})),d=w.dominantAngles([...a,...f]);return w.parts(t,e,o,d.length>0?d:[l],n,s)}static dominantAngles(t,e=3){const o=new Map;for(const n of t){const s=Math.hypot(n.to.east-n.from.east,n.to.north-n.from.north);if(s<1e-6)continue;const r=(Math.atan2(n.to.north-n.from.north,n.to.east-n.from.east)%(Math.PI/2)+Math.PI/2)%(Math.PI/2),a=Math.round(r/(Math.PI/60));o.set(a,(o.get(a)??0)+s)}return[...o.entries()].sort((n,s)=>s[1]-n[1]).slice(0,e).map(([n])=>n*Math.PI/60)}static parts(t,e,o,n,s,r){let a=t.map((c,l)=>l);const i=[];for(let c=0;c<w.MAX_PARTS&&a.length>0;c++){let l;for(const M of n){const f=w.largestIn(t,e,a,M,s);f&&(l===void 0||f.area>l.area)&&(l=f)}if(l===void 0||l.area<r)break;const h=l.covered;Math.min(l.rectangle.maxU-l.rectangle.minU,l.rectangle.maxV-l.rectangle.minV)>=w.MIN_DEPTH_M&&i.push(w.building(l.rectangle,l.angle,w.roof([...h].map(M=>o[M])))),a=a.filter(M=>!h.has(M))}return i}static largestIn(t,e,o,n,s){const r=Math.cos(n),a=Math.sin(n);let i=1/0,c=-1/0,l=1/0,h=-1/0;const u=[],M=[];for(const D of o){const y=t[D]*r+e[D]*a,b=-t[D]*a+e[D]*r;u.push(y),M.push(b),i=Math.min(i,y),c=Math.max(c,y),l=Math.min(l,b),h=Math.max(h,b)}const f=Math.max(s*Math.SQRT2,Math.max(c-i,h-l)/w.MAX_CELLS),d=Math.max(1,Math.ceil((c-i)/f+1e-9)),m=Math.max(1,Math.ceil((h-l)/f+1e-9)),E=new Uint8Array(d*m),P=o.map((D,y)=>{const b=Math.min(d-1,Math.max(0,Math.floor((u[y]-i)/f))),T=Math.min(m-1,Math.max(0,Math.floor((M[y]-l)/f)));return E[T*d+b]=1,T*d+b}),p=w.largestRectangle(E,d,m);if(p===void 0)return;const O=new Set;return o.forEach((D,y)=>{const b=P[y]%d,T=Math.floor(P[y]/d);b>=p.column&&b<p.column+p.columns&&T>=p.row&&T<p.row+p.rows&&O.add(D)}),{area:p.area*f*f,angle:n,covered:O,rectangle:{minU:i+p.column*f,maxU:i+(p.column+p.columns)*f,minV:l+p.row*f,maxV:l+(p.row+p.rows)*f}}}static largestRectangle(t,e,o){const n=new Array(e).fill(0);let s;for(let r=0;r<o;r++){for(let i=0;i<e;i++)n[i]=t[r*e+i]?n[i]+1:0;const a=[];for(let i=0;i<=e;i++){const c=i===e?0:n[i];for(;a.length>0&&n[a[a.length-1]]>=c;){const l=a.pop(),h=a.length===0?i:i-a[a.length-1]-1,u=n[l]*h;(s===void 0||u>s.area)&&(s={column:i-h,row:r-n[l]+1,columns:h,rows:n[l],area:u})}a.push(i)}}return s}static roof(t){const e=[...t].sort((o,n)=>o-n);return Math.max(2,e[Math.min(e.length-1,Math.floor(e.length*w.ROOF_PERCENTILE))])}static building(t,e,o){const n=Math.cos(e),s=Math.sin(e),r=t.maxU-t.minU,a=t.maxV-t.minV,i=(t.minU+t.maxU)/2,c=(t.minV+t.maxV)/2,h=((90-(r<a?e:e+Math.PI/2)*180/Math.PI)%180+180)%180;return{eastM:w.tenth(i*n-c*s),northM:w.tenth(i*s+c*n),widthM:w.tenth(Math.max(r,a)),lengthM:w.tenth(Math.min(r,a)),heightM:w.tenth(o),headingDeg:Math.round(h*10)/10}}static smallestRectangle(t){let e={area:1/0,angle:0,rectangle:{minU:0,maxU:0,minV:0,maxV:0}};for(let o=0;o<Math.max(1,t.length);o++){const[n,s]=t[o]??[0,0],[r,a]=t[(o+1)%t.length]??[1,0],i=Math.atan2(a-s,r-n),c=Math.cos(i),l=Math.sin(i);let h=1/0,u=-1/0,M=1/0,f=-1/0;for(const[m,E]of t){const P=m*c+E*l,p=-m*l+E*c;h=Math.min(h,P),u=Math.max(u,P),M=Math.min(M,p),f=Math.max(f,p)}const d=(u-h)*(f-M);d<e.area&&(e={area:d,angle:i,rectangle:{minU:h,maxU:u,minV:M,maxV:f}})}return e}static hull(t){const e=[...t].sort((a,i)=>a[0]-i[0]||a[1]-i[1]),o=e.filter((a,i)=>i===0||a[0]!==e[i-1][0]||a[1]!==e[i-1][1]);if(o.length<3)return o;const n=(a,i,c)=>(i[0]-a[0])*(c[1]-a[1])-(i[1]-a[1])*(c[0]-a[0]),s=[];for(const a of o){for(;s.length>=2&&n(s[s.length-2],s[s.length-1],a)<=0;)s.pop();s.push(a)}const r=[];for(const a of[...o].reverse()){for(;r.length>=2&&n(r[r.length-2],r[r.length-1],a)<=0;)r.pop();r.push(a)}return[...s.slice(0,-1),...r.slice(0,-1)]}static tenth(t){return Math.round(t*10)/10}}class x{constructor(t,e,o,n){this.heights=t,this.vegetation=e,this.width=o,this.height=n}static SUMMIT_RADIUS_M=2.5;static CROWN_REACH_M=8;static MAX_WIDTH_M=16;static MIN_WIDTH_M=2;static tenth(t){return Math.round(t*10)/10}trees(t){const e=t.metresPerPixel,o=t.minHeightM??4,n=t.eyeM??1.6,s=t.minElevationDeg??2,r=Math.max(1,Math.round(x.SUMMIT_RADIUS_M/e)),a=Math.round(x.CROWN_REACH_M/e),i=this.width/2,c=this.height/2,l=[];for(let h=0;h<this.height;h++)for(let u=0;u<this.width;u++){const M=this.canopy(u,h);if(M<o)continue;const f=(u+.5-i)*e,d=(c-h-.5)*e,m=Math.hypot(f,d);if(m>t.radiusM||!this.isSummit(u,h,M,r))continue;const E=Math.atan2(M-n,Math.max(m,1))*180/Math.PI;E<s||l.push({tree:{eastM:x.tenth(f),northM:x.tenth(d),heightM:x.tenth(M),widthM:x.tenth(this.crownWidth(u,h,M,e,a))},elevation:E})}return l.sort((h,u)=>u.elevation-h.elevation),l.slice(0,t.maxTrees??150).map(h=>h.tree)}canopy(t,e){if(t<0||e<0||t>=this.width||e>=this.height)return 0;const o=e*this.width+t;return this.vegetation[o]?this.heights[o]:0}isSummit(t,e,o,n){for(let s=-n;s<=n;s++)for(let r=-n;r<=n;r++){if(r===0&&s===0)continue;const a=this.canopy(t+r,e+s);if(a>o||a===o&&(s<0||s===0&&r<0))return!1}return!0}crownWidth(t,e,o,n,s){let r=0;for(let i=-s;i<=s;i++)for(let c=-s;c<=s;c++)Math.hypot(c,i)*n<=x.CROWN_REACH_M&&this.canopy(t+c,e+i)>=o*.5&&r++;const a=2*Math.sqrt(r*n*n/Math.PI);return Math.max(x.MIN_WIDTH_M,Math.min(a,x.MAX_WIDTH_M))}}class A{static WALLS={1:"stone",2:"meuliere",3:"concrete",4:"brick",5:"block",6:"wood",9:"other"};static ROOFS={1:"tile",2:"slate",3:"zinc",4:"concrete",9:"other"};static WALL_COLOURS={stone:"#b9ae9b",meuliere:"#b8a78a",concrete:"#b4b2ac",brick:"#a65f48",block:"#d0c6b2",wood:"#7d5c3e",other:"#c9c0ae"};static PLASTER="#d3cab8";static ROOF_COLOURS={tile:"#a0583f",slate:"#4a5059",zinc:"#8d969c",concrete:"#8f8e8a",other:"#7a7872"};static VARIATION=.12;static of(t,e){const o=String(t??"").trim();if(!/^\d{1,2}$/.test(o))return;const n=Number(o),s=n%10!==0?n%10:Math.floor(n/10);return(e==="wall"?A.WALLS:A.ROOFS)[String(s)]}static roofColor(t){return t?A.ROOF_COLOURS[t]:void 0}static fit(t){return t.map(e=>{if(e.facade)return e;const o={color:A.wallColor(e)};return{...e,facade:o}})}static wallColor(t){const e=t.wallMaterial?A.WALL_COLOURS[t.wallMaterial]:A.PLASTER,o=Math.sin(t.eastM*12.9898+t.northM*78.233)*43758.5453,n=1+(o-Math.floor(o)-.5)*2*A.VARIATION,s=r=>Math.max(0,Math.min(255,Math.round(parseInt(e.slice(r,r+2),16)*n))).toString(16).padStart(2,"0");return`#${s(1)}${s(3)}${s(5)}`}}class g{static MIN_RISE_M=1.2;static MIN_EAVES_M=2.5;static MIN_SEEN=.35;static NO_DATA=-9999;static BANDS=7;static INSET_M=.9;static LIFT=1.18;static PEAK=.3;static LEVEL=.45;static fit(t,e,o){return!e&&!o&&!t.some(n=>n.roofMaterial)?t:t.map(n=>{const s=e?g.shapeOf(n,e):void 0,r=(o?g.colorOf(n,o):void 0)??A.roofColor(n.roofMaterial);if(!s&&!r)return n;const a={shape:s?.shape??"flat",...s?.ridge==="depth"?{ridge:"depth"}:{},riseM:s?.riseM??0,...r?{color:r}:{}},i=s&&s.shape!=="flat"?s.topM:n.heightM;return{...n,heightM:i,roof:a}})}static world(t,e,o){const n=t.headingDeg*Math.PI/180;return{east:t.eastM+e*Math.cos(n)+o*Math.sin(n),north:t.northM-e*Math.sin(n)+o*Math.cos(n)}}static shapeOf(t,e){const o=g.BANDS,n=Math.max(e.metresPerPixel,.5),s=t.widthM/2-g.INSET_M,r=t.lengthM/2-g.INSET_M;if(s<=0||r<=0)return;const a=[],i=Array.from({length:o},()=>({sum:0,n:0})),c=Array.from({length:o},()=>({sum:0,n:0}));let l=0;for(let y=-s;y<=s;y+=n)for(let b=-r;b<=r;b+=n){l++;const{east:T,north:X}=g.world(t,y,b),C=Math.floor(T/e.metresPerPixel+e.size/2),z=Math.floor(e.size/2-X/e.metresPerPixel);if(C<0||z<0||C>=e.size||z>=e.size)continue;const V=z*e.size+C,U=e.heights[V];if(e.vegetation[V]||U===g.NO_DATA||!(U>0))continue;a.push(U);const W=i[Math.min(o-1,Math.floor((y+s)/(2*s)*o))],$=c[Math.min(o-1,Math.floor((b+r)/(2*r)*o))];W.sum+=U,W.n++,$.sum+=U,$.n++}if(l===0||a.length/l<g.MIN_SEEN)return;a.sort((y,b)=>y-b);const h=g.percentile(a,.08),u=g.percentile(a,.92),M=u-h;if(h<g.MIN_EAVES_M||M>Math.min(t.widthM,t.lengthM))return;if(M<g.MIN_RISE_M)return{shape:"flat",riseM:0,topM:u};const f=g.profile(i),d=g.profile(c);if(!f||!d)return;const m=g.peak(d)/M,E=g.peak(f)/M,P=g.spread(f)/M,p=g.spread(d)/M,O=m>=g.PEAK&&P<=g.LEVEL,D=E>=g.PEAK&&p<=g.LEVEL;if(O&&(!D||m>=E))return{shape:"gable",ridge:"width",riseM:M,topM:u};if(D)return{shape:"gable",ridge:"depth",riseM:M,topM:u};if(m>=g.PEAK&&E>=g.PEAK/2)return{shape:"hip",ridge:"width",riseM:M,topM:u}}static profile(t){const e=t.map(o=>o.n>0?o.sum/o.n:NaN);return e.filter(Number.isFinite).length>=t.length-1?e.map((o,n)=>Number.isFinite(o)?o:g.neighbour(e,n)):void 0}static neighbour(t,e){return Number.isFinite(t[e-1])?t[e-1]:t[e+1]}static peak(t){const e=Math.max(1,Math.floor(t.length/3)),o=s=>s.reduce((r,a)=>r+a,0)/s.length,n=[...t.slice(0,e),...t.slice(t.length-e)];return o(t.slice(e,t.length-e))-o(n)}static spread(t){return Math.max(...t)-Math.min(...t)}static percentile(t,e){return t[Math.min(t.length-1,Math.floor(t.length*e))]}static colorOf(t,e){const o=Math.max(e.metresPerPixel,.5),n=Math.max(t.widthM/2-g.INSET_M,o),s=Math.max(t.lengthM/2-g.INSET_M,o),r=[],a=[],i=[];for(let h=-n;h<=n;h+=o)for(let u=-s;u<=s;u+=o){const{east:M,north:f}=g.world(t,h,u),d=Math.floor(M/e.metresPerPixel+e.size/2),m=Math.floor(e.size/2-f/e.metresPerPixel);if(d<0||m<0||d>=e.size||m>=e.size)continue;const E=(m*e.size+d)*e.samples,P=e.values[E],p=e.values[E+1],O=e.values[E+2];p>P&&p>O||(r.push(P),a.push(p),i.push(O))}if(r.length===0)return;const c=h=>h.sort((u,M)=>u-M)[Math.floor(h.length/2)],l=h=>Math.min(255,Math.round(h*g.LIFT)).toString(16).padStart(2,"0");return`#${l(c(r))}${l(c(a))}${l(c(i))}`}}class v{static DEFAULT_HEIGHT_M=6;static METRES_PER_DEGREE=111320;static STREET_SPACING_M=35;static KERB_OFFSET_M=1;static DEFAULT_WIDTH_M=5.5;static PATIENCE_MS=15e3;static MAX=150;client;constructor(t={}){this.client=new k(t)}static today(){return new Date().toISOString().slice(0,10)}async near(t,e){const o=e/v.METRES_PER_DEGREE,n=e/(v.METRES_PER_DEGREE*Math.cos(t.lat*Math.PI/180)),s=[t.lat-o,t.lng-n,t.lat+o,t.lng+n].map(i=>i.toFixed(6)).join(",");let r;const a=new Promise(i=>{r=setTimeout(()=>i(void 0),v.PATIENCE_MS)});try{const i=await Promise.race([this.client.query(`[out:json][timeout:25];(node["highway"="street_lamp"](${s});way["highway"]["lit"="yes"](${s}););out geom;`),a]);return i?v.lampsFrom(i,t,e):[]}catch{return[]}finally{clearTimeout(r)}}static lampsFrom(t,e,o){const n=[];for(const r of t.elements??[]){if(r.type==="way"){v.alongStreet(r,e,n);continue}if(typeof r.lat!="number"||typeof r.lon!="number")continue;const a=(r.lon-e.lng)*v.METRES_PER_DEGREE*Math.cos(e.lat*Math.PI/180),i=(r.lat-e.lat)*v.METRES_PER_DEGREE;if(Math.hypot(a,i)>o)continue;const c=Number.parseFloat(r.tags?.height??"");n.push({eastM:Math.round(a*10)/10,northM:Math.round(i*10)/10,...Number.isFinite(c)&&c>=2&&c<=20?{heightM:c}:{}})}const s=n.filter(r=>Math.hypot(r.eastM,r.northM)<=o);return n.length=0,n.push(...s),n.sort((r,a)=>Math.hypot(r.eastM,r.northM)-Math.hypot(a.eastM,a.northM)),n.slice(0,v.MAX)}static alongStreet(t,e,o){const n=t.geometry;if(!n||n.length<2)return;const s=f=>[(f.lon-e.lng)*v.METRES_PER_DEGREE*Math.cos(e.lat*Math.PI/180),(f.lat-e.lat)*v.METRES_PER_DEGREE],r=n.map(s),a=Number.parseFloat(t.tags?.width??""),i=Number.parseInt(t.tags?.lanes??"",10),l=(Number.isFinite(a)&&a>0?a:Number.isFinite(i)&&i>0?i*3.5:v.DEFAULT_WIDTH_M)/2+v.KERB_OFFSET_M;let h=1,u=v.STREET_SPACING_M/2,M=0;for(let f=1;f<r.length;f++){const[d,m]=r[f-1],[E,P]=r[f],p=Math.hypot(E-d,P-m);if(p===0)continue;const O=(E-d)/p,D=(P-m)/p;for(;u<=M+p;u+=v.STREET_SPACING_M){const y=u-M;o.push({eastM:Math.round((d+O*y+D*l*h)*10)/10,northM:Math.round((m+D*y-O*l*h)*10)/10}),h=-h}M+=p}}}class j{id="ign-lidar-hd";name="IGN LiDAR HD";attribution="Canopy and roofs © IGN, LiDAR HD (Licence Ouverte 2.0), as surveyed on a day the service does not give";creditUrl="https://geoservices.ign.fr/lidarhd";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","The LiDAR HD survey covers France only");const o=R.lidarRadius(e),[n,s,r,a,i]=await Promise.all([this.platform.lidar(t,o),this.platform.photo(t,o).catch(()=>{}),this.lamps?.near(t,e),this.platform.laserDate(t),this.platform.photoDate(t)]);return{trees:new x(n.heights,n.vegetation,n.size,n.size).trees({metresPerPixel:n.metresPerPixel,radiusM:o}),buildings:A.fit(g.fit(new w(n.heights,n.building,n.size,n.size).buildings({metresPerPixel:n.metresPerPixel,radiusM:o,maxBuildings:300}),n,s)),bridges:[],lines:[],...r?{lamps:r}:{},dates:{...a?{laser:a}:{},...s&&i?{photo:i}:{},...r?.length?{lamps:v.today()}:{}}}}}class I{static METRES_PER_DEGREE=111320;static NAMED_NATURES={Silo:"Silo"};static DEFAULT_HEIGHT_M=6;static FLOOR_HEIGHT_M=3;static FINE_M=.5;static COARSE_M=1;static MAX_CELLS=4e4;static RECTANGLE_FILL=.9;static buildings(t,e,o,n=12,s=300){const r=[];for(const i of t){const c=I.rings(i,e);if(c.length===0)continue;const l=Number(i.properties.nombre_d_etages),h=Number(i.properties.hauteur),u=Number.isFinite(h)&&h>0?h:Number.isFinite(l)&&l>0?l*I.FLOOR_HEIGHT_M:I.DEFAULT_HEIGHT_M;for(const M of I.rectanglesOf(c,u,n)){if(Math.hypot(M.eastM,M.northM)>o)continue;const f=A.of(i.properties.materiaux_des_murs,"wall"),d=A.of(i.properties.materiaux_de_la_toiture,"roof"),m=I.NAMED_NATURES[String(i.properties.nature??"")];r.push({...M,...m?{name:m}:{},...Number.isFinite(l)&&l>0?{floors:l}:{},...f?{wallMaterial:f}:{},...d?{roofMaterial:d}:{}})}}const a=i=>Math.hypot(i.eastM,i.northM);if(r.length>s){const i=c=>(c.heightM+6)*Math.sqrt(c.widthM*c.lengthM)/Math.max(a(c),50);r.sort((c,l)=>i(l)-i(c)),r.length=s}return r.sort((i,c)=>a(i)-a(c)),r}static named(t,e,o){const n=e.map(s=>({name:typeof s.properties.toponyme=="string"?s.properties.toponyme:"",rings:I.rings(s,o)})).filter(s=>s.name.length>0&&s.rings.length>0);return n.length===0?t:t.map(s=>{const r=n.find(a=>I.inside(s.eastM,s.northM,a.rings));return r?{...s,name:r.name}:s})}static inside(t,e,o){let n=0;for(const s of o)for(let r=0;r+1<s.length;r++){const a=s[r],i=s[r+1];a.north<=e!=i.north<=e&&a.east+(e-a.north)/(i.north-a.north)*(i.east-a.east)>t&&n++}return n%2===1}static rings(t,e){const o=t.geometry,n=o.type==="Polygon"?[o.coordinates]:o.type==="MultiPolygon"?o.coordinates:[],s=Math.cos(e.lat*Math.PI/180),r=[];for(const a of n)for(const i of a)r.push(i.map(([c,l])=>({east:(c-e.lng)*I.METRES_PER_DEGREE*s,north:(l-e.lat)*I.METRES_PER_DEGREE})));return r}static rectanglesOf(t,e,o){let n=1/0,s=-1/0,r=1/0,a=-1/0;for(const u of t)for(const M of u)n=Math.min(n,M.east),s=Math.max(s,M.east),r=Math.min(r,M.north),a=Math.max(a,M.north);let i=I.FINE_M;(s-n)*(a-r)/(i*i)>I.MAX_CELLS&&(i=I.COARSE_M);const c=[],l=[];for(let u=r+i/2;u<a;u+=i){const M=[];for(const f of t)for(let d=0;d+1<f.length;d++){const m=f[d],E=f[d+1];m.north<=u!=E.north<=u&&M.push(m.east+(u-m.north)/(E.north-m.north)*(E.east-m.east))}M.sort((f,d)=>f-d);for(let f=0;f+1<M.length;f+=2)for(let d=Math.ceil((M[f]-i/2)/i)*i+i/2;d<M[f+1];d+=i)c.push(d),l.push(u)}if(c.length*i*i<o)return[];const h=t.flatMap(u=>u.slice(0,-1).map((M,f)=>({from:M,to:u[f+1]})));return w.ofPoints(c,l,new Array(c.length).fill(e),i,o,I.RECTANGLE_FILL,h)}}class q{id="ign-lidar-topo";name="IGN LiDAR HD + BD TOPO";attribution="Canopy © IGN, LiDAR HD; buildings, bridges and lines © IGN, BD TOPO (Licence Ouverte 2.0), as surveyed on days the services do not give";creditUrl="https://geoservices.ign.fr/lidarhd";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","The IGN's surveys cover France only");const o=R.radius(e),[n,s,r,a,i,c]=await Promise.allSettled([this.platform.lidar(t,o),this.platform.vectors(t,o,!0),this.platform.photo(t,o),this.lamps?.near(t,e)??Promise.resolve(void 0),this.platform.laserDate(t),this.platform.photoDate(t)]),l=R.lidarRadius(o);if(n.status==="rejected"&&s.status==="rejected")throw n.reason;const h=n.status==="fulfilled"?n.value:void 0,u=s.status==="fulfilled"?s.value:void 0,M=u!==void 0&&u.buildings.length===0&&u.bridges.length===0&&u.lines.length===0;if(n.status==="rejected"&&n.reason instanceof S&&n.reason.reason==="no-coverage"&&M)throw n.reason;return{trees:h?new x(h.heights,h.vegetation,h.size,h.size).trees({metresPerPixel:h.metresPerPixel,radiusM:l}):[],buildings:this.roofed(this.buildings(h,u,t,o),h,r.status==="fulfilled"?r.value:void 0),bridges:u?F.bridges(u,t,o,h?.structures):[],lines:u?F.lines(u,t,o,h?.structures):[],...a.status==="fulfilled"&&a.value?{lamps:a.value}:{},dates:{...n.status==="fulfilled"&&i.status==="fulfilled"&&i.value?{laser:i.value}:{},...r.status==="fulfilled"&&c.status==="fulfilled"&&c.value?{photo:c.value}:{},...u?.mapDate?{map:u.mapDate}:{},...a.status==="fulfilled"&&a.value?.length?{lamps:v.today()}:{}}}}roofed(t,e,o){return A.fit(g.fit(t,e,o))}buildings(t,e,o,n){const s=e?I.buildings(e.buildings,o,n):[],r=s.length>0||t===void 0?s:new w(t.heights,t.building,t.size,t.size).buildings({metresPerPixel:t.metresPerPixel,radiusM:R.lidarRadius(n),maxBuildings:300});return e?I.named(r,e.zones,o):r}}class Y{id="ign-bd-topo";name="IGN BD TOPO";attribution="Buildings, bridges and lines © IGN, BD TOPO (Licence Ouverte 2.0), as drawn on a day the service does not give";creditUrl="https://geoservices.ign.fr/bdtopo";contemporary=!0;platform;lamps;constructor(t={},e){this.platform=new R(t),this.lamps=e}covers(t,e){return R.covers(t,e)}async getSurroundings(t,e){if(!this.covers(t.lat,t.lng))throw new S("no-coverage","BD TOPO covers France only");const o=R.radius(e),[n,s,r,a]=await Promise.all([this.platform.vectors(t,o,!0),this.platform.photo(t,o).catch(()=>{}),this.lamps?.near(t,e),this.platform.photoDate(t)]);return{trees:[],buildings:A.fit(g.fit(I.named(I.buildings(n.buildings,t,o),n.zones,t),void 0,s)),bridges:F.bridges(n,t,o,void 0),lines:F.lines(n,t,o,void 0),...r?{lamps:r}:{},dates:{...n.mapDate?{map:n.mapDate}:{},...s&&a?{photo:a}:{},...r?.length?{lamps:v.today()}:{}}}}}class N{static ROOF_SHAPES={flat:"flat",gabled:"gable",gambrel:"gable",saltbox:"gable","half-hipped":"hip",hipped:"hip",pyramidal:"hip",mansard:"hip",round:"gable"};static WALL_MATERIALS={brick:"brick",stone:"stone",granite:"stone",sandstone:"stone",limestone:"stone",concrete:"concrete",cement_block:"block",block:"block",wood:"wood",timber_framing:"wood"};static ROOF_MATERIALS={roof_tiles:"tile",tiles:"tile",tile:"tile",slate:"slate",metal:"zinc",zinc:"zinc",tin:"zinc",copper:"zinc",concrete:"concrete",eternit:"other"};static COLOURS={white:"#e8e6e0",grey:"#8c8c8a",gray:"#8c8c8a",black:"#3a3a3a",red:"#a8483a",brown:"#6e4f38",beige:"#d6c9a8",yellow:"#d8c070",orange:"#c9773a",green:"#5f7a52",blue:"#506a8a",tan:"#c0a37a",cream:"#e6dcc0",pink:"#c99a96",silver:"#b4b6b6"};static height(t){const e=Number.parseFloat((t??"").replace(",","."));return Number.isFinite(e)&&e>=2&&e<=500?e:void 0}static levels(t){const e=Number.parseInt(t??"",10);return Number.isFinite(e)&&e>=1&&e<=150?e:void 0}static roofShape(t){return N.ROOF_SHAPES[(t??"").trim().toLowerCase()]}static wallMaterial(t){return N.WALL_MATERIALS[(t??"").trim().toLowerCase()]}static roofMaterial(t){return N.ROOF_MATERIALS[(t??"").trim().toLowerCase()]}static colour(t){const e=(t??"").trim().toLowerCase();return/^#[0-9a-f]{6}$/.test(e)?e:/^#[0-9a-f]{3}$/.test(e)?`#${e[1]}${e[1]}${e[2]}${e[2]}${e[3]}${e[3]}`:N.COLOURS[e]}}class L{id="osm-buildings";name="OpenStreetMap buildings";attribution="Buildings © OpenStreetMap contributors (ODbL), as drawn on the day of the data, which a recording is not";creditUrl="https://www.openstreetmap.org/copyright";contemporary=!0;static MAX_RADIUS_M=1500;static MIN_AREA_M2=12;static MAX_BUILDINGS=300;client;lamps;constructor(t={},e){this.client=new k(t),this.lamps=e}covers(){return!0}async getSurroundings(t,e){const o=Math.min(e,L.MAX_RADIUS_M);let n;try{n=await this.client.query(`[out:json][timeout:30];(way["building"](around:${o},${t.lat},${t.lng});relation["building"]["type"="multipolygon"](around:${o},${t.lat},${t.lng}););out geom;`)}catch(a){throw new S("unreachable",a instanceof Error?a.message:String(a))}const s=await this.lamps?.near(t,e),r=(n.osm3s?.timestamp_osm_base??"").slice(0,10);return{trees:[],buildings:A.fit(L.buildings(n,t,o)),bridges:[],lines:[],...s?{lamps:s}:{},dates:{.../^\d{4}-\d{2}-\d{2}$/.test(r)?{map:r}:{},...s?.length?{lamps:v.today()}:{}}}}static buildings(t,e,o){const n=[];for(const s of t.elements??[]){const r=L.ringsOf(s);if(r.length===0)continue;const a=s.tags??{},i=N.levels(a["building:levels"]),c=N.height(a.height),l={geometry:{type:"Polygon",coordinates:r},properties:{...c?{hauteur:c}:{},...i?{nombre_d_etages:i}:{}}};for(const h of I.buildings([l],e,o,L.MIN_AREA_M2,50))n.push(L.described(h,a,c!==void 0))}return n.sort((s,r)=>Math.hypot(s.eastM,s.northM)-Math.hypot(r.eastM,r.northM)),n.slice(0,L.MAX_BUILDINGS)}static ringsOf(t){const e=n=>{if(!n||n.length<4)return;const s=n.map(i=>[i.lon,i.lat]),r=s[0],a=s[s.length-1];return r[0]===a[0]&&r[1]===a[1]?s:void 0};if(t.type==="way"){const n=e(t.geometry);return n?[n]:[]}return(t.members??[]).filter(n=>n.role==="outer").map(n=>e(n.geometry)).filter(n=>n!==void 0).slice(0,1)}static described(t,e,o){const n=N.wallMaterial(e["building:material"]),s=N.roofMaterial(e["roof:material"]),r=N.colour(e["building:colour"]),a=N.colour(e["roof:colour"])??A.roofColor(s),i=N.roofShape(e["roof:shape"]),c=r?{color:r}:void 0;let l,h=t.heightM;if(i==="gable"||i==="hip"){const u=N.height(e["roof:height"]),M=Math.round((u??Math.min(5,Math.max(1.5,.29*Math.min(t.widthM,t.lengthM))))*10)/10;o||(h=Math.round((t.heightM+M)*10)/10),l={shape:i,riseM:M,...e["roof:orientation"]==="across"?{ridge:"depth"}:{},...a?{color:a}:{}}}else(i==="flat"||a)&&(l={shape:"flat",riseM:0,...a?{color:a}:{}});return{...t,heightM:h,...e.name?{name:e.name}:{},...n?{wallMaterial:n}:{},...s?{roofMaterial:s}:{},...c?{facade:c}:{},...l?{roof:l}:{}}}}const _=[{id:"ign-lidar-topo",name:"IGN LiDAR HD + BD TOPO",credit:"© IGN, LiDAR HD and BD TOPO, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/lidarhd",create:()=>new q({},new v)},{id:"ign-lidar-hd",name:"IGN LiDAR HD",credit:"© IGN, LiDAR HD, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/lidarhd",create:()=>new j({},new v)},{id:"ign-bd-topo",name:"IGN BD TOPO",credit:"© IGN, BD TOPO, Licence Ouverte 2.0; street lamps © OpenStreetMap contributors",creditUrl:"https://geoservices.ign.fr/bdtopo",create:()=>new Y({},new v)},{id:"osm-buildings",name:"OpenStreetMap buildings",credit:"Buildings and street lamps © OpenStreetMap contributors (ODbL)",creditUrl:"https://www.openstreetmap.org/copyright",create:()=>new L({},new v)}];class G{static DEFAULT_ID=_[0].id;static WORLD_ID="osm-buildings";static NONE="none";static byId(t){return t===G.NONE?void 0:_.find(e=>e.id===t)??_[0]}static providerFor(t,e,o){const n=G.byId(t)?.create();if(t!==void 0&&t!==G.DEFAULT_ID)return n?.covers(e,o)?n:void 0;const s=_.find(a=>a.id===G.WORLD_ID).create(),r=[n,s].filter(a=>a!==void 0&&a.covers(e,o));return r.length>1?new K(r):r[0]}}export{_ as SURROUNDINGS_SOURCES,G as SurroundingsSources};
|
package/dist-embed-sighting-editor/{trafficRuntime-XVgt3vom.js → trafficRuntime-BtljEizX.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{A as ct,k as lt,T as U,R as L,m as P,r as ht,n as ut}from"./rr0-sighting-editor.mjs";import{C as Y}from"./ContrailGrowth-CuAzwIoc.js";class A{static OCTAVES=[63,125,250,500,1e3,2e3,4e3,8e3];static STANDARD={temperatureC:15,relativeHumidity:.7,pressureKPa:101.325};static REFERENCE_PRESSURE_KPA=101.325;static REFERENCE_TEMPERATURE_K=293.15;static TRIPLE_POINT_K=273.16;static speedOfSound(t){return 20.0468*Math.sqrt(t+273.15)}static absorptionDbPerKm(t,e=A.STANDARD){const a=e.temperatureC+273.15,n=A.REFERENCE_TEMPERATURE_K,i=e.pressureKPa/A.REFERENCE_PRESSURE_KPA,r=10**(-6.8346*(A.TRIPLE_POINT_K/a)**1.261+4.6151),s=e.relativeHumidity*100*r/i,o=i*(24+40400*s*(.02+s)/(.391+s)),l=i*(a/n)**-.5*(9+280*s*Math.exp(-4.17*((a/n)**(-1/3)-1))),c=t*t;return 8.686*c*(184e-13*(1/i)*Math.sqrt(a/n)+(a/n)**-2.5*(.01275*Math.exp(-2239.1/a)/(o+c/o)+.1068*Math.exp(-3352/a)/(l+c/l)))*1e3}static aWeightingDb(t){const e=t*t,a=12194**2*e*e/((e+20.6**2)*Math.sqrt((e+107.7**2)*(e+737.9**2))*(e+12194**2));return 20*Math.log10(a)+2}static sumDb(t){return 10*Math.log10(t.reduce((e,a)=>e+10**(a/10),0))}}const dt=.25,$=.4,J=6,j=12,pt=3,x=.2,mt=.2,gt=24,At=3,Et=12,Z=1,ft=.7,Mt=1.5,bt=.8,Dt=1.3,Q=.9,tt=5,St=25,Rt=.5,Bt=.12,et=[.13,.31],vt=.16,Ot=1.6,It=3.6,at=1.1,Tt=.3,_t=1e3,nt=.7;class V{context;noiseBuffer;roughBuffers=new Map;voices=new Map;bus;reverb;reverbOut;level=1;master;paused=!0;requested=[];headingDeg=0;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(t){console.warn("AircraftAudio: Web Audio unavailable, aircraft stay silent:",t);return}}ct.unlock(this.context),this.apply()}setLevel(t){this.level=t,this.master&&(this.master.gain.value=t)}setPaused(t){t!==this.paused&&(this.paused=t,this.apply())}setVoices(t,e=0){this.requested=t,this.headingDeg=e,this.apply()}dispose(){for(const t of[...this.voices.keys()])this.release(t);this.context?.close(),this.context=void 0,this.master=void 0,this.bus=void 0}apply(){const t=this.context;if(!t)return;const e=this.paused?[]:this.requested,a=new Set(e.map(n=>n.id));for(const n of[...this.voices.keys()])a.has(n)||this.release(n);for(const n of e){const i=this.voices.get(n.id)??this.build(t,n);this.retarget(t,i,n)}}build(t,e){const a=t.createGain();a.gain.value=0;let n;const i=this.busOf(t);typeof t.createStereoPanner=="function"?(n=t.createStereoPanner(),a.connect(n).connect(i)):a.connect(i);const r=t.createGain(),s=e.character?.modulationDepth??0;r.gain.value=1-s/2;const o=t.createOscillator();o.type="sine";const l=t.createGain();l.gain.value=s/2,o.connect(l).connect(r.gain),r.connect(a);const c=t.createBufferSource();c.buffer=this.noise(t),c.loop=!0,c.loopStart=Math.random()*J*.5;const d=[],u=[],h=[];for(const D of A.OCTAVES){const v=t.createBiquadFilter();v.type="bandpass",v.frequency.value=D,v.Q.value=Z;const C=t.createBiquadFilter();C.type="bandpass",C.frequency.value=D,C.Q.value=Z;const w=t.createGain();w.gain.value=0,c.connect(v).connect(C).connect(w).connect(r),d.push(v),u.push(C),h.push(w)}const p=[];for(const[D,v]of[[e.character?.toneHz,e.character?.toneShare],[e.character?.humHz,e.character?.humShare]])if(D!==void 0)for(let C=1;C<=At;C++){const w=t.createOscillator();w.type="sine",w.frequency.value=D*C;const W=t.createGain();W.gain.value=0,w.connect(W).connect(a),p.push({oscillator:w,gain:W,harmonic:C,baseHz:D,share:v??0})}const m=t.createGain();m.gain.value=0,m.connect(a.gain);const f=et.map(D=>{const v=t.createOscillator();return v.type="sine",v.frequency.value=D,v.connect(m),v}),b=t.createGain();b.gain.value=e.character?.roughness??x;const N=t.createBufferSource();N.buffer=this.roughTable(t,pt),N.loop=!0,N.loopStart=Math.random()*j*.5,N.connect(b).connect(r.gain);const _=t.createGain();_.gain.value=(e.character?.roughness??x)*mt;const R=t.createBufferSource();R.buffer=this.roughTable(t,gt),R.loop=!0,R.loopStart=Math.random()*j*.5,R.connect(_).connect(r.gain);let O;const G=this.busOf(t)&&this.reverb;G&&(O=t.createGain(),O.gain.value=0,a.connect(O).connect(G));const z=[c,N,R,o,...p.map(D=>D.oscillator),...f];for(const D of z)D.start();const q={nodes:[a,r,l,b,_,m,...p.map(D=>D.gain),...d,...u,...h,...O?[O]:[],...n?[n]:[],...z],sources:z,filters:d,seconds:u,gains:h,tones:p,lfo:o,lfoDepth:l,wander:f,wanderDepth:m,roughDepth:b,crackleDepth:_,beat:r,output:a,panner:n,send:O};return this.voices.set(e.id,q),q}retarget(t,e,a){const n=t.currentTime,i=(p,m)=>p.setTargetAtTime(m,n,dt),r=Math.min(Mt,Math.max(ft,a.dopplerRatio)),s=Math.min(Dt,Math.max(bt,Math.sqrt(a.dopplerRatio))),o=t.sampleRate*.45,l=a.bandAmplitudes.map((p,m)=>p*V.unweighting(A.OCTAVES[m])),c=Math.sqrt(l.reduce((p,m)=>p+m*m,0)),d=Math.min(1,Math.max(0,(a.distanceKm-tt)/(St-tt)));A.OCTAVES.forEach((p,m)=>{i(e.filters[m].frequency,Math.min(o,p*s)),i(e.seconds[m].frequency,Math.min(o,p*s)),i(e.gains[m].gain,l[m]*Math.sqrt(_t/p))});const u=a.character;for(const{oscillator:p,gain:m,harmonic:f,baseHz:b,share:N}of e.tones){const _=b*f;i(p.frequency,_*r);const R=_*r;i(m.gain,R<o?N*vt*l[V.bandOf(R)]/f:0)}u?.modulationHz!==void 0&&i(e.lfo.frequency,u.modulationHz*r);const h=c>0?nt*(1-Math.exp(-c/nt))/c:1;if(i(e.output.gain,h*(1-Tt*d)),i(e.wanderDepth.gain,h*Bt*d/et.length),e.send&&i(e.send.gain,Ot*d),e.panner){const p=(a.azimuthDeg-this.headingDeg)*Math.PI/180;i(e.panner.pan,Math.max(-Q,Math.min(Q,Math.sin(p)))*(1-Rt*d))}}static unweighting(t){return 10**(Math.min(Et,Math.max(0,-A.aWeightingDb(t)))/20)}static bandOf(t){let e=0;return A.OCTAVES.forEach((a,n)=>{Math.abs(Math.log(t/a))<Math.abs(Math.log(t/A.OCTAVES[e]))&&(e=n)}),e}roughTable(t,e){const a=this.roughBuffers.get(e);if(a)return a;const n=Math.floor(t.sampleRate*j),i=t.createBuffer(1,n,t.sampleRate),r=i.getChannelData(0),s=Array.from({length:j*e},()=>(Math.random()*2-1)**3),o=t.sampleRate/e;for(let l=0;l<r.length;l++){const c=l/o,d=Math.floor(c),u=c-d,h=u*u*(3-2*u);r[l]=s[d%s.length]*(1-h)+s[(d+1)%s.length]*h}return this.roughBuffers.set(e,i),i}roomImpulse(t){const e=Math.floor(t.sampleRate*It),a=t.createBuffer(2,e,t.sampleRate);for(let n=0;n<2;n++){const i=a.getChannelData(n);let r=0;for(let s=0;s<e;s++){const o=s/t.sampleRate,l=.6*Math.exp(-o/(at*2))+.04;r+=l*(Math.random()*2-1-r),i[s]=r*Math.exp(-o/at)}}return a}busOf(t){if(this.bus)return this.bus;const e=t.createGain();if(e.gain.value=this.level,e.connect(t.destination),this.master=e,typeof t.createConvolver=="function"){const a=t.createConvolver();a.buffer=this.roomImpulse(t);const n=t.createGain();n.gain.value=.9,a.connect(n),this.reverb=a,this.reverbOut=n}if(typeof t.createDynamicsCompressor=="function"){const a=t.createDynamicsCompressor();a.threshold.value=-6,a.knee.value=3,a.ratio.value=4,a.attack.value=.005,a.release.value=.25,a.connect(e),this.bus=a}else this.bus=e;return this.reverbOut?.connect(this.bus),this.bus}release(t){const e=this.voices.get(t);if(!e)return;this.voices.delete(t);const a=this.context;a&&(e.output.gain.setTargetAtTime(0,a.currentTime,$/3),setTimeout(()=>{for(const n of e.sources)try{n.stop()}catch{}for(const n of e.nodes)n.disconnect()},$*1e3*3))}noise(t){if(this.noiseBuffer)return this.noiseBuffer;const e=t.createBuffer(1,t.sampleRate*J,t.sampleRate),a=e.getChannelData(0);for(let n=0;n<a.length;n++)a[n]=Math.random()*2-1;return this.noiseBuffer=e,e}}class I{static BASE_DB_A=30;static WIND_DB_A=[[0,10],[1,28],[2,36],[3,40],[5,46],[8,52],[12,58],[20,66]];static RAIN_DB_A=[[0,0],[.1,42],[.3,50],[.6,58],[1,65]];static BED_RMS=.2;static MINIMUM_REFERENCE=.008;static WIND_BED_MIN_MS=1;static WIND_BED_FULL_MS=20;static dbA(t){const e=[I.BASE_DB_A,I.along(I.WIND_DB_A,t.windSpeed)];return(t.precipitationType==="rain"||t.precipitationType==="hail")&&e.push(I.along(I.RAIN_DB_A,t.precipitationIntensity)),10*Math.log10(e.filter(a=>a>0).reduce((a,n)=>a+10**(n/10),0))}static bedVolume(t){const e=t.windSpeed>I.WIND_BED_MIN_MS?Math.min(t.windSpeed/I.WIND_BED_FULL_MS,1):0,a=t.precipitationType==="hail"?Math.min(1,t.precipitationIntensity*1.3+.2):t.precipitationType==="rain"?t.precipitationIntensity:0;return Math.hypot(e,a)}static referenceAmplitude(t){return Math.max(I.BED_RMS*I.bedVolume(t),I.MINIMUM_REFERENCE)}static along(t,e){if(e<=t[0][0])return t[0][1];for(let a=1;a<t.length;a++)if(e<=t[a][0]){const[n,i]=t[a-1],[r,s]=t[a];return i+(s-i)*(e-n)/(r-n)}return t[t.length-1][1]}}class y{static TYPES={A318:["Airbus A318","airliner-narrow",34.1,31.4],A319:["Airbus A319","airliner-narrow",34.1,33.8],A320:["Airbus A320","airliner-narrow",34.1,37.6],A321:["Airbus A321","airliner-narrow",34.1,44.5],A19N:["Airbus A319neo","airliner-narrow",35.8,33.8],A20N:["Airbus A320neo","airliner-narrow",35.8,37.6],A21N:["Airbus A321neo","airliner-narrow",35.8,44.5],B737:["Boeing 737-700","airliner-narrow",34.3,33.6],B738:["Boeing 737-800","airliner-narrow",35.8,39.5],B739:["Boeing 737-900","airliner-narrow",35.8,42.1],B38M:["Boeing 737 MAX 8","airliner-narrow",35.9,39.5],B39M:["Boeing 737 MAX 9","airliner-narrow",35.9,42.2],B752:["Boeing 757-200","airliner-narrow",38,47.3],B753:["Boeing 757-300","airliner-narrow",38,54.4],B762:["Boeing 767-200","airliner-wide",47.6,48.5],B763:["Boeing 767-300","airliner-wide",47.6,54.9],B764:["Boeing 767-400","airliner-wide",51.9,61.4],B772:["Boeing 777-200","airliner-wide",60.9,63.7],B77L:["Boeing 777-200LR","airliner-wide",64.8,63.7],B77W:["Boeing 777-300ER","airliner-wide",64.8,73.9],B788:["Boeing 787-8","airliner-wide",60.1,56.7],B789:["Boeing 787-9","airliner-wide",60.1,62.8],B78X:["Boeing 787-10","airliner-wide",60.1,68.3],B744:["Boeing 747-400","airliner-wide",64.4,70.7],B748:["Boeing 747-8","airliner-wide",68.4,76.3],A306:["Airbus A300-600","airliner-wide",44.8,54.1],A310:["Airbus A310","airliner-wide",43.9,46.7],A332:["Airbus A330-200","airliner-wide",60.3,58.8],A333:["Airbus A330-300","airliner-wide",60.3,63.7],A338:["Airbus A330-800","airliner-wide",64,58.8],A339:["Airbus A330-900","airliner-wide",64,63.7],A343:["Airbus A340-300","airliner-wide",60.3,63.7],A346:["Airbus A340-600","airliner-wide",63.5,75.4],A359:["Airbus A350-900","airliner-wide",64.8,66.8],A35K:["Airbus A350-1000","airliner-wide",64.8,73.8],A388:["Airbus A380-800","airliner-wide",79.8,72.7],E170:["Embraer 170","regional-jet",26,29.9],E75L:["Embraer 175","regional-jet",26,31.7],E75S:["Embraer 175","regional-jet",26,31.7],E190:["Embraer 190","regional-jet",28.7,36.2],E195:["Embraer 195","regional-jet",28.7,38.7],E290:["Embraer 190-E2","regional-jet",33.7,36.2],E295:["Embraer 195-E2","regional-jet",33.7,41.5],CRJ2:["Bombardier CRJ200","regional-jet",21.2,26.8],CRJ7:["Bombardier CRJ700","regional-jet",23.2,32.3],CRJ9:["Bombardier CRJ900","regional-jet",24.9,36.2],CRJX:["Bombardier CRJ1000","regional-jet",26.2,39.1],AT72:["ATR 72","regional-turboprop",27.1,27.2],AT76:["ATR 72-600","regional-turboprop",27.1,27.2],AT45:["ATR 42","regional-turboprop",24.6,22.7],DH8D:["Dash 8 Q400","regional-turboprop",28.4,32.8],DH8C:["Dash 8-300","regional-turboprop",27.4,25.7],SF34:["Saab 340","regional-turboprop",21.4,19.7],B190:["Beechcraft 1900","regional-turboprop",17.7,17.6],C25A:["Cessna CitationJet CJ2","business-jet",14.3,14.4],C56X:["Cessna Citation Excel","business-jet",17.2,16],C68A:["Cessna Citation Latitude","business-jet",22,18.9],C550:["Cessna Citation II","business-jet",15.8,14.4],E55P:["Embraer Phenom 300","business-jet",15.9,15.6],E50P:["Embraer Phenom 100","business-jet",12.3,12.8],GLF5:["Gulfstream G550","business-jet",28.5,29.4],FA7X:["Dassault Falcon 7X","business-jet",26.2,23.4],CL60:["Bombardier Challenger 600","business-jet",19.6,20.9],LJ45:["Learjet 45","business-jet",14.6,17.7],H25B:["Hawker 800","business-jet",15.7,15.6],C172:["Cessna 172","light-piston",11,8.3],C152:["Cessna 152","light-piston",10.2,7.3],C182:["Cessna 182","light-piston",10.9,8.8],P28A:["Piper Cherokee","light-piston",10.7,7.2],PA34:["Piper Seneca","light-piston",11.8,8.7],SR20:["Cirrus SR20","light-piston",10.9,7.9],SR22:["Cirrus SR22","light-piston",11.7,7.9],S22T:["Cirrus SR22T","light-piston",11.7,7.9],DA40:["Diamond DA40","light-piston",11.6,8.1],DA42:["Diamond DA42","light-piston",13.4,8.6],BE20:["Beechcraft King Air 200","turboprop-light",16.6,13.3],BE58:["Beechcraft Baron","light-piston",11.5,9.1],C208:["Cessna Caravan","turboprop-light",15.9,11.5],PC12:["Pilatus PC-12","turboprop-light",16.3,14.4],TBM9:["Daher TBM 900","turboprop-light",12.8,10.7],EC35:["Airbus H135","helicopter-light",10.2,12.1],EC45:["Airbus H145","helicopter-medium",11,13.6],AS50:["Airbus H125","helicopter-light",10.7,12.9],EC30:["Airbus H130","helicopter-light",10.7,12.6],B407:["Bell 407","helicopter-light",10.7,12.7],B06:["Bell 206","helicopter-light",10.2,12],B429:["Bell 429","helicopter-medium",10.9,13],B505:["Bell 505","helicopter-light",10.4,11.9],R44:["Robinson R44","helicopter-light",10.1,11.7],R22:["Robinson R22","helicopter-light",7.7,8.8],A109:["Agusta A109","helicopter-medium",11,13],A139:["Leonardo AW139","helicopter-medium",13.8,16.7],S92:["Sikorsky S-92","helicopter-heavy",17.2,20.9],H60:["Sikorsky UH-60 Black Hawk","helicopter-heavy",16.4,19.8],T38:["Northrop T-38 Talon","military-jet",7.7,14.1],HAWK:["BAE Hawk","military-jet",9.4,11.9],GLID:["Glider","glider",17,7],BALL:["Balloon","balloon",15,20],ULAC:["Ultralight","ultralight",9.5,6],C42:["Ikarus C42","ultralight",9.4,6.3],DRON:["Drone","unmanned",1,.6]};static CATEGORIES={A1:["Light aircraft","light-piston",11,8.3],A2:["Small aircraft","business-jet",15,15],A3:["Large aircraft","airliner-narrow",34.1,37.6],A4:["High-vortex large aircraft","airliner-narrow",38,47.3],A5:["Heavy aircraft","airliner-wide",60.3,63.7],A6:["High-performance aircraft","military-jet",8,14],A7:["Rotorcraft","helicopter-medium",10.7,12.7],B1:["Glider","glider",17,7],B2:["Lighter-than-air","balloon",15,20],B4:["Ultralight","ultralight",9.5,6],B6:["Unmanned aircraft","unmanned",1.5,1]};static GENERIC={name:"Airliner",kind:"airliner-narrow",spanM:34.1,lengthM:37.6,basis:"nothing"};static of(t){const e=t?.type?.toUpperCase(),a=e?y.TYPES[e]:void 0;if(a)return{code:e,name:a[0],kind:a[1],spanM:a[2],lengthM:a[3],basis:"type"};const n=t?.category?y.CATEGORIES[t.category]:void 0;return n?{name:n[0],kind:n[1],spanM:n[2],lengthM:n[3],basis:"category"}:{...y.GENERIC}}static isHelicopter(t){return t.kind.startsWith("helicopter")}}class H{static DUSK_SUN_ELEVATION_DEG=-.83;static ALWAYS=new Set(["airliner-narrow","airliner-wide","regional-turboprop","regional-jet","business-jet"]);static NONE=new Set(["glider","balloon"]);static BRIGHTNESS={"light-piston":[.5,.2,.5],"turboprop-light":[.7,.4,.8],ultralight:[.4,.2,.4],unmanned:[0,.05,0],"helicopter-light":[1,1,1],"helicopter-medium":[1,1,1],"helicopter-heavy":[1,1,1],"military-jet":[1,1,1]};static lampsOn(t,e){if(H.NONE.has(t))return{navigation:!1,beacon:!1,strobes:!1};if(H.ALWAYS.has(t))return{navigation:!0,beacon:!0,strobes:!0};const a=e<H.DUSK_SUN_ELEVATION_DEG;return t==="unmanned"?{navigation:!1,beacon:!1,strobes:a}:{navigation:a,beacon:a,strobes:a}}static lights(t,e){const a=H.lampsOn(t.kind,e),n=t.kind.startsWith("helicopter"),i=lt.find(h=>h.id===(n?"helicopter":"airliner")).create(),[r,s]=n?[5,7]:[17,18],o=t.spanM/2/r,l=t.lengthM/2/s,[c,d,u]=H.BRIGHTNESS[t.kind]??[1,1,1];return i.filter(h=>h.id.startsWith("nav")?a.navigation:h.id.startsWith("strobe")?a.strobes:h.id.startsWith("beacon")?a.beacon:!0).filter(h=>!(h.id.startsWith("beacon")&&!a.beacon)).map(h=>{const p=h.id.startsWith("nav")?c:h.id.startsWith("strobe")?d:u;return{...h,offsetM:{x:h.offsetM.x*o,y:h.offsetM.y,z:h.offsetM.z*l},intensity:(h.intensity??1)*p}}).filter(h=>(h.intensity??1)>0)}static bodyColor(t){return t.kind==="military-jet"?"#8a8e94":t.kind.startsWith("helicopter")?"#c8ccd1":t.kind==="unmanned"?"#2c2e33":"#dde0e4"}}class F{static FAMILIES={"amvlab-a320":["A318","A319","A320","A321","A19N","A20N","A21N"],"amvlab-b737":["B732","B733","B734","B735","B736","B737","B738","B739","B37M","B38M","B39M","B3XM"],"amvlab-a350":["A359","A35K"],"amvlab-a380":["A388"],"amvlab-b787":["B788","B789","B78X"]};static BY_TYPE=new Map(Object.entries(F.FAMILIES).flatMap(([t,e])=>e.map(a=>[a,t])));static idOf(t){return t?F.BY_TYPE.get(t.toUpperCase()):void 0}}class M{static ID_PREFIX=U.ID_PREFIX;static MAX_OBJECTS=40;static FT_TO_M=.3048;static DEG=Math.PI/180;static from(t,e,a,n=M.MAX_OBJECTS,i={}){return new Ct(t,e,a,n,i).build()}static idOf(t,e){return U.idOf(t,e)}static keyOf(t){return U.keyOf(t)}static HEIGHT_OVER_LENGTH=.29;static sizeOf(t){return{widthM:t.spanM,lengthM:t.lengthM,heightM:t.lengthM*M.HEIGHT_OVER_LENGTH}}static offsetOf(t,e){const a=L.seenFrom(t,{lat:e.lat,lng:e.lng,heightM:e.altitudeFt*M.FT_TO_M}),n=a.distanceKm*1e3,i=n*Math.cos(a.altitudeDeg*M.DEG);return{eastM:i*Math.sin(a.azimuthDeg*M.DEG),northM:i*Math.cos(a.azimuthDeg*M.DEG),upM:n*Math.sin(a.altitudeDeg*M.DEG),altitudeDeg:a.altitudeDeg,distanceKm:a.distanceKm}}static segments(t){const e=[];let a=[];for(const n of t.points){const i=a[a.length-1];i&&n.t-i.t>P.MAX_GAP_S*1e3&&(e.push(a),a=[]),a.push(n)}return e.push(a),e.filter(n=>n.length>=2)}static bearingDeg(t,e){return(Math.atan2(e.eastM-t.eastM,e.northM-t.northM)/M.DEG+360)%360}}class Ct{constructor(t,e,a,n,i){this.tracks=t,this.observer=e,this.startMs=a,this.maxObjects=n,this.options=i}build(){const t=[];for(const a of this.tracks){const n=M.keyOf(a),i=this.options.models?.get(n)??y.of(void 0);M.segments(a).forEach((r,s)=>{const o=r.map(h=>({point:h,at:M.offsetOf(this.observer,h)}));if(!o.some(h=>h.at.altitudeDeg>=0))return;const l=o.map((h,p)=>({t:h.point.t-this.startMs,eastM:h.at.eastM,northM:h.at.northM,altitudeM:h.at.upM,headingDeg:h.point.trackDeg??(p+1<o.length?M.bearingDeg(h.at,o[p+1].at):M.bearingDeg(o[p-1].at,h.at))})),c=M.idOf(a,s),d=o[Math.floor(o.length/2)].point,u=this.options.sunElevationAt?.(d)??90;t.push({object:{id:c,kind:"aircraft",eastM:l[0].eastM,northM:l[0].northM,headingDeg:l[0].headingDeg,sizeM:M.sizeOf(i),model:F.idOf(i.code)?{id:F.idOf(i.code)}:void 0,color:H.bodyColor(i),lights:H.lights(i,u),track:l},presence:{fromMs:l[0].t,untilMs:l[l.length-1].t},flight:{icao:a.icao,nonIcao:a.nonIcao,points:r},closestKm:Math.min(...o.map(h=>h.at.distanceKm))})})}t.sort((a,n)=>a.closestKm-n.closestKm);const e=t.slice(0,this.maxObjects);return{objects:e.map(a=>a.object),presence:new Map(e.map(a=>[a.object.id,a.presence])),flights:new Map(e.map(a=>[a.object.id,a.flight])),total:t.length,shown:e.length}}}const ot=[{id:"turbofan-narrow-body",name:"Turbofan airliner, narrow-body",kinds:["airliner-narrow"],totalDbA:148,shapeDb:[-32,-17,-9,-6,-5,-7,-12,-22],character:{toneHz:1800,toneShare:.3,humHz:90,humShare:.7}},{id:"turbofan-wide-body",name:"Turbofan airliner, wide-body",kinds:["airliner-wide"],totalDbA:151,shapeDb:[-28,-14,-7,-4,-3,-6,-12,-22],character:{toneHz:1200,toneShare:.3,humHz:70,humShare:.7}},{id:"regional-jet",name:"Regional jet",kinds:["regional-jet"],totalDbA:144,shapeDb:[-32,-17,-9,-5,-4,-6,-11,-21],character:{toneHz:1900,toneShare:.3,humHz:95,humShare:.7}},{id:"business-jet",name:"Business jet",kinds:["business-jet"],totalDbA:141,shapeDb:[-30,-15,-8,-4,-3,-4,-9,-19],character:{toneHz:2500,toneShare:.25,humHz:110,humShare:.7}},{id:"turboprop",name:"Turboprop",kinds:["regional-turboprop","turboprop-light"],totalDbA:138,shapeDb:[-14,-9,-8,-6,-8,-13,-20,-28],character:{toneHz:80,toneShare:.5}},{id:"light-piston",name:"Light piston aircraft",kinds:["light-piston"],totalDbA:130,shapeDb:[-18,-9,-5,-4,-8,-14,-22,-30],character:{toneHz:80,toneShare:.4}},{id:"ultralight",name:"Ultralight",kinds:["ultralight"],totalDbA:124,shapeDb:[-16,-8,-5,-4,-9,-15,-23,-31],character:{toneHz:140,toneShare:.4}},{id:"helicopter-light",name:"Light helicopter",kinds:["helicopter-light"],totalDbA:134,shapeDb:[-15,-8,-4,-3,-6,-10,-16,-24],character:{modulationHz:20,modulationDepth:.5}},{id:"helicopter-medium",name:"Medium helicopter",kinds:["helicopter-medium"],totalDbA:138,shapeDb:[-14,-7,-4,-3,-6,-10,-16,-24],character:{modulationHz:22,modulationDepth:.5}},{id:"helicopter-heavy",name:"Heavy helicopter",kinds:["helicopter-heavy"],totalDbA:144,shapeDb:[-12,-6,-4,-4,-7,-11,-17,-25],character:{modulationHz:17,modulationDepth:.5}},{id:"military-jet",name:"Military jet",kinds:["military-jet"],totalDbA:152,shapeDb:[-26,-12,-6,-3,-2,-3,-7,-15]},{id:"glider",name:"Glider",kinds:["glider"],totalDbA:85,shapeDb:[-30,-22,-15,-10,-8,-8,-14,-22]},{id:"balloon",name:"Hot-air balloon",kinds:["balloon"],totalDbA:100,shapeDb:[-25,-15,-9,-5,-3,-6,-12,-20]},{id:"unmanned",name:"Unmanned aircraft",kinds:["unmanned"],totalDbA:88,shapeDb:[-40,-30,-20,-12,-6,-3,-3,-8],character:{toneHz:450,toneShare:1}}],it=ot.map(T=>({id:T.id,name:T.name,soundPowerA:T.shapeDb.map(t=>t+T.totalDbA-A.sumDb(T.shapeDb)),character:T.character}));class g{static DEFAULT_AMBIENT_DB_A=35;static DETECTION_MARGIN_DB=3;static AMBIENT_SHAPE_DB=[-12,-7,-5,-5,-7,-11,-17,-25];static HEARING_FLOOR_DB_A=0;static ITERATIONS=30;static CONVERGED_MS=.5;static VELOCITY_STEP_MS=500;static noiseOfKind(t){const e=ot.find(a=>a.kinds.includes(t));return it.find(a=>a.id===(e?.id??"turbofan-narrow-body"))}static bandsAt(t,e,a=A.STANDARD){const n=20*Math.log10(Math.max(e*1e3,1))+11;return A.OCTAVES.map((i,r)=>t.soundPowerA[r]-n-A.absorptionDbPerKm(i,a)*e)}static couldBeHeard(t,e,a=g.DEFAULT_AMBIENT_DB_A,n=A.STANDARD){const i=g.ambientBands(a);return g.bandsAt(t,e,n).some((r,s)=>r>=Math.max(i[s]+g.DETECTION_MARGIN_DB,g.HEARING_FLOOR_DB_A))}static ambientBands(t){const e=g.AMBIENT_SHAPE_DB,a=A.sumDb(e);return e.map(n=>t+n-a)}static heardAt(t,e,a,n={}){const i=n.air??A.STANDARD,r=A.speedOfSound(i.temperatureC),s=t.points[t.points.length-1]?.t;if(s===void 0)return;let o=Math.min(a,s),l=P.positionAt(t,o);if(!l)return;let c=L.seenFrom(e,l.geo);for(let R=0;R<g.ITERATIONS;R++){const O=a-c.distanceKm*1e3*1e3/r,G=Math.abs(O-o);if(o=O,l=P.positionAt(t,o),!l)return;if(c=L.seenFrom(e,l.geo),G<g.CONVERGED_MS)break}const d=P.positionAt(t,a),u=d?L.seenFrom(e,d.geo):c,h=n.noise??it[0],p=n.ambientDbA??g.DEFAULT_AMBIENT_DB_A,m=g.ambientBands(p),f=g.bandsAt(h,c.distanceKm,i),b=f.map((R,O)=>R>=Math.max(m[O]+g.DETECTION_MARGIN_DB,g.HEARING_FLOOR_DB_A)),N=f.indexOf(Math.max(...f)),_=b.lastIndexOf(!0);return{track:t,audible:b.some(Boolean),levelDbA:A.sumDb(f),bandsDbA:f,ambientDbA:p,delayS:(a-o)/1e3,emittedAtMs:o,sound:c,seen:u,lagDeg:g.angleBetween(c,u),dopplerRatio:g.doppler(t,e,o,r),dominantHz:A.OCTAVES[N],cutoffHz:_<0?void 0:A.OCTAVES[_]}}static profile(t,e,a,n,i=2e3,r={}){const s=[];let o,l,c=a;for(let d=a;d<=n;d+=i){const u=g.heardAt(t,e,d,r);u&&(!l||u.levelDbA>l.levelDbA)&&(l=u,c=d),u?.audible?o?o.untilMs=d:s.push(o={fromMs:d,untilMs:d}):o=void 0}return{track:t,audible:s,peakDbA:l?.levelDbA??-1/0,peakAtMs:c,peakDelayS:l?.delayS??0}}static angleBetween(t,e){const a=c=>{const d=c.altitudeDeg*Math.PI/180,u=c.azimuthDeg*Math.PI/180;return[Math.cos(d)*Math.sin(u),Math.cos(d)*Math.cos(u),Math.sin(d)]},[n,i,r]=a(t),[s,o,l]=a(e);return Math.acos(Math.min(1,Math.max(-1,n*s+i*o+r*l)))*180/Math.PI}static doppler(t,e,a,n){const i=g.VELOCITY_STEP_MS,r=P.positionAt(t,a-i),s=P.positionAt(t,a+i);if(!r||!s)return 1;const o=L.seenFrom(e,r.geo).distanceKm*1e3,l=L.seenFrom(e,s.geo).distanceKm*1e3;return 1/(1-(o-l)/(2*i/1e3)/n)}}class Lt{static at(t,e,a,n,i={}){const r=P.seenFrom(t,a,n);if(!r)return;const s=y.of(e),o=g.heardAt(t,a,n,{noise:g.noiseOfKind(s.kind),...i});return{hex:`${t.nonIcao?"~":""}${t.icao.toString(16).padStart(6,"0")}`,registration:e?.registration,typeCode:s.code??e?.type,name:s.basis==="nothing"?void 0:s.name,kind:s.kind,basis:s.basis,military:e?.military===!0,restricted:e?.restricted===!0,altitudeFt:r.altitudeFt,groundSpeedKt:r.groundSpeedKt,trackDeg:r.trackDeg,sky:r.sky,angularRateDegPerS:r.angularRateDegPerS,hearing:o&&{audible:o.audible,levelDbA:o.levelDbA,ambientDbA:o.ambientDbA,delayS:o.delayS,lagDeg:o.lagDeg,dopplerRatio:o.dopplerRatio,cutoffHz:o.cutoffHz,dominantHz:o.dominantHz}}}}class S{static REFERENCE_AMPLITUDE=.02;static MASKING_RANGE_DB=6;static KNEE_DB=20;static COMPRESSION_RATIO=1.5;static EXPANSION_RATIO=1.8;static DEFAULT_MAX_VOICES=4;static candidates(t,e,a,n,i={}){const r=new Set;for(const[s,o]of t){const l=g.noiseOfKind(y.of(e.get(a(o))).kind);let c=1/0;for(const d of o.points)c=Math.min(c,L.seenFrom(n,{lat:d.lat,lng:d.lng,heightM:d.altitudeFt*.3048}).distanceKm);g.couldBeHeard(l,c,i.ambientDbA,i.air)&&r.add(s)}return r}static voices(t,e,a,n,i,r,s={}){const o=[];for(const l of t){const c=e.get(l);if(!c||s.only?.(l)===!1)continue;const d=s.kindOf?.(l)??y.of(a.get(n(c))).kind,u=g.noiseOfKind(d),h=g.heardAt(c,i,r,{...s,noise:u});h?.audible&&o.push({id:l,kind:d,bandAmplitudes:S.bandAmplitudes(h.bandsDbA,h.ambientDbA,s.referenceAmplitude),levelDbA:h.levelDbA,distanceKm:h.sound.distanceKm,dopplerRatio:h.dopplerRatio,azimuthDeg:h.sound.azimuthDeg,elevationDeg:h.sound.altitudeDeg,character:u.character})}return o.sort((l,c)=>c.levelDbA-l.levelDbA).slice(0,s.maxVoices??S.DEFAULT_MAX_VOICES)}static bandAmplitudes(t,e,a=S.REFERENCE_AMPLITUDE){const n=g.ambientBands(e),i=g.DETECTION_MARGIN_DB,r=A.sumDb(t),s=r-e,o=s>=S.KNEE_DB?S.KNEE_DB+(s-S.KNEE_DB)/S.COMPRESSION_RATIO:S.KNEE_DB-(S.KNEE_DB-s)*S.EXPANSION_RATIO,l=a*10**(o/20);return t.map((c,d)=>{const u=Math.min(1,Math.max(0,(c-n[d]-i+S.MASKING_RANGE_DB)/(2*S.MASKING_RANGE_DB)));return u*u*(3-2*u)*l*10**((c-r)/20)})}static BANDS_HZ=A.OCTAVES}class K{static ENGLISH={unnamed:"Aircraft {hex}",altitude:"{value} ft",speed:"{value} kt",heading:"heading {value}°",position:"{distance} km away, {elevation}° above the horizon, {bearing}",heard:"Heard at {level} dB(A), left the aircraft {delay} s ago, {lag}° behind it: {character}{pitch}",rumble:"a low rumble, nothing above {cutoff} Hz",muffled:"a dull noise, nothing above {cutoff} Hz",broad:"a broad roar",pitchHigher:", pitch {percent} % higher",pitchLower:", pitch {percent} % lower",inaudible:"Not audible here: {level} dB(A) against {ambient} dB(A) of ambient noise",soundUnknown:"Its sound cannot be worked out: the aircraft was not recorded yet when it left",contrailPersistent:"Leaves a trail that lasts: the air is supersaturated over ice ({rhi} % over ice)",contrailShort:"Leaves a short-lived trail: the air is dry over ice ({rhi} % over ice)",military:"military",restricted:"identity withheld by its owner",candidate:"A compatible candidate, not an identification"};static RUMBLE_BELOW_HZ=500;static DULL_BELOW_HZ=2e3;static PITCH_NOTICED=.03;static text(t,e,a,n){const i=(u,h=0)=>u.toLocaleString(n,{maximumFractionDigits:h}),r=(u,h)=>u.replace(/\{(\w+)\}/g,(p,m)=>h[m]??`{${m}}`),s=[],o=[t.registration,t.name].filter(Boolean).join(" · ");s.push(o||r(e.unnamed,{hex:t.hex}));const l=[r(e.altitude,{value:i(Math.round(t.altitudeFt/100)*100)}),t.groundSpeedKt===void 0?void 0:r(e.speed,{value:i(Math.round(t.groundSpeedKt))}),t.trackDeg===void 0?void 0:r(e.heading,{value:i(Math.round(t.trackDeg))})].filter(Boolean);s.push(l.join(" · ")),s.push(r(e.position,{distance:i(t.sky.distanceKm,t.sky.distanceKm<10?1:0),elevation:i(Math.round(t.sky.altitudeDeg)),bearing:a(t.sky.azimuthDeg)}));const c=t.hearing;if(!c)s.push(e.soundUnknown);else if(!c.audible)s.push(r(e.inaudible,{level:i(Math.round(c.levelDbA)),ambient:i(Math.round(c.ambientDbA))}));else{const u=c.cutoffHz??0,h=u<=K.RUMBLE_BELOW_HZ?e.rumble:u<=K.DULL_BELOW_HZ?e.muffled:e.broad,p=c.dopplerRatio-1,m=Math.abs(p)<K.PITCH_NOTICED?"":r(p>0?e.pitchHigher:e.pitchLower,{percent:i(Math.round(Math.abs(p)*100))});s.push(r(e.heard,{level:i(Math.round(c.levelDbA)),delay:i(Math.round(c.delayS)),lag:i(Math.round(c.lagDeg)),character:r(h,{cutoff:i(u)}),pitch:m}))}t.contrail&&s.push(r(t.contrail.persistent?e.contrailPersistent:e.contrailShort,{rhi:i(Math.round(t.contrail.iceRelativeHumidity*100))}));const d=[t.military?e.military:void 0,t.restricted?e.restricted:void 0].filter(Boolean);return d.length>0&&s.push(d.join(" · ")),s.push(e.candidate),s.join(`
|
|
2
|
-
`)}}class X{static EFFICIENCY={"airliner-narrow":.34,"airliner-wide":.38,"regional-jet":.32,"business-jet":.3,"military-jet":.22,unknown:.34};static efficiencyOf(t){return X.EFFICIENCY[t]}}class E{static EMISSION_INDEX_WATER=1.25;static COMBUSTION_HEAT=432e5;static AIR_HEAT_CAPACITY=1004;static MOLAR_MASS_RATIO=.622;static FT_TO_M=.3048;static TROPOPAUSE_M=11e3;static TROPOPAUSE_HPA=226.32;static ISOTHERMAL_SCALE_HEIGHT_M=6341.62;static pressureAtAltitudeFt(t){const e=t*E.FT_TO_M;return e<=E.TROPOPAUSE_M?1013.25*(1-225577e-10*e)**5.25588:E.TROPOPAUSE_HPA*Math.exp(-(e-E.TROPOPAUSE_M)/E.ISOTHERMAL_SCALE_HEIGHT_M)}static saturationOverWaterPa(t){const e=t+273.15;return Math.exp(54.842763-6763.22/e-4.21*Math.log(e)+367e-6*e+Math.tanh(.0415*(e-218.8))*(53.878-1331.22/e-9.44523*Math.log(e)+.014025*e))}static saturationOverIcePa(t){const e=t+273.15;return Math.exp(9.550426-5723.265/e+3.53068*Math.log(e)-.00728332*e)}static iceRelativeHumidity(t,e){return e*E.saturationOverWaterPa(t)/E.saturationOverIcePa(t)}static mixingSlope(t,e){return E.EMISSION_INDEX_WATER*E.AIR_HEAT_CAPACITY*t*100/(E.MOLAR_MASS_RATIO*E.COMBUSTION_HEAT*(1-e))}static criticalTemperatureC(t,e){const a=Math.log(E.mixingSlope(t,e)-.053);return-46.46+9.43*a+.72*a*a}static requiredHumidity(t,e,a){const n=E.criticalTemperatureC(t,e);return(E.mixingSlope(t,e)*(a-n)+E.saturationOverWaterPa(n))/E.saturationOverWaterPa(a)}static evaluate(t,e){const a=E.criticalTemperatureC(t.pressureHpa,e),n=E.requiredHumidity(t.pressureHpa,e,t.temperatureC),i=E.iceRelativeHumidity(t.temperatureC,t.relativeHumidity),r=t.temperatureC<a&&t.relativeHumidity>=n;return{forms:r,persistent:r&&i>=1,criticalTemperatureC:a,requiredHumidity:n,iceRelativeHumidity:i}}}class
|
|
1
|
+
import{A as ct,m as lt,T as U,R as L,n as P,r as ht,o as ut}from"./rr0-sighting-editor.mjs";import{C as Y}from"./ContrailGrowth-CuAzwIoc.js";class A{static OCTAVES=[63,125,250,500,1e3,2e3,4e3,8e3];static STANDARD={temperatureC:15,relativeHumidity:.7,pressureKPa:101.325};static REFERENCE_PRESSURE_KPA=101.325;static REFERENCE_TEMPERATURE_K=293.15;static TRIPLE_POINT_K=273.16;static speedOfSound(t){return 20.0468*Math.sqrt(t+273.15)}static absorptionDbPerKm(t,e=A.STANDARD){const a=e.temperatureC+273.15,n=A.REFERENCE_TEMPERATURE_K,i=e.pressureKPa/A.REFERENCE_PRESSURE_KPA,r=10**(-6.8346*(A.TRIPLE_POINT_K/a)**1.261+4.6151),s=e.relativeHumidity*100*r/i,o=i*(24+40400*s*(.02+s)/(.391+s)),l=i*(a/n)**-.5*(9+280*s*Math.exp(-4.17*((a/n)**(-1/3)-1))),c=t*t;return 8.686*c*(184e-13*(1/i)*Math.sqrt(a/n)+(a/n)**-2.5*(.01275*Math.exp(-2239.1/a)/(o+c/o)+.1068*Math.exp(-3352/a)/(l+c/l)))*1e3}static aWeightingDb(t){const e=t*t,a=12194**2*e*e/((e+20.6**2)*Math.sqrt((e+107.7**2)*(e+737.9**2))*(e+12194**2));return 20*Math.log10(a)+2}static sumDb(t){return 10*Math.log10(t.reduce((e,a)=>e+10**(a/10),0))}}const dt=.25,$=.4,J=6,j=12,pt=3,x=.2,mt=.2,gt=24,At=3,Et=12,Z=1,ft=.7,Mt=1.5,bt=.8,Dt=1.3,Q=.9,tt=5,St=25,Rt=.5,Bt=.12,et=[.13,.31],vt=.16,Ot=1.6,It=3.6,at=1.1,Tt=.3,_t=1e3,nt=.7;class V{context;noiseBuffer;roughBuffers=new Map;voices=new Map;bus;reverb;reverbOut;level=1;master;paused=!0;requested=[];headingDeg=0;resume(){if(!this.context){if(typeof AudioContext>"u")return;try{this.context=new AudioContext}catch(t){console.warn("AircraftAudio: Web Audio unavailable, aircraft stay silent:",t);return}}ct.unlock(this.context),this.apply()}setLevel(t){this.level=t,this.master&&(this.master.gain.value=t)}setPaused(t){t!==this.paused&&(this.paused=t,this.apply())}setVoices(t,e=0){this.requested=t,this.headingDeg=e,this.apply()}dispose(){for(const t of[...this.voices.keys()])this.release(t);this.context?.close(),this.context=void 0,this.master=void 0,this.bus=void 0}apply(){const t=this.context;if(!t)return;const e=this.paused?[]:this.requested,a=new Set(e.map(n=>n.id));for(const n of[...this.voices.keys()])a.has(n)||this.release(n);for(const n of e){const i=this.voices.get(n.id)??this.build(t,n);this.retarget(t,i,n)}}build(t,e){const a=t.createGain();a.gain.value=0;let n;const i=this.busOf(t);typeof t.createStereoPanner=="function"?(n=t.createStereoPanner(),a.connect(n).connect(i)):a.connect(i);const r=t.createGain(),s=e.character?.modulationDepth??0;r.gain.value=1-s/2;const o=t.createOscillator();o.type="sine";const l=t.createGain();l.gain.value=s/2,o.connect(l).connect(r.gain),r.connect(a);const c=t.createBufferSource();c.buffer=this.noise(t),c.loop=!0,c.loopStart=Math.random()*J*.5;const d=[],u=[],h=[];for(const D of A.OCTAVES){const v=t.createBiquadFilter();v.type="bandpass",v.frequency.value=D,v.Q.value=Z;const C=t.createBiquadFilter();C.type="bandpass",C.frequency.value=D,C.Q.value=Z;const w=t.createGain();w.gain.value=0,c.connect(v).connect(C).connect(w).connect(r),d.push(v),u.push(C),h.push(w)}const p=[];for(const[D,v]of[[e.character?.toneHz,e.character?.toneShare],[e.character?.humHz,e.character?.humShare]])if(D!==void 0)for(let C=1;C<=At;C++){const w=t.createOscillator();w.type="sine",w.frequency.value=D*C;const W=t.createGain();W.gain.value=0,w.connect(W).connect(a),p.push({oscillator:w,gain:W,harmonic:C,baseHz:D,share:v??0})}const m=t.createGain();m.gain.value=0,m.connect(a.gain);const f=et.map(D=>{const v=t.createOscillator();return v.type="sine",v.frequency.value=D,v.connect(m),v}),b=t.createGain();b.gain.value=e.character?.roughness??x;const N=t.createBufferSource();N.buffer=this.roughTable(t,pt),N.loop=!0,N.loopStart=Math.random()*j*.5,N.connect(b).connect(r.gain);const _=t.createGain();_.gain.value=(e.character?.roughness??x)*mt;const R=t.createBufferSource();R.buffer=this.roughTable(t,gt),R.loop=!0,R.loopStart=Math.random()*j*.5,R.connect(_).connect(r.gain);let O;const G=this.busOf(t)&&this.reverb;G&&(O=t.createGain(),O.gain.value=0,a.connect(O).connect(G));const z=[c,N,R,o,...p.map(D=>D.oscillator),...f];for(const D of z)D.start();const q={nodes:[a,r,l,b,_,m,...p.map(D=>D.gain),...d,...u,...h,...O?[O]:[],...n?[n]:[],...z],sources:z,filters:d,seconds:u,gains:h,tones:p,lfo:o,lfoDepth:l,wander:f,wanderDepth:m,roughDepth:b,crackleDepth:_,beat:r,output:a,panner:n,send:O};return this.voices.set(e.id,q),q}retarget(t,e,a){const n=t.currentTime,i=(p,m)=>p.setTargetAtTime(m,n,dt),r=Math.min(Mt,Math.max(ft,a.dopplerRatio)),s=Math.min(Dt,Math.max(bt,Math.sqrt(a.dopplerRatio))),o=t.sampleRate*.45,l=a.bandAmplitudes.map((p,m)=>p*V.unweighting(A.OCTAVES[m])),c=Math.sqrt(l.reduce((p,m)=>p+m*m,0)),d=Math.min(1,Math.max(0,(a.distanceKm-tt)/(St-tt)));A.OCTAVES.forEach((p,m)=>{i(e.filters[m].frequency,Math.min(o,p*s)),i(e.seconds[m].frequency,Math.min(o,p*s)),i(e.gains[m].gain,l[m]*Math.sqrt(_t/p))});const u=a.character;for(const{oscillator:p,gain:m,harmonic:f,baseHz:b,share:N}of e.tones){const _=b*f;i(p.frequency,_*r);const R=_*r;i(m.gain,R<o?N*vt*l[V.bandOf(R)]/f:0)}u?.modulationHz!==void 0&&i(e.lfo.frequency,u.modulationHz*r);const h=c>0?nt*(1-Math.exp(-c/nt))/c:1;if(i(e.output.gain,h*(1-Tt*d)),i(e.wanderDepth.gain,h*Bt*d/et.length),e.send&&i(e.send.gain,Ot*d),e.panner){const p=(a.azimuthDeg-this.headingDeg)*Math.PI/180;i(e.panner.pan,Math.max(-Q,Math.min(Q,Math.sin(p)))*(1-Rt*d))}}static unweighting(t){return 10**(Math.min(Et,Math.max(0,-A.aWeightingDb(t)))/20)}static bandOf(t){let e=0;return A.OCTAVES.forEach((a,n)=>{Math.abs(Math.log(t/a))<Math.abs(Math.log(t/A.OCTAVES[e]))&&(e=n)}),e}roughTable(t,e){const a=this.roughBuffers.get(e);if(a)return a;const n=Math.floor(t.sampleRate*j),i=t.createBuffer(1,n,t.sampleRate),r=i.getChannelData(0),s=Array.from({length:j*e},()=>(Math.random()*2-1)**3),o=t.sampleRate/e;for(let l=0;l<r.length;l++){const c=l/o,d=Math.floor(c),u=c-d,h=u*u*(3-2*u);r[l]=s[d%s.length]*(1-h)+s[(d+1)%s.length]*h}return this.roughBuffers.set(e,i),i}roomImpulse(t){const e=Math.floor(t.sampleRate*It),a=t.createBuffer(2,e,t.sampleRate);for(let n=0;n<2;n++){const i=a.getChannelData(n);let r=0;for(let s=0;s<e;s++){const o=s/t.sampleRate,l=.6*Math.exp(-o/(at*2))+.04;r+=l*(Math.random()*2-1-r),i[s]=r*Math.exp(-o/at)}}return a}busOf(t){if(this.bus)return this.bus;const e=t.createGain();if(e.gain.value=this.level,e.connect(t.destination),this.master=e,typeof t.createConvolver=="function"){const a=t.createConvolver();a.buffer=this.roomImpulse(t);const n=t.createGain();n.gain.value=.9,a.connect(n),this.reverb=a,this.reverbOut=n}if(typeof t.createDynamicsCompressor=="function"){const a=t.createDynamicsCompressor();a.threshold.value=-6,a.knee.value=3,a.ratio.value=4,a.attack.value=.005,a.release.value=.25,a.connect(e),this.bus=a}else this.bus=e;return this.reverbOut?.connect(this.bus),this.bus}release(t){const e=this.voices.get(t);if(!e)return;this.voices.delete(t);const a=this.context;a&&(e.output.gain.setTargetAtTime(0,a.currentTime,$/3),setTimeout(()=>{for(const n of e.sources)try{n.stop()}catch{}for(const n of e.nodes)n.disconnect()},$*1e3*3))}noise(t){if(this.noiseBuffer)return this.noiseBuffer;const e=t.createBuffer(1,t.sampleRate*J,t.sampleRate),a=e.getChannelData(0);for(let n=0;n<a.length;n++)a[n]=Math.random()*2-1;return this.noiseBuffer=e,e}}class I{static BASE_DB_A=30;static WIND_DB_A=[[0,10],[1,28],[2,36],[3,40],[5,46],[8,52],[12,58],[20,66]];static RAIN_DB_A=[[0,0],[.1,42],[.3,50],[.6,58],[1,65]];static BED_RMS=.2;static MINIMUM_REFERENCE=.008;static WIND_BED_MIN_MS=1;static WIND_BED_FULL_MS=20;static dbA(t){const e=[I.BASE_DB_A,I.along(I.WIND_DB_A,t.windSpeed)];return(t.precipitationType==="rain"||t.precipitationType==="hail")&&e.push(I.along(I.RAIN_DB_A,t.precipitationIntensity)),10*Math.log10(e.filter(a=>a>0).reduce((a,n)=>a+10**(n/10),0))}static bedVolume(t){const e=t.windSpeed>I.WIND_BED_MIN_MS?Math.min(t.windSpeed/I.WIND_BED_FULL_MS,1):0,a=t.precipitationType==="hail"?Math.min(1,t.precipitationIntensity*1.3+.2):t.precipitationType==="rain"?t.precipitationIntensity:0;return Math.hypot(e,a)}static referenceAmplitude(t){return Math.max(I.BED_RMS*I.bedVolume(t),I.MINIMUM_REFERENCE)}static along(t,e){if(e<=t[0][0])return t[0][1];for(let a=1;a<t.length;a++)if(e<=t[a][0]){const[n,i]=t[a-1],[r,s]=t[a];return i+(s-i)*(e-n)/(r-n)}return t[t.length-1][1]}}class y{static TYPES={A318:["Airbus A318","airliner-narrow",34.1,31.4],A319:["Airbus A319","airliner-narrow",34.1,33.8],A320:["Airbus A320","airliner-narrow",34.1,37.6],A321:["Airbus A321","airliner-narrow",34.1,44.5],A19N:["Airbus A319neo","airliner-narrow",35.8,33.8],A20N:["Airbus A320neo","airliner-narrow",35.8,37.6],A21N:["Airbus A321neo","airliner-narrow",35.8,44.5],B737:["Boeing 737-700","airliner-narrow",34.3,33.6],B738:["Boeing 737-800","airliner-narrow",35.8,39.5],B739:["Boeing 737-900","airliner-narrow",35.8,42.1],B38M:["Boeing 737 MAX 8","airliner-narrow",35.9,39.5],B39M:["Boeing 737 MAX 9","airliner-narrow",35.9,42.2],B752:["Boeing 757-200","airliner-narrow",38,47.3],B753:["Boeing 757-300","airliner-narrow",38,54.4],B762:["Boeing 767-200","airliner-wide",47.6,48.5],B763:["Boeing 767-300","airliner-wide",47.6,54.9],B764:["Boeing 767-400","airliner-wide",51.9,61.4],B772:["Boeing 777-200","airliner-wide",60.9,63.7],B77L:["Boeing 777-200LR","airliner-wide",64.8,63.7],B77W:["Boeing 777-300ER","airliner-wide",64.8,73.9],B788:["Boeing 787-8","airliner-wide",60.1,56.7],B789:["Boeing 787-9","airliner-wide",60.1,62.8],B78X:["Boeing 787-10","airliner-wide",60.1,68.3],B744:["Boeing 747-400","airliner-wide",64.4,70.7],B748:["Boeing 747-8","airliner-wide",68.4,76.3],A306:["Airbus A300-600","airliner-wide",44.8,54.1],A310:["Airbus A310","airliner-wide",43.9,46.7],A332:["Airbus A330-200","airliner-wide",60.3,58.8],A333:["Airbus A330-300","airliner-wide",60.3,63.7],A338:["Airbus A330-800","airliner-wide",64,58.8],A339:["Airbus A330-900","airliner-wide",64,63.7],A343:["Airbus A340-300","airliner-wide",60.3,63.7],A346:["Airbus A340-600","airliner-wide",63.5,75.4],A359:["Airbus A350-900","airliner-wide",64.8,66.8],A35K:["Airbus A350-1000","airliner-wide",64.8,73.8],A388:["Airbus A380-800","airliner-wide",79.8,72.7],E170:["Embraer 170","regional-jet",26,29.9],E75L:["Embraer 175","regional-jet",26,31.7],E75S:["Embraer 175","regional-jet",26,31.7],E190:["Embraer 190","regional-jet",28.7,36.2],E195:["Embraer 195","regional-jet",28.7,38.7],E290:["Embraer 190-E2","regional-jet",33.7,36.2],E295:["Embraer 195-E2","regional-jet",33.7,41.5],CRJ2:["Bombardier CRJ200","regional-jet",21.2,26.8],CRJ7:["Bombardier CRJ700","regional-jet",23.2,32.3],CRJ9:["Bombardier CRJ900","regional-jet",24.9,36.2],CRJX:["Bombardier CRJ1000","regional-jet",26.2,39.1],AT72:["ATR 72","regional-turboprop",27.1,27.2],AT76:["ATR 72-600","regional-turboprop",27.1,27.2],AT45:["ATR 42","regional-turboprop",24.6,22.7],DH8D:["Dash 8 Q400","regional-turboprop",28.4,32.8],DH8C:["Dash 8-300","regional-turboprop",27.4,25.7],SF34:["Saab 340","regional-turboprop",21.4,19.7],B190:["Beechcraft 1900","regional-turboprop",17.7,17.6],C25A:["Cessna CitationJet CJ2","business-jet",14.3,14.4],C56X:["Cessna Citation Excel","business-jet",17.2,16],C68A:["Cessna Citation Latitude","business-jet",22,18.9],C550:["Cessna Citation II","business-jet",15.8,14.4],E55P:["Embraer Phenom 300","business-jet",15.9,15.6],E50P:["Embraer Phenom 100","business-jet",12.3,12.8],GLF5:["Gulfstream G550","business-jet",28.5,29.4],FA7X:["Dassault Falcon 7X","business-jet",26.2,23.4],CL60:["Bombardier Challenger 600","business-jet",19.6,20.9],LJ45:["Learjet 45","business-jet",14.6,17.7],H25B:["Hawker 800","business-jet",15.7,15.6],C172:["Cessna 172","light-piston",11,8.3],C152:["Cessna 152","light-piston",10.2,7.3],C182:["Cessna 182","light-piston",10.9,8.8],P28A:["Piper Cherokee","light-piston",10.7,7.2],PA34:["Piper Seneca","light-piston",11.8,8.7],SR20:["Cirrus SR20","light-piston",10.9,7.9],SR22:["Cirrus SR22","light-piston",11.7,7.9],S22T:["Cirrus SR22T","light-piston",11.7,7.9],DA40:["Diamond DA40","light-piston",11.6,8.1],DA42:["Diamond DA42","light-piston",13.4,8.6],BE20:["Beechcraft King Air 200","turboprop-light",16.6,13.3],BE58:["Beechcraft Baron","light-piston",11.5,9.1],C208:["Cessna Caravan","turboprop-light",15.9,11.5],PC12:["Pilatus PC-12","turboprop-light",16.3,14.4],TBM9:["Daher TBM 900","turboprop-light",12.8,10.7],EC35:["Airbus H135","helicopter-light",10.2,12.1],EC45:["Airbus H145","helicopter-medium",11,13.6],AS50:["Airbus H125","helicopter-light",10.7,12.9],EC30:["Airbus H130","helicopter-light",10.7,12.6],B407:["Bell 407","helicopter-light",10.7,12.7],B06:["Bell 206","helicopter-light",10.2,12],B429:["Bell 429","helicopter-medium",10.9,13],B505:["Bell 505","helicopter-light",10.4,11.9],R44:["Robinson R44","helicopter-light",10.1,11.7],R22:["Robinson R22","helicopter-light",7.7,8.8],A109:["Agusta A109","helicopter-medium",11,13],A139:["Leonardo AW139","helicopter-medium",13.8,16.7],S92:["Sikorsky S-92","helicopter-heavy",17.2,20.9],H60:["Sikorsky UH-60 Black Hawk","helicopter-heavy",16.4,19.8],T38:["Northrop T-38 Talon","military-jet",7.7,14.1],HAWK:["BAE Hawk","military-jet",9.4,11.9],GLID:["Glider","glider",17,7],BALL:["Balloon","balloon",15,20],ULAC:["Ultralight","ultralight",9.5,6],C42:["Ikarus C42","ultralight",9.4,6.3],DRON:["Drone","unmanned",1,.6]};static CATEGORIES={A1:["Light aircraft","light-piston",11,8.3],A2:["Small aircraft","business-jet",15,15],A3:["Large aircraft","airliner-narrow",34.1,37.6],A4:["High-vortex large aircraft","airliner-narrow",38,47.3],A5:["Heavy aircraft","airliner-wide",60.3,63.7],A6:["High-performance aircraft","military-jet",8,14],A7:["Rotorcraft","helicopter-medium",10.7,12.7],B1:["Glider","glider",17,7],B2:["Lighter-than-air","balloon",15,20],B4:["Ultralight","ultralight",9.5,6],B6:["Unmanned aircraft","unmanned",1.5,1]};static GENERIC={name:"Airliner",kind:"airliner-narrow",spanM:34.1,lengthM:37.6,basis:"nothing"};static of(t){const e=t?.type?.toUpperCase(),a=e?y.TYPES[e]:void 0;if(a)return{code:e,name:a[0],kind:a[1],spanM:a[2],lengthM:a[3],basis:"type"};const n=t?.category?y.CATEGORIES[t.category]:void 0;return n?{name:n[0],kind:n[1],spanM:n[2],lengthM:n[3],basis:"category"}:{...y.GENERIC}}static isHelicopter(t){return t.kind.startsWith("helicopter")}}class k{static DUSK_SUN_ELEVATION_DEG=-.83;static ALWAYS=new Set(["airliner-narrow","airliner-wide","regional-turboprop","regional-jet","business-jet"]);static NONE=new Set(["glider","balloon"]);static BRIGHTNESS={"light-piston":[.5,.2,.5],"turboprop-light":[.7,.4,.8],ultralight:[.4,.2,.4],unmanned:[0,.05,0],"helicopter-light":[1,1,1],"helicopter-medium":[1,1,1],"helicopter-heavy":[1,1,1],"military-jet":[1,1,1]};static lampsOn(t,e){if(k.NONE.has(t))return{navigation:!1,beacon:!1,strobes:!1};if(k.ALWAYS.has(t))return{navigation:!0,beacon:!0,strobes:!0};const a=e<k.DUSK_SUN_ELEVATION_DEG;return t==="unmanned"?{navigation:!1,beacon:!1,strobes:a}:{navigation:a,beacon:a,strobes:a}}static lights(t,e){const a=k.lampsOn(t.kind,e),n=t.kind.startsWith("helicopter"),i=lt.find(h=>h.id===(n?"helicopter":"airliner")).create(),[r,s]=n?[5,7]:[17,18],o=t.spanM/2/r,l=t.lengthM/2/s,[c,d,u]=k.BRIGHTNESS[t.kind]??[1,1,1];return i.filter(h=>h.id.startsWith("nav")?a.navigation:h.id.startsWith("strobe")?a.strobes:h.id.startsWith("beacon")?a.beacon:!0).filter(h=>!(h.id.startsWith("beacon")&&!a.beacon)).map(h=>{const p=h.id.startsWith("nav")?c:h.id.startsWith("strobe")?d:u;return{...h,offsetM:{x:h.offsetM.x*o,y:h.offsetM.y,z:h.offsetM.z*l},intensity:(h.intensity??1)*p}}).filter(h=>(h.intensity??1)>0)}static bodyColor(t){return t.kind==="military-jet"?"#8a8e94":t.kind.startsWith("helicopter")?"#c8ccd1":t.kind==="unmanned"?"#2c2e33":"#dde0e4"}}class F{static FAMILIES={"amvlab-a320":["A318","A319","A320","A321","A19N","A20N","A21N"],"amvlab-b737":["B732","B733","B734","B735","B736","B737","B738","B739","B37M","B38M","B39M","B3XM"],"amvlab-a350":["A359","A35K"],"amvlab-a380":["A388"],"amvlab-b787":["B788","B789","B78X"]};static BY_TYPE=new Map(Object.entries(F.FAMILIES).flatMap(([t,e])=>e.map(a=>[a,t])));static idOf(t){return t?F.BY_TYPE.get(t.toUpperCase()):void 0}}class M{static ID_PREFIX=U.ID_PREFIX;static MAX_OBJECTS=40;static FT_TO_M=.3048;static DEG=Math.PI/180;static from(t,e,a,n=M.MAX_OBJECTS,i={}){return new Ct(t,e,a,n,i).build()}static idOf(t,e){return U.idOf(t,e)}static keyOf(t){return U.keyOf(t)}static HEIGHT_OVER_LENGTH=.29;static sizeOf(t){return{widthM:t.spanM,lengthM:t.lengthM,heightM:t.lengthM*M.HEIGHT_OVER_LENGTH}}static offsetOf(t,e){const a=L.seenFrom(t,{lat:e.lat,lng:e.lng,heightM:e.altitudeFt*M.FT_TO_M}),n=a.distanceKm*1e3,i=n*Math.cos(a.altitudeDeg*M.DEG);return{eastM:i*Math.sin(a.azimuthDeg*M.DEG),northM:i*Math.cos(a.azimuthDeg*M.DEG),upM:n*Math.sin(a.altitudeDeg*M.DEG),altitudeDeg:a.altitudeDeg,distanceKm:a.distanceKm}}static segments(t){const e=[];let a=[];for(const n of t.points){const i=a[a.length-1];i&&n.t-i.t>P.MAX_GAP_S*1e3&&(e.push(a),a=[]),a.push(n)}return e.push(a),e.filter(n=>n.length>=2)}static bearingDeg(t,e){return(Math.atan2(e.eastM-t.eastM,e.northM-t.northM)/M.DEG+360)%360}}class Ct{constructor(t,e,a,n,i){this.tracks=t,this.observer=e,this.startMs=a,this.maxObjects=n,this.options=i}build(){const t=[];for(const a of this.tracks){const n=M.keyOf(a),i=this.options.models?.get(n)??y.of(void 0);M.segments(a).forEach((r,s)=>{const o=r.map(h=>({point:h,at:M.offsetOf(this.observer,h)}));if(!o.some(h=>h.at.altitudeDeg>=0))return;const l=o.map((h,p)=>({t:h.point.t-this.startMs,eastM:h.at.eastM,northM:h.at.northM,altitudeM:h.at.upM,headingDeg:h.point.trackDeg??(p+1<o.length?M.bearingDeg(h.at,o[p+1].at):M.bearingDeg(o[p-1].at,h.at))})),c=M.idOf(a,s),d=o[Math.floor(o.length/2)].point,u=this.options.sunElevationAt?.(d)??90;t.push({object:{id:c,kind:"aircraft",eastM:l[0].eastM,northM:l[0].northM,headingDeg:l[0].headingDeg,sizeM:M.sizeOf(i),model:F.idOf(i.code)?{id:F.idOf(i.code)}:void 0,color:k.bodyColor(i),lights:k.lights(i,u),track:l},presence:{fromMs:l[0].t,untilMs:l[l.length-1].t},flight:{icao:a.icao,nonIcao:a.nonIcao,points:r},closestKm:Math.min(...o.map(h=>h.at.distanceKm))})})}t.sort((a,n)=>a.closestKm-n.closestKm);const e=t.slice(0,this.maxObjects);return{objects:e.map(a=>a.object),presence:new Map(e.map(a=>[a.object.id,a.presence])),flights:new Map(e.map(a=>[a.object.id,a.flight])),total:t.length,shown:e.length}}}const ot=[{id:"turbofan-narrow-body",name:"Turbofan airliner, narrow-body",kinds:["airliner-narrow"],totalDbA:148,shapeDb:[-32,-17,-9,-6,-5,-7,-12,-22],character:{toneHz:1800,toneShare:.3,humHz:90,humShare:.7}},{id:"turbofan-wide-body",name:"Turbofan airliner, wide-body",kinds:["airliner-wide"],totalDbA:151,shapeDb:[-28,-14,-7,-4,-3,-6,-12,-22],character:{toneHz:1200,toneShare:.3,humHz:70,humShare:.7}},{id:"regional-jet",name:"Regional jet",kinds:["regional-jet"],totalDbA:144,shapeDb:[-32,-17,-9,-5,-4,-6,-11,-21],character:{toneHz:1900,toneShare:.3,humHz:95,humShare:.7}},{id:"business-jet",name:"Business jet",kinds:["business-jet"],totalDbA:141,shapeDb:[-30,-15,-8,-4,-3,-4,-9,-19],character:{toneHz:2500,toneShare:.25,humHz:110,humShare:.7}},{id:"turboprop",name:"Turboprop",kinds:["regional-turboprop","turboprop-light"],totalDbA:138,shapeDb:[-14,-9,-8,-6,-8,-13,-20,-28],character:{toneHz:80,toneShare:.5}},{id:"light-piston",name:"Light piston aircraft",kinds:["light-piston"],totalDbA:130,shapeDb:[-18,-9,-5,-4,-8,-14,-22,-30],character:{toneHz:80,toneShare:.4}},{id:"ultralight",name:"Ultralight",kinds:["ultralight"],totalDbA:124,shapeDb:[-16,-8,-5,-4,-9,-15,-23,-31],character:{toneHz:140,toneShare:.4}},{id:"helicopter-light",name:"Light helicopter",kinds:["helicopter-light"],totalDbA:134,shapeDb:[-15,-8,-4,-3,-6,-10,-16,-24],character:{modulationHz:20,modulationDepth:.5}},{id:"helicopter-medium",name:"Medium helicopter",kinds:["helicopter-medium"],totalDbA:138,shapeDb:[-14,-7,-4,-3,-6,-10,-16,-24],character:{modulationHz:22,modulationDepth:.5}},{id:"helicopter-heavy",name:"Heavy helicopter",kinds:["helicopter-heavy"],totalDbA:144,shapeDb:[-12,-6,-4,-4,-7,-11,-17,-25],character:{modulationHz:17,modulationDepth:.5}},{id:"military-jet",name:"Military jet",kinds:["military-jet"],totalDbA:152,shapeDb:[-26,-12,-6,-3,-2,-3,-7,-15]},{id:"glider",name:"Glider",kinds:["glider"],totalDbA:85,shapeDb:[-30,-22,-15,-10,-8,-8,-14,-22]},{id:"balloon",name:"Hot-air balloon",kinds:["balloon"],totalDbA:100,shapeDb:[-25,-15,-9,-5,-3,-6,-12,-20]},{id:"unmanned",name:"Unmanned aircraft",kinds:["unmanned"],totalDbA:88,shapeDb:[-40,-30,-20,-12,-6,-3,-3,-8],character:{toneHz:450,toneShare:1}}],it=ot.map(T=>({id:T.id,name:T.name,soundPowerA:T.shapeDb.map(t=>t+T.totalDbA-A.sumDb(T.shapeDb)),character:T.character}));class g{static DEFAULT_AMBIENT_DB_A=35;static DETECTION_MARGIN_DB=3;static AMBIENT_SHAPE_DB=[-12,-7,-5,-5,-7,-11,-17,-25];static HEARING_FLOOR_DB_A=0;static ITERATIONS=30;static CONVERGED_MS=.5;static VELOCITY_STEP_MS=500;static noiseOfKind(t){const e=ot.find(a=>a.kinds.includes(t));return it.find(a=>a.id===(e?.id??"turbofan-narrow-body"))}static bandsAt(t,e,a=A.STANDARD){const n=20*Math.log10(Math.max(e*1e3,1))+11;return A.OCTAVES.map((i,r)=>t.soundPowerA[r]-n-A.absorptionDbPerKm(i,a)*e)}static couldBeHeard(t,e,a=g.DEFAULT_AMBIENT_DB_A,n=A.STANDARD){const i=g.ambientBands(a);return g.bandsAt(t,e,n).some((r,s)=>r>=Math.max(i[s]+g.DETECTION_MARGIN_DB,g.HEARING_FLOOR_DB_A))}static ambientBands(t){const e=g.AMBIENT_SHAPE_DB,a=A.sumDb(e);return e.map(n=>t+n-a)}static heardAt(t,e,a,n={}){const i=n.air??A.STANDARD,r=A.speedOfSound(i.temperatureC),s=t.points[t.points.length-1]?.t;if(s===void 0)return;let o=Math.min(a,s),l=P.positionAt(t,o);if(!l)return;let c=L.seenFrom(e,l.geo);for(let R=0;R<g.ITERATIONS;R++){const O=a-c.distanceKm*1e3*1e3/r,G=Math.abs(O-o);if(o=O,l=P.positionAt(t,o),!l)return;if(c=L.seenFrom(e,l.geo),G<g.CONVERGED_MS)break}const d=P.positionAt(t,a),u=d?L.seenFrom(e,d.geo):c,h=n.noise??it[0],p=n.ambientDbA??g.DEFAULT_AMBIENT_DB_A,m=g.ambientBands(p),f=g.bandsAt(h,c.distanceKm,i),b=f.map((R,O)=>R>=Math.max(m[O]+g.DETECTION_MARGIN_DB,g.HEARING_FLOOR_DB_A)),N=f.indexOf(Math.max(...f)),_=b.lastIndexOf(!0);return{track:t,audible:b.some(Boolean),levelDbA:A.sumDb(f),bandsDbA:f,ambientDbA:p,delayS:(a-o)/1e3,emittedAtMs:o,sound:c,seen:u,lagDeg:g.angleBetween(c,u),dopplerRatio:g.doppler(t,e,o,r),dominantHz:A.OCTAVES[N],cutoffHz:_<0?void 0:A.OCTAVES[_]}}static profile(t,e,a,n,i=2e3,r={}){const s=[];let o,l,c=a;for(let d=a;d<=n;d+=i){const u=g.heardAt(t,e,d,r);u&&(!l||u.levelDbA>l.levelDbA)&&(l=u,c=d),u?.audible?o?o.untilMs=d:s.push(o={fromMs:d,untilMs:d}):o=void 0}return{track:t,audible:s,peakDbA:l?.levelDbA??-1/0,peakAtMs:c,peakDelayS:l?.delayS??0}}static angleBetween(t,e){const a=c=>{const d=c.altitudeDeg*Math.PI/180,u=c.azimuthDeg*Math.PI/180;return[Math.cos(d)*Math.sin(u),Math.cos(d)*Math.cos(u),Math.sin(d)]},[n,i,r]=a(t),[s,o,l]=a(e);return Math.acos(Math.min(1,Math.max(-1,n*s+i*o+r*l)))*180/Math.PI}static doppler(t,e,a,n){const i=g.VELOCITY_STEP_MS,r=P.positionAt(t,a-i),s=P.positionAt(t,a+i);if(!r||!s)return 1;const o=L.seenFrom(e,r.geo).distanceKm*1e3,l=L.seenFrom(e,s.geo).distanceKm*1e3;return 1/(1-(o-l)/(2*i/1e3)/n)}}class Lt{static at(t,e,a,n,i={}){const r=P.seenFrom(t,a,n);if(!r)return;const s=y.of(e),o=g.heardAt(t,a,n,{noise:g.noiseOfKind(s.kind),...i});return{hex:`${t.nonIcao?"~":""}${t.icao.toString(16).padStart(6,"0")}`,registration:e?.registration,typeCode:s.code??e?.type,name:s.basis==="nothing"?void 0:s.name,kind:s.kind,basis:s.basis,military:e?.military===!0,restricted:e?.restricted===!0,altitudeFt:r.altitudeFt,groundSpeedKt:r.groundSpeedKt,trackDeg:r.trackDeg,sky:r.sky,angularRateDegPerS:r.angularRateDegPerS,hearing:o&&{audible:o.audible,levelDbA:o.levelDbA,ambientDbA:o.ambientDbA,delayS:o.delayS,lagDeg:o.lagDeg,dopplerRatio:o.dopplerRatio,cutoffHz:o.cutoffHz,dominantHz:o.dominantHz}}}}class S{static REFERENCE_AMPLITUDE=.02;static MASKING_RANGE_DB=6;static KNEE_DB=20;static COMPRESSION_RATIO=1.5;static EXPANSION_RATIO=1.8;static DEFAULT_MAX_VOICES=4;static candidates(t,e,a,n,i={}){const r=new Set;for(const[s,o]of t){const l=g.noiseOfKind(y.of(e.get(a(o))).kind);let c=1/0;for(const d of o.points)c=Math.min(c,L.seenFrom(n,{lat:d.lat,lng:d.lng,heightM:d.altitudeFt*.3048}).distanceKm);g.couldBeHeard(l,c,i.ambientDbA,i.air)&&r.add(s)}return r}static voices(t,e,a,n,i,r,s={}){const o=[];for(const l of t){const c=e.get(l);if(!c||s.only?.(l)===!1)continue;const d=s.kindOf?.(l)??y.of(a.get(n(c))).kind,u=g.noiseOfKind(d),h=g.heardAt(c,i,r,{...s,noise:u});h?.audible&&o.push({id:l,kind:d,bandAmplitudes:S.bandAmplitudes(h.bandsDbA,h.ambientDbA,s.referenceAmplitude),levelDbA:h.levelDbA,distanceKm:h.sound.distanceKm,dopplerRatio:h.dopplerRatio,azimuthDeg:h.sound.azimuthDeg,elevationDeg:h.sound.altitudeDeg,character:u.character})}return o.sort((l,c)=>c.levelDbA-l.levelDbA).slice(0,s.maxVoices??S.DEFAULT_MAX_VOICES)}static bandAmplitudes(t,e,a=S.REFERENCE_AMPLITUDE){const n=g.ambientBands(e),i=g.DETECTION_MARGIN_DB,r=A.sumDb(t),s=r-e,o=s>=S.KNEE_DB?S.KNEE_DB+(s-S.KNEE_DB)/S.COMPRESSION_RATIO:S.KNEE_DB-(S.KNEE_DB-s)*S.EXPANSION_RATIO,l=a*10**(o/20);return t.map((c,d)=>{const u=Math.min(1,Math.max(0,(c-n[d]-i+S.MASKING_RANGE_DB)/(2*S.MASKING_RANGE_DB)));return u*u*(3-2*u)*l*10**((c-r)/20)})}static BANDS_HZ=A.OCTAVES}class K{static ENGLISH={unnamed:"Aircraft {hex}",altitude:"{value} ft",speed:"{value} kt",heading:"heading {value}°",position:"{distance} km away, {elevation}° above the horizon, {bearing}",heard:"Heard at {level} dB(A), left the aircraft {delay} s ago, {lag}° behind it: {character}{pitch}",rumble:"a low rumble, nothing above {cutoff} Hz",muffled:"a dull noise, nothing above {cutoff} Hz",broad:"a broad roar",pitchHigher:", pitch {percent} % higher",pitchLower:", pitch {percent} % lower",inaudible:"Not audible here: {level} dB(A) against {ambient} dB(A) of ambient noise",soundUnknown:"Its sound cannot be worked out: the aircraft was not recorded yet when it left",contrailPersistent:"Leaves a trail that lasts: the air is supersaturated over ice ({rhi} % over ice)",contrailShort:"Leaves a short-lived trail: the air is dry over ice ({rhi} % over ice)",military:"military",restricted:"identity withheld by its owner",candidate:"A compatible candidate, not an identification"};static RUMBLE_BELOW_HZ=500;static DULL_BELOW_HZ=2e3;static PITCH_NOTICED=.03;static text(t,e,a,n){const i=(u,h=0)=>u.toLocaleString(n,{maximumFractionDigits:h}),r=(u,h)=>u.replace(/\{(\w+)\}/g,(p,m)=>h[m]??`{${m}}`),s=[],o=[t.registration,t.name].filter(Boolean).join(" · ");s.push(o||r(e.unnamed,{hex:t.hex}));const l=[r(e.altitude,{value:i(Math.round(t.altitudeFt/100)*100)}),t.groundSpeedKt===void 0?void 0:r(e.speed,{value:i(Math.round(t.groundSpeedKt))}),t.trackDeg===void 0?void 0:r(e.heading,{value:i(Math.round(t.trackDeg))})].filter(Boolean);s.push(l.join(" · ")),s.push(r(e.position,{distance:i(t.sky.distanceKm,t.sky.distanceKm<10?1:0),elevation:i(Math.round(t.sky.altitudeDeg)),bearing:a(t.sky.azimuthDeg)}));const c=t.hearing;if(!c)s.push(e.soundUnknown);else if(!c.audible)s.push(r(e.inaudible,{level:i(Math.round(c.levelDbA)),ambient:i(Math.round(c.ambientDbA))}));else{const u=c.cutoffHz??0,h=u<=K.RUMBLE_BELOW_HZ?e.rumble:u<=K.DULL_BELOW_HZ?e.muffled:e.broad,p=c.dopplerRatio-1,m=Math.abs(p)<K.PITCH_NOTICED?"":r(p>0?e.pitchHigher:e.pitchLower,{percent:i(Math.round(Math.abs(p)*100))});s.push(r(e.heard,{level:i(Math.round(c.levelDbA)),delay:i(Math.round(c.delayS)),lag:i(Math.round(c.lagDeg)),character:r(h,{cutoff:i(u)}),pitch:m}))}t.contrail&&s.push(r(t.contrail.persistent?e.contrailPersistent:e.contrailShort,{rhi:i(Math.round(t.contrail.iceRelativeHumidity*100))}));const d=[t.military?e.military:void 0,t.restricted?e.restricted:void 0].filter(Boolean);return d.length>0&&s.push(d.join(" · ")),s.push(e.candidate),s.join(`
|
|
2
|
+
`)}}class X{static EFFICIENCY={"airliner-narrow":.34,"airliner-wide":.38,"regional-jet":.32,"business-jet":.3,"military-jet":.22,unknown:.34};static efficiencyOf(t){return X.EFFICIENCY[t]}}class E{static EMISSION_INDEX_WATER=1.25;static COMBUSTION_HEAT=432e5;static AIR_HEAT_CAPACITY=1004;static MOLAR_MASS_RATIO=.622;static FT_TO_M=.3048;static TROPOPAUSE_M=11e3;static TROPOPAUSE_HPA=226.32;static ISOTHERMAL_SCALE_HEIGHT_M=6341.62;static pressureAtAltitudeFt(t){const e=t*E.FT_TO_M;return e<=E.TROPOPAUSE_M?1013.25*(1-225577e-10*e)**5.25588:E.TROPOPAUSE_HPA*Math.exp(-(e-E.TROPOPAUSE_M)/E.ISOTHERMAL_SCALE_HEIGHT_M)}static saturationOverWaterPa(t){const e=t+273.15;return Math.exp(54.842763-6763.22/e-4.21*Math.log(e)+367e-6*e+Math.tanh(.0415*(e-218.8))*(53.878-1331.22/e-9.44523*Math.log(e)+.014025*e))}static saturationOverIcePa(t){const e=t+273.15;return Math.exp(9.550426-5723.265/e+3.53068*Math.log(e)-.00728332*e)}static iceRelativeHumidity(t,e){return e*E.saturationOverWaterPa(t)/E.saturationOverIcePa(t)}static mixingSlope(t,e){return E.EMISSION_INDEX_WATER*E.AIR_HEAT_CAPACITY*t*100/(E.MOLAR_MASS_RATIO*E.COMBUSTION_HEAT*(1-e))}static criticalTemperatureC(t,e){const a=Math.log(E.mixingSlope(t,e)-.053);return-46.46+9.43*a+.72*a*a}static requiredHumidity(t,e,a){const n=E.criticalTemperatureC(t,e);return(E.mixingSlope(t,e)*(a-n)+E.saturationOverWaterPa(n))/E.saturationOverWaterPa(a)}static evaluate(t,e){const a=E.criticalTemperatureC(t.pressureHpa,e),n=E.requiredHumidity(t.pressureHpa,e,t.temperatureC),i=E.iceRelativeHumidity(t.temperatureC,t.relativeHumidity),r=t.temperatureC<a&&t.relativeHumidity>=n;return{forms:r,persistent:r&&i>=1,criticalTemperatureC:a,requiredHumidity:n,iceRelativeHumidity:i}}}class H{static HOLD_MS=36e5;static DEG=Math.PI/180;static GAS_CONSTANT_OVER_GRAVITY=287.05/9.80665;static at(t,e,a){if(t.length===0)return;const n=t[0],i=t[t.length-1];if(e<n.t-H.HOLD_MS||e>i.t+H.HOLD_MS)return;const r=t.findIndex(h=>h.t>e),s=r===0?t[0]:t[(r===-1?t.length:r)-1],o=r===-1||r===0?s:t[r],l=H.atLevel(s,a),c=H.atLevel(o,a);if(!l||!c)return;const d=o.t>s.t?Math.min(1,Math.max(0,(e-s.t)/(o.t-s.t))):0,u=(h,p)=>h+(p-h)*d;return{pressureHpa:a,temperatureC:u(l.temperatureC,c.temperatureC),relativeHumidity:u(l.relativeHumidity,c.relativeHumidity),driftEastMs:u(l.driftEastMs,c.driftEastMs),driftNorthMs:u(l.driftNorthMs,c.driftNorthMs),shearPerS:u(l.shearPerS,c.shearPerS)}}static atLevel(t,e){const a=t.levels;for(let n=0;n+1<a.length;n++){const i=a[n],r=a[n+1];if(e>i.pressureHpa||e<r.pressureHpa)continue;const s=(Math.log(i.pressureHpa)-Math.log(e))/(Math.log(i.pressureHpa)-Math.log(r.pressureHpa)),o=(h,p)=>h+(p-h)*s,l=h=>{const p=h.windFromDeg*H.DEG;return{east:-h.windSpeedMs*Math.sin(p),north:-h.windSpeedMs*Math.cos(p)}},c=l(i),d=l(r),u=H.GAS_CONSTANT_OVER_GRAVITY*((i.temperatureC+r.temperatureC)/2+273.15)*Math.log(i.pressureHpa/r.pressureHpa);return{pressureHpa:e,temperatureC:o(i.temperatureC,r.temperatureC),relativeHumidity:o(i.relativeHumidity,r.relativeHumidity),driftEastMs:o(c.east,d.east),driftNorthMs:o(c.north,d.north),shearPerS:Math.hypot(d.east-c.east,d.north-c.north)/u}}}}class Pt{static plan(t,e,a,n){if(a.length===0)return[];const i=[];for(const r of t.objects){const s=t.flights.get(r.id),o=r.track;if(!s||!o||o.length!==s.points.length)continue;const l=e?.get(U.keyOf(s))??y.of(void 0),c=X.efficiencyOf(l.kind);if(c===void 0)continue;const d=[];o.forEach((u,h)=>{const p=s.points[h],m=E.pressureAtAltitudeFt(p.altitudeFt),f=H.at(a,n+u.t,m),b=f&&E.evaluate(f,c);d.push({tMs:u.t,eastM:u.eastM,northM:u.northM,upM:u.altitudeM??0,forms:b?.forms??!1,persistent:b?.persistent??!1,lifetimeS:b?Y.lifetimeS(b.iceRelativeHumidity):0,driftEastMs:f?.driftEastMs??0,driftNorthMs:f?.driftNorthMs??0,shearPerS:f?.shearPerS??Y.TYPICAL_SHEAR_PER_S,iceRelativeHumidity:b?.iceRelativeHumidity??0})}),d.some((u,h)=>u.forms&&d[h+1]?.forms)&&i.push({id:r.id,spanM:l.spanM,points:d})}return i}}const st=[500,400,300,250,200,150],Nt=["temperature","relative_humidity","wind_speed","wind_direction"],yt="2022-01-01",rt=.25;class wt{fetchImpl;baseUrl;cache=new Map;constructor(t={}){this.fetchImpl=t.fetchImpl??fetch.bind(globalThis),this.baseUrl=t.baseUrl??"https://historical-forecast-api.open-meteo.com/v1/forecast"}between(t,e,a){const n=new Date(e).toISOString().slice(0,10);if(n<yt)return Promise.resolve({status:"outside"});const i=new Date(a).toISOString().slice(0,10),s=`${`${Math.round(t.lat/rt)},${Math.round(t.lng/rt)}`}|${n}|${i}`;let o=this.cache.get(s);return o||(o=this.read(t,n,i),this.cache.set(s,o),o.then(l=>{l.status==="failed"&&this.cache.delete(s)})),o}async read(t,e,a){const n=st.flatMap(s=>Nt.map(o=>`${o}_${s}hPa`)),i=new URLSearchParams({latitude:String(t.lat),longitude:String(t.lng),start_date:e,end_date:a,hourly:n.join(","),wind_speed_unit:"ms",timezone:"UTC"}),r=`${this.baseUrl}?${i}`;try{const s=await this.fetchImpl(r);if(!s.ok)return{status:"failed"};const o=await s.json();return this.decode(o,{id:"open-meteo-forecast",name:"Open-Meteo (historical forecast)",url:r})}catch{return{status:"failed"}}}decode(t,e){const a=t.hourly;if(!a?.time)return{status:"outside"};const n=[];return a.time.forEach((i,r)=>{const s=[];for(const o of st){const l=p=>a[`${p}_${o}hPa`]?.[r],c=l("temperature"),d=l("relative_humidity"),u=l("wind_speed"),h=l("wind_direction");if([c,d,u,h].some(p=>p==null))return;s.push({pressureHpa:o,temperatureC:c,relativeHumidity:d/100,windSpeedMs:u,windFromDeg:h})}n.push({t:Date.parse(`${i}Z`),levels:s})}),n.length>0?{status:"found",samples:n,source:e}:{status:"outside"}}}const Ft=[{id:"open-meteo-forecast",name:"Open-Meteo (historical forecast)",credit:"Weather data by Open-Meteo.com, CC BY 4.0",creditUrl:"https://open-meteo.com/en/docs/historical-forecast-api",create:()=>new wt}];class B{static DEFAULT_KIND="airliner-narrow";static STEP_MS=1e3;static LEAD_MS=12e4;static FT_PER_M=1/.3048;static ID_PREFIX="decor:";tracks=new Map;static kindOf(t,e){if(t.kind!=="aircraft"||!B.flies(t))return;const a=t.aircraftSound??e??B.DEFAULT_KIND;return a==="silent"?void 0:a}static flies(t){return(t.track??[]).some(e=>(e.altitudeM??0)>0)}voices(t,e,a,n={},i=()=>{}){const r=t.event.place?.[0];if(!r)return[];const s=new Map,o=new Map,l=Math.max(t.observerTrack.duration,(t.event.durationSeconds??0)*1e3);for(const c of t.decor){const d=B.kindOf(c,i(c));if(!d)continue;const u=`${B.ID_PREFIX}${c.id}`;s.set(u,this.trackOf(c,u,r,l)),o.set(u,d)}for(const c of this.tracks.keys())s.has(c)||this.tracks.delete(c);return s.size===0?[]:S.voices(new Set(s.keys()),s,new Map,c=>String(c.icao),a,e,{...n,kindOf:c=>o.get(c)})}trackOf(t,e,a,n){const i=JSON.stringify([t.track,t.eastM,t.northM,t.headingDeg,a.lat,a.lng,n]),r=this.tracks.get(e);if(r?.key===i)return r.track;const s=[];for(let u=0;u<=n;u+=B.STEP_MS)s.push(B.pointAt(t,u,a));(s.length===0||s[s.length-1].t<n)&&s.push(B.pointAt(t,n,a));const o=s[0],l=s[1]??o,c=u=>(l[u]-o[u])/(l.t-o.t||1);for(let u=B.STEP_MS*10;u<=B.LEAD_MS;u+=B.STEP_MS*10)s.unshift({t:-u,lat:o.lat-c("lat")*u,lng:o.lng-c("lng")*u,altitudeFt:Math.max(0,o.altitudeFt-c("altitudeFt")*u)});const d={icao:B.hash(e),nonIcao:!0,points:s};return this.tracks.set(e,{key:i,track:d}),d}static pointAt(t,e,a){const n=ht(t,e),i=ut(n.eastM,-n.northM,a.lat,a.lng);return{t:e,lat:i.lat,lng:i.lng,altitudeFt:n.altitudeM*B.FT_PER_M}}static hash(t){let e=0;for(let a=0;a<t.length;a++)e=e*31+t.charCodeAt(a)>>>0;return e}}export{V as AircraftAudio,Pt as AircraftContrails,y as AircraftModels,I as AmbientNoise,B as DecorAircraftHearing,M as TrafficDecor,Lt as TrafficInfos,S as TrafficSound,K as TrafficTooltip,Ft as UPPER_AIR_SOURCES};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rr0/ufoathome",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.124.0",
|
|
5
5
|
"description": "UFO@home — record and replay a UFO sighting's shape, movement and appearance",
|
|
6
6
|
"author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
|
|
7
7
|
"license": "MIT",
|