@tileflow/core 0.1.0-alpha.36 → 0.1.0-alpha.38

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.
@@ -0,0 +1 @@
1
+ "use strict";(()=>{var Be=new Set,ln=!1;function Pe(e,n,t=2){let i=n&&n.length,o=i?n[0]*t:e.length;Be.size&&Be.clear();let r=jn(e,0,o,t,!0),s=[];if(!r||r.next===r.prev)return s;let a=0,l=0,u=0;if(i&&(r=wt(e,n,r,t)),e.length>80*t){a=e[0],l=e[1];let c=a,f=l;for(let d=t;d<o;d+=t){let g=e[d],m=e[d+1];g<a&&(a=g),m<l&&(l=m),g>c&&(c=g),m>f&&(f=m)}u=Math.max(c-a,f-l),u=u!==0?32767/u:0}return un(r,s,a,l,u),s}function jn(e,n,t,i,o){let r=null;if(o===Ut(e,n,t,i)>0)for(let s=n;s<t;s+=i)r=In(s/i|0,e[s],e[s+1],r);else for(let s=t-i;s>=n;s-=i)r=In(s/i|0,e[s],e[s+1],r);return r&&Ue(r,r.next)&&(Oe(r),r=r.next),r}function ge(e,n=e){let t=n===e,i=e,o;do o=!1,i!==i.next&&(Be.size===0||!Be.has(i))&&(Ue(i,i.next)||Q(i.prev,i,i.next)===0)?((t||i===n)&&(n=i.prev),ln=!0,Oe(i),i=i.prev,o=!0):(t||i!==n)&&(i=i.next,o=!t);while(o||i!==n);return n}function un(e,n,t,i,o){o&&At(e,t,i,o);let r=e,s=!1;for(;e.prev!==e.next;){let a=e.prev,l=e.next;if(Q(a,e,l)<0&&(o?yt(e,t,i,o):pt(e))){n.push(a.i,e.i,l.i),Oe(e),e=l,r=l;continue}if(e=l,e===r){if(ln=!1,e=ge(e),ln){r=e;continue}if(!s){e=bt(e,n),r=e,s=!0;continue}gt(e,n,t,i,o);break}}}function pt(e){let n=e.prev,t=e,i=e.next,o=n.x,r=t.x,s=i.x,a=n.y,l=t.y,u=i.y,c=Math.min(o,r,s),f=Math.min(a,l,u),d=Math.max(o,r,s),g=Math.max(a,l,u),m=i.next;for(;m!==n;){if(m.x>=c&&m.x<=d&&m.y>=f&&m.y<=g&&!(o===m.x&&a===m.y)&&Ke(o,a,r,l,s,u,m.x,m.y)&&Q(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function yt(e,n,t,i){let o=e.prev,r=e,s=e.next,a=o.x,l=r.x,u=s.x,c=o.y,f=r.y,d=s.y,g=Math.min(a,l,u),m=Math.min(c,f,d),p=Math.max(a,l,u),h=Math.max(c,f,d),A=mn(g,m,n,t,i),F=mn(p,h,n,t,i),k=e.prevZ;for(;k&&k.z>=A;){if(k.x>=g&&k.x<=p&&k.y>=m&&k.y<=h&&k!==s&&!(a===k.x&&c===k.y)&&Ke(a,c,l,f,u,d,k.x,k.y)&&Q(k.prev,k,k.next)>=0)return!1;k=k.prevZ}let M=e.nextZ;for(;M&&M.z<=F;){if(M.x>=g&&M.x<=p&&M.y>=m&&M.y<=h&&M!==s&&!(a===M.x&&c===M.y)&&Ke(a,c,l,f,u,d,M.x,M.y)&&Q(M.prev,M,M.next)>=0)return!1;M=M.nextZ}return!0}function bt(e,n){let t=e,i=!1;do{let o=t.prev,r=t.next.next;Nn(o,t,t.next,r,!1)&&Ce(o,r)&&Ce(r,o)&&(n.push(o.i,t.i,r.i),Oe(t),Oe(t.next),t=e=r,i=!0),t=t.next}while(t!==e);return i?ge(t):t}function gt(e,n,t,i,o){let r=e;do{let s=r.next.next;for(;s!==r.prev;){if(r.i!==s.i&&_t(r,s)){let a=Vn(r,s);r=ge(r,r.next),a=ge(a,a.next),un(r,n,t,i,o),un(a,n,t,i,o);return}s=s.next}r=r.next}while(r!==e)}var cn=!1;function wt(e,n,t,i){let o=[];for(let r=0,s=n.length;r<s;r++){let a=n[r]*i,l=r<s-1?n[r+1]*i:e.length,u=jn(e,a,l,i,!1);u===u.next&&Be.add(u),o.push(St(u))}o.sort(xt),Mt(e.length/i,n.length),zn(t,t),cn=!0;for(let r=0;r<o.length;r++)t=Tt(o[r],t);return cn=!1,ge(t)}function xt(e,n){return e.x-n.x||e.y-n.y||(e.next.y-e.y)/(e.next.x-e.x)-(n.next.y-n.y)/(n.next.x-n.x)}function Tt(e,n){let t=vt(e,n);if(!t)return n;let i=Vn(t,e),o=i.next;return zn(t,o.next),ge(i,i.next),ge(t,t.next)}var Dn=16,X=new Float64Array(0),Je=0,fn=[],dn=[];function Mt(e,n){let t=Math.ceil((e+2*n)/Dn)+n+2;X.length<t*4&&(X=new Float64Array(t*4)),Je=0}function zn(e,n){let t=e;do{let i=Je++;fn[i]=t;let o=t.x,r=t.y,s=t.x,a=t.y,l=0;do{let c=t.next;t.z=i,c.x<o&&(o=c.x),c.x>s&&(s=c.x),c.y<r&&(r=c.y),c.y>a&&(a=c.y),t=c}while(++l<Dn&&t!==n);dn[i]=t;let u=i*4;X[u]=o,X[u+1]=r,X[u+2]=s,X[u+3]=a}while(t!==n)}function kt(e,n){let t=e.z*4;n.x<X[t]&&(X[t]=n.x),n.y<X[t+1]&&(X[t+1]=n.y),n.x>X[t+2]&&(X[t+2]=n.x),n.y>X[t+3]&&(X[t+3]=n.y)}function Pn(e){let n=dn[e];for(;n.prev.next!==n;)n=n.next;return dn[e]=n,n}function En(e){let n=fn[e];for(;n.prev.next!==n;)n=n.next;return fn[e]=n,n}function vt(e,n){let t=n,i=e.x,o=e.y,r=-1/0,s;if(Ue(e,t))return t;for(let d=0,g=0;d<Je;d++,g+=4){if(o<X[g+1]||o>X[g+3]||X[g]>i||X[g+2]<=r)continue;let m=Pn(d);t=En(d);do{if(t.prev.next===t){if(Ue(e,t.next))return t.next;if(o<=t.y&&o>=t.next.y&&t.next.y!==t.y){let p=t.x+(o-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(p<=i&&p>r&&(r=p,s=t.x<t.next.x?t:t.next,p===i))return s}}t=t.next}while(t!==m)}if(!s)return null;let a=s.x,l=s.y,u=Math.min(o,l),c=Math.max(o,l),f=1/0;for(let d=0,g=0;d<Je;d++,g+=4){if(X[g+2]<a||X[g]>i||X[g+3]<u||X[g+1]>c)continue;let m=Pn(d);t=En(d);do{if(t.prev.next===t&&i>=t.x&&t.x>=a&&i!==t.x&&Ke(o<l?i:r,o,a,l,o<l?r:i,o,t.x,t.y)){let p=Math.abs(o-t.y)/(i-t.x);(Ce(t,e)||t.y===o&&t.next.y===o&&t.next.x>i)&&(p<f||p===f&&(t.x>s.x||t.x===s.x&&Rt(s,t)))&&(s=t,f=p)}t=t.next}while(t!==m)}return s}function Rt(e,n){return Q(e.prev,e,n.prev)<0&&Q(n.next,e,e.next)<0}var ie=[],ve=[],be=new Uint32Array(0),_e=new Uint32Array(0),Le=new Uint32Array(256);function At(e,n,t,i){let o=e,r=0;do o.z=mn(o.x,o.y,n,t,i),ie[r++]=o,o=o.next;while(o!==e);Ft(r);let s=null;for(let a=0;a<r;a++){let l=ie[a];l.prevZ=s,s&&(s.nextZ=l),s=l}s.nextZ=null,ie.fill(null,0,r),ve.fill(null,0,r)}function Ft(e){if(e<=32){for(let n=1;n<e;n++){let t=ie[n],i=t.z,o=n-1;for(;o>=0&&ie[o].z>i;)ie[o+1]=ie[o],o--;ie[o+1]=t}return}be.length<e&&(be=new Uint32Array(e),_e=new Uint32Array(e),ve=new Array(e));for(let n=0;n<e;n++)be[n]=ie[n].z;Xe(e,ie,be,ve,_e,0),Xe(e,ve,_e,ie,be,8),Xe(e,ie,be,ve,_e,16),Xe(e,ve,_e,ie,be,24)}function Xe(e,n,t,i,o,r){Le.fill(0);for(let a=0;a<e;a++)Le[t[a]>>>r&255]++;let s=0;for(let a=0;a<256;a++){let l=Le[a];Le[a]=s,s+=l}for(let a=0;a<e;a++){let l=t[a],u=Le[l>>>r&255]++;i[u]=n[a],o[u]=l}}function mn(e,n,t,i,o){return e=(e-t)*o|0,n=(n-i)*o|0,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e|n<<1}function St(e){let n=e,t=e;do(n.x<t.x||n.x===t.x&&n.y<t.y)&&(t=n),n=n.next;while(n!==e);return t}function Ke(e,n,t,i,o,r,s,a){return(o-s)*(n-a)>=(e-s)*(r-a)&&(e-s)*(i-a)>=(t-s)*(n-a)&&(t-s)*(r-a)>=(o-s)*(i-a)}function _t(e,n){let t=Ue(e,n)&&Q(e.prev,e,e.next)>0&&Q(n.prev,n,n.next)>0;return e.next.i!==n.i&&(t||Ce(e,n)&&Ce(n,e)&&(Q(e.prev,e,n.prev)!==0||Q(e,n.prev,n)!==0))&&!Lt(e,n)&&(t||Bt(e,n))}function Q(e,n,t){return(n.y-e.y)*(t.x-n.x)-(n.x-e.x)*(t.y-n.y)}function Ue(e,n){return e.x===n.x&&e.y===n.y}function Nn(e,n,t,i,o=!0){let r=Q(e,n,t),s=Q(e,n,i),a=Q(t,i,e),l=Q(t,i,n);return(r>0&&s<0||r<0&&s>0)&&(a>0&&l<0||a<0&&l>0)?!0:o?!!(r===0&&Ye(e,t,n)||s===0&&Ye(e,i,n)||a===0&&Ye(t,e,i)||l===0&&Ye(t,n,i)):!1}function Ye(e,n,t){return n.x<=Math.max(e.x,t.x)&&n.x>=Math.min(e.x,t.x)&&n.y<=Math.max(e.y,t.y)&&n.y>=Math.min(e.y,t.y)}function Lt(e,n){let t=Math.min(e.x,n.x),i=Math.max(e.x,n.x),o=Math.min(e.y,n.y),r=Math.max(e.y,n.y),s=e;do{let a=s.next;if(s.x>i&&a.x>i||s.x<t&&a.x<t||s.y>r&&a.y>r||s.y<o&&a.y<o){s=a;continue}if(s.i!==e.i&&a.i!==e.i&&s.i!==n.i&&a.i!==n.i&&Nn(s,a,e,n))return!0;s=a}while(s!==e);return!1}function Ce(e,n){return Q(e.prev,e,e.next)<0?Q(e,n,e.next)>=0&&Q(e,e.prev,n)>=0:Q(e,n,e.prev)<0||Q(e,e.next,n)<0}function Bt(e,n){let t=e,i=!1,o=(e.x+n.x)/2,r=(e.y+n.y)/2;do{let s=t.next;t.y>r!=s.y>r&&o<(s.x-t.x)*(r-t.y)/(s.y-t.y)+t.x&&(i=!i),t=s}while(t!==e);return i}function Vn(e,n){let t=hn(e.i,e.x,e.y),i=hn(n.i,n.x,n.y),o=e.next,r=n.prev;return e.next=n,n.prev=e,t.next=o,o.prev=t,i.next=t,t.prev=i,r.next=i,i.prev=r,i}function In(e,n,t,i){let o=hn(e,n,t);return i?(o.next=i.next,o.prev=i,i.next.prev=o,i.next=o):(o.prev=o,o.next=o),o}function Oe(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ),cn&&kt(e.prev,e.next)}function hn(e,n,t){return{i:e,x:n,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Ut(e,n,t,i){let o=0;for(let r=n,s=t-i;r<t;r+=i)o+=(e[s]-e[r])*(e[r+1]+e[s+1]),s=r;return o}var Ct=.7,Ot=Math.tan(Math.PI/6),Pt=Math.tan(35*Math.PI/180),Et=Math.tan(Math.PI/18),Ee=.65,Ie=.4;function Zn(e,n,t,i,o,r,s){let a=String(t.roof_shape??"");if(!a||a==="flat"||a==="sawtooth"||Nt(e,s))return;let l=Dt(e);if(!l||Math.abs(pn(e))<Ct*4*l.halfU*l.halfV)return;let u=l.u,c=l.v,f=l.halfU*r,d=l.halfV*r;f<d&&([u,c,f,d]=[c,u,d,f]);let g=Hn(t.roof_direction),m=g===void 0?void 0:[Math.sin(g*Math.PI/180),-Math.cos(g*Math.PI/180)];(m!==void 0?Math.abs(m[0]*u[0]+m[1]*u[1])>Math.abs(m[0]*c[0]+m[1]*c[1]):t.roof_orientation==="across")&&a!=="skillion"&&([u,c,f,d]=[c,u,d,f]);let h=jt(a,f,d,m&&[Gn(m,u),Gn(m,c)]);if(!h)return;let A=Math.max(0,o-i),F=Hn(t.roof_height),k,M;if(F!==void 0&&F>0)M=o,k=Math.max(i+Math.min(2,A/2),o-F);else{let y=Math.min(It(a,d),A*.6,30);t.height_source==="height"?(M=o,k=o-y):(k=o,M=o+y)}let B=M-k;if(!(B>=.3))return;let[C,E]=l.centre,O=(y,b)=>{let x=(y-C)*r,T=(b-E)*r;return[x*u[0]+T*u[1],x*c[0]+T*c[1]]},I=(y,b)=>[C+(y*u[0]+b*c[0])/r,E+(y*u[1]+b*c[1])/r],N=(y,b)=>{let x=1/0;for(let[T,S,v]of h)x=Math.min(x,T*y+S*b+v);return Math.max(0,x)},K=(y,b)=>k+B*N(...O(y,b)),G=[];for(let y=0;y+1<n.length;y+=2)G.push(...O(n[y],n[y+1]));pn(G)<0&&Vt(G);let z=1.01,Y=[-f*z,-d*z,f*z,-d*z,f*z,d*z,-f*z,d*z],w=[];for(let[y,b]of h.entries()){let x=Y;for(let[P,Z]of h.entries()){if(P===y)continue;let D=b[0]-Z[0],H=b[1]-Z[1],W=b[2]-Z[2];if(Math.abs(D)+Math.abs(H)+Math.abs(W)<1e-9){P<y&&(x=[]);continue}if(x=$n(x,D,H,W),x.length<6)break}if(x.length<6)continue;let T=G,S=x.length/2;for(let P=0;P<S&&T.length>=6;P+=1){let[Z,D]=[x[P*2],x[P*2+1]],[H,W]=[x[(P+1)%S*2],x[(P+1)%S*2+1]];T=$n(T,W-D,Z-H,-(W-D)*Z-(Z-H)*D)}if(T.length<6||Math.abs(pn(T))<1e-6)continue;let v=[];for(let P=0;P<T.length;P+=2)v.push(...I(T[P],T[P+1]));let L=Pe(v);if(L.length===0)continue;let U=B*b[0],V=B*b[1],R=U*u[0]+V*c[0],_=U*u[1]+V*c[1],j=Math.hypot(R,_,1);w.push({ring:v,triangles:L,normal:[-R/j,-_/j]})}if(w.length!==0)return{eaves:k,ridge:M,faces:w,heightAt:K,breaks(y,b,x,T){let[S,v]=O(y,b),[L,U]=O(x,T),V=[];for(let R=0;R<h.length;R+=1)for(let _=R+1;_<h.length;_+=1){let j=(ae,$)=>(h[R][0]-h[_][0])*ae+(h[R][1]-h[_][1])*$+(h[R][2]-h[_][2]),P=j(S,v),Z=j(L,U);if(P===Z||P*Z>0)continue;let D=P/(P-Z);if(D<=1e-4||D>=1-1e-4)continue;let H=S+(L-S)*D,W=v+(U-v)*D;h[R][0]*H+h[R][1]*W+h[R][2]<=N(H,W)+1e-6&&V.push(D)}return[...new Set(V.map(R=>Math.round(R*1e6)/1e6))].sort((R,_)=>R-_)}}}function It(e,n){switch(e){case"skillion":return 2*n*Et;case"pyramidal":return n*Pt;case"gambrel":case"mansard":return n*.8;case"dome":case"onion":case"round":return n;default:return n*Ot}}function jt(e,n,t,i){let o=n,r=t,s=[[0,-1/r,1],[0,1/r,1]],a=u=>u.flatMap(c=>[1,-1].flatMap(f=>{let d=c==="v"?r:o,g=Ee/(Ie*r)*f,m=(1-Ee)/((1-Ie)*r)*f,p=h=>c==="v"?[0,-h]:[-h,0];return[[...p(g),Ee/(Ie*r)*d],[...p(m),Ee+(1-Ee)/((1-Ie)*r)*(d-Ie*r)]]})),l=[.99,.8,.5,.2];switch(e){case"gabled":return s;case"saltbox":{let u=r/3;return[[0,-1/(r-u),1+u/(r-u)],[0,1/(r+u),1-u/(r+u)]]}case"hipped":return[...s,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...s,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...s,[-1/o,0,1],[1/o,0,1]];case"gambrel":return a(["v"]);case"mansard":return a(["v","u"]);case"skillion":{let[u,c]=i??[0,1],f=Math.abs(u)*o+Math.abs(c)*r;return f>0?[[-u/(2*f),-c/(2*f),.5]]:void 0}case"round":return[[0,0,1],...l.flatMap(u=>{let c=Math.sqrt(1-u*u);return[1,-1].map(f=>[0,-f*u/(r*c),1/c])})];case"dome":case"onion":return[[0,0,1],...l.flatMap(u=>{let c=Math.sqrt(1-u*u);return Array.from({length:12},(f,d)=>{let g=d*Math.PI/6;return[-u*Math.cos(g)/(o*c),-u*Math.sin(g)/(r*c),1/c]})})];default:return}}function $n(e,n,t,i){let o=e.length/2,r=[];for(let s=0;s<o;s+=1){let a=e[s*2],l=e[s*2+1],u=e[(s+1)%o*2],c=e[(s+1)%o*2+1],f=n*a+t*l+i,d=n*u+t*c+i;if(f<=0&&r.push(a,l),f<0&&d>0||f>0&&d<0){let g=f/(f-d);r.push(a+(u-a)*g,l+(c-l)*g)}}return r}function Dt(e){let n=zt(e);if(n.length<3)return;let t,i=1/0;for(let o=0;o<n.length;o+=1){let[r,s]=n[o],[a,l]=n[(o+1)%n.length],u=Math.hypot(a-r,l-s);if(u===0)continue;let c=[(a-r)/u,(l-s)/u],f=[-c[1],c[0]],[d,g,m,p]=[1/0,-1/0,1/0,-1/0];for(let[A,F]of n){let k=A*c[0]+F*c[1],M=A*f[0]+F*f[1];d=Math.min(d,k),g=Math.max(g,k),m=Math.min(m,M),p=Math.max(p,M)}let h=(g-d)*(p-m);if(h<i){i=h;let A=(d+g)/2,F=(m+p)/2;t={centre:[A*c[0]+F*f[0],A*c[1]+F*f[1]],u:c,v:f,halfU:(g-d)/2,halfV:(p-m)/2}}}return t&&t.halfU>0&&t.halfV>0?t:void 0}function zt(e){let n=[];for(let r=0;r+1<e.length;r+=2)n.push([e[r],e[r+1]]);n.sort((r,s)=>r[0]-s[0]||r[1]-s[1]);let t=(r,s,a)=>(s[0]-r[0])*(a[1]-r[1])-(s[1]-r[1])*(a[0]-r[0]),i=[];for(let r of n){for(;i.length>=2&&t(i.at(-2),i.at(-1),r)<=0;)i.pop();i.push(r)}let o=[];for(let r of n.reverse()){for(;o.length>=2&&t(o.at(-2),o.at(-1),r)<=0;)o.pop();o.push(r)}return[...i.slice(0,-1),...o.slice(0,-1)]}function Nt(e,n){let[t,i,o,r]=[1/0,-1/0,1/0,-1/0];for(let a=0;a+1<e.length;a+=2)t=Math.min(t,e[a]),i=Math.max(i,e[a]),o=Math.min(o,e[a+1]),r=Math.max(r,e[a+1]);let s=e.length/2;for(let a=0;a<s;a+=1){let l=e[a*2],u=e[a*2+1],c=e[(a+1)%s*2],f=e[(a+1)%s*2+1];if(t<0&&l===t&&c===t||i>n&&l===i&&c===i||o<0&&u===o&&f===o||r>n&&u===r&&f===r)return!0}return!1}function pn(e){let n=0,t=e.length/2;for(let i=0;i<t;i+=1){let o=(i+1)%t;n+=e[i*2]*e[o*2+1]-e[o*2]*e[i*2+1]}return n/2}function Vt(e){let n=[];for(let t=0;t<e.length;t+=2)n.push([e[t],e[t+1]]);n.reverse(),e.splice(0,e.length,...n.flat())}function Gn(e,n){return e[0]*n[0]+e[1]*n[1]}function Hn(e){let n=typeof e=="string"?Number(e):e;return typeof n=="number"&&Number.isFinite(n)?n:void 0}var jr=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),Dr=Object.freeze({unclassified:0,wall:1,door:2,roof:3,window:4,lamp:5,logo:6}),zr=Object.freeze([1,1,4,1,3]),Nr=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function yn(e,n,t){for(let i of e){let o=!1;for(let r=0,s=i.length-2;r<i.length;s=r,r+=2){let a=i[r],l=i[r+1],u=i[s],c=i[s+1];l>t!=c>t&&n<(u-a)*(t-l)/(c-l)+a&&(o=!o)}if(o)return!0}return!1}function Yn(e,n,t){for(let i of n){let[o,r,s,a]=Xn(i);for(let l of e){let[u,c,f,d]=Xn(l);if(!(u>s-t||f<o+t||c>a-t||d<r+t)&&(Wn(l,i,t)||Wn(i,l,t)))return!0}}return!1}function Wn(e,n,t){let i=(o,r)=>yn([n],o,r)&&Gt(n,o,r)>t;for(let o=0;o+1<e.length;o+=2)if(i(e[o],e[o+1]))return!0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[s,a,l,u]=[e[r],e[r+1],e[o],e[o+1]],c=[0,1];for(let f=0,d=n.length-2;f<n.length;d=f,f+=2){let g=$t(s,a,l,u,n[d],n[d+1],n[f],n[f+1]);g!==void 0&&c.push(g)}if(c.length!==2){c.sort((f,d)=>f-d);for(let f=1;f<c.length;f+=1){let d=(c[f-1]+c[f])/2;if(i(s+(l-s)*d,a+(u-a)*d))return!0}}}return!1}function $t(e,n,t,i,o,r,s,a){let l=t-e,u=i-n,c=s-o,f=a-r,d=l*f-u*c;if(d===0)return;let g=((o-e)*f-(r-n)*c)/d,m=((o-e)*u-(r-n)*l)/d;return g>0&&g<1&&m>=0&&m<=1?g:void 0}function Gt(e,n,t){let i=1/0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[s,a,l,u]=[e[r],e[r+1],e[o],e[o+1]],c=(l-s)**2+(u-a)**2,f=c>0?Math.max(0,Math.min(1,((n-s)*(l-s)+(t-a)*(u-a))/c)):0;i=Math.min(i,Math.hypot(n-(s+(l-s)*f),t-(a+(u-a)*f)))}return i}function Xn(e){let[n,t,i,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<e.length;r+=2)n=Math.min(n,e[r]),i=Math.max(i,e[r]),t=Math.min(t,e[r+1]),o=Math.max(o,e[r+1]);return[n,t,i,o]}var ue=8192,pe=28,Ht={commercial:1,active:2,destination:3},Zt={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(e=>[e,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(e=>[e,3]))},Wt={dining:1,entertainment:2,nightlife:3},bn={...Object.fromEntries(["alcoholic_beverage_venue","bar","beer","biergarten","brewery","casino","cocktail_bar","dance_club","karaoke","lounge","music_venue","night_club","nightclub","pub","wine_bar"].map(e=>[e,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(e=>[e,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(e=>[e,1]))},Xt=6,Yt=.5,Jt=.5;function Kn(e,n){let t=ue/e.extent,i=n.tileMetres/ue,o=n.defaultHeight??5,r=Number.isFinite(n.edgeRadius)?Math.max(0,Math.min(1,n.edgeRadius))/i:0,s=new wn,a=new Qe,l=new Qe,u=[],c=[],f=0,d=er(e,n);for(let[M,B]of e.features.entries()){if(B.type!=="Polygon"||u.length>=65535)continue;let C=B.properties,E=C.hide_3d;if(E===!0||E===1||E==="1"||E==="true")continue;let O=Math.min(6e3,Jn(C.render_height)??o),I=Math.min(O,Jn(C.render_min_height)??0),N=Ht[String(C.building_tone??"")]??0,K=Zt[String(C.class??"")]??0,G=d.get(M)??0,z=u.length,Y=!1;for(let w of nr(B.geometry,t)){let y=w.map(rr).filter(_=>_.length>=6);if(y.length===0||qe(y[0])===0)continue;let[b,x]=tr(y[0]);if(n.masks?.length&&(yn(n.masks,b,x)||Yn(y,n.masks,Jt/i)))continue;let T={cell:Math.min(7,Math.max(0,Math.floor(x/1024)))*8+Math.min(7,Math.max(0,Math.floor(b/1024))),bounds:[1/0,1/0,-1/0,-1/0,O+.1],roofsFrom:a.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let _ of y)for(let j=0;j<_.length;j+=2)T.bounds[0]=Math.min(T.bounds[0],_[j]-1),T.bounds[1]=Math.min(T.bounds[1],_[j+1]-1),T.bounds[2]=Math.max(T.bounds[2],_[j]+1),T.bounds[3]=Math.max(T.bounds[3],_[j+1]+1);Y=!0,f=Math.max(f,O);let S=Number.isSafeInteger(B.id),v=gn(S?B.id*.0731:b*.0731+x*.19137+z*1.618),L=!S&&y[0].some(_=>_<=0||_>=ue)?gn(O*7.31+I*3.17+N):v,U={base:I,top:O,jitter:L,seed:v,tone:N,group:K,use:G,cx:b,cy:x,index:z},V=y.map((_,j)=>qt(_,j,r,n.cornerAngle));y=V.map(({ring:_})=>_);let R=w.length===1&&y.length===1?Zn(w[0],y[0],C,I,O,i,ue):void 0;R?(f=Math.max(f,R.ridge),T.bounds[4]=Math.max(T.bounds[4],R.ridge+.1),Qt(R,U,s,a)):Kt(y,U,s,a);for(let[_,j]of y.entries()){let P=qe(j)>0,Z=_===0?P?-1:1:P?1:-1,D=j.length/2,H=$=>{let J=($%D+D)%D;return[j[J*2],j[J*2+1]]},W=$=>{let[J,re]=H($-1),[ne,te]=H($),[le,de]=H($+1);if(we(J,re,ne,te)||we(ne,te,le,de))return!1;let me=Math.hypot(ne-J,te-re),ce=Math.hypot(le-ne,de-te);if(!me||!ce)return!1;let De=-(te-re)/me*Z,ze=(ne-J)/me*Z;return(De*(le-ne)+ze*(de-te))/ce>Yt},se=($,J)=>R?R.heightAt($,J):O,ae=0;for(let $=0;$<D;$+=1){let[J,re]=H($),[ne,te]=H($+1),le=Math.hypot(ne-J,te-re);if(le===0||we(J,re,ne,te))continue;let de=-(te-re)/le*Z,me=(ne-J)/le*Z,ce=V[_].normals[$]??[de,me],De=V[_].normals[($+1)%D]??[de,me],ze=oe=>{let Ae=ce[0]+(De[0]-ce[0])*oe,Fe=ce[1]+(De[1]-ce[1])*oe,ye=Math.hypot(Ae,Fe)||1;return[Ae/ye,Fe/ye]},he=[0,...R?R.breaks(J,re,ne,te):[],1];for(let oe=0;oe+1<he.length;oe+=1){let Ae=oe===0,Fe=oe+2===he.length,ye=J+(ne-J)*he[oe],Ne=re+(te-re)*he[oe],Ve=J+(ne-J)*he[oe+1],$e=re+(te-re)*he[oe+1],Se=Math.hypot(Ve-ye,$e-Ne)/ue*n.tileMetres,Ge=(Ae&&W($)?1:0)|(Fe&&W($+1)?2:0)|(_>0?4:0),He=(Ae&&xn(J,re)?1:0)|(Fe&&xn(ne,te)?2:0),an=ae+Se,Ze=ze(he[oe]),We=ze(he[oe+1]),Cn=se(ye,Ne),On=se(Ve,$e),ke=s.write(U,ye,Ne,0,Ze[0],Ze[1],1,ae,Ge,Se,0,He,Cn);s.write(U,Ve,$e,0,We[0],We[1],1,an,Ge,Se,1,He,On),s.write(U,ye,Ne,1,Ze[0],Ze[1],1,ae,Ge,Se,0,He,Cn),s.write(U,Ve,$e,1,We[0],We[1],1,an,Ge,Se,1,He,On),l.push(ke,ke+1,ke+2,ke+1,ke+3,ke+2),ae=an}}}T.roofsCount=a.length-T.roofsFrom,T.wallsCount=l.length-T.wallsFrom,c.push(T)}Y&&u.push(C)}if(s.count===0)return;let g=new Uint32Array(a.length+l.length),m=[],p=0,h=a.length,A=a.values(),F=l.values();c.sort((M,B)=>M.cell-B.cell);let k=-1;for(let M of c){M.cell!==k&&(k=M.cell,m.push({bounds:[...M.bounds],roofsFrom:p,roofsCount:0,wallsFrom:h,wallsCount:0}));let B=m.at(-1);for(let C=0;C<5;C+=1)B.bounds[C]=C<2?Math.min(B.bounds[C],M.bounds[C]):Math.max(B.bounds[C],M.bounds[C]);g.set(A.subarray(M.roofsFrom,M.roofsFrom+M.roofsCount),p),g.set(F.subarray(M.wallsFrom,M.wallsFrom+M.wallsCount),h),p+=M.roofsCount,h+=M.wallsCount,B.roofsCount+=M.roofsCount,B.wallsCount+=M.wallsCount}return{groups:m,vertices:s.bytes(),indices:g,wallsFrom:a.length,top:f,features:u}}function Kt(e,n,t,i){let o=[],r=[];for(let l of e)r.push(o.length/2),o.push(...l);r.push(o.length/2);let s=(l,u)=>{let c=r.findIndex((p,h)=>l>=p&&l<r[h+1]),[f,d]=[r[c],r[c+1]-1];if(u<f||u>d)return!1;let[g,m]=l<u?[l,u]:[u,l];return m-g!==1&&!(g===f&&m===d)?!1:!we(o[l*2],o[l*2+1],o[u*2],o[u*2+1])},a=Pe(o,r.slice(1,-1),2);for(let l=0;l+2<a.length;l+=3){let u=[a[l],a[l+1],a[l+2]],c=0;for(let f=0;f<3;f+=1)s(u[(f+1)%3],u[(f+2)%3])&&(c|=1<<f);for(let[f,d]of u.entries())i.push(t.write(n,o[d*2],o[d*2+1],1,0,0,0,0,0,0,f,c))}}function Qt(e,n,t,i){for(let o of e.faces){let r=o.ring,s=r.length/2,a=(l,u)=>{let[c,f]=l<u?[l,u]:[u,l];return f-c!==1&&!(c===0&&f===s-1)?!1:!we(r[l*2],r[l*2+1],r[u*2],r[u*2+1])};for(let l=0;l+2<o.triangles.length;l+=3){let u=[o.triangles[l],o.triangles[l+1],o.triangles[l+2]],c=0;for(let f=0;f<3;f+=1)a(u[(f+1)%3],u[(f+2)%3])&&(c|=1<<f);for(let[f,d]of u.entries()){let g=r[d*2],m=r[d*2+1];i.push(t.write(n,g,m,1,o.normal[0],o.normal[1],0,0,0,0,f,c,e.heightAt(g,m)))}}}}function qt(e,n,t,i=Math.PI/6){if(t<1)return{ring:e,normals:[]};let o=e.length/2,r=qe(e)>0,s=n===0?r?-1:1:r?1:-1,a=[],l=[],u=(c,f,d)=>{c=Re(c),f=Re(f),!(a.at(-2)===c&&a.at(-1)===f)&&(a.push(c,f),l.push(d))};for(let c=0;c<o;c+=1){let f=(c+o-1)%o,d=(c+1)%o,[g,m]=[e[f*2],e[f*2+1]],[p,h]=[e[c*2],e[c*2+1]],[A,F]=[e[d*2],e[d*2+1]],k=Math.hypot(p-g,h-m),M=Math.hypot(A-p,F-h);if(!k||!M||xn(p,h)||we(g,m,p,h)||we(p,h,A,F)){u(p,h);continue}let[B,C]=[(p-g)/k,(h-m)/k],[E,O]=[(A-p)/M,(F-h)/M],I=Math.atan2(B*O-C*E,B*E+C*O);if(I*s>=-.08||Math.abs(I)>Math.PI*.85){u(p,h);continue}let N=Math.tan(Math.abs(I)/2),K=Math.min(t*N,k*.2,M*.2),G=K/N;if(G<1){u(p,h);continue}let[z,Y]=[-C*s,B*s],[w,y]=[p-B*K-z*G,h-C*K-Y*G],b=Math.atan2(Y,z),x=Math.max(i<=Math.PI/6?2:1,Math.ceil(Math.abs(I)/Math.max(Math.PI/6,Math.min(Math.PI/3,i))));for(let T=0;T<=x;T+=1){let S=b+I*T/x,v=[Math.cos(S),Math.sin(S)];u(w+v[0]*G,y+v[1]*G,v)}}return a[0]===a.at(-2)&&a[1]===a.at(-1)&&(a.splice(-2),l.pop()),a.length>=6?{ring:a,normals:l}:{ring:e,normals:[]}}var wn=class{#e=new ArrayBuffer(4096*pe);#n=new DataView(this.#e);count=0;write(n,t,i,o,r,s,a,l,u,c,f=0,d=0,g=n.top){let m=pe;if((this.count+1)*m>this.#e.byteLength){let A=new ArrayBuffer(this.#e.byteLength*2);new Uint8Array(A).set(new Uint8Array(this.#e)),this.#e=A,this.#n=new DataView(A)}let p=this.#n,h=this.count*m;return p.setUint16(h,Re(t),!0),p.setUint16(h+2,Re(i),!0),p.setUint16(h+4,Math.min(65535,Math.round(n.base*10)),!0),p.setUint16(h+6,Math.min(65535,Math.max(0,Math.round(g*10))),!0),p.setInt8(h+8,Math.round(r*127)),p.setInt8(h+9,Math.round(s*127)),p.setUint8(h+10,o),p.setUint8(h+11,a),p.setUint16(h+12,Math.round(l*10)%65536,!0),p.setUint8(h+14,Math.round(n.jitter*255)),p.setUint8(h+15,Math.round(n.seed*255)),p.setUint16(h+16,Re(n.cx),!0),p.setUint16(h+18,Re(n.cy),!0),p.setUint8(h+20,n.tone+4*n.group),p.setUint8(h+21,n.use),p.setUint8(h+22,u),p.setUint8(h+23,Math.min(255,Math.round(c))),p.setUint16(h+24,n.index,!0),p.setUint8(h+26,f),p.setUint8(h+27,d),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#e.slice(0,this.count*pe))}},Qe=class{#e=new Uint32Array(8192);length=0;push(...n){if(this.length+n.length>this.#e.length){let t=new Uint32Array(Math.max(this.length+n.length,this.#e.length*2));t.set(this.#e),this.#e=t}for(let t of n)this.#e[this.length++]=t}values(){return this.#e.subarray(0,this.length)}};function er(e,n){let t=new Map;if(e.features.some(a=>a.properties.night_use!==void 0)){for(let[a,l]of e.features.entries()){let u=Wt[String(l.properties.night_use??"")];u&&t.set(a,u)}return t}let i=n.places;if(!i||i.features.length===0)return t;let o=Xt/n.tileMetres*e.extent,r=[];for(let[a,l]of e.features.entries()){if(l.type!=="Polygon")continue;let u=l.properties.hide_3d;if(u===!0||u===1||u==="1"||u==="true")continue;let c=[1/0,1/0,-1/0,-1/0];for(let f of l.geometry)for(let d=0;d+1<f.length;d+=2)c[0]=Math.min(c[0],f[d]),c[1]=Math.min(c[1],f[d+1]),c[2]=Math.max(c[2],f[d]),c[3]=Math.max(c[3],f[d+1]);r.push({index:a,rings:l.geometry,box:c})}let s=e.extent/i.extent;for(let a of i.features){if(a.type!=="Point")continue;let l=a.properties,u=bn[String(l.type??"")]??bn[String(l.subclass??"")]??bn[String(l.class??"")];if(u)for(let c of a.geometry)for(let f=0;f+1<c.length;f+=2){let d=c[f]*s,g=c[f+1]*s,m,p,h=o;for(let F of r){let[k,M,B,C]=F.box;if(d<k-o||d>B+o||g<M-o||g>C+o)continue;let E=!1;for(let O of F.rings)for(let I=0,N=O.length-2;I+1<O.length;N=I,I+=2){let K=O[N],G=O[N+1],z=O[I],Y=O[I+1];Y>g!=G>g&&d<(K-z)*(g-Y)/(G-Y)+z&&(E=!E);let w=z-K,y=Y-G,b=w*w+y*y,x=b?Math.min(1,Math.max(0,((d-K)*w+(g-G)*y)/b)):0,T=Math.hypot(d-K-x*w,g-G-x*y);T<h&&(h=T,p=F.index)}if(E){m=F.index;break}}let A=m??p;A!==void 0&&t.set(A,Math.max(t.get(A)??0,u))}}return t}function Re(e){return Math.min(ue,Math.max(0,Math.round(e)))}function Jn(e){let n=Number(e);return Number.isFinite(n)&&n>0?n:void 0}function nr(e,n){let t=[],i;for(let o of e){let r=[];for(let a=0;a+1<o.length;a+=2){let l=o[a]*n,u=o[a+1]*n;r.length&&l===r.at(-2)&&u===r.at(-1)||r.push(l,u)}if(r.length>=6&&r[0]===r.at(-2)&&r[1]===r.at(-1)&&(r.length-=2),r.length<6)continue;let s=Math.sign(qe(r));s&&(i??=s,s===i||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function qe(e){let n=0,t=e.length/2;for(let i=0;i<t;i+=1){let o=(i+1)%t;n+=e[i*2]*e[o*2+1]-e[o*2]*e[i*2+1]}return n}function tr(e){let n=0,t=0,i=e.length/2;for(let o=0;o<i;o+=1)n+=e[o*2],t+=e[o*2+1];return[n/i,t/i]}function xn(e,n){return e<=0||n<=0||e>=ue||n>=ue}function we(e,n,t,i){let o=ue;return e<=0&&t<=0||e>=o&&t>=o||n<=0&&i<=0||n>=o&&i>=o}function rr(e){let n=ue,t=[...e],i=[[0,0,o=>o>=0],[0,n,o=>o<=n],[1,0,o=>o>=0],[1,n,o=>o<=n]];for(let[o,r,s]of i){let a=[],l=t.length/2;for(let u=0;u<l;u+=1){let c=[t[u*2],t[u*2+1]],f=[t[(u+1)%l*2],t[(u+1)%l*2+1]],d=s(c[o]),g=s(f[o]);if(d&&a.push(c[0],c[1]),d!==g){let m=(r-c[o])/(f[o]-c[o]);a.push(c[0]+(f[0]-c[0])*m,c[1]+(f[1]-c[1])*m)}}if(t=a,t.length<6)return[]}return t}function gn(e){let n=Math.sin(e*12.9898+78.233)*43758.5453;return n-Math.floor(n)}function Qn(e){let n=(e.indices.length-e.wallsFrom)/6,t=new Uint8Array(n*16);for(let i=0;i<n;i+=1){let o=e.wallsFrom+i*6,r=e.indices[o]*pe,s=e.indices[o+1]*pe;t.set(e.vertices.subarray(r,r+4),i*16),t.set(e.vertices.subarray(s,s+4),i*16+4),t.set(e.vertices.subarray(r+4,r+8),i*16+8),t[i*16+12]=e.vertices[r+14],t[i*16+13]=e.vertices[r+27]}return t}function qn(e){let n=e.vertices.length/pe;return{mesh:{...e.groups?{groups:e.groups}:{},wallsFrom:e.wallsFrom,top:e.top,features:e.features,vertexCount:n,indexCount:e.indices.length},data:{vertices:e.vertices,indices:n<=65536?Uint16Array.from(e.indices):e.indices,contact:Qn(e)}}}function et({data:e}){return[e.vertices.buffer,e.indices.buffer,e.contact.buffer]}function Tn(e){let n=or(e);return n?[Math.min(1,Math.max(0,n[0]/255)),Math.min(1,Math.max(0,n[1]/255)),Math.min(1,Math.max(0,n[2]/255)),Math.min(1,Math.max(0,n[3]))]:null}function or(e){let n=e.trim().toLowerCase(),t=/^#([\da-f]{3,8})$/u.exec(n)?.[1];if(t&&[3,4,6,8].includes(t.length)){let A=(t.length<=4?[...t].map(F=>F+F).join(""):t).match(/../gu).map(F=>Number.parseInt(F,16));return[A[0],A[1],A[2],A.length>3?A[3]/255:1]}let i=/^(rgba?|hsla?)\(([^)]+)\)$/u.exec(n);if(!i)return null;let o=i[2].split(/[\s,/]+/u).filter(Boolean);if(o.length<3||o.length>4)return null;let r=o[3]===void 0?1:en(o[3],1);if(i[1].startsWith("rgb")){let[h,A,F]=o.slice(0,3).map(k=>en(k,255));return[h,A,F,r].some(k=>k===null)?null:[h,A,F,r]}let s=Number.parseFloat(o[0]),a=en(o[1],1),l=en(o[2],1);if(!Number.isFinite(s)||a===null||l===null||r===null)return null;let u=(1-Math.abs(2*l-1))*a,c=(s%360+360)%360/60,f=u*(1-Math.abs(c%2-1)),[d,g,m]=c<1?[u,f,0]:c<2?[f,u,0]:c<3?[0,u,f]:c<4?[0,f,u]:c<5?[f,0,u]:[u,0,f],p=l-u/2;return[(d+p)*255,(g+p)*255,(m+p)*255,r]}function en(e,n){let t=e.endsWith("%"),i=Number.parseFloat(e);return Number.isFinite(i)?t?i/100*n:i:null}var fe=8192,Mn=12,ir={primary:{spacing:30,kerb:10},secondary:{spacing:32,kerb:8.5},tertiary:{spacing:34,kerb:7},minor:{spacing:36,kerb:5.5},pedestrian:{spacing:26,kerb:3}},sr={motorway:{spacing:45,kerb:14},trunk:{spacing:40,kerb:12},service:{spacing:38,kerb:4},path:{spacing:28,kerb:1.5},track:{spacing:40,kerb:2.5}},ar={spacing:30,kerb:0},lr=12,ur=18,cr=9,fr={sodium:"#ffa64d",high_pressure_sodium:"#ffa64d",low_pressure_sodium:"#ff9326",led:"#fff1db",mercury:"#e3ecff",metal_halide:"#f4f4ff",fluorescent:"#eef9ff",electric:"#ffd59a",incandescent:"#ffd59a",halogen:"#ffe3b0",gas:"#ffd28a",gaslight:"#ffd28a"},dr={orange:"#ffa64d",yellow:"#ffd27a",warm_white:"#ffe2b8",white:"#fff4e6",cool_white:"#eef4ff",neutral_white:"#fff8f0",blue:"#b8d0ff",green:"#c4ffd0",red:"#ff9a8a"},mr=new Set(["wall","wall_mounted","wall_bracket"]),hr=new Set(["suspended","cable","catenary","wire"]);function ot(e,n){let t=n.estimate??"lit",i=new vn,o=new nn(ur),r=new nn(cr),s=e.streetFurniture;if(s){let m=fe/s.extent;for(let p of s.features){let h=p.properties;if(p.type!=="Point"||h.subclass!=="street_lamp")continue;let A=pr(h),F=yr(h),k=Math.max(1,Math.min(4,Number(h.light_count)||1)),M=Math.min(24,Math.max(5,A*1.7*(k>1?1.15:1)));for(let B of p.geometry)for(let C=0;C+1<B.length;C+=2){let E=B[C]*m,O=B[C+1]*m;if(!tt(E,O))continue;i.write(E,O,A,M,!0,F);let I=rt(E,O,n.tileMetres);o.add(...I),r.add(...I)}}}let a=i.count;if(t==="none")return i.done(a);let l=e.transportation,u=0;if(l){let m=n.tileMetres/l.extent;for(let p of l.features)if(!(p.type!=="LineString"||!nt(p.properties,"streets")))for(let h of p.geometry)u+=br(h,l.extent)*m}let c=u>0&&a/(u/1e3)>=lr,f=t==="streets"&&!c?"streets":"lit",d=(m,p,h,A)=>{let F=fe/m.extent,k=[];for(let N=0;N+1<p.length;N+=2)k.push(p[N]*F,p[N+1]*F);let M=n.tileMetres/fe,B=h.kerb*.45/M,C=Rn(k[0]*.013+k[1]*.029+A)*h.spacing,E=0,O=C,I=Rn(k[0]+k[1]*3.1)<.5?1:-1;for(let N=2;N<k.length;N+=2){let K=k[N-2],G=k[N-1],z=k[N],Y=k[N+1],w=Math.hypot(z-K,Y-G)*M;if(w!==0){for(;O<=E+w;){let y=(O-E)/w,b=-(Y-G)/(w/M)*I,x=(z-K)/(w/M)*I,T=K+(z-K)*y+b*B,S=G+(Y-G)*y+x*B;if(O+=h.kerb>0?h.spacing/2:h.spacing,h.kerb>0&&(I=-I),!tt(T,S))continue;let v=rt(T,S,n.tileMetres);o.near(...v)||r.near(...v)||(r.add(...v),i.write(T,S,7,15,!1))}E+=w}}};if(l)for(let m of l.features){if(m.type!=="LineString")continue;let p=nt(m.properties,f);if(p)for(let h of m.geometry)d(l,h,p,1)}let g=e.sidewalks;if(g){for(let m of g.features)if(!(m.type==="Point"||!kn(m.properties.lit)))for(let p of m.geometry)d(g,p,ar,2)}return i.done(a)}function nt(e,n){if(e.brunnel==="tunnel"||e.covered===!0)return;let t=e.lit;if(t===!1||t==="no")return;let i=String(e.class??""),o=String(e.subclass??""),r=i==="path"&&(o==="pedestrian"||o==="living_street")?"pedestrian":i,s=ir[r];if(s&&(n==="streets"||kn(t)))return s;let a=sr[r];if(a&&kn(t))return a}function kn(e){return e===!0||e==="yes"||e===1||e==="1"||e==="true"}function pr(e){let n=Number(e.height);if(Number.isFinite(n)&&n>=.5&&n<=40)return n;let t=String(e.lamp_mount??"").toLowerCase();return mr.has(t)?4.5:hr.has(t)?7.5:7}function yr(e){let n=e.light_colour??e["light:colour"];if(typeof n=="string"&&n){let r=n.toLowerCase().replaceAll(" ","_"),s=Tn(dr[r]??n);if(s)return[s[0],s[1],s[2]]}let t=String(e.lamp_type??"").toLowerCase().replaceAll("-","_").replaceAll(" ","_"),i=fr[t];if(!i)return;let o=Tn(i);return[o[0],o[1],o[2]]}function tt(e,n){return e>=0&&n>=0&&e<fe&&n<fe}function rt(e,n,t){return[e/fe*t,n/fe*t]}function br(e,n){let t=0;for(let i=2;i+1<e.length;i+=2){let o=(e[i-2]+e[i])/2,r=(e[i-1]+e[i+1])/2;o<0||r<0||o>=n||r>=n||(t+=Math.hypot(e[i]-e[i-2],e[i+1]-e[i-1]))}return t}var nn=class{constructor(n){this.size=n}size;#e=new Map;add(n,t){let i=`${Math.floor(n/this.size)}/${Math.floor(t/this.size)}`,o=this.#e.get(i);o?o.push([n,t]):this.#e.set(i,[[n,t]])}near(n,t){let i=Math.floor(n/this.size),o=Math.floor(t/this.size);for(let r=-1;r<=1;r+=1)for(let s=-1;s<=1;s+=1)for(let[a,l]of this.#e.get(`${i+r}/${o+s}`)??[])if(Math.hypot(a-n,l-t)<this.size)return!0;return!1}},vn=class{#e=new ArrayBuffer(256*Mn);#n=new DataView(this.#e);count=0;write(n,t,i,o,r,s){let a=Mn;if((this.count+1)*a>this.#e.byteLength){let c=new ArrayBuffer(this.#e.byteLength*2);new Uint8Array(c).set(new Uint8Array(this.#e)),this.#e=c,this.#n=new DataView(c)}let l=this.#n,u=this.count*a;l.setUint16(u,Math.min(fe,Math.max(0,Math.round(n))),!0),l.setUint16(u+2,Math.min(fe,Math.max(0,Math.round(t))),!0),l.setUint16(u+4,Math.min(65535,Math.round(i*10)),!0),l.setUint8(u+6,Math.min(255,Math.round(o))),l.setUint8(u+7,(r?1:0)|(s?2:0)),l.setUint8(u+8,Math.round((s?.[0]??0)*255)),l.setUint8(u+9,Math.round((s?.[1]??0)*255)),l.setUint8(u+10,Math.round((s?.[2]??0)*255)),l.setUint8(u+11,Math.round(Rn(n*.0731+t*.19137)*255)),this.count+=1}done(n){if(this.count!==0)return{lamps:new Uint8Array(this.#e.slice(0,this.count*Mn)),mapped:n,estimated:this.count-n}}};function Rn(e){let n=Math.sin(e*12.9898+78.233)*43758.5453;return n-Math.floor(n)}var eo=Object.freeze({source:"tileflow",layers:Object.freeze({streetFurniture:"street_furniture",transportation:"transportation",sidewalk:"sidewalk"}),estimate:"lit",color:"#ffcf8f",intensity:1});var Me=["round","open","tall","conifer","columnar","palm"];var lt=Object.freeze({height:"height",diameterCrown:"diameter_crown",circumference:"circumference",genus:"genus",species:"species",leafType:"leaf_type",taxon:"taxon",canopyRank:"rank",canopyClearance:"clearance_px_z15",canopyCover:"cover_pct",canopyHeight:"height_m",subclass:"subclass",hedge:"hedge"}),_n=Object.freeze({round:1,open:1,tall:1}),tn=16,q=8192,rn=16,it={round:{height:[6,12],crown:[.66,.9]},open:{height:[5,10],crown:[.8,1.05]},tall:{height:[9,17],crown:[.48,.68]},conifer:{height:[7,16],crown:[.46,.66]},columnar:{height:[9,18],crown:[.22,.36]},palm:{height:[12,20],crown:[.42,.55]}};var on=[2,45],An=[1,25],gr=1.2,wr=1.5,xr=1.3,Tr=1,Mr=[.3,10],kr=new Set(["acoelorrhaphe","adonidia","archontophoenix","areca","arecastrum","bismarckia","brahea","butia","caryota","chamaedorea","chamaerops","coccothrinax","cocos","dypsis","elaeis","howea","jubaea","licuala","livistona","phoenix","pritchardia","ptychosperma","rhapis","roystonea","sabal","serenoa","syagrus","thrinax","trachycarpus","veitchia","washingtonia","wodyetia"]),vr=new Set(["cupressus","calocedrus","thuja"]),Rr=new Set(["abies","araucaria","cedrus","cryptomeria","juniperus","larix","metasequoia","picea","pinus","pseudotsuga","sequoia","sequoiadendron","taxodium","taxus","tsuga"]),st=Object.freeze({...Object.fromEntries(["acacia","albizia","arbutus","bursera","ceratonia","delonix","ficus","gleditsia","jacaranda","olea","quercus","robinia","schinus","sophora","styphnolobium","tamarix"].map(e=>[e,"open"])),...Object.fromEntries(["betula","celtis","fraxinus","ginkgo","liquidambar","liriodendron","magnolia","platanus","populus","pyrus","ulmus","zelkova"].map(e=>[e,"tall"])),...Object.fromEntries(["acer","aesculus","carpinus","castanea","catalpa","cercis","citrus","fagus","lagerstroemia","malus","melia","morus","prunus","sorbus","tilia"].map(e=>[e,"round"]))});function at(e,n={}){let t={...lt,...n},i=String(e[t.leafType]??"").toLowerCase(),o=String(e[t.taxon]??"").toLowerCase(),r=Fn(e[t.genus])??Fn(e[t.species])??(o.endsWith("aceae")?void 0:Fn(e[t.taxon]));if(i.includes("palm")||o.includes("arecaceae")||r&&kr.has(r))return"palm";if(o.includes("cupressaceae")&&!r||o.includes("pinaceae"))return"conifer";if(r&&vr.has(r))return"columnar";if(i.includes("needle")||r&&Rr.has(r))return"conifer";if(r&&st[r])return st[r];if(i.includes("broad"))return"broadleaf"}function Fn(e){return typeof e!="string"?void 0:e.trim().split(/\s+/u)[0]?.toLowerCase()||void 0}function ut(e,n){let t={...lt,...n.fields},i=n.unknownForms??_n,o=n.regions??[],{tile:r,tileMetres:s}=n,a=s/2**(15-r.z)/512,l=[],u=0,c=0,f=(w,y)=>{if(o.length===0)return i;let[b,x]=Fr(r,w/q,y/q);for(let T of o){let[S,v,L,U]=T.bounds;if(x>=v&&x<=U&&(S<=L?b>=S&&b<=L:b>=S||b<=L))return T.unknownForms}return i},d=(w,y,b,x)=>{let T=it[w],S=Sn(T.crown,ee(y,2)),v=b,L=x;return v===void 0&&L===void 0&&(v=Sn(T.height,ee(y,1))),v===void 0&&(v=L/S),L===void 0&&(L=v*S),{height:Te(v,on),crown:Te(L,An)}},g=(w,y,b,x,T,S)=>{let v=je(r.x*q+w,r.y*q+y,7),L=b===void 0?sn(f(w,y),ee(v,3)):b==="broadleaf"?sn(Ar(f(w,y)),ee(v,3)):b,U=x===void 0&&T===void 0&&S!==void 0?d(L,v,Sn(it[L].height,Te(S/3,[0,1]))):d(L,v,x,T);l.push({x:w,y,...U,turn:ee(v,4),shade:ee(v,5),form:Me.indexOf(L),rank:Math.floor(ee(v,6)*rn),seed:ee(v,7),canopy:0}),u+=1},m=e.trees;if(m){let w=q/m.extent;for(let y of m.features){if(y.type!=="Point")continue;let b=y.properties,x=at(b,t),T=xe(b[t.height],on),S=xe(b[t.diameterCrown],An),v=xe(b[t.circumference],[.1,12]);for(let L of y.geometry)for(let U=0;U+1<L.length;U+=2){let V=L[U],R=L[U+1];V<0||R<0||V>=m.extent||R>=m.extent||g(V*w,R*w,x,T,S,v)}}}let p=e.trees;if(p){let w=q/p.extent,y=(n.rowSpacing??8)/(s/p.extent);for(let b of p.features){if(b.type!=="LineString")continue;let x=b.properties,T=at(x,t),S=xe(x[t.height],on),v=xe(x[t.diameterCrown],An);for(let L of b.geometry){let U=y/2,V=0;for(let R=0;R+3<L.length;R+=2){let[_,j,P,Z]=[L[R],L[R+1],L[R+2],L[R+3]],D=Math.hypot(P-_,Z-j);for(;U<=V+D;){let H=D>0?(U-V)/D:0,W=_+(P-_)*H,se=j+(Z-j)*H;W>=0&&se>=0&&W<p.extent&&se<p.extent&&g(W*w,se*w,T,S,v,void 0),U+=y}V+=D}}}}let h=e.canopy;if(h){let w=q/h.extent;for(let y of h.features){if(y.type!=="Point")continue;let b=y.properties,x=Te(Math.floor(Number(b[t.canopyRank]??8)),[0,rn-1]),T=Math.max(4,Number(b[t.canopyClearance]??8))*a,S=Number(b[t.canopyCover]??30),v=S>=60?3:S>=30?2:1;for(let L of y.geometry)for(let U=0;U+1<L.length;U+=2){let V=L[U],R=L[U+1];if(V<0||R<0||V>=h.extent||R>=h.extent)continue;let _=V*w,j=R*w,P=je(r.x*q+_,r.y*q+j,11),Z=f(_,j),D=xe(b[t.canopyHeight],on),H=Te(Math.min(T*(v===1?1.3:.95),11)*(.85+ee(P,1)*.25),[3,12]),W=Math.max(0,Math.min(H*.5,T-H/2)),se=ee(P,2)*Math.PI*2;for(let ae=0;ae<v;ae+=1){let $=je(P,ae,13),J=se+ae*Math.PI*2/v,re=v===1?W*.3*ee($,1):W,ne=_+Math.cos(J)*re/(s/q),te=j+Math.sin(J)*re/(s/q);if(ne<0||te<0||ne>=q||te>=q)continue;let le=sn(Z,ee($,3)),de=H*(.85+ee($,2)*.3),me=D===void 0?void 0:D*(.85+ee($,6)*.25),ce=d(le,$,me,le==="palm"?void 0:de);l.push({x:ne,y:te,...ce,turn:ee($,4),shade:ee($,5),form:Me.indexOf(le),rank:x,seed:ee($,7),canopy:1}),c+=1}}}}let A=[],F=e.hedges;if(F){let w=q/F.extent,y=gr/(s/F.extent);for(let b of F.features){if(b.type==="Point"||b.properties[t.subclass]!=="hedge")continue;let x=b.properties[t.hedge]==="hedge_bank"?Tr:0,T=(xe(b.properties[t.height],Mr)??xr)+x;for(let S of b.geometry){let v=y/2,L=0;for(let U=0;U+3<S.length;U+=2){let[V,R,_,j]=[S[U],S[U+1],S[U+2],S[U+3]],P=Math.hypot(_-V,j-R),Z=(Math.atan2(R-j,_-V)/(Math.PI*2)+1)%1;for(;v<=L+P;){let D=P>0?(v-L)/P:0,H=V+(_-V)*D,W=R+(j-R)*D;if(H>=0&&W>=0&&H<F.extent&&W<F.extent){let se=je(r.x*q+H*w,r.y*q+W*w,17);A.push({x:H*w,y:W*w,height:T,turn:Z,shade:ee(se,5),seed:ee(se,7)})}v+=y}L+=P}}}}let k=w=>Te(Math.floor(n.variants?.[Me[w]]??1),[1,4]),M=w=>Math.min(k(w.form)-1,Math.floor(ee(Math.floor(w.seed*256),9)*k(w.form))),B=l.map(M),C=l.map((w,y)=>y);C.sort((w,y)=>l[w].form-l[y].form||B[w]-B[y]||l[w].rank-l[y].rank);let E=new Uint8Array(l.length*tn),O=new DataView(E.buffer),I=0,N=()=>({start:0,count:0,upTo:new Array(rn).fill(0)}),K=Object.fromEntries(Me.map((w,y)=>[w,{...N(),variants:Array.from({length:k(y)},N)}])),G=(w,y,b)=>{w.count===0&&(w.start=y),w.count+=1;for(let x=b;x<rn;x+=1)w.upTo[x]+=1};C.forEach((w,y)=>{let b=l[w],x=y*tn;O.setUint16(x,Math.min(q-1,Math.round(b.x)),!0),O.setUint16(x+2,Math.min(q-1,Math.round(b.y)),!0),O.setUint16(x+4,Math.round(b.height*10),!0),O.setUint16(x+6,Math.round(b.crown*10),!0),E[x+8]=Math.floor(b.turn*256)&255,E[x+9]=Math.floor(b.shade*256)&255,E[x+10]=b.form,E[x+11]=b.rank,E[x+12]=Math.floor(b.seed*256)&255,E[x+13]=b.canopy,E[x+14]=B[w],I=Math.max(I,b.height);let T=K[Me[b.form]];G(T,y,b.rank),G(T.variants[B[w]],y,b.rank)});let z=new Uint8Array(A.length*tn),Y=new DataView(z.buffer);return A.forEach((w,y)=>{let b=y*tn;Y.setUint16(b,Math.min(q-1,Math.round(w.x)),!0),Y.setUint16(b+2,Math.min(q-1,Math.round(w.y)),!0),Y.setUint16(b+4,Math.round(w.height*10),!0),Y.setUint16(b+6,Math.round(wr*10),!0),z[b+8]=Math.round(w.turn*256)&255,z[b+9]=Math.floor(w.shade*256)&255,z[b+12]=Math.floor(w.seed*256)&255,I=Math.max(I,w.height)}),{trees:E,forms:K,hedges:z,tallest:I,mapped:u,canopy:c}}function Ar(e){let n={round:e.round??0,open:e.open??0,tall:e.tall??0};return n.round+n.open+n.tall>0?n:_n}function sn(e,n){let t=0;for(let o of Me)t+=Math.max(0,e[o]??0);if(t<=0)return sn(_n,n);let i=n*t;for(let o of Me){let r=Math.max(0,e[o]??0);if(i<r)return o;i-=r}return"round"}function xe(e,n){let t=typeof e=="number"?e:typeof e=="string"?Number.parseFloat(e):NaN;return Number.isFinite(t)&&t>0?Te(t,n):void 0}function Te(e,[n,t]){return Math.min(t,Math.max(n,e))}function Sn([e,n],t){return e+(n-e)*t}function Fr(e,n,t){let i=2**e.z,o=(e.x+n)/i*360-180,r=Math.atan(Math.sinh(Math.PI*(1-2*(e.y+t)/i)))*180/Math.PI;return[o,r]}function je(e,n,t){let i=(Math.imul(Math.floor(e)|0,668265261)^Math.imul(Math.floor(n)|0,374761393)^Math.imul(t,2654435761))>>>0;return i=Math.imul(i^i>>>15,2246822507)>>>0,i=Math.imul(i^i>>>13,3266489909)>>>0,(i^i>>>16)>>>0}function ee(e,n){return je(e,n,20973)/4294967296}var Sr=[void 0,"Point","LineString","Polygon"];function ct(e,n={}){let t=n.layers?new Set(n.layers):void 0,i={},o=new Bn(e,0,e.length);for(;o.more();){let[r,s]=o.tag();if(r!==3){o.skip(s);continue}let a=o.message(),l=_r(a,t);l&&!(l.name in i)&&(i[l.name]=l.layer)}return i}function _r(e,n){let t,i=4096,o=[],r=[],s=[];for(;e.more();){let[a,l]=e.tag();if(a===1?t=e.string():a===2?s.push(e.message()):a===3?o.push(e.string()):a===4?r.push(Ur(e.message())):a===5?i=e.varint():e.skip(l),t!==void 0&&n&&!n.has(t))return}if(!(t===void 0||n&&!n.has(t)))return{name:t,layer:Object.freeze({extent:i,features:Object.freeze(s.flatMap(a=>{let l=Lr(a,o,r);return l?[l]:[]}))})}}function Lr(e,n,t){let i,o,r={},s=[];for(;e.more();){let[a,l]=e.tag();if(a===1)i=e.varint();else if(a===2){let u=e.packed();for(let c=0;c+1<u.length;c+=2){let f=n[u[c]],d=t[u[c+1]];f!==void 0&&d!==void 0&&(r[f]=d)}}else a===3?o=Sr[e.varint()]:a===4?s=e.packed():e.skip(l)}if(o)return Object.freeze({...i===void 0?{}:{id:i},type:o,properties:Object.freeze(r),geometry:Object.freeze(Br(s,o))})}function Br(e,n){let t=[],i,o=0,r=0,s=0;for(;s<e.length;){let a=e[s++],l=a&7,u=a>>>3;if(l===7){i&&i.length>=2&&i.push(i[0],i[1]);continue}if(l!==1&&l!==2)throw new Error(`Unknown geometry command ${l} in vector tile.`);if(s+u*2>e.length)throw new Error("Truncated vector tile geometry.");for(let c=0;c<u;c+=1){if(o+=Ln(e[s++]),r+=Ln(e[s++]),l===1&&(n!=="Point"||c===0)&&t.push(i=[]),!i)throw new Error("Vector tile geometry draws a line before moving to its start.");i.push(o,r)}}return t}var Ln=e=>e%2===1?-(e+1)/2:e/2;function Ur(e){let n="";for(;e.more();){let[t,i]=e.tag();t===1?n=e.string():t===2?n=e.float():t===3?n=e.double():t===4?n=e.varint(!0):t===5?n=e.varint():t===6?n=Ln(e.varint()):t===7?n=e.varint()!==0:e.skip(i)}return n}var Cr=new TextDecoder,Bn=class e{constructor(n,t,i){this.bytes=n;this.end=i;this.position=t,this.view=new DataView(n.buffer,n.byteOffset,n.byteLength)}bytes;end;position;view;more(){return this.position<this.end}tag(){let n=this.varint();return[Math.floor(n/8),n&7]}varint(n=!1){let t=0,i=0;for(let o=0;o<10;o+=1){if(this.position>=this.end)throw new Error("Truncated vector tile.");let r=this.bytes[this.position++];if(o<4?t|=(r&127)<<o*7:o===4?(t|=(r&15)<<28,i=(r&112)>>4):o<9?i|=(r&127)<<(o-5)*7+3:i|=(r&1)<<31,r<128)return n?i*4294967296+(t>>>0):(i>>>0)*4294967296+(t>>>0)}throw new Error("Invalid varint in vector tile.")}span(){let n=this.varint(),t=this.position;if(t+n>this.end)throw new Error("Truncated vector tile.");return this.position=t+n,[t,t+n]}message(){let[n,t]=this.span();return new e(this.bytes,n,t)}string(){let[n,t]=this.span();return Cr.decode(this.bytes.subarray(n,t))}packed(){let[n,t]=this.span(),i=new e(this.bytes,n,t),o=[];for(;i.more();)o.push(i.varint());return o}float(){if(this.position+4>this.end)throw new Error("Truncated vector tile.");let n=this.view.getFloat32(this.position,!0);return this.position+=4,n}double(){if(this.position+8>this.end)throw new Error("Truncated vector tile.");let n=this.view.getFloat64(this.position,!0);return this.position+=8,n}skip(n){if(n===0)this.varint();else if(n===1)this.position+=8;else if(n===2)this.span();else if(n===5)this.position+=4;else throw new Error(`Unsupported protocol buffer wire type ${n} in vector tile.`);if(this.position>this.end)throw new Error("Truncated vector tile.")}};function ft(e){return Object.values(e.layers).filter(n=>typeof n=="string")}function dt(e,n){if(e.kind==="buildings"){let r=n(e.layers.building);if(!r)return;let s=n(e.layers.places),a=Kn(r,{...e.options,...s?{places:s}:{}});return a&&qn(a)}if(e.kind==="trees"){let r=n(e.layers.tree),s=n(e.layers.canopy),a=n(e.layers.hedge);if(!r&&!s&&!a)return;let l=ut({...r?{trees:r}:{},...s?{canopy:s}:{},...a?{hedges:a}:{}},e.options);return l.trees.length>0||l.hedges.length>0?l:void 0}let t=n(e.layers.streetFurniture),i=n(e.layers.transportation),o=n(e.layers.sidewalk);return ot({...t?{streetFurniture:t}:{},...i?{transportation:i}:{},...o?{sidewalks:o}:{}},e.options)}function mt(e,n){if(!n)return[];if(e==="buildings")return et(n);if(e==="trees"){let{trees:t,hedges:i}=n;return[t.buffer,i.buffer].filter((o,r,s)=>s.indexOf(o)===r)}return[n.lamps.buffer]}function ht(e=16){let n=new Map;return{read(t,i,o){let r=n.get(t);for(n.delete(t),r??={bytes:i,layers:new Map},n.set(t,r);n.size>e;)n.delete(n.keys().next().value);let s=o.filter(l=>!r.layers.has(l));if(s.length>0){let l=ct(r.bytes,{layers:s});for(let u of s)r.layers.set(u,l[u])}let a=r.layers;return l=>l===void 0?void 0:a.get(l)}}}var Un=globalThis,Or=ht();Un.onmessage=({data:{url:e,bytes:n,task:t}})=>{try{let i=dt(t,Or.read(e,n,ft(t)));Un.postMessage({result:i},mt(t.kind,i))}catch(i){Un.postMessage({error:String(i)})}};})();
@@ -65,12 +65,15 @@ state on the map.
65
65
  ## Requests and cache
66
66
 
67
67
  - **One request per tile.** However many reads wait for a tile, it is requested once. A read whose
68
- signal aborts stops waiting, but the request finishes for the cache and the other reads.
68
+ signal aborts stops waiting. Once every read waiting for a request has stopped, a request still
69
+ waiting its turn is dropped; one already sent finishes for the cache, or stops when `cacheSize`
70
+ is 0.
69
71
  - **Cache.** The reader keeps up to `cacheSize` decoded tiles (128 by default), the most recently
70
72
  read first. It remembers a tile the source does not have (404 or 204) as absent, and does not
71
73
  remember errors. `clear()` forgets everything.
72
74
  - **Concurrency.** At most `concurrency` requests (6 by default) run at once; the rest wait their
73
- turn.
75
+ turn, lowest `priority` first (a read option, 0 by default). `readArea` reads the tiles nearest
76
+ its centre first.
74
77
  - **Requests.** `transformRequest` rewrites each request as a MapLibre map's option does. Pass the
75
78
  map's own so tile reads follow the same rules. The browser's HTTP cache applies as for any
76
79
  request.
@@ -259,3 +259,11 @@ data; passes placed beside a moved target follow it. Operations sharing a placem
259
259
  declaration order, the first lowest. For symbols, a higher layer is also placed first in MapLibre
260
260
  collisions, so placement decides both drawing and precedence. An owner whose layers are all
261
261
  render passes still satisfies other operations' `requirements`.
262
+
263
+ A target exists only in the mode that draws it: buildings draw `buildings.flat.fill` and
264
+ `buildings.flat.outline` in mode `'flat'`, and `buildings.extrusion` in mode `'3d'`. `refine()`
265
+ keeps the inherited render stack, so refining an inherited module's `mode` fails when a pass is
266
+ placed beside, or a refinement targets, the other mode's target; the error names both modes.
267
+ Streets' shadows and its extrusion pass stand beside its flat buildings. To draw native extrusions
268
+ in a map that extends Streets, remove that stack with `refine({mode: '3d', renderStack: reset()})`,
269
+ or declare `buildings({mode: '3d'})` to replace the module.
@@ -8,16 +8,73 @@ Showing, hiding and changing height are immediate. Illuminated outlines, random
8
8
  invented windows and night lights remain off. Every effect is independently overridable and
9
9
  independent of the map's trees.
10
10
 
11
+ Streets supplies architectural flood lighting and subtle model wall emission for its dark
12
+ presets. These controls are independent of invented windows and the optional `nightLight` effects.
13
+
11
14
  Tileflow landmarks and a map's own building models can stand in the same 3D scene, replacing
12
15
  the buildings under their footprints.
13
16
 
14
17
  It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
15
18
  labels. It needs WebGL2 and the Mercator projection, which MapLibre's globe turns into from about
16
- zoom 12. On every map, buildings and placed models appear automatically from zoom 15,
17
- with opacity rising linearly from 0 at 15 to 1 at 15.25. Zooming back out reverses the fade.
18
- Their height stays unchanged throughout; shadows and contact shading fade with them.
19
+ zoom 12. On every map the city comes in as the camera zooms in: placed models and landmarks first,
20
+ fading in and rising from the ground between zooms 14.2 and 14.6, then the buildings between 15 and
21
+ 15.3. Their shadows and contact shading follow their height. Zooming back out lowers and fades them
22
+ again. With reduced motion (`prefers-reduced-motion`) they keep their height and only fade.
19
23
  `hide()` keeps them hidden at every zoom until `show()` is called.
20
24
 
25
+ A map sets these bands with the buildings module's `threeDimensional.entrance`, each a
26
+ `[from, to]` pair of zooms; a band left out keeps its default:
27
+
28
+ ```ts
29
+ buildings({threeDimensional: {entrance: {buildings: [15.5, 16], models: [14.5, 15]}}});
30
+ ```
31
+
32
+ The renderer reads its tiles from a zoom below its `minzoom`, so a band that starts earlier than
33
+ that comes in from there.
34
+
35
+ ## The map's 3D scene
36
+
37
+ React, Vue and Svelte `Map` components show the 3D scene of every map whose style declares one, by
38
+ default: buildings with Tileflow landmarks and the map's Team building models, 3D trees, and street
39
+ lights where the map enables them. They load the 3D renderers from `@tileflow/core/3d` only when
40
+ the style declares a scene, so a 2D map does not download them. Set `viewMode="2d"` (Vue:
41
+ `view-mode="2d"`) to opt out: no 3D buildings or street lights are mounted, and vegetation follows
42
+ its [display policy](trees-3d.md#visibility-and-representation) for the 2D view. Changing it back to
43
+ `3d` shows the scene on the same map. React Native, image mode, Capture and Static Maps do not draw
44
+ the scene.
45
+
46
+ Applications that build their own MapLibre map mount the same scene with `createTileflowMap3D`.
47
+ It mounts each renderer the style declares, follows theme and style changes, and releases a
48
+ renderer when the style no longer declares it:
49
+
50
+ <!-- docs:check -->
51
+
52
+ ```ts
53
+ import {
54
+ createTileflowMap3D,
55
+ type TileflowMap3DMap,
56
+ type TileflowThemeController,
57
+ } from '@tileflow/core/browser';
58
+
59
+ export function showTheScene(map: TileflowMap3DMap, controller: TileflowThemeController) {
60
+ const scene = createTileflowMap3D({map, controller});
61
+ // Shown on creation; hide() and show() switch the whole scene, dispose() removes it.
62
+ return scene;
63
+ }
64
+ ```
65
+
66
+ `hide()` takes the buildings down and releases the street lights; `show()` brings them back. Both
67
+ take the same transition as the buildings' `show()` and `hide()`. `setVegetation(false)` hides
68
+ every tree and `setVegetation(undefined)` restores the map's default. To load the renderers only
69
+ for maps that need them, call `attachTileflowMap3D({map, controller, load: () =>
70
+ import('@tileflow/core/3d')})` from `@tileflow/core/browser`, as the framework components do; its
71
+ `setViewMode()` and `setVegetation()` apply once the scene mounts.
72
+
73
+ A MapLibre application that loads a compiled `style.json` directly gets no 3D scene: buildings,
74
+ landmarks, models, trees and lights are custom WebGL layers added only by this runtime. Where a map
75
+ draws a plain `fill-extrusion` building pass, the style ships it hidden and grouped under
76
+ `metadata['tileflow:3d-toggle'] = 'building'`; showing those layers gives extruded footprints only.
77
+
21
78
  ## Show the buildings
22
79
 
23
80
  This example assumes an existing MapLibre map showing a Tileflow map whose vector source is called
@@ -146,11 +203,17 @@ or the renderer is removed; it resizes with the drawing buffer.
146
203
  Maps on Tileflow World show Tileflow's landmarks, such as the Eiffel Tower, as 3D models in place of
147
204
  the buildings under them. They come from a landmarks tileset kept apart from the map's data: each of
148
205
  its tiles carries the models standing in it, with their levels of detail and footprints, and each
149
- region's index names its tiles by their hash. The renderer reads the tileset's TileJSON once the
150
- buildings show in 3D, then only the indexes of the regions round the camera and the landmark tiles
151
- near it, so a landmark in Paris costs nothing in Madrid. New and improved landmarks reach every map
152
- without a new build of the map. A tileset that cannot be read is tried again after five minutes,
153
- and the buildings stay drawn meanwhile.
206
+ region's index names its tiles by their hash, with where each landmark stands and its footprint.
207
+ The renderer reads the tileset's TileJSON, then only the indexes of the regions round the camera
208
+ and the landmark tiles near it, from a zoom before the buildings show, so a landmark in Paris costs
209
+ nothing in Madrid. New and improved landmarks reach every map without a new build of the map.
210
+
211
+ The buildings under a landmark are never raised and then taken away. A tile of buildings is built
212
+ once the indexes round it have arrived, waiting at most a second and a half; while a landmark's
213
+ tile is still on its way, the footprint its index names keeps the buildings under it flat, and the
214
+ landmark fades in when it arrives. A landmark tile that fails, or has not arrived three seconds
215
+ after the buildings round it show, lets those buildings rise until it does. A tileset that cannot be read is tried again
216
+ after five minutes, and the buildings stay drawn meanwhile.
154
217
 
155
218
  A map switches them off, or names the TileJSON of another landmarks tileset:
156
219
 
@@ -185,7 +248,7 @@ longer raised while it stands. Put glTF binaries (`.glb`) in a directory, declar
185
248
  <!-- docs:check -->
186
249
 
187
250
  ```ts
188
- import {defineMap, refine} from '@tileflow/core';
251
+ import {defineMap, fixed, refine} from '@tileflow/core';
189
252
  import {streets, streetsModels} from '@tileflow/maps';
190
253
 
191
254
  export default defineMap({
@@ -201,7 +264,10 @@ export default defineMap({
201
264
  buildings: refine({
202
265
  threeDimensional: {
203
266
  models: [{model: 'eiffel-tower', at: [2.294481, 48.858258], heading: 0}],
204
- modelColors: {surface: '#7a5c45', accent: '#5b4332'},
267
+ modelColors: {
268
+ surface: fixed('#7a5c45', {reason: "The tower's bronze paint"}),
269
+ accent: fixed('#5b4332', {reason: "The tower's bronze paint, in shadow"}),
270
+ },
205
271
  },
206
272
  }),
207
273
  },
@@ -219,8 +285,14 @@ How to prepare a model:
219
285
  `roof` (an exporter's `.001` suffix is ignored); a model whose materials name a tree's parts is a
220
286
  [tree model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md)
221
287
  instead. Each part takes its colour from `modelColors`, or from the buildings' colour when the
222
- design gives it none, so the model follows the theme from day to night; vertex colours lighter or
288
+ design gives it none, so the model follows the theme from day to night. Like every visual
289
+ colour, a `modelColors` entry is a theme token such as `token.color('buildings.modelRoof')`, or
290
+ a literal wrapped in `fixed(value, {reason})`; vertex colours lighter or
223
291
  darker than the rest of their part shade it within that colour.
292
+ - **Architectural roles control lighting.** A material may set
293
+ `extras.tileflow.lightingRole` to `wall`, `door`, `roof`, `window`, `lamp`, `logo` or
294
+ `unclassified`. This preserves its palette slot. Without a role, `surface`, `secondary` and
295
+ `accent` are walls, `glass` is a window, and `roof` is a roof.
224
296
  - **Metres round an origin on the ground.** The origin is where the model stands, with the model
225
297
  above it and its own north along +y once read Z up; nothing may lie more than half a metre below
226
298
  it.
@@ -237,12 +309,49 @@ The build reads each model, reports a problem with the file and what to change,
237
309
  footprint and writes it beside the style as a prepared model file: a glTF binary whose meshes are
238
310
  quantised (`KHR_mesh_quantization`) and compressed by meshoptimizer (`EXT_meshopt_compression`),
239
311
  which any glTF viewer opens. The renderer downloads a model only when it stands near the tiles round
240
- the camera, keeps the nearest dozen, and builds the tiles under it again without the buildings that
241
- reach more than half a metre into its footprint; a neighbour that only touches it stays. A model
242
- that cannot be downloaded leaves those buildings standing. Reading a prepared model file loads
312
+ the camera, whether or not those tiles hold buildings, keeps the nearest dozen, and builds the tiles
313
+ under it again without the buildings that
314
+ reach more than half a metre into its footprint; a neighbour that only touches it stays. A tile of
315
+ buildings near a model waits for it, at most a second and a half, so its buildings are not raised
316
+ and then taken away, and the model fades in when it comes to stand. A model that cannot be
317
+ downloaded leaves those buildings standing. Reading a prepared model file loads
243
318
  meshoptimizer's WebAssembly decoder, so a page with a Content Security Policy allows
244
319
  `'wasm-unsafe-eval'` in its `script-src`.
245
320
 
321
+ Preparation also computes local geometric ambient visibility, independently of the model's
322
+ colour. `modelOcclusion` sets its strength from zero to one (default `0.75`); `occlusion: false`
323
+ disables it. Occluded cavities receive less ambient light while direct sun and dynamic shadows
324
+ remain independent. Existing prepared files with fully exposed vertices retain their appearance.
325
+ Reprepare their original GLBs to gain geometric occlusion. See
326
+ [model preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/building-models.md)
327
+ for authoring, sampling and costs.
328
+
329
+ ## Team 3D models
330
+
331
+ On Tileflow Hosted, a map's building models are its Team's 3D models: each is published once, under
332
+ its name, and every map of the Team that places a model of that name draws it. Locally, the map
333
+ draws its own `.glb` files, so the map is previewed and developed without an account.
334
+
335
+ ```sh
336
+ npx tileflow models publish ./models/eiffel-tower.glb --team @acme
337
+ npx tileflow models status eiffel-tower --team @acme
338
+ ```
339
+
340
+ `tileflow models publish` prepares each building model as a build does and publishes it as the
341
+ model's new current version; with no path, it publishes the building models of every map in the
342
+ project's config. Publishing the bytes of the current version again changes nothing. A hosted map
343
+ follows each model's current version: a new version, or `tileflow models restore`, which makes the
344
+ previous version current again, changes every map that places it the next time the map loads,
345
+ without deploying the maps again. A model keeps its current and previous versions.
346
+
347
+ `tileflow deploy` writes each placement of a building model as the placeholder
348
+ `tileflow://team-models/<name>` (`tileflowTeamModelUrl(name)`), which the deploy replaces with the
349
+ model's address in the map's Team; a model the Team has not published fails the deploy. The deploy
350
+ also reports a model whose published version was prepared from other files than the map's, so an
351
+ outdated model is easy to spot. A model's file, and what Tileflow shows about it, can be read
352
+ without decoding its meshes with `readTileflowModelFileSummary`. Models are served publicly and may
353
+ be cached, so they must not hold secrets or restricted personal data.
354
+
246
355
  ## Roofs
247
356
 
248
357
  A building whose tiles carry a roof shape (World's `roof_shape`) gets that roof: `gabled`,
@@ -331,6 +440,42 @@ by day and night, mixed between the themes either side of a blend's position.
331
440
 
332
441
  ## Night light
333
442
 
443
+ Architectural illumination has two independent controls:
444
+
445
+ - `floodLight` selects ordinary buildings by height. Ground light starts at `minGroundHeight`
446
+ (30 m by default), with a stable per-building `groundRadius` in metres (default `[30, 100]`).
447
+ Wall light starts above `minWallHeight` (200 m); its reach is building height times
448
+ `wallHeightRatio` (default `1/3`). `intensity` is zero to one and `color` is a theme colour.
449
+ The effect fades with scene brightness; `brightnessScale` compensates an authored light-colour
450
+ transform and defaults to one. Overlapping ground contributions use their strongest value.
451
+ - `modelLighting` assigns physical materials and emission to architectural roles. Each profile accepts
452
+ `emissiveStrength` (zero to five), optional `emissiveColor` (otherwise the surface's own colour), and
453
+ `emissiveHeight`: `[beginHeight, endHeight, beginMultiplier, endMultiplier, curve]`.
454
+ Heights are fractions of the source mesh's vertical bounds (the full model for legacy files),
455
+ multipliers are zero to one, and the exponent is
456
+ `10 ** curve`, with `curve` from -1 to 1. Equal heights give a constant multiplier of one.
457
+ Every gradient component, strength and colour may use theme tokens. Emission is mixed in linear light and is independent
458
+ of ambient occlusion and solar shadows. Values above one saturate the surface emission at one;
459
+ they also control the falloff of explicitly authored area emitters.
460
+
461
+ Physical profiles also accept `color`, `colorMix` (zero to one, default one when a tint exists),
462
+ `roughness` and `metallic` (zero to one). Explicit `modelColors` override role tints. Missing
463
+ physical overrides retain the glTF material. `emissiveVariation` adds a stable, model-seeded amount
464
+ from zero to its value. Optional `colorVariation` supplies independent `[min, max]` ranges for
465
+ `hue` (0–360), `saturation` and `lightness` (0–100), with `space: 'srgb'` (default) or `'linear'`.
466
+ The same model identity keeps its samples across LODs and theme changes.
467
+
468
+ Streets leaves ordinary flood lighting off in its light presets. Its dusk/night wall profile uses a strength
469
+ of `0.1` and `[0.5, 0, 0, 1, 0.8]`, concentrating a warm wash near the foot. Monochrome uses a
470
+ weaker neutral wall profile and no architectural ground flood. Prepared models without role
471
+ metadata use their documented palette mapping. Windows use metallic reflection and varied emission;
472
+ doors and logos have separate profiles, and roofs remain non-emissive. Day/dawn profiles follow
473
+ the scene's brighter material response. A role does not generate missing windows or ground emitters.
474
+ See [building-model preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/building-models.md)
475
+ for glTF channels, explicit area emitters, compatibility and resource costs.
476
+
477
+ The separate `nightLight` control retains its decorative building and shop effects:
478
+
334
479
  With `nightLight` above 0, the buildings light the street from data at night (as the sun's
335
480
  `brightness` falls):
336
481
 
@@ -355,7 +500,8 @@ download.
355
500
 
356
501
  ## Options
357
502
 
358
- The renderer's own options say only how it is delivered: `minzoom` (15 by default, with a 0.25-zoom opacity ramp), `maxTiles`,
503
+ The renderer's own options say only how it is delivered: `minzoom` (15 by default; the default
504
+ entrance bands start from it), `maxTiles`,
359
505
  `beforeId` to place the layer before another one, and `transformRequest` to apply the map's request
360
506
  rules to the tiles it reads.
361
507
 
@@ -365,10 +511,14 @@ During partial opacity, a depth-only geometry pass resolves the nearest surfaces
365
511
  blending, preventing rear walls from bleeding through. At full opacity only the normal geometry
366
512
  pass runs; zoom fading schedules no extra animation frames or per-zoom mesh rebuilds.
367
513
 
368
- The browser bundle prepares building meshes in one worker per renderer and transfers the finished
369
- vertex and index buffers. No worker URL or extra asset hosting is needed. A Content Security Policy
370
- must allow `worker-src blob:` to use this path; if workers are unavailable or blocked, the renderer
371
- falls back to local meshing. The worker is terminated on `dispose()`. Pending tile updates retain
514
+ The browser bundle builds from the tiles' bytes in one worker that the page's 3D buildings, trees
515
+ and street lights share, nearest tiles first: it decodes each tile once for all of them, meshes its
516
+ buildings, keeps their indices to 16 bits when the vertices allow, lays out the walls' contact-band
517
+ segments and transfers the finished arrays. The renderer uploads them to the GPU and keeps no copy;
518
+ a style reloaded in full keeps the uploaded buffers. No worker URL or extra asset hosting is needed.
519
+ A Content Security Policy must allow `worker-src blob:` to use this path; if workers are
520
+ unavailable or blocked, the layers build in the page instead. The worker is terminated when the
521
+ last of those layers is disposed. Tiles the camera has left are not downloaded or built. Pending tile updates retain
372
522
  the previous mesh until their replacement is ready; cancelled and obsolete results are discarded.
373
523
 
374
524
  Mercator rendering batches buildings spatially and skips batches outside the camera. Shadow
@@ -385,7 +535,16 @@ Mercator texel grid stays stable during small pans, including changes of latitud
385
535
  the meshes' physical metre scale. One filtered depth comparison keeps edges sharp.
386
536
  Missing camera capabilities, terrain or a view near the horizon use the full loaded area instead.
387
537
  The depth pass runs again when its focused area, light or casting geometry changes and is reused
388
- when they stay unchanged.
538
+ when they stay unchanged. While the camera moves, the focused area takes a quarter of its span as a
539
+ margin on each side and is kept while it still covers the view at a similar resolution, so the
540
+ depth pass is not drawn on every frame; once the camera stops, it is fitted to the view again.
541
+
542
+ Model ambient visibility and lighting roles retain the existing 16-byte vertex stride. They add
543
+ no geometry pass or texture. Architectural ground lighting uses a separate one-byte-per-pixel
544
+ mask and one geometry/composite pair of passes while active; its mask is released when inactive.
545
+ The contact band and the architectural light draw their masks in the layer's prerender, before
546
+ the map's main pass, so the map's framebuffer is never switched while its layers draw.
547
+ At 1280 × 720 and DPR 1, the mask occupies 921,600 bytes, excluding driver overhead.
389
548
 
390
549
  ## Limits
391
550
 
@@ -395,8 +554,9 @@ when they stay unchanged.
395
554
  - Roofs follow the building's smallest enclosing rectangle: a building with courtyards or far from a
396
555
  rectangle keeps a flat roof, and `sawtooth` roofs are drawn flat.
397
556
  - A colour expression is evaluated at zoom 16.
398
- - Building models and landmarks take no edges or night light of their own yet, and cannot be
399
- picked or styled by state.
400
- - A hosted map takes up to 64 building models of its own, 32 MiB once prepared; `tileflow deploy`
401
- uploads them with the map. A deploy with `--with-native` does not take them, since MapLibre Native
402
- draws no models. Models named by `url` deploy as they are.
557
+ - Building models and landmarks take no illuminated edges and cannot be picked or styled by state.
558
+ - Model materials use semantic colours and role emission; physical roughness, metallic
559
+ reflections and authored textures are not rendered.
560
+ - A hosted map places its building models as its Team's 3D models: `tileflow deploy` uploads no
561
+ model files (see [Team 3D models](#team-3d-models)). A deploy with `--with-native` does not take
562
+ them, since MapLibre Native draws no models. Models named by `url` deploy as they are.
@@ -160,17 +160,26 @@ texture's marks remain approximately constant in screen pixels across classes, r
160
160
 
161
161
  `classes.pedestrian` styles line-like pedestrian ways. Polygon pedestrian plazas are a distinct
162
162
  geometry and use `areas.pedestrian`, including optional fill opacity, outline color, or sprite
163
- pattern. This prevents plazas from degrading into a thin polygon outline.
163
+ pattern. This prevents plazas from degrading into a thin polygon outline. With a
164
+ `transportationOutline` binding, as in Tileflow World, area outlines stroke only the outer boundary
165
+ of each area, never its holes.
164
166
 
165
167
  Road treatments refine a class without creating another class or exposing source fields.
166
168
  `construction`, `expressway`, `indoor`, `official`, `ramp`, and `unpaved` live under `modifiers`;
167
169
  general, bicycle, foot, horse, and toll restrictions live under `restrictions`; `alley`,
168
170
  `crossover`, `driveway`, `parkingAisle`, and `yard` live under `serviceTypes`; and exact
169
- mountain-bike difficulty values live under `mountainBike`. Treatments reuse
171
+ mountain-bike difficulty values, which apply to paths and tracks, live under `mountainBike`. Treatments reuse
170
172
  surface/tunnel/bridge and casing/fill/shadow paint controls, plus a relative `widthScale`. The
171
173
  compiler emits feature-driven paint expressions inside the existing stable class layers rather
172
174
  than multiplying every possible combination.
173
175
 
176
+ A solid dash, `[1, 0]`, is emitted as no `line-dasharray` at all, so MapLibre draws the line with
177
+ its solid-line shader rather than its dash shader. A treatment's dash that depends on the road
178
+ class is settled for each class layer: a dash for construction paths leaves a motorway's layers
179
+ solid. Roundabout rings state their road widths at each zoom as numbers where the widths allow,
180
+ and name the feature conditions they repeat once with `let`, which keeps their style compact and
181
+ quick to validate.
182
+
174
183
  Road names, shields, and motorway junctions remain label concerns:
175
184
 
176
185
  ```ts
@@ -180,8 +180,8 @@ layer is a protection tint, while urban parks and gardens come from
180
180
  `landcover.class=grass/subclass=park|garden`. The ambiguous legacy `park` class is therefore not
181
181
  a public land-cover target.
182
182
 
183
- Tileflow World V1 makes `bathymetry`, `globallandcover`, `circular_feature`, `sidewalk`, and
184
- `street_furniture` mandatory. Use `tileflowWorldV1Schema()` for that product instead of making raw
183
+ Tileflow World V1 makes `bathymetry`, `globallandcover`, `circular_feature`, `sidewalk`,
184
+ `street_furniture`, and `transportation_outline` mandatory. Use `tileflowWorldV1Schema()` for that product instead of making raw
185
185
  layer names part of a style. Publication tooling calls `validateTileflowWorldV1Tilejson(...)` and
186
186
  fails closed unless each layer occurs exactly once, declares its contracted native zooms, and
187
187
  advertises the typed fields consumed by the maps. This includes bathymetry z0–z9, global land cover
@@ -212,48 +212,29 @@ export default defineMap({
212
212
  marine: 'chart', // 'none' | 'bathymetry' | 'nautical' | 'chart'
213
213
  modules: {
214
214
  nautical: nautical({
215
- soundings: {minZoom: 13},
216
- aids: {minZoom: 11},
217
- lights: {minZoom: 12},
218
- hazards: {minZoom: 12},
219
- hazardAreas: {
220
- fill: {opacity: fixed(0.1, {reason: 'Chart hazard wash is invariant'})},
221
- },
222
- wrecks: {minZoom: 12},
223
- wreckAreas: {
224
- outline: {opacity: fixed(0.6, {reason: 'Chart wreck outline is invariant'})},
225
- },
226
- reefs: {fill: {opacity: fixed(0.3, {reason: 'Chart reef wash is invariant'})}},
215
+ buoys: {minZoom: 12},
216
+ lights: {minZoom: 9},
227
217
  navigationAreas: {
228
218
  fill: {opacity: fixed(0.05, {reason: 'Chart navigation-area wash is invariant'})},
229
219
  },
230
- coverage: {outline: {visible: false}},
231
- labels: {
232
- navigationAreas: {minZoom: 11},
233
- reefs: {minZoom: 13},
234
- },
220
+ reefs: {fill: {opacity: fixed(0.3, {reason: 'Chart reef wash is invariant'})}},
221
+ labels: {navigationAreas: {minZoom: 12}},
235
222
  }),
236
223
  },
237
224
  });
238
225
  ```
239
226
 
240
227
  `bathymetry` resolves to vector source `tileflow-bathymetry` and supplies the 13-stop
241
- `bathymetry-v1` band contract to `water(...)`. `nautical` resolves to `tileflow-nautical`; the
242
- `nautical(...)` module
243
- styles its `sounding`, `aid`, `light`, `hazard`, `wreck`, `reef`, `navigation_area`, and `coverage`
244
- layers. The schema also names the product's provider-neutral provenance fields (`provenance`,
245
- `provider`, `cell`, `edition`, `update`, `scale`, `coverage`, and `licence`). `chart` enables both.
246
- Point features label depth, name, or light character as applicable. The `lighthouses` symbol style
247
- targets `aid` points whose canonical `class` is `lighthouse`, leaving other aids under `aids`.
248
- Polygon reefs, navigation
249
- areas, hazards, wrecks, and named/provider coverage have separate collision-aware labels under
250
- `nautical({labels: {...}})`; unnamed coverage does not emit empty text.
228
+ `bathymetry-v1` band contract to `water(...)`. `nautical` resolves to `tileflow-nautical` at
229
+ `/tiles/nautical/tiles.json`; the `nautical(...)` module draws its `nautical-v1` buoys, beacons,
230
+ lighthouses, lights, hazards, wrecks, reefs, navigation areas, routes, cables, harbours, ports, and
231
+ a restrained coverage indication. `chart` enables both. Every map that draws Nautical declares a
232
+ "Not for navigation" notice, which the browser runtimes render on the map. The
233
+ [Nautical marks guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/nautical-marks.md)
234
+ describes the schema, the semantic groups, the theme image tokens, and the notice.
251
235
  Core does not hard-code a nautical provider or attribution: the selected TileJSON owns its actual
252
236
  coverage and attribution, and derived vector tiles are not represented as a certified ENC for
253
- navigation. The V1 geometry contract preserves `sounding`, `aid`, and `light` as points;
254
- `reef`, `navigation_area`, and `coverage` as polygons; and both point and polygon geometries for
255
- `hazard` and `wreck`. Point symbols and polygon areas compile separately, without fabricated
256
- centroids.
237
+ navigation. Point symbols and polygon areas compile separately, without fabricated centroids.
257
238
  Advanced callers can override either TileJSON URL, attribution, or source ID and can set the other
258
239
  member to `false`:
259
240
 
@@ -483,7 +464,10 @@ add `tileflow:sources` plus `tileflow:sourceRequirements` for independent produc
483
464
  Detailed city datasets can bind `sidewalk`, `streetFurniture`, and `circularFeature` source layers
484
465
  through `openMapTiles({layers, fields})`. When present, `roads.sidewalks` owns source-backed
485
466
  pedestrian polygons, `roads.crossings` owns oriented crossing icons, and `roads.roundabouts` owns
486
- metric circular road rings. These bindings are optional: a generic OpenMapTiles source remains
467
+ metric circular road rings. A `transportationOutline` binding names lines that trace the outer
468
+ boundary of each road area; when bound, `roads.areas` outlines stroke those lines instead of the
469
+ area polygons, so the holes of a plaza (tree pits, planting beds) are not stroked. These bindings
470
+ are optional: a generic OpenMapTiles source remains
487
471
  valid and the compiler omits unsupported detail instead of inventing geometry. Crossing icons are
488
472
  explicit because the data contract cannot assume a sprite name. They require positive physical
489
473
  `markings` evidence and reject `crossing=no`; both fields are semantic bindings, so custom schemas