maplibre-gl-nightlayer
Version:
A Night Layer for MapLibre GL JS
73 lines (58 loc) • 7.79 kB
JavaScript
import{Decimal as f}from"@kikuchan/decimal";import{MercatorCoordinate as v,createTileMesh as A}from"maplibre-gl";function x(n){return n*Math.PI/180}function w(n){return n/Math.PI*180}const R=86400n,S=719528n,y=146097n;function m(n,t){const i=n/t;return n%t!==0n&&n<0n!=t<0n?i-1n:i}function D(n,t){return n-m(n,t)*t}function E(n){return n%4n!==0n?!1:n%100n!==0n?!0:n%400n===0n}function T(n){const t=n.floor(0),i=t.integer(),u=n.sub(t),a=m(i,R),s=D(i,R);let e=a+S;e-=60n;const r=m(e,y),o=e-r*y,c=m(o-o/1460n+o/36524n-o/146096n,365n);let h=c+r*400n;const d=o-(365n*c+c/4n-c/100n),l=(5n*d+2n)/153n,p=d-(153n*l+2n)/5n+1n;let b=l+3n;b>12n&&(b-=12n,h+=1n);const C=[31n,E(h)?29n:28n,31n,30n,31n,30n,31n,31n,30n,31n,30n,31n];let _=p;for(let g=0;g<Number(b)-1;g+=1)_+=C[g];const M=f(s.toString()).add(u);return{dayOfYear:_,secondsOfDayDecimal:M}}function L(n){const[t,i,u,a]=n.map(s=>s/255);return[t*a,i*a,u*a,a]}function I(n){n==null&&(n=f(Date.now()/1e3));const{dayOfYear:t,secondsOfDayDecimal:i}=T(n),u=f(t.toString()).add(i.div(86400)).number(),a=2*Math.PI/365.24,s=x(23.44),e=.0167,r=(u+9)*a,o=r+2*e*Math.sin((u-3)*a),c=(r-Math.atan2(Math.sin(o),Math.cos(o)*Math.cos(s)))/Math.PI,h=720*(c-Math.trunc(c+.5)),l=-15*(i.div(3600).number()-12+h/60),p=w(Math.asin(Math.sin(-s)*Math.cos(o)));return{lng:l,lat:p}}class B{id="nightlayer";type="custom";renderingMode="2d";#e;#a;#h;#f;#s;#o;#u;#c;#n;#t;#i;#r;#l;#m;constructor(t={}){this.#a=t.opacity??.5,this.#u=t.twilightSteps??0,this.#c=t.twilightAttenuation??.5,this.#o=t.updateInterval??1e4,this.setColor(t.color??[0,0,0,255]),this.setDaytimeColor(t.daytimeColor??[0,0,0,0]),this.setDate(t.date??null)}#d(){const t=this.#e===null;this.#s&&(clearInterval(this.#s),this.#s=void 0),t&&this.#o>0&&(this.#s=setInterval(()=>this.#t?.triggerRepaint(),this.#o))}getSubsolarPoint(){return I(this.#e)}getDate(){return this.#e?new Date(this.#e.mul(1e3).number()):null}getTime(){return this.#e}setDate(t){this.setTime(t)}setTime(t){t instanceof Date&&(t=f(t.getTime()).div(1e3)),this.#e=f(t),this.#d(),this.#t?.triggerRepaint()}getOpacity(){return this.#a}setOpacity(t){this.#a=t,this.#t?.triggerRepaint()}getColor(){return this.#h}setColor(t){this.#h=[...t.slice(0,3),t?.[3]??255],this.#t?.triggerRepaint()}getDaytimeColor(){return this.#f}setDaytimeColor(t){this.#f=[...t.slice(0,3),t?.[3]??255],this.#t?.triggerRepaint()}getTwilightSteps(){return this.#u}setTwilightSteps(t){this.#u=t,this.#t?.triggerRepaint()}getTwilightAttenuation(){return this.#c}setTwilightAttenuation(t){this.#c=t,this.#t?.triggerRepaint()}getUpdateInterval(){return this.#o}setUpdateInterval(t){this.#o=t,this.#d()}onAdd(t,i){this.#n=new Map,this.#i=i.createBuffer(),this.#r=i.createBuffer(),this.#t=t}onRemove(){this.#n=void 0,this.#t=void 0,this.#i=void 0,this.#r=void 0}#p(t,i){const a=`#version 300 es
precision highp float;
in vec2 a_position;
out vec2 v_position;
${!i.vertexShaderPrelude?`
uniform mat4 u_matrix;
vec4 projectTile(vec2 pos) {
return u_matrix * vec4(pos, 0., 1.);
}
`:`
${i.vertexShaderPrelude}
${i.define}
`}
void main() {
gl_Position = projectTile(a_position);
v_position = a_position;
}
`,s=`#version 300 es
precision highp float;
in vec2 v_position;
out vec4 fragColor;
uniform vec2 u_subsolar;
uniform float u_opacity;
uniform float u_twilight_steps;
uniform float u_twilight_attenuation;
uniform vec4 u_daytime_color;
uniform vec4 u_night_color;
vec2 mercatorToLngLat(vec2 mercator) {
// 0 <= x <= 1, 0 <= y <= 1
float x = mercator.x;
float y = mercator.y;
float lng = x * 360.0 - 180.0;
float lat = degrees(2.0 * atan(exp(${Math.PI} * (1.0 - 2.0 * y))) - ${Math.PI/2});
return vec2(lng, lat);
}
void main() {
vec2 lnglat = mercatorToLngLat(v_position);
vec2 observer = radians(lnglat);
vec2 subsolar = radians(u_subsolar);
float A = sin(observer.y) * sin(subsolar.y);
float B = cos(observer.y) * cos(subsolar.y) * cos(subsolar.x - observer.x);
float altitude = degrees(asin(A + B));
float twilightStepAngle = 6.0;
float twilightBegins = -0.8333333;
float twilightSteps = u_twilight_steps;
// Attenuation for each twilightStepAngle
float att = u_twilight_attenuation;
float twilightLevel = -altitude / twilightStepAngle;
if (twilightSteps > 0.) {
twilightLevel = ceil(clamp(twilightLevel, 0., twilightSteps));
if (altitude > twilightBegins) {
// XXX: Adjust for sunset/sunrise line
twilightLevel = 0.;
}
}
float brightness = clamp(pow(clamp(1. - att, 0., 1.), twilightLevel), 0., 1.);
fragColor = mix(u_night_color, u_daytime_color, brightness) * u_opacity;
}`,e=t.createShader(t.VERTEX_SHADER);if(!e)throw new Error("failed to create vertex shader");t.shaderSource(e,a),t.compileShader(e);const r=t.createShader(t.FRAGMENT_SHADER);if(!r)throw new Error("failed to create fragment shader");t.shaderSource(r,s),t.compileShader(r);const o=t.createProgram();if(!o)throw new Error("failed to create program");return t.attachShader(o,e),t.attachShader(o,r),t.linkProgram(o),o}#b(t){if(!this.#t||!this.#i||!this.#r)return;const[i,u]=this.#t.getBounds().toArray(),a=Math.floor(v.fromLngLat(i).x),s=Math.ceil(v.fromLngLat(u).x),e=this.#t.style?.projection?.name==="globe"?"globe":[a,s].join(":");if(e===this.#m)return;let r;e==="globe"?(r=A({granularity:100,generateBorders:!1,extendToNorthPole:!0,extendToSouthPole:!0},"16bit"),r.vertices=new Float32Array(new Int16Array(r.vertices)).map(o=>o/8192).buffer):r={vertices:new Float32Array([a,0,s,0,a,1,a,1,s,0,s,1]).buffer,indices:new Uint16Array([0,1,2,3,4,5]).buffer,uses32bitIndices:!1},t.bindBuffer(t.ARRAY_BUFFER,this.#i),t.bufferData(t.ARRAY_BUFFER,r.vertices,t.DYNAMIC_DRAW),t.bindBuffer(t.ARRAY_BUFFER,null),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this.#r),t.bufferData(t.ELEMENT_ARRAY_BUFFER,r.indices,t.DYNAMIC_DRAW),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,null),this.#l=r.indices.byteLength/2,this.#m=e}render(t,i,u){if(u||(u=i),!this.#n||!this.#i||!this.#r||!this.#t)return;const a=u.shaderData||{},s=a.variantName||"default";this.#n.has(s)||this.#n.set(s,this.#p(t,a));const e=this.#n.get(s);if(!e)return;const{lng:r,lat:o}=this.getSubsolarPoint();if(t.useProgram(e),t.uniform2fv(t.getUniformLocation(e,"u_subsolar"),[r,o]),t.uniform1f(t.getUniformLocation(e,"u_opacity"),this.#a),t.uniform4fv(t.getUniformLocation(e,"u_night_color"),L(this.#h)),t.uniform4fv(t.getUniformLocation(e,"u_daytime_color"),L(this.#f)),t.uniform1f(t.getUniformLocation(e,"u_twilight_steps"),this.#u),t.uniform1f(t.getUniformLocation(e,"u_twilight_attenuation"),this.#c),"getProjectionDataForCustomLayer"in this.#t.transform){const h=this.#t.transform.getProjectionDataForCustomLayer(!0);t.uniformMatrix4fv(t.getUniformLocation(e,"u_projection_fallback_matrix"),!1,h.fallbackMatrix),t.uniformMatrix4fv(t.getUniformLocation(e,"u_projection_matrix"),!1,h.mainMatrix),t.uniform4f(t.getUniformLocation(e,"u_projection_tile_mercator_coords"),...h.tileMercatorCoords),t.uniform4f(t.getUniformLocation(e,"u_projection_clipping_plane"),...h.clippingPlane),t.uniform1f(t.getUniformLocation(e,"u_projection_transition"),h.projectionTransition)}else t.uniformMatrix4fv(t.getUniformLocation(e,"u_matrix"),!1,i);this.#b(t),t.bindBuffer(t.ARRAY_BUFFER,this.#i),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,this.#r);const c=t.getAttribLocation(e,"a_position");t.enableVertexAttribArray(c),t.vertexAttribPointer(c,2,t.FLOAT,!1,0,0),t.drawElements(t.TRIANGLES,this.#l,t.UNSIGNED_SHORT,0)}}var F=B;export{B as NightLayer,F as default,I as getSubsolarPoint};