mapbox-exif-layer 1.0.2 → 1.0.3

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/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{Evented as e}from"mapbox-gl";import t from"exifreader";function r(e,t,r){const o=e.createProgram(),a=n(e,e.VERTEX_SHADER,t),i=n(e,e.FRAGMENT_SHADER,r);if(e.attachShader(o,a),e.attachShader(o,i),t.includes("out vec2 v_position")&&(t.includes("out float v_age")?e.transformFeedbackVaryings(o,["v_position","v_age"],e.SEPARATE_ATTRIBS):e.transformFeedbackVaryings(o,["v_position"],e.SEPARATE_ATTRIBS)),e.linkProgram(o),!e.getProgramParameter(o,e.LINK_STATUS))throw new Error(e.getProgramInfoLog(o));const s={program:o},u=e.getProgramParameter(o,e.ACTIVE_ATTRIBUTES);for(let t=0;t<u;t++){const r=e.getActiveAttrib(o,t);s[r.name]=e.getAttribLocation(o,r.name)}const l=e.getProgramParameter(o,e.ACTIVE_UNIFORMS);for(let t=0;t<l;t++){const r=e.getActiveUniform(o,t);s[r.name]=e.getUniformLocation(o,r.name)}return s}function n(e,t,r){const n=e.createShader(t);if(e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(e.getShaderInfoLog(n));return n}function o(e,t,r,n,o){const a=e.createTexture();return e.bindTexture(e.TEXTURE_2D,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),r instanceof Uint8Array?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,r):r instanceof Image?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r):e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,null),a}function a(e,t){const r=e.createBuffer();return e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,t,e.STATIC_DRAW),r}function i(e){return.621371*e}function s(e){return 2.23694*e}class u extends e{constructor({id:e,source:t,color:r,bounds:n,particleCount:o=5e3,readyForDisplay:a=!1,ageThreshold:i=500,maxAge:s=1e3,velocityFactor:u=.05,fadeOpacity:l=.9,updateInterval:c=50,pointSize:f=5,trailLength:m=3,trailSizeDecay:d=.8,unit:h="mph"}){super(),this.id=e,this.type="custom",this.renderingMode="2d",this.source=t,this.color=r,this.bounds=n,this.particleCount=o,this.sourceLoaded=!1,this.readyForDisplay=a,this.velocityFactor=u,this.fadeOpacity=l,this.updateInterval=c,this.pointSize=f,this.trailLength=m,this.trailSizeDecay=d,this.ageThreshold=i,this.maxAge=s,this.speedRange=[0,100],this.unit=h}onAdd(e,t){this.map=e,this.gl=t,this.updateProgram=r(t,"#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Current position [0,1]\n in float a_age; // Particle age for tracking circular patterns\n out vec2 v_position; // Updated position for transform feedback\n out float v_age; // Updated age for transform feedback\n \n uniform sampler2D u_velocity_texture; // Texture with normalized velocities [0,1]\n uniform mediump vec4 u_bounds;\n uniform mediump float u_speed_factor;\n uniform mediump float u_time;\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump float u_age_threshold; // Age threshold for reset probability\n uniform mediump float u_max_age; // Maximum age before forced reset\n uniform mediump float u_percent_reset; // Percentage of particles to reset on source update\n uniform bool u_should_reset; // Flag to indicate if we should apply the percentage reset\n \n // Random function based on time and position\n float random(vec2 co) {\n float a = 12.9898;\n float b = 78.233;\n float c = 43758.5453;\n float dt = dot(co, vec2(a,b));\n float sn = mod(dt, 3.14);\n return fract(sin(sn) * c + u_time);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n // Function to generate random position within extent\n vec2 generateRandomPosition(vec2 seed) {\n return vec2(\n random(seed + vec2(1.23, 4.56)),\n random(seed + vec2(7.89, 0.12))\n );\n }\n \n // Function to generate a position on one of the boundaries\n vec2 generateBoundaryPosition(vec2 seed) {\n // Choose which boundary (0=left, 1=top, 2=right, 3=bottom)\n float boundary = floor(random(seed) * 4.0);\n \n // Position along the boundary\n float pos = random(seed + vec2(boundary));\n \n // Small offset to prevent immediate out-of-bounds\n float offset = 0.001;\n \n if (boundary < 1.0) { // Left\n return vec2(offset, pos);\n } else if (boundary < 2.0) { // Top\n return vec2(pos, offset);\n } else if (boundary < 3.0) { // Right\n return vec2(1.0 - offset, pos);\n } else { // Bottom\n return vec2(pos, 1.0 - offset);\n }\n }\n \n void main() {\n // Sample normalized velocity from texture [0,1]\n vec4 velocity = texture(u_velocity_texture, a_position);\n \n // Denormalize velocities to actual mph values\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n \n // Calculate wind speed\n float windSpeed = length(vec2(u, v));\n \n // Convert position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], a_position.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - a_position.y);\n \n // Convert wind velocity to degree offsets using actual mph values\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), lat);\n \n // Convert degree offsets back to normalized coordinates\n vec2 normalizedVelocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n // Update position with velocity\n vec2 newPos = a_position + normalizedVelocity * u_speed_factor;\n \n // Reset rules\n bool shouldReset = false;\n \n // Get current age of particle and increment\n float age = a_age + 1.0;\n \n // Reset if out of bounds or very low wind speed\n if (newPos.x < 0.0 || newPos.x > 1.0 || newPos.y < 0.0 || newPos.y > 1.0 || windSpeed < 1.5) {\n shouldReset = true;\n }\n \n // Reset based on age with increasing probability\n if (age > u_age_threshold) {\n float resetProbability = (age - u_age_threshold) / (u_max_age - u_age_threshold);\n if (random(a_position + vec2(u_time * 0.1, age * 0.01)) < resetProbability) {\n shouldReset = true;\n }\n }\n \n // Force reset of very old particles\n if (age > u_max_age) {\n shouldReset = true;\n }\n \n // Additional reset based on percentParticleWhenSetSource if flag is set\n if (!shouldReset && u_should_reset) {\n if (random(a_position + vec2(u_time)) < u_percent_reset) {\n shouldReset = true;\n }\n }\n \n // Handle reset by generating a new position\n if (shouldReset) {\n newPos = generateRandomPosition(a_position + vec2(u_time));\n\n age = 0.0; // Reset age\n }\n \n v_position = newPos;\n v_age = age;\n }\n","#version 300 es\n precision mediump float;\n \n out vec4 fragColor;\n \n void main() {\n // For update step, we don't need to output anything visual\n // We're just using transform feedback to capture the new positions\n fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n }\n"),this.renderProgram=r(t,"#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Position in [0,1] range\n in float a_trail_offset; // Trail offset (0=main particle, 1,2,3=trail segments)\n \n uniform mediump mat4 u_matrix;\n uniform mediump vec4 u_bounds; // [minX, maxY, maxX, minY]\n uniform mediump float u_point_size; // Base point size\n uniform mediump float u_speed_factor; // Speed multiplier\n uniform mediump float u_trail_size_decay; // Size decay rate for trail particles\n uniform sampler2D u_velocity_texture; // Velocity texture\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n \n out vec2 v_position; // Pass position to fragment shader\n // out float v_opacity; // Varying opacity for trail\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n return vec2(x, y);\n }\n \n // Convert position to geographic coordinates\n vec2 positionToGeo(vec2 pos) {\n float lng = mix(u_bounds[0], u_bounds[2], pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - pos.y); // Invert y for correct mapping\n return vec2(lng, lat);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n void main() {\n // Trail offset (0 = main particle, >0 = trail segment)\n float trailOffset = a_trail_offset;\n \n // Main particle position from buffer\n vec2 mainPos = a_position;\n \n // Current position is main position for main particle (offset=0)\n vec2 currentPos = mainPos;\n \n // Sample velocity at the main particle's position\n vec4 velocityData = texture(u_velocity_texture, mainPos);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocityData.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocityData.g);\n \n // Calculate velocity in normalized coordinates\n vec2 geo = positionToGeo(mainPos);\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), geo.y);\n vec2 velocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n if (trailOffset > 0.0) {\n // Compute trail position by moving backwards from main particle along its velocity path\n // The strength of the offset is based on trail segment position\n currentPos = mainPos - velocity * u_speed_factor * trailOffset * 1.5;\n }\n \n // Convert normalized position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], currentPos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - currentPos.y);\n \n // Project to Web Mercator\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n \n // Pass position to fragment shader\n v_position = currentPos;\n \n // Compute opacity for trail particles (1.0 for main particle, decreasing for trail)\n // v_opacity = trailOffset == 0.0 ? 1.0 : 1.0 - (trailOffset / float(3));\n \n // Compute point size (decreasing for trail particles)\n float size = trailOffset == 0.0 ? u_point_size : u_point_size * pow(u_trail_size_decay, trailOffset);\n gl_PointSize = size;\n }\n","#version 300 es\n precision mediump float;\n \n uniform sampler2D u_wind_color; // Colormap texture\n uniform sampler2D u_velocity_texture; // Wind velocity texture\n uniform mediump float u_opacity; // Global opacity control\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump vec2 u_speed_range; // Speed range for normalization\n \n in vec2 v_position; // Current position\n out vec4 fragColor; // Output color\n \n void main() {\n // Calculate distance from center for circular particles\n vec2 center = gl_PointCoord - vec2(0.5);\n float dist = length(center);\n \n // Discard pixels outside the circle\n if (dist > 0.5) {\n discard;\n }\n \n // Create soft-edged circular particles\n float edgeFactor = 1.0 - smoothstep(0.45, 0.5, dist);\n \n // Sample wind velocity for coloring\n vec4 velocity = texture(u_velocity_texture, v_position);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n float speed = length(vec2(u, v));\n float normalizedSpeed = (speed - u_speed_range[0]) / (u_speed_range[1] - u_speed_range[0]);\n \n // Sample color from colormap using normalized speed\n vec4 color = texture(u_wind_color, vec2(normalizedSpeed, 0.5));\n \n // Ensure we have some minimum color intensity\n color.rgb = max(color.rgb, vec3(0.2));\n \n // Combine edge fade with opacity\n float finalAlpha = edgeFactor * u_opacity;\n \n // Output final color with alpha\n fragColor = vec4(color.rgb, finalAlpha);\n }\n");const n=new Float32Array(2*this.particleCount),o=new Float32Array(this.particleCount),i=Math.ceil(Math.sqrt(this.particleCount));for(let e=0;e<this.particleCount;e++){const t=e%i/(i-1),r=Math.floor(e/i)/(i-1),a=1/(i-1),s=.25*(Math.random()-.5)*a,u=.25*(Math.random()-.5)*a;n[2*e]=Math.max(0,Math.min(1,t+s)),n[2*e+1]=Math.max(0,Math.min(1,r+u)),o[e]=Math.floor(100*Math.random())}this.particleBufferA=a(t,n),this.particleBufferB=a(t,n),this.currentBuffer=this.particleBufferA,this.nextBuffer=this.particleBufferB,this.ageBufferA=a(t,o),this.ageBufferB=a(t,o),this.currentAgeBuffer=this.ageBufferA,this.nextAgeBuffer=this.ageBufferB;const s=new Float32Array(this.trailLength+1);for(let e=0;e<=this.trailLength;e++)s[e]=e;this.trailOffsetBuffer=a(t,s),this.transformFeedback=t.createTransformFeedback(),this.lastTime=0,this.setSource(this.source,0)}setSource(e,r=.5){this.source!=e&&(this.source=e);const n=new Image;n.crossOrigin="anonymous",fetch(e,{cache:"no-store"}).then((e=>Promise.all([e.clone().blob(),e.arrayBuffer().then((e=>new Uint8Array(e).buffer))]))).then((([e,a])=>{const u=URL.createObjectURL(e);n.onload=()=>{URL.revokeObjectURL(u),this.sourceTexture=o(this.gl,this.gl.LINEAR,n),this.sourceLoaded=!0,r>0&&(this.percentParticleWhenSetSource=r,this.shouldResetParticles=!0),this.map.triggerRepaint()},n.onerror=e=>{console.warn("ParticleMotion: Error loading source image:",e),URL.revokeObjectURL(u)},(async()=>{try{const e=await t.load(a);if(e.ImageDescription&&e.ImageDescription.description){const t=e.ImageDescription.description.match(/(-?\d+\.?\d*),(-?\d+\.?\d*);(-?\d+\.?\d*),(-?\d+\.?\d*);(-?\d+\.?\d*),(-?\d+\.?\d*)/);if(t){let e=parseFloat(t[1]),r=parseFloat(t[2]),a=parseFloat(t[3]),l=parseFloat(t[4]),c=parseFloat(t[5]),f=parseFloat(t[6]);if("kph"===this.unit?(e=i(e),r=i(r),a=i(a),l=i(l),c=i(c),f=i(f)):"mps"===this.unit&&(e=s(e),r=s(r),a=s(a),l=s(l),c=s(c),f=s(f)),!(isNaN(e)||isNaN(r)||isNaN(a)||isNaN(l)||isNaN(c)||isNaN(f)))return this.valueRange_u=[e,r],this.valueRange_v=[a,l],this.speedRange=[c,f],this.colormapTexture=function(e,t,r){t.sort(((e,t)=>e[0]-t[0]));const n=new Uint8Array(1024),[a,i]=r;for(let e=0;e<256;e++){const r=a+e/255*(i-a);let o=0;for(;o<t.length-1&&t[o+1][0]<r;)o++;const s=Math.min(o+1,t.length-1),u=t[o],l=t[s],c=s>o?(r-u[0])/(l[0]-u[0]):0,f=4*e;for(let e=0;e<3;e++)n[f+e]=Math.round(u[1][e]+c*(l[1][e]-u[1][e]));n[f+3]=255}return o(e,e.LINEAR,n,256,1)}(this.gl,this.color,this.speedRange),void(n.src=u)}}console.warn("ParticleMotion: No valid value ranges found in EXIF data"),URL.revokeObjectURL(u)}catch(e){console.warn("ParticleMotion: Error reading EXIF data:",e),URL.revokeObjectURL(u)}})()})).catch((e=>{console.warn("ParticleMotion: Error fetching image:",e)}))}render(e,t){if(!this.sourceLoaded||!this.readyForDisplay)return;const r=performance.now();this.lastTime||(this.lastTime=r);r-this.lastTime>=this.updateInterval&&(this.lastTime=r,e.colorMask(!1,!1,!1,!1),e.disable(e.BLEND),e.useProgram(this.updateProgram.program),e.uniform1f(this.updateProgram.u_time,r/1e3),e.uniform1f(this.updateProgram.u_speed_factor,this.velocityFactor),e.uniform4fv(this.updateProgram.u_bounds,this.bounds),e.uniform2fv(this.updateProgram.u_value_range_u,this.valueRange_u),e.uniform2fv(this.updateProgram.u_value_range_v,this.valueRange_v),e.uniform2fv(this.updateProgram.u_speed_range,this.speedRange),e.uniform1f(this.updateProgram.u_age_threshold,this.ageThreshold),e.uniform1f(this.updateProgram.u_max_age,this.maxAge),e.uniform1f(this.updateProgram.u_percent_reset,this.percentParticleWhenSetSource||0),e.uniform1i(this.updateProgram.u_should_reset,this.shouldResetParticles?1:0),this.shouldResetParticles=!1,e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.updateProgram.u_velocity_texture,0),e.bindBuffer(e.ARRAY_BUFFER,this.currentBuffer),e.enableVertexAttribArray(this.updateProgram.a_position),e.vertexAttribPointer(this.updateProgram.a_position,2,e.FLOAT,!1,0,0),e.bindBuffer(e.ARRAY_BUFFER,this.currentAgeBuffer),e.enableVertexAttribArray(this.updateProgram.a_age),e.vertexAttribPointer(this.updateProgram.a_age,1,e.FLOAT,!1,0,0),e.bindTransformFeedback(e.TRANSFORM_FEEDBACK,this.transformFeedback),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,0,this.nextBuffer),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,1,this.nextAgeBuffer),e.beginTransformFeedback(e.POINTS),e.drawArrays(e.POINTS,0,this.particleCount),e.endTransformFeedback(),e.bindTransformFeedback(e.TRANSFORM_FEEDBACK,null),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,0,null),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,1,null),e.colorMask(!0,!0,!0,!0),[this.currentBuffer,this.nextBuffer]=[this.nextBuffer,this.currentBuffer],[this.currentAgeBuffer,this.nextAgeBuffer]=[this.nextAgeBuffer,this.currentAgeBuffer]),e.bindFramebuffer(e.FRAMEBUFFER,null),e.viewport(0,0,e.canvas.width,e.canvas.height),e.useProgram(this.renderProgram.program),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),e.uniformMatrix4fv(this.renderProgram.u_matrix,!1,t),e.uniform4fv(this.renderProgram.u_bounds,this.bounds),e.uniform1f(this.renderProgram.u_point_size,this.pointSize),e.uniform1f(this.renderProgram.u_opacity,this.fadeOpacity),e.uniform1f(this.renderProgram.u_speed_factor,this.velocityFactor),e.uniform1f(this.renderProgram.u_trail_size_decay,this.trailSizeDecay),e.uniform2fv(this.renderProgram.u_value_range_u,this.valueRange_u),e.uniform2fv(this.renderProgram.u_value_range_v,this.valueRange_v),e.uniform2fv(this.renderProgram.u_speed_range,this.speedRange),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.renderProgram.u_velocity_texture,0),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.colormapTexture),e.uniform1i(this.renderProgram.u_wind_color,1),e.bindBuffer(e.ARRAY_BUFFER,this.currentBuffer),e.enableVertexAttribArray(this.renderProgram.a_position),e.vertexAttribPointer(this.renderProgram.a_position,2,e.FLOAT,!1,0,0),e.bindBuffer(e.ARRAY_BUFFER,this.trailOffsetBuffer),e.enableVertexAttribArray(this.renderProgram.a_trail_offset),e.vertexAttribPointer(this.renderProgram.a_trail_offset,1,e.FLOAT,!1,0,0),e.vertexAttribDivisor(this.renderProgram.a_trail_offset,1),e.drawArraysInstanced(e.POINTS,0,this.particleCount,this.trailLength+1),e.vertexAttribDivisor(this.renderProgram.a_trail_offset,0),e.disable(e.BLEND),this.map.triggerRepaint()}onRemove(e,t){if(this.updateProgram){const e=t.getAttachedShaders(this.updateProgram.program);e&&e.forEach((e=>t.deleteShader(e))),t.deleteProgram(this.updateProgram.program)}if(this.renderProgram){const e=t.getAttachedShaders(this.renderProgram.program);e&&e.forEach((e=>t.deleteShader(e))),t.deleteProgram(this.renderProgram.program)}this.particleBufferA&&t.deleteBuffer(this.particleBufferA),this.particleBufferB&&t.deleteBuffer(this.particleBufferB),this.ageBufferA&&t.deleteBuffer(this.ageBufferA),this.ageBufferB&&t.deleteBuffer(this.ageBufferB),this.trailOffsetBuffer&&t.deleteBuffer(this.trailOffsetBuffer),this.transformFeedback&&t.deleteTransformFeedback(this.transformFeedback),this.sourceTexture&&t.deleteTexture(this.sourceTexture),this.colormapTexture&&t.deleteTexture(this.colormapTexture),this.particleBufferA=null,this.particleBufferB=null,this.ageBufferA=null,this.ageBufferB=null,this.currentBuffer=null,this.nextBuffer=null,this.currentAgeBuffer=null,this.nextAgeBuffer=null,this.trailOffsetBuffer=null,this.transformFeedback=null,this.sourceTexture=null,this.colormapTexture=null,this.updateProgram=null,this.renderProgram=null,this.gl=null,this.map=null,this.sourceLoaded=!1}}function l(e,t,r){const n=e.createShader(t);if(e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(e.getShaderInfoLog(n));return n}function c(e,t,r,n,o){const a=e.createTexture();return e.bindTexture(e.TEXTURE_2D,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),r instanceof Uint8Array?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,r):e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r),a}class f extends e{constructor({id:e,source:t,color:r,bounds:n,opacity:o=1,readyForDisplay:a=!1}){super(),this.id=e,this.type="custom",this.renderingMode="2d",this.source=t,this.color=r,this.opacity=o,this.bounds=n,this.sourceLoaded=!1,this.readyForDisplay=a}onAdd(e,t){this.map=e,this.gl=t,this.program=function(e,t,r){const n=e.createProgram(),o=l(e,e.VERTEX_SHADER,t),a=l(e,e.FRAGMENT_SHADER,r);if(e.attachShader(n,o),e.attachShader(n,a),e.linkProgram(n),!e.getProgramParameter(n,e.LINK_STATUS))throw new Error(e.getProgramInfoLog(n));const i={program:n},s=e.getProgramParameter(n,e.ACTIVE_ATTRIBUTES);for(let t=0;t<s;t++){const r=e.getActiveAttrib(n,t);i[r.name]=e.getAttribLocation(n,r.name)}const u=e.getProgramParameter(n,e.ACTIVE_UNIFORMS);for(let t=0;t<u;t++){const r=e.getActiveUniform(n,t);i[r.name]=e.getUniformLocation(n,r.name)}return i}(t,"\n precision mediump float;\n \n attribute vec2 a_pos;\n uniform mat4 u_matrix;\n uniform vec4 u_bounds; // [minX, maxY, maxX, minY]\n \n varying vec2 v_tex_pos;\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n // y is already in [0,1] range\n return vec2(x, y);\n }\n \n void main() {\n // Map input position [0,1] to geographic bounds\n float lng = mix(u_bounds[0], u_bounds[2], a_pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], a_pos.y);\n \n // Convert to Web Mercator coordinates in [0,1] range\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n \n // Pass texture coordinates\n v_tex_pos = vec2(a_pos.x, 1.0 - a_pos.y);\n \n // Apply matrix transformation\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n }\n","\n precision mediump float;\n \n uniform sampler2D u_image;\n uniform sampler2D u_colormap;\n uniform float u_opacity;\n uniform vec2 u_value_range; // [min, max] of original values\n \n varying vec2 v_tex_pos;\n \n void main() {\n // Get the R value from the source image\n vec4 pixel = texture2D(u_image, v_tex_pos);\n float normalized = pixel.r;\n \n // Use value as index into colormap\n vec4 color = texture2D(u_colormap, vec2(normalized, 0.5));\n \n // Apply global opacity\n gl_FragColor = vec4(color.rgb, color.a * u_opacity);\n }\n");const r=new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]);this.vertexBuffer=function(e,t){const r=e.createBuffer();return e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,t,e.STATIC_DRAW),r}(t,r),this.setSource(this.source)}setSource(e,r=null){this.source!=e&&(this.source=e),null!=r&&(this.color=r);const n=new Image;n.crossOrigin="anonymous",fetch(e,{cache:"no-store"}).then((e=>Promise.all([e.clone().blob(),e.arrayBuffer().then((e=>new Uint8Array(e).buffer))]))).then((([e,r])=>{const o=URL.createObjectURL(e);this.valueRange=[0,255],(async()=>{try{const e=await t.load(r);if(e.ImageDescription&&e.ImageDescription.description){const t=e.ImageDescription.description.match(/(-?\d+\.?\d*),(-?\d+\.?\d*)/);if(t){const e=parseFloat(t[1]),r=parseFloat(t[2]);isNaN(e)||isNaN(r)||(this.valueRange=[e,r])}}n.onload=()=>{URL.revokeObjectURL(o),this.gl&&(this.sourceTexture=c(this.gl,this.gl.LINEAR,n),this.valueRange&&(this.colormapTexture=function(e,t,r){t.sort(((e,t)=>e[0]-t[0]));const n=new Uint8Array(1024),[o,a]=r;for(let e=0;e<256;e++){const r=o+e/255*(a-o);let i=0;for(;i<t.length-1&&t[i+1][0]<r;)i++;const s=Math.min(i+1,t.length-1),u=t[i],l=t[s],c=s>i?(r-u[0])/(l[0]-u[0]):0,f=4*e;for(let e=0;e<3;e++)n[f+e]=Math.round(u[1][e]+c*(l[1][e]-u[1][e]));null!=u[1][3]&&255!=u[1][3]?n[f+3]=u[1][3]:n[f+3]=255}return c(e,e.NEAREST,n,256,1)}(this.gl,this.color,this.valueRange)),this.sourceLoaded=!0,this.map&&this.map.triggerRepaint())},n.onerror=e=>{URL.revokeObjectURL(o),console.error("Error loading source image:",e)},n.src=o}catch(e){console.warn("Error reading EXIF data:",e),n.src=o}})()})).catch((e=>{console.error("Error fetching image:",e)}))}onRemove(){const e=this.gl;if(e){if(this.sourceTexture&&e.deleteTexture(this.sourceTexture),this.colormapTexture&&e.deleteTexture(this.colormapTexture),this.vertexBuffer&&e.deleteBuffer(this.vertexBuffer),this.program){const t=e.getAttachedShaders(this.program.program);t&&t.forEach((t=>e.deleteShader(t))),e.deleteProgram(this.program.program)}this.sourceTexture=null,this.colormapTexture=null,this.vertexBuffer=null,this.program=null,this.gl=null,this.map=null,this.sourceLoaded=!1}}render(e,t){this.sourceLoaded&&this.readyForDisplay&&(e.useProgram(this.program.program),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),e.uniformMatrix4fv(this.program.u_matrix,!1,t),e.uniform4fv(this.program.u_bounds,this.bounds),e.uniform1f(this.program.u_opacity,this.opacity),e.uniform2fv(this.program.u_value_range,this.valueRange),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.program.u_image,0),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.colormapTexture),e.uniform1i(this.program.u_colormap,1),e.bindBuffer(e.ARRAY_BUFFER,this.vertexBuffer),e.enableVertexAttribArray(this.program.a_pos),e.vertexAttribPointer(this.program.a_pos,2,e.FLOAT,!1,0,0),e.drawArrays(e.TRIANGLES,0,6),e.disable(e.BLEND))}}export{u as ParticleMotion,f as SmoothRaster};
1
+ import{Evented as e}from"mapbox-gl";import t from"exifreader";function r(e,t,r){const o=e.createProgram(),a=n(e,e.VERTEX_SHADER,t),i=n(e,e.FRAGMENT_SHADER,r);if(e.attachShader(o,a),e.attachShader(o,i),t.includes("out vec2 v_position")&&(t.includes("out float v_age")?e.transformFeedbackVaryings(o,["v_position","v_age"],e.SEPARATE_ATTRIBS):e.transformFeedbackVaryings(o,["v_position"],e.SEPARATE_ATTRIBS)),e.linkProgram(o),!e.getProgramParameter(o,e.LINK_STATUS))throw new Error(e.getProgramInfoLog(o));const s={program:o},u=e.getProgramParameter(o,e.ACTIVE_ATTRIBUTES);for(let t=0;t<u;t++){const r=e.getActiveAttrib(o,t);s[r.name]=e.getAttribLocation(o,r.name)}const l=e.getProgramParameter(o,e.ACTIVE_UNIFORMS);for(let t=0;t<l;t++){const r=e.getActiveUniform(o,t);s[r.name]=e.getUniformLocation(o,r.name)}return s}function n(e,t,r){const n=e.createShader(t);if(e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(e.getShaderInfoLog(n));return n}function o(e,t,r,n,o){const a=e.createTexture();return e.bindTexture(e.TEXTURE_2D,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),r instanceof Uint8Array?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,r):r instanceof Image?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r):e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,null),a}function a(e,t){const r=e.createBuffer();return e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,t,e.STATIC_DRAW),r}function i(e){return.621371*e}function s(e){return 2.23694*e}class u extends e{constructor({id:e,source:t,color:r,bounds:n,particleCount:o=5e3,readyForDisplay:a=!1,ageThreshold:i=500,maxAge:s=1e3,velocityFactor:u=.05,fadeOpacity:l=.9,updateInterval:c=50,pointSize:f=5,trailLength:m=3,trailSizeDecay:d=.8,unit:h="mph",cacheOption:g="no-cache",slot:p}){super(),this.id=e,this.type="custom",this.renderingMode="2d",void 0!==p&&(this.slot=p),this.source=t,this.color=r,this.bounds=n,this.particleCount=o,this.sourceLoaded=!1,this.readyForDisplay=a,this.velocityFactor=u,this.fadeOpacity=l,this.updateInterval=c,this.pointSize=f,this.trailLength=m,this.trailSizeDecay=d,this.ageThreshold=i,this.maxAge=s,this.speedRange=[0,100],this.unit=h,this.cacheOption=g}onAdd(e,t){this.map=e,this.gl=t,this.updateProgram=r(t,"#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Current position [0,1]\n in float a_age; // Particle age for tracking circular patterns\n out vec2 v_position; // Updated position for transform feedback\n out float v_age; // Updated age for transform feedback\n \n uniform sampler2D u_velocity_texture; // Texture with normalized velocities [0,1]\n uniform mediump vec4 u_bounds;\n uniform mediump float u_speed_factor;\n uniform mediump float u_time;\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump float u_age_threshold; // Age threshold for reset probability\n uniform mediump float u_max_age; // Maximum age before forced reset\n uniform mediump float u_percent_reset; // Percentage of particles to reset on source update\n uniform bool u_should_reset; // Flag to indicate if we should apply the percentage reset\n \n // Random function based on time and position\n float random(vec2 co) {\n float a = 12.9898;\n float b = 78.233;\n float c = 43758.5453;\n float dt = dot(co, vec2(a,b));\n float sn = mod(dt, 3.14);\n return fract(sin(sn) * c + u_time);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n // Function to generate random position within extent\n vec2 generateRandomPosition(vec2 seed) {\n return vec2(\n random(seed + vec2(1.23, 4.56)),\n random(seed + vec2(7.89, 0.12))\n );\n }\n \n // Function to generate a position on one of the boundaries\n vec2 generateBoundaryPosition(vec2 seed) {\n // Choose which boundary (0=left, 1=top, 2=right, 3=bottom)\n float boundary = floor(random(seed) * 4.0);\n \n // Position along the boundary\n float pos = random(seed + vec2(boundary));\n \n // Small offset to prevent immediate out-of-bounds\n float offset = 0.001;\n \n if (boundary < 1.0) { // Left\n return vec2(offset, pos);\n } else if (boundary < 2.0) { // Top\n return vec2(pos, offset);\n } else if (boundary < 3.0) { // Right\n return vec2(1.0 - offset, pos);\n } else { // Bottom\n return vec2(pos, 1.0 - offset);\n }\n }\n \n void main() {\n // Sample normalized velocity from texture [0,1]\n vec4 velocity = texture(u_velocity_texture, a_position);\n \n // Denormalize velocities to actual mph values\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n \n // Calculate wind speed\n float windSpeed = length(vec2(u, v));\n \n // Convert position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], a_position.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - a_position.y);\n \n // Convert wind velocity to degree offsets using actual mph values\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), lat);\n \n // Convert degree offsets back to normalized coordinates\n vec2 normalizedVelocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n // Update position with velocity\n vec2 newPos = a_position + normalizedVelocity * u_speed_factor;\n \n // Reset rules\n bool shouldReset = false;\n \n // Get current age of particle and increment\n float age = a_age + 1.0;\n \n // Reset if out of bounds or very low wind speed\n if (newPos.x < 0.0 || newPos.x > 1.0 || newPos.y < 0.0 || newPos.y > 1.0 || windSpeed < 1.5) {\n shouldReset = true;\n }\n \n // Reset based on age with increasing probability\n if (age > u_age_threshold) {\n float resetProbability = (age - u_age_threshold) / (u_max_age - u_age_threshold);\n if (random(a_position + vec2(u_time * 0.1, age * 0.01)) < resetProbability) {\n shouldReset = true;\n }\n }\n \n // Force reset of very old particles\n if (age > u_max_age) {\n shouldReset = true;\n }\n \n // Additional reset based on percentParticleWhenSetSource if flag is set\n if (!shouldReset && u_should_reset) {\n if (random(a_position + vec2(u_time)) < u_percent_reset) {\n shouldReset = true;\n }\n }\n \n // Handle reset by generating a new position\n if (shouldReset) {\n newPos = generateRandomPosition(a_position + vec2(u_time));\n\n age = 0.0; // Reset age\n }\n \n v_position = newPos;\n v_age = age;\n }\n","#version 300 es\n precision mediump float;\n \n out vec4 fragColor;\n \n void main() {\n // For update step, we don't need to output anything visual\n // We're just using transform feedback to capture the new positions\n fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n }\n"),this.renderProgram=r(t,"#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Position in [0,1] range\n in float a_trail_offset; // Trail offset (0=main particle, 1,2,3=trail segments)\n \n uniform mediump mat4 u_matrix;\n uniform mediump vec4 u_bounds; // [minX, maxY, maxX, minY]\n uniform mediump float u_point_size; // Base point size\n uniform mediump float u_speed_factor; // Speed multiplier\n uniform mediump float u_trail_size_decay; // Size decay rate for trail particles\n uniform sampler2D u_velocity_texture; // Velocity texture\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n \n out vec2 v_position; // Pass position to fragment shader\n // out float v_opacity; // Varying opacity for trail\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n return vec2(x, y);\n }\n \n // Convert position to geographic coordinates\n vec2 positionToGeo(vec2 pos) {\n float lng = mix(u_bounds[0], u_bounds[2], pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - pos.y); // Invert y for correct mapping\n return vec2(lng, lat);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n void main() {\n // Trail offset (0 = main particle, >0 = trail segment)\n float trailOffset = a_trail_offset;\n \n // Main particle position from buffer\n vec2 mainPos = a_position;\n \n // Current position is main position for main particle (offset=0)\n vec2 currentPos = mainPos;\n \n // Sample velocity at the main particle's position\n vec4 velocityData = texture(u_velocity_texture, mainPos);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocityData.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocityData.g);\n \n // Calculate velocity in normalized coordinates\n vec2 geo = positionToGeo(mainPos);\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), geo.y);\n vec2 velocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n if (trailOffset > 0.0) {\n // Compute trail position by moving backwards from main particle along its velocity path\n // The strength of the offset is based on trail segment position\n currentPos = mainPos - velocity * u_speed_factor * trailOffset * 1.5;\n }\n \n // Convert normalized position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], currentPos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - currentPos.y);\n \n // Project to Web Mercator\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n \n // Pass position to fragment shader\n v_position = currentPos;\n \n // Compute opacity for trail particles (1.0 for main particle, decreasing for trail)\n // v_opacity = trailOffset == 0.0 ? 1.0 : 1.0 - (trailOffset / float(3));\n \n // Compute point size (decreasing for trail particles)\n float size = trailOffset == 0.0 ? u_point_size : u_point_size * pow(u_trail_size_decay, trailOffset);\n gl_PointSize = size;\n }\n","#version 300 es\n precision mediump float;\n \n uniform sampler2D u_wind_color; // Colormap texture\n uniform sampler2D u_velocity_texture; // Wind velocity texture\n uniform mediump float u_opacity; // Global opacity control\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump vec2 u_speed_range; // Speed range for normalization\n \n in vec2 v_position; // Current position\n out vec4 fragColor; // Output color\n \n void main() {\n // Calculate distance from center for circular particles\n vec2 center = gl_PointCoord - vec2(0.5);\n float dist = length(center);\n \n // Discard pixels outside the circle\n if (dist > 0.5) {\n discard;\n }\n \n // Create soft-edged circular particles\n float edgeFactor = 1.0 - smoothstep(0.45, 0.5, dist);\n \n // Sample wind velocity for coloring\n vec4 velocity = texture(u_velocity_texture, v_position);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n float speed = length(vec2(u, v));\n float normalizedSpeed = (speed - u_speed_range[0]) / (u_speed_range[1] - u_speed_range[0]);\n \n // Sample color from colormap using normalized speed\n vec4 color = texture(u_wind_color, vec2(normalizedSpeed, 0.5));\n \n // Ensure we have some minimum color intensity\n color.rgb = max(color.rgb, vec3(0.2));\n \n // Combine edge fade with opacity\n float finalAlpha = edgeFactor * u_opacity;\n \n // Output final color with alpha\n fragColor = vec4(color.rgb, finalAlpha);\n }\n");const n=new Float32Array(2*this.particleCount),o=new Float32Array(this.particleCount),i=Math.ceil(Math.sqrt(this.particleCount));for(let e=0;e<this.particleCount;e++){const t=e%i/(i-1),r=Math.floor(e/i)/(i-1),a=1/(i-1),s=.25*(Math.random()-.5)*a,u=.25*(Math.random()-.5)*a;n[2*e]=Math.max(0,Math.min(1,t+s)),n[2*e+1]=Math.max(0,Math.min(1,r+u)),o[e]=Math.floor(100*Math.random())}this.particleBufferA=a(t,n),this.particleBufferB=a(t,n),this.currentBuffer=this.particleBufferA,this.nextBuffer=this.particleBufferB,this.ageBufferA=a(t,o),this.ageBufferB=a(t,o),this.currentAgeBuffer=this.ageBufferA,this.nextAgeBuffer=this.ageBufferB;const s=new Float32Array(this.trailLength+1);for(let e=0;e<=this.trailLength;e++)s[e]=e;this.trailOffsetBuffer=a(t,s),this.transformFeedback=t.createTransformFeedback(),this.lastTime=0,this.setSource(this.source,0)}setSource(e,r=.5){this.source!=e&&(this.source=e);const n=new Image;n.crossOrigin="anonymous",fetch(e,{cache:this.cacheOption}).then((e=>Promise.all([e.clone().blob(),e.arrayBuffer().then((e=>new Uint8Array(e).buffer))]))).then((([e,a])=>{const u=URL.createObjectURL(e);n.onload=()=>{URL.revokeObjectURL(u),this.sourceTexture=o(this.gl,this.gl.LINEAR,n),this.sourceLoaded=!0,r>0&&(this.percentParticleWhenSetSource=r,this.shouldResetParticles=!0),this.map.triggerRepaint()},n.onerror=e=>{console.warn("ParticleMotion: Error loading source image:",e),URL.revokeObjectURL(u)},(async()=>{try{const e=await t.load(a);if(e.ImageDescription&&e.ImageDescription.description){const t=e.ImageDescription.description.match(/(-?\d+\.?\d*),(-?\d+\.?\d*);(-?\d+\.?\d*),(-?\d+\.?\d*);(-?\d+\.?\d*),(-?\d+\.?\d*)/);if(t){let e=parseFloat(t[1]),r=parseFloat(t[2]),a=parseFloat(t[3]),l=parseFloat(t[4]),c=parseFloat(t[5]),f=parseFloat(t[6]);if("kph"===this.unit?(e=i(e),r=i(r),a=i(a),l=i(l),c=i(c),f=i(f)):"mps"===this.unit&&(e=s(e),r=s(r),a=s(a),l=s(l),c=s(c),f=s(f)),!(isNaN(e)||isNaN(r)||isNaN(a)||isNaN(l)||isNaN(c)||isNaN(f)))return this.valueRange_u=[e,r],this.valueRange_v=[a,l],this.speedRange=[c,f],this.colormapTexture=function(e,t,r){t.sort(((e,t)=>e[0]-t[0]));const n=new Uint8Array(1024),[a,i]=r;for(let e=0;e<256;e++){const r=a+e/255*(i-a);let o=0;for(;o<t.length-1&&t[o+1][0]<r;)o++;const s=Math.min(o+1,t.length-1),u=t[o],l=t[s],c=s>o?(r-u[0])/(l[0]-u[0]):0,f=4*e;for(let e=0;e<3;e++)n[f+e]=Math.round(u[1][e]+c*(l[1][e]-u[1][e]));n[f+3]=255}return o(e,e.LINEAR,n,256,1)}(this.gl,this.color,this.speedRange),void(n.src=u)}}console.warn("ParticleMotion: No valid value ranges found in EXIF data"),URL.revokeObjectURL(u)}catch(e){console.warn("ParticleMotion: Error reading EXIF data:",e),URL.revokeObjectURL(u)}})()})).catch((e=>{console.warn("ParticleMotion: Error fetching image:",e)}))}render(e,t){if(!this.sourceLoaded||!this.readyForDisplay)return;const r=performance.now();this.lastTime||(this.lastTime=r);r-this.lastTime>=this.updateInterval&&(this.lastTime=r,e.colorMask(!1,!1,!1,!1),e.disable(e.BLEND),e.useProgram(this.updateProgram.program),e.uniform1f(this.updateProgram.u_time,r/1e3),e.uniform1f(this.updateProgram.u_speed_factor,this.velocityFactor),e.uniform4fv(this.updateProgram.u_bounds,this.bounds),e.uniform2fv(this.updateProgram.u_value_range_u,this.valueRange_u),e.uniform2fv(this.updateProgram.u_value_range_v,this.valueRange_v),e.uniform2fv(this.updateProgram.u_speed_range,this.speedRange),e.uniform1f(this.updateProgram.u_age_threshold,this.ageThreshold),e.uniform1f(this.updateProgram.u_max_age,this.maxAge),e.uniform1f(this.updateProgram.u_percent_reset,this.percentParticleWhenSetSource||0),e.uniform1i(this.updateProgram.u_should_reset,this.shouldResetParticles?1:0),this.shouldResetParticles=!1,e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.updateProgram.u_velocity_texture,0),e.bindBuffer(e.ARRAY_BUFFER,this.currentBuffer),e.enableVertexAttribArray(this.updateProgram.a_position),e.vertexAttribPointer(this.updateProgram.a_position,2,e.FLOAT,!1,0,0),e.bindBuffer(e.ARRAY_BUFFER,this.currentAgeBuffer),e.enableVertexAttribArray(this.updateProgram.a_age),e.vertexAttribPointer(this.updateProgram.a_age,1,e.FLOAT,!1,0,0),e.bindTransformFeedback(e.TRANSFORM_FEEDBACK,this.transformFeedback),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,0,this.nextBuffer),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,1,this.nextAgeBuffer),e.beginTransformFeedback(e.POINTS),e.drawArrays(e.POINTS,0,this.particleCount),e.endTransformFeedback(),e.bindTransformFeedback(e.TRANSFORM_FEEDBACK,null),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,0,null),e.bindBufferBase(e.TRANSFORM_FEEDBACK_BUFFER,1,null),e.colorMask(!0,!0,!0,!0),[this.currentBuffer,this.nextBuffer]=[this.nextBuffer,this.currentBuffer],[this.currentAgeBuffer,this.nextAgeBuffer]=[this.nextAgeBuffer,this.currentAgeBuffer]),e.bindFramebuffer(e.FRAMEBUFFER,null),e.viewport(0,0,e.canvas.width,e.canvas.height),e.useProgram(this.renderProgram.program),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),e.uniformMatrix4fv(this.renderProgram.u_matrix,!1,t),e.uniform4fv(this.renderProgram.u_bounds,this.bounds),e.uniform1f(this.renderProgram.u_point_size,this.pointSize),e.uniform1f(this.renderProgram.u_opacity,this.fadeOpacity),e.uniform1f(this.renderProgram.u_speed_factor,this.velocityFactor),e.uniform1f(this.renderProgram.u_trail_size_decay,this.trailSizeDecay),e.uniform2fv(this.renderProgram.u_value_range_u,this.valueRange_u),e.uniform2fv(this.renderProgram.u_value_range_v,this.valueRange_v),e.uniform2fv(this.renderProgram.u_speed_range,this.speedRange),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.renderProgram.u_velocity_texture,0),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.colormapTexture),e.uniform1i(this.renderProgram.u_wind_color,1),e.bindBuffer(e.ARRAY_BUFFER,this.currentBuffer),e.enableVertexAttribArray(this.renderProgram.a_position),e.vertexAttribPointer(this.renderProgram.a_position,2,e.FLOAT,!1,0,0),e.bindBuffer(e.ARRAY_BUFFER,this.trailOffsetBuffer),e.enableVertexAttribArray(this.renderProgram.a_trail_offset),e.vertexAttribPointer(this.renderProgram.a_trail_offset,1,e.FLOAT,!1,0,0),e.vertexAttribDivisor(this.renderProgram.a_trail_offset,1),e.drawArraysInstanced(e.POINTS,0,this.particleCount,this.trailLength+1),e.vertexAttribDivisor(this.renderProgram.a_trail_offset,0),e.disable(e.BLEND),this.map.triggerRepaint()}onRemove(e,t){if(this.updateProgram){const e=t.getAttachedShaders(this.updateProgram.program);e&&e.forEach((e=>t.deleteShader(e))),t.deleteProgram(this.updateProgram.program)}if(this.renderProgram){const e=t.getAttachedShaders(this.renderProgram.program);e&&e.forEach((e=>t.deleteShader(e))),t.deleteProgram(this.renderProgram.program)}this.particleBufferA&&t.deleteBuffer(this.particleBufferA),this.particleBufferB&&t.deleteBuffer(this.particleBufferB),this.ageBufferA&&t.deleteBuffer(this.ageBufferA),this.ageBufferB&&t.deleteBuffer(this.ageBufferB),this.trailOffsetBuffer&&t.deleteBuffer(this.trailOffsetBuffer),this.transformFeedback&&t.deleteTransformFeedback(this.transformFeedback),this.sourceTexture&&t.deleteTexture(this.sourceTexture),this.colormapTexture&&t.deleteTexture(this.colormapTexture),this.particleBufferA=null,this.particleBufferB=null,this.ageBufferA=null,this.ageBufferB=null,this.currentBuffer=null,this.nextBuffer=null,this.currentAgeBuffer=null,this.nextAgeBuffer=null,this.trailOffsetBuffer=null,this.transformFeedback=null,this.sourceTexture=null,this.colormapTexture=null,this.updateProgram=null,this.renderProgram=null,this.gl=null,this.map=null,this.sourceLoaded=!1}}function l(e,t,r){const n=e.createShader(t);if(e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(e.getShaderInfoLog(n));return n}function c(e,t,r,n,o){const a=e.createTexture();return e.bindTexture(e.TEXTURE_2D,a),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,e.CLAMP_TO_EDGE),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,t),r instanceof Uint8Array?e.texImage2D(e.TEXTURE_2D,0,e.RGBA,n,o,0,e.RGBA,e.UNSIGNED_BYTE,r):e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r),a}class f extends e{constructor({id:e,source:t,color:r,bounds:n,opacity:o=1,readyForDisplay:a=!1,cacheOption:i="no-cache",slot:s}){super(),this.id=e,this.type="custom",this.renderingMode="2d",void 0!==s&&(this.slot=s),this.source=t,this.color=r,this.opacity=o,this.bounds=n,this.sourceLoaded=!1,this.readyForDisplay=a,this.cacheOption=i}onAdd(e,t){this.map=e,this.gl=t,this.program=function(e,t,r){const n=e.createProgram(),o=l(e,e.VERTEX_SHADER,t),a=l(e,e.FRAGMENT_SHADER,r);if(e.attachShader(n,o),e.attachShader(n,a),e.linkProgram(n),!e.getProgramParameter(n,e.LINK_STATUS))throw new Error(e.getProgramInfoLog(n));const i={program:n},s=e.getProgramParameter(n,e.ACTIVE_ATTRIBUTES);for(let t=0;t<s;t++){const r=e.getActiveAttrib(n,t);i[r.name]=e.getAttribLocation(n,r.name)}const u=e.getProgramParameter(n,e.ACTIVE_UNIFORMS);for(let t=0;t<u;t++){const r=e.getActiveUniform(n,t);i[r.name]=e.getUniformLocation(n,r.name)}return i}(t,"\n precision mediump float;\n \n attribute vec2 a_pos;\n uniform mat4 u_matrix;\n uniform vec4 u_bounds; // [minX, maxY, maxX, minY]\n \n varying vec2 v_tex_pos;\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n // y is already in [0,1] range\n return vec2(x, y);\n }\n \n void main() {\n // Map input position [0,1] to geographic bounds\n float lng = mix(u_bounds[0], u_bounds[2], a_pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], a_pos.y);\n \n // Convert to Web Mercator coordinates in [0,1] range\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n \n // Pass texture coordinates\n v_tex_pos = vec2(a_pos.x, 1.0 - a_pos.y);\n \n // Apply matrix transformation\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n }\n","\n precision mediump float;\n \n uniform sampler2D u_image;\n uniform sampler2D u_colormap;\n uniform float u_opacity;\n uniform vec2 u_value_range; // [min, max] of original values\n \n varying vec2 v_tex_pos;\n \n void main() {\n // Get the R value from the source image\n vec4 pixel = texture2D(u_image, v_tex_pos);\n float normalized = pixel.r;\n \n // Use value as index into colormap\n vec4 color = texture2D(u_colormap, vec2(normalized, 0.5));\n \n // Apply global opacity\n gl_FragColor = vec4(color.rgb, color.a * u_opacity);\n }\n");const r=new Float32Array([0,0,1,0,0,1,0,1,1,0,1,1]);this.vertexBuffer=function(e,t){const r=e.createBuffer();return e.bindBuffer(e.ARRAY_BUFFER,r),e.bufferData(e.ARRAY_BUFFER,t,e.STATIC_DRAW),r}(t,r),this.setSource(this.source)}setSource(e,r=null){this.source!=e&&(this.source=e),null!=r&&(this.color=r);const n=new Image;n.crossOrigin="anonymous",fetch(e,{cache:this.cacheOption}).then((e=>Promise.all([e.clone().blob(),e.arrayBuffer().then((e=>new Uint8Array(e).buffer))]))).then((([e,r])=>{const o=URL.createObjectURL(e);this.valueRange=[0,255],(async()=>{try{const e=await t.load(r);if(e.ImageDescription&&e.ImageDescription.description){const t=e.ImageDescription.description.match(/(-?\d+\.?\d*),(-?\d+\.?\d*)/);if(t){const e=parseFloat(t[1]),r=parseFloat(t[2]);isNaN(e)||isNaN(r)||(this.valueRange=[e,r])}}n.onload=()=>{URL.revokeObjectURL(o),this.gl&&(this.sourceTexture=c(this.gl,this.gl.LINEAR,n),this.valueRange&&(this.colormapTexture=function(e,t,r){t.sort(((e,t)=>e[0]-t[0]));const n=new Uint8Array(1024),[o,a]=r;for(let e=0;e<256;e++){const r=o+e/255*(a-o);let i=0;for(;i<t.length-1&&t[i+1][0]<r;)i++;const s=Math.min(i+1,t.length-1),u=t[i],l=t[s],c=s>i?(r-u[0])/(l[0]-u[0]):0,f=4*e;for(let e=0;e<3;e++)n[f+e]=Math.round(u[1][e]+c*(l[1][e]-u[1][e]));null!=u[1][3]&&255!=u[1][3]?n[f+3]=u[1][3]:n[f+3]=255}return c(e,e.NEAREST,n,256,1)}(this.gl,this.color,this.valueRange)),this.sourceLoaded=!0,this.map&&this.map.triggerRepaint())},n.onerror=e=>{URL.revokeObjectURL(o),console.error("Error loading source image:",e)},n.src=o}catch(e){console.warn("Error reading EXIF data:",e),n.src=o}})()})).catch((e=>{console.error("Error fetching image:",e)}))}onRemove(){const e=this.gl;if(e){if(this.sourceTexture&&e.deleteTexture(this.sourceTexture),this.colormapTexture&&e.deleteTexture(this.colormapTexture),this.vertexBuffer&&e.deleteBuffer(this.vertexBuffer),this.program){const t=e.getAttachedShaders(this.program.program);t&&t.forEach((t=>e.deleteShader(t))),e.deleteProgram(this.program.program)}this.sourceTexture=null,this.colormapTexture=null,this.vertexBuffer=null,this.program=null,this.gl=null,this.map=null,this.sourceLoaded=!1}}render(e,t){this.sourceLoaded&&this.readyForDisplay&&(e.useProgram(this.program.program),e.enable(e.BLEND),e.blendFunc(e.SRC_ALPHA,e.ONE_MINUS_SRC_ALPHA),e.uniformMatrix4fv(this.program.u_matrix,!1,t),e.uniform4fv(this.program.u_bounds,this.bounds),e.uniform1f(this.program.u_opacity,this.opacity),e.uniform2fv(this.program.u_value_range,this.valueRange),e.activeTexture(e.TEXTURE0),e.bindTexture(e.TEXTURE_2D,this.sourceTexture),e.uniform1i(this.program.u_image,0),e.activeTexture(e.TEXTURE1),e.bindTexture(e.TEXTURE_2D,this.colormapTexture),e.uniform1i(this.program.u_colormap,1),e.bindBuffer(e.ARRAY_BUFFER,this.vertexBuffer),e.enableVertexAttribArray(this.program.a_pos),e.vertexAttribPointer(this.program.a_pos,2,e.FLOAT,!1,0,0),e.drawArrays(e.TRIANGLES,0,6),e.disable(e.BLEND))}}export{u as ParticleMotion,f as SmoothRaster};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/ParticleMotion.js","../src/SmoothRaster.js"],"sourcesContent":["import {Evented} from 'mapbox-gl';\nimport ExifReader from 'exifreader';\n\nconst vertexShader = \n `#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Current position [0,1]\n in float a_age; // Particle age for tracking circular patterns\n out vec2 v_position; // Updated position for transform feedback\n out float v_age; // Updated age for transform feedback\n \n uniform sampler2D u_velocity_texture; // Texture with normalized velocities [0,1]\n uniform mediump vec4 u_bounds;\n uniform mediump float u_speed_factor;\n uniform mediump float u_time;\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump float u_age_threshold; // Age threshold for reset probability\n uniform mediump float u_max_age; // Maximum age before forced reset\n uniform mediump float u_percent_reset; // Percentage of particles to reset on source update\n uniform bool u_should_reset; // Flag to indicate if we should apply the percentage reset\n \n // Random function based on time and position\n float random(vec2 co) {\n float a = 12.9898;\n float b = 78.233;\n float c = 43758.5453;\n float dt = dot(co, vec2(a,b));\n float sn = mod(dt, 3.14);\n return fract(sin(sn) * c + u_time);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n // Function to generate random position within extent\n vec2 generateRandomPosition(vec2 seed) {\n return vec2(\n random(seed + vec2(1.23, 4.56)),\n random(seed + vec2(7.89, 0.12))\n );\n }\n \n // Function to generate a position on one of the boundaries\n vec2 generateBoundaryPosition(vec2 seed) {\n // Choose which boundary (0=left, 1=top, 2=right, 3=bottom)\n float boundary = floor(random(seed) * 4.0);\n \n // Position along the boundary\n float pos = random(seed + vec2(boundary));\n \n // Small offset to prevent immediate out-of-bounds\n float offset = 0.001;\n \n if (boundary < 1.0) { // Left\n return vec2(offset, pos);\n } else if (boundary < 2.0) { // Top\n return vec2(pos, offset);\n } else if (boundary < 3.0) { // Right\n return vec2(1.0 - offset, pos);\n } else { // Bottom\n return vec2(pos, 1.0 - offset);\n }\n }\n \n void main() {\n // Sample normalized velocity from texture [0,1]\n vec4 velocity = texture(u_velocity_texture, a_position);\n \n // Denormalize velocities to actual mph values\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n \n // Calculate wind speed\n float windSpeed = length(vec2(u, v));\n \n // Convert position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], a_position.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - a_position.y);\n \n // Convert wind velocity to degree offsets using actual mph values\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), lat);\n \n // Convert degree offsets back to normalized coordinates\n vec2 normalizedVelocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n // Update position with velocity\n vec2 newPos = a_position + normalizedVelocity * u_speed_factor;\n \n // Reset rules\n bool shouldReset = false;\n \n // Get current age of particle and increment\n float age = a_age + 1.0;\n \n // Reset if out of bounds or very low wind speed\n if (newPos.x < 0.0 || newPos.x > 1.0 || newPos.y < 0.0 || newPos.y > 1.0 || windSpeed < 1.5) {\n shouldReset = true;\n }\n \n // Reset based on age with increasing probability\n if (age > u_age_threshold) {\n float resetProbability = (age - u_age_threshold) / (u_max_age - u_age_threshold);\n if (random(a_position + vec2(u_time * 0.1, age * 0.01)) < resetProbability) {\n shouldReset = true;\n }\n }\n \n // Force reset of very old particles\n if (age > u_max_age) {\n shouldReset = true;\n }\n \n // Additional reset based on percentParticleWhenSetSource if flag is set\n if (!shouldReset && u_should_reset) {\n if (random(a_position + vec2(u_time)) < u_percent_reset) {\n shouldReset = true;\n }\n }\n \n // Handle reset by generating a new position\n if (shouldReset) {\n newPos = generateRandomPosition(a_position + vec2(u_time));\n\n age = 0.0; // Reset age\n }\n \n v_position = newPos;\n v_age = age;\n }\n`;\n\nconst fragmentShader = \n `#version 300 es\n precision mediump float;\n \n uniform sampler2D u_wind_color; // Colormap texture\n uniform sampler2D u_velocity_texture; // Wind velocity texture\n uniform mediump float u_opacity; // Global opacity control\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump vec2 u_speed_range; // Speed range for normalization\n \n in vec2 v_position; // Current position\n out vec4 fragColor; // Output color\n \n void main() {\n // Calculate distance from center for circular particles\n vec2 center = gl_PointCoord - vec2(0.5);\n float dist = length(center);\n \n // Discard pixels outside the circle\n if (dist > 0.5) {\n discard;\n }\n \n // Create soft-edged circular particles\n float edgeFactor = 1.0 - smoothstep(0.45, 0.5, dist);\n \n // Sample wind velocity for coloring\n vec4 velocity = texture(u_velocity_texture, v_position);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n float speed = length(vec2(u, v));\n float normalizedSpeed = (speed - u_speed_range[0]) / (u_speed_range[1] - u_speed_range[0]);\n \n // Sample color from colormap using normalized speed\n vec4 color = texture(u_wind_color, vec2(normalizedSpeed, 0.5));\n \n // Ensure we have some minimum color intensity\n color.rgb = max(color.rgb, vec3(0.2));\n \n // Combine edge fade with opacity\n float finalAlpha = edgeFactor * u_opacity;\n \n // Output final color with alpha\n fragColor = vec4(color.rgb, finalAlpha);\n }\n`;\n\nconst renderVertexShader = \n `#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Position in [0,1] range\n in float a_trail_offset; // Trail offset (0=main particle, 1,2,3=trail segments)\n \n uniform mediump mat4 u_matrix;\n uniform mediump vec4 u_bounds; // [minX, maxY, maxX, minY]\n uniform mediump float u_point_size; // Base point size\n uniform mediump float u_speed_factor; // Speed multiplier\n uniform mediump float u_trail_size_decay; // Size decay rate for trail particles\n uniform sampler2D u_velocity_texture; // Velocity texture\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n \n out vec2 v_position; // Pass position to fragment shader\n // out float v_opacity; // Varying opacity for trail\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n return vec2(x, y);\n }\n \n // Convert position to geographic coordinates\n vec2 positionToGeo(vec2 pos) {\n float lng = mix(u_bounds[0], u_bounds[2], pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - pos.y); // Invert y for correct mapping\n return vec2(lng, lat);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n void main() {\n // Trail offset (0 = main particle, >0 = trail segment)\n float trailOffset = a_trail_offset;\n \n // Main particle position from buffer\n vec2 mainPos = a_position;\n \n // Current position is main position for main particle (offset=0)\n vec2 currentPos = mainPos;\n \n // Sample velocity at the main particle's position\n vec4 velocityData = texture(u_velocity_texture, mainPos);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocityData.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocityData.g);\n \n // Calculate velocity in normalized coordinates\n vec2 geo = positionToGeo(mainPos);\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), geo.y);\n vec2 velocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n if (trailOffset > 0.0) {\n // Compute trail position by moving backwards from main particle along its velocity path\n // The strength of the offset is based on trail segment position\n currentPos = mainPos - velocity * u_speed_factor * trailOffset * 1.5;\n }\n \n // Convert normalized position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], currentPos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - currentPos.y);\n \n // Project to Web Mercator\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n \n // Pass position to fragment shader\n v_position = currentPos;\n \n // Compute opacity for trail particles (1.0 for main particle, decreasing for trail)\n // v_opacity = trailOffset == 0.0 ? 1.0 : 1.0 - (trailOffset / float(3));\n \n // Compute point size (decreasing for trail particles)\n float size = trailOffset == 0.0 ? u_point_size : u_point_size * pow(u_trail_size_decay, trailOffset);\n gl_PointSize = size;\n }\n`;\n\nconst updateFragmentShader = \n `#version 300 es\n precision mediump float;\n \n out vec4 fragColor;\n \n void main() {\n // For update step, we don't need to output anything visual\n // We're just using transform feedback to capture the new positions\n fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n }\n`;\n\nfunction createProgram(gl, vertexSource, fragmentSource) {\n const program = gl.createProgram();\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n\n // Specify transform feedback varyings if this is the update program\n if (vertexSource.includes('out vec2 v_position')) {\n // Check if we also have age tracking\n if (vertexSource.includes('out float v_age')) {\n gl.transformFeedbackVaryings(program, ['v_position', 'v_age'], gl.SEPARATE_ATTRIBS);\n } else {\n gl.transformFeedbackVaryings(program, ['v_position'], gl.SEPARATE_ATTRIBS);\n }\n }\n\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(gl.getProgramInfoLog(program));\n }\n\n const wrapper = {program: program};\n\n const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);\n for (let i = 0; i < numAttributes; i++) {\n const attribute = gl.getActiveAttrib(program, i);\n wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);\n }\n const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\n for (let i = 0; i < numUniforms; i++) {\n const uniform = gl.getActiveUniform(program, i);\n wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);\n }\n\n return wrapper;\n}\n\nfunction createShader(gl, type, source) {\n const shader = gl.createShader(type);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(gl.getShaderInfoLog(shader));\n }\n return shader;\n}\n\nfunction createTexture(gl, filter, data, width, height) {\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);\n \n if (data instanceof Uint8Array) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else if (data instanceof Image) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else {\n // For null data (empty texture)\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n }\n return texture;\n}\n\nfunction createBuffer(gl, data) {\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);\n return buffer;\n}\n\nfunction createColormap(gl, colors, valueRange) {\n // Sort colors by value\n colors.sort((a, b) => a[0] - b[0]);\n \n // Create a 256x1 texture for the colormap\n const data = new Uint8Array(256 * 4);\n const [minVal, maxVal] = valueRange;\n \n // Fill the colormap texture\n for (let i = 0; i < 256; i++) {\n // Convert texture position [0,255] to actual data value\n const value = minVal + (maxVal - minVal) * (i / 255);\n \n // Find the color stops that bracket this value\n let lowIndex = 0;\n while (lowIndex < colors.length - 1 && colors[lowIndex + 1][0] < value) {\n lowIndex++;\n }\n const highIndex = Math.min(lowIndex + 1, colors.length - 1);\n \n // Interpolate between the two colors\n const low = colors[lowIndex];\n const high = colors[highIndex];\n const t = highIndex > lowIndex ? \n (value - low[0]) / (high[0] - low[0]) : 0;\n \n const idx = i * 4;\n for (let j = 0; j < 3; j++) {\n data[idx + j] = Math.round(low[1][j] + t * (high[1][j] - low[1][j]));\n }\n data[idx + 3] = 255;\n }\n \n return createTexture(gl, gl.LINEAR, data, 256, 1);\n}\n\n// Add unit conversion functions before the class definition\nfunction kphToMph(kph) {\n return kph * 0.621371;\n}\n\nfunction mpsToMph(mps) {\n return mps * 2.23694;\n}\n\nexport default class ParticleMotion extends Evented {\n constructor({id, source, color, bounds, particleCount = 5000, readyForDisplay = false, ageThreshold = 500, maxAge = 1000,\n velocityFactor = 0.05, fadeOpacity = 0.9, updateInterval = 50, pointSize = 5.0, trailLength = 3, trailSizeDecay = 0.8, unit = 'mph'}) {\n super();\n \n this.id = id;\n this.type = 'custom';\n this.renderingMode = '2d';\n \n this.source = source;\n this.color = color;\n this.bounds = bounds;\n this.particleCount = particleCount;\n \n this.sourceLoaded = false;\n this.readyForDisplay = readyForDisplay;\n \n // Particle behavior settings\n this.velocityFactor = velocityFactor; // Speed multiplier for particle motion\n this.fadeOpacity = fadeOpacity; // Global opacity for particles\n this.updateInterval = updateInterval; // Minimum time (ms) between particle updates\n this.pointSize = pointSize; // Size of particles in pixels\n \n // Trail settings\n this.trailLength = trailLength; // Number of trailing particles per main particle\n this.trailSizeDecay = trailSizeDecay; // How quickly the point size decreases for trail particles\n \n // Age-based reset settings\n this.ageThreshold = ageThreshold; // Age threshold before reset probability increases\n this.maxAge = maxAge; // Maximum age before forced reset\n \n // Default speed range in case we can't read from EXIF\n this.speedRange = [0, 100];\n \n this.unit = unit; // Store the unit\n }\n\n onAdd(map, gl) {\n this.map = map;\n this.gl = gl;\n\n // Create programs with appropriate fragment shaders\n this.updateProgram = createProgram(gl, vertexShader, updateFragmentShader);\n this.renderProgram = createProgram(gl, renderVertexShader, fragmentShader);\n\n // Initialize particle positions with a uniform grid distribution\n const positions = new Float32Array(this.particleCount * 2);\n const ages = new Float32Array(this.particleCount);\n const gridSize = Math.ceil(Math.sqrt(this.particleCount));\n \n for (let i = 0; i < this.particleCount; i++) {\n const x = i % gridSize;\n const y = Math.floor(i / gridSize);\n \n // Calculate base position in [0,1] range\n const baseX = x / (gridSize - 1);\n const baseY = y / (gridSize - 1);\n \n // Add very small jitter to prevent negative values\n const gridSpacing = 1.0 / (gridSize - 1);\n const jitterX = (Math.random() - 0.5) * 0.25 * gridSpacing;\n const jitterY = (Math.random() - 0.5) * 0.25 * gridSpacing;\n \n // Combine base position with jitter\n positions[i * 2] = Math.max(0, Math.min(1, baseX + jitterX));\n positions[i * 2 + 1] = Math.max(0, Math.min(1, baseY + jitterY));\n \n // Initialize ages to random values to prevent synchronized updates\n ages[i] = Math.floor(Math.random() * 100);\n }\n \n // Create double-buffered particle position buffers (for main particles only)\n this.particleBufferA = createBuffer(gl, positions);\n this.particleBufferB = createBuffer(gl, positions);\n this.currentBuffer = this.particleBufferA;\n this.nextBuffer = this.particleBufferB;\n \n // Create age buffers for tracking particle lifecycles\n this.ageBufferA = createBuffer(gl, ages);\n this.ageBufferB = createBuffer(gl, ages);\n this.currentAgeBuffer = this.ageBufferA;\n this.nextAgeBuffer = this.ageBufferB;\n \n // Create trail offset buffer (for trail rendering)\n // We'll use instanced rendering to draw main particle + trails\n const trailOffsets = new Float32Array(this.trailLength + 1);\n for (let i = 0; i <= this.trailLength; i++) {\n trailOffsets[i] = i; // 0 = main particle, 1,2,3... = trail segments\n }\n this.trailOffsetBuffer = createBuffer(gl, trailOffsets);\n \n // Create transform feedback object\n this.transformFeedback = gl.createTransformFeedback();\n \n // Initialize time for animation\n this.lastTime = 0;\n\n // Load source image\n this.setSource(this.source, 0.0);\n }\n\n setSource(source, percentParticleWhenSetSource = 0.5) {\n if (this.source != source) {\n this.source = source;\n }\n\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n \n fetch(source, {\n cache: 'no-store'\n })\n .then(response => \n Promise.all([\n response.clone().blob(),\n response.arrayBuffer().then(buffer => new Uint8Array(buffer).buffer)\n ])\n )\n .then(([blob, arrayBuffer]) => {\n const objectURL = URL.createObjectURL(blob);\n\n image.onload = () => {\n URL.revokeObjectURL(objectURL);\n this.sourceTexture = createTexture(this.gl, this.gl.LINEAR, image);\n this.sourceLoaded = true;\n \n // Only apply percentParticleWhenSetSource if this is not the first source set\n if (percentParticleWhenSetSource > 0.0) {\n this.percentParticleWhenSetSource = percentParticleWhenSetSource;\n this.shouldResetParticles = true;\n }\n \n this.map.triggerRepaint();\n };\n\n image.onerror = (err) => {\n console.warn('ParticleMotion: Error loading source image:', err);\n URL.revokeObjectURL(objectURL);\n };\n\n (async () => {\n try {\n const tags = await ExifReader.load(arrayBuffer);\n \n if (tags[\"ImageDescription\"] && tags[\"ImageDescription\"].description) {\n const description = tags[\"ImageDescription\"].description;\n \n const matches = description.match(/(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*);(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*);(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)/);\n if (matches) {\n let min_u = parseFloat(matches[1]);\n let max_u = parseFloat(matches[2]);\n let min_v = parseFloat(matches[3]);\n let max_v = parseFloat(matches[4]);\n let min_speed = parseFloat(matches[5]);\n let max_speed = parseFloat(matches[6]);\n \n // Convert units if necessary\n if (this.unit === 'kph') {\n min_u = kphToMph(min_u);\n max_u = kphToMph(max_u);\n min_v = kphToMph(min_v);\n max_v = kphToMph(max_v);\n min_speed = kphToMph(min_speed);\n max_speed = kphToMph(max_speed);\n } else if (this.unit === 'mps') {\n min_u = mpsToMph(min_u);\n max_u = mpsToMph(max_u);\n min_v = mpsToMph(min_v);\n max_v = mpsToMph(max_v);\n min_speed = mpsToMph(min_speed);\n max_speed = mpsToMph(max_speed);\n }\n \n if (!isNaN(min_u) && !isNaN(max_u) && !isNaN(min_v) && !isNaN(max_v) && !isNaN(min_speed) && !isNaN(max_speed)) {\n this.valueRange_u = [min_u, max_u];\n this.valueRange_v = [min_v, max_v];\n this.speedRange = [min_speed, max_speed];\n \n this.colormapTexture = createColormap(this.gl, this.color, this.speedRange);\n \n image.src = objectURL;\n return;\n }\n }\n }\n \n console.warn('ParticleMotion: No valid value ranges found in EXIF data');\n URL.revokeObjectURL(objectURL);\n \n } catch (error) {\n console.warn('ParticleMotion: Error reading EXIF data:', error);\n URL.revokeObjectURL(objectURL);\n }\n })();\n })\n .catch(error => {\n console.warn('ParticleMotion: Error fetching image:', error);\n });\n }\n\n render(gl, matrix) {\n if (!this.sourceLoaded || !this.readyForDisplay) {\n return;\n }\n\n // Update particle positions with throttling\n const currentTime = performance.now();\n if (!this.lastTime) this.lastTime = currentTime;\n const deltaTime = currentTime - this.lastTime;\n \n // Only update particles if enough time has passed\n const shouldUpdate = deltaTime >= this.updateInterval;\n if (shouldUpdate) {\n this.lastTime = currentTime;\n\n // ---------- UPDATE STEP ----------\n // Prevent rendering during update step\n gl.colorMask(false, false, false, false);\n gl.disable(gl.BLEND);\n \n gl.useProgram(this.updateProgram.program);\n \n // Set uniforms for update\n gl.uniform1f(this.updateProgram.u_time, currentTime / 1000);\n gl.uniform1f(this.updateProgram.u_speed_factor, this.velocityFactor);\n gl.uniform4fv(this.updateProgram.u_bounds, this.bounds);\n gl.uniform2fv(this.updateProgram.u_value_range_u, this.valueRange_u);\n gl.uniform2fv(this.updateProgram.u_value_range_v, this.valueRange_v);\n gl.uniform2fv(this.updateProgram.u_speed_range, this.speedRange);\n gl.uniform1f(this.updateProgram.u_age_threshold, this.ageThreshold);\n gl.uniform1f(this.updateProgram.u_max_age, this.maxAge);\n \n // Set new uniforms for particle reset\n gl.uniform1f(this.updateProgram.u_percent_reset, this.percentParticleWhenSetSource || 0.0);\n gl.uniform1i(this.updateProgram.u_should_reset, this.shouldResetParticles ? 1 : 0);\n // Reset the flag after setting it\n this.shouldResetParticles = false;\n \n // Bind velocity texture\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.updateProgram.u_velocity_texture, 0);\n \n // Bind current particle positions buffer as input\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentBuffer);\n gl.enableVertexAttribArray(this.updateProgram.a_position);\n gl.vertexAttribPointer(this.updateProgram.a_position, 2, gl.FLOAT, false, 0, 0);\n \n // Bind current age buffer as input\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentAgeBuffer);\n gl.enableVertexAttribArray(this.updateProgram.a_age);\n gl.vertexAttribPointer(this.updateProgram.a_age, 1, gl.FLOAT, false, 0, 0);\n \n // Bind transform feedback and next buffers\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.transformFeedback);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.nextBuffer); // Position\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, this.nextAgeBuffer); // Age\n \n // Begin transform feedback\n gl.beginTransformFeedback(gl.POINTS);\n \n // Draw particles to update positions (but nothing will be rendered due to colorMask)\n gl.drawArrays(gl.POINTS, 0, this.particleCount);\n \n // End transform feedback\n gl.endTransformFeedback();\n \n // Clean up state\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);\n \n // Re-enable drawing to color buffer\n gl.colorMask(true, true, true, true);\n \n // Swap buffers\n [this.currentBuffer, this.nextBuffer] = [this.nextBuffer, this.currentBuffer];\n [this.currentAgeBuffer, this.nextAgeBuffer] = [this.nextAgeBuffer, this.currentAgeBuffer];\n }\n \n // ---------- RENDER STEP ----------\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);\n \n gl.useProgram(this.renderProgram.program);\n \n // Set up blending for transparency\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n \n // Set uniforms for rendering\n gl.uniformMatrix4fv(this.renderProgram.u_matrix, false, matrix);\n gl.uniform4fv(this.renderProgram.u_bounds, this.bounds);\n gl.uniform1f(this.renderProgram.u_point_size, this.pointSize);\n gl.uniform1f(this.renderProgram.u_opacity, this.fadeOpacity);\n gl.uniform1f(this.renderProgram.u_speed_factor, this.velocityFactor);\n gl.uniform1f(this.renderProgram.u_trail_size_decay, this.trailSizeDecay);\n gl.uniform2fv(this.renderProgram.u_value_range_u, this.valueRange_u);\n gl.uniform2fv(this.renderProgram.u_value_range_v, this.valueRange_v);\n gl.uniform2fv(this.renderProgram.u_speed_range, this.speedRange);\n \n // Bind textures\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.renderProgram.u_velocity_texture, 0);\n \n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, this.colormapTexture);\n gl.uniform1i(this.renderProgram.u_wind_color, 1);\n \n // Bind current particle positions\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentBuffer);\n gl.enableVertexAttribArray(this.renderProgram.a_position);\n gl.vertexAttribPointer(this.renderProgram.a_position, 2, gl.FLOAT, false, 0, 0);\n \n // Set up instanced rendering for trails\n gl.bindBuffer(gl.ARRAY_BUFFER, this.trailOffsetBuffer);\n gl.enableVertexAttribArray(this.renderProgram.a_trail_offset);\n gl.vertexAttribPointer(this.renderProgram.a_trail_offset, 1, gl.FLOAT, false, 0, 0);\n gl.vertexAttribDivisor(this.renderProgram.a_trail_offset, 1); // This makes it instanced\n \n // Draw trails using instanced rendering\n // Each main particle will be drawn (trailLength+1) times with different offsets\n gl.drawArraysInstanced(gl.POINTS, 0, this.particleCount, this.trailLength + 1);\n \n // Reset vertex attrib divisor\n gl.vertexAttribDivisor(this.renderProgram.a_trail_offset, 0);\n \n gl.disable(gl.BLEND);\n \n // Request next frame\n this.map.triggerRepaint();\n }\n\n onRemove(map, gl) {\n // Clean up WebGL resources\n if (this.updateProgram) {\n const shaders = gl.getAttachedShaders(this.updateProgram.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.updateProgram.program);\n }\n if (this.renderProgram) {\n const shaders = gl.getAttachedShaders(this.renderProgram.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.renderProgram.program);\n }\n\n // Delete buffers\n if (this.particleBufferA) gl.deleteBuffer(this.particleBufferA);\n if (this.particleBufferB) gl.deleteBuffer(this.particleBufferB);\n if (this.ageBufferA) gl.deleteBuffer(this.ageBufferA);\n if (this.ageBufferB) gl.deleteBuffer(this.ageBufferB);\n if (this.trailOffsetBuffer) gl.deleteBuffer(this.trailOffsetBuffer);\n\n // Delete transform feedback\n if (this.transformFeedback) gl.deleteTransformFeedback(this.transformFeedback);\n\n // Delete textures\n if (this.sourceTexture) gl.deleteTexture(this.sourceTexture);\n if (this.colormapTexture) gl.deleteTexture(this.colormapTexture);\n\n // Clear references\n this.particleBufferA = null;\n this.particleBufferB = null;\n this.ageBufferA = null;\n this.ageBufferB = null;\n this.currentBuffer = null;\n this.nextBuffer = null;\n this.currentAgeBuffer = null;\n this.nextAgeBuffer = null;\n this.trailOffsetBuffer = null;\n this.transformFeedback = null;\n this.sourceTexture = null;\n this.colormapTexture = null;\n this.updateProgram = null;\n this.renderProgram = null;\n this.gl = null;\n this.map = null;\n this.sourceLoaded = false;\n }\n} ","import {Evented} from 'mapbox-gl';\nimport ExifReader from 'exifreader';\n\nconst vertexShader = `\n precision mediump float;\n \n attribute vec2 a_pos;\n uniform mat4 u_matrix;\n uniform vec4 u_bounds; // [minX, maxY, maxX, minY]\n \n varying vec2 v_tex_pos;\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n // y is already in [0,1] range\n return vec2(x, y);\n }\n \n void main() {\n // Map input position [0,1] to geographic bounds\n float lng = mix(u_bounds[0], u_bounds[2], a_pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], a_pos.y);\n \n // Convert to Web Mercator coordinates in [0,1] range\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n \n // Pass texture coordinates\n v_tex_pos = vec2(a_pos.x, 1.0 - a_pos.y);\n \n // Apply matrix transformation\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n }\n`;\n\nconst fragmentShader = `\n precision mediump float;\n \n uniform sampler2D u_image;\n uniform sampler2D u_colormap;\n uniform float u_opacity;\n uniform vec2 u_value_range; // [min, max] of original values\n \n varying vec2 v_tex_pos;\n \n void main() {\n // Get the R value from the source image\n vec4 pixel = texture2D(u_image, v_tex_pos);\n float normalized = pixel.r;\n \n // Use value as index into colormap\n vec4 color = texture2D(u_colormap, vec2(normalized, 0.5));\n \n // Apply global opacity\n gl_FragColor = vec4(color.rgb, color.a * u_opacity);\n }\n`;\n\nfunction createProgram(gl, vertexSource, fragmentSource) {\n const program = gl.createProgram();\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(gl.getProgramInfoLog(program));\n }\n\n const wrapper = {program: program};\n\n const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);\n for (let i = 0; i < numAttributes; i++) {\n const attribute = gl.getActiveAttrib(program, i);\n wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);\n }\n const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\n for (let i = 0; i < numUniforms; i++) {\n const uniform = gl.getActiveUniform(program, i);\n wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);\n }\n\n return wrapper;\n}\n\nfunction createShader(gl, type, source) {\n const shader = gl.createShader(type);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(gl.getShaderInfoLog(shader));\n }\n return shader;\n}\n\nfunction createTexture(gl, filter, data, width, height) {\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);\n \n if (data instanceof Uint8Array) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);\n }\n return texture;\n}\n\nfunction createBuffer(gl, data) {\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);\n return buffer;\n}\n\nfunction createColormap(gl, colors, valueRange) {\n // Sort colors by value\n colors.sort((a, b) => a[0] - b[0]);\n \n // Create a 256x1 texture for the colormap\n const data = new Uint8Array(256 * 4);\n const [minVal, maxVal] = valueRange;\n \n // Fill the colormap texture\n for (let i = 0; i < 256; i++) {\n // Convert texture position [0,255] to actual data value\n const value = minVal + (maxVal - minVal) * (i / 255);\n \n // Find the color stops that bracket this value\n let lowIndex = 0;\n while (lowIndex < colors.length - 1 && colors[lowIndex + 1][0] < value) {\n lowIndex++;\n }\n const highIndex = Math.min(lowIndex + 1, colors.length - 1);\n \n // Interpolate between the two colors\n const low = colors[lowIndex];\n const high = colors[highIndex];\n const t = highIndex > lowIndex ? \n (value - low[0]) / (high[0] - low[0]) : 0;\n \n const idx = i * 4;\n for (let j = 0; j < 3; j++) {\n data[idx + j] = Math.round(low[1][j] + t * (high[1][j] - low[1][j]));\n }\n\n if (low[1][3] != null && low[1][3] != 255) {\n data[idx + 3] = low[1][3];\n }\n else {\n data[idx + 3] = 255;\n }\n }\n \n return createTexture(gl, gl.NEAREST, data, 256, 1);\n}\n\nexport default class SmoothRaster extends Evented {\n constructor({id, source, color, bounds, opacity = 1.0, readyForDisplay = false}) {\n super();\n \n this.id = id;\n this.type = 'custom';\n this.renderingMode = '2d';\n \n this.source = source;\n this.color = color;\n this.opacity = opacity;\n this.bounds = bounds;\n \n this.sourceLoaded = false;\n this.readyForDisplay = readyForDisplay;\n }\n\n onAdd(map, gl) {\n this.map = map;\n this.gl = gl;\n\n // Create program\n this.program = createProgram(gl, vertexShader, fragmentShader);\n\n // Create vertex buffer for a full-screen quad\n const vertices = new Float32Array([\n 0, 0,\n 1, 0,\n 0, 1,\n 0, 1,\n 1, 0,\n 1, 1\n ]);\n this.vertexBuffer = createBuffer(gl, vertices);\n\n // Load source image\n this.setSource(this.source);\n }\n\n setSource(source, color = null) {\n if (this.source != source) {\n this.source = source;\n }\n\n if (color != null) {\n this.color = color;\n }\n\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n \n fetch(source, {\n cache: 'no-store'\n })\n .then(response => \n Promise.all([\n response.clone().blob(),\n response.arrayBuffer().then(buffer => new Uint8Array(buffer).buffer)\n ])\n )\n .then(([blob, arrayBuffer]) => {\n const objectURL = URL.createObjectURL(blob);\n\n // Set default value range if EXIF reading fails\n this.valueRange = [0, 255]; // Default range\n \n (async () => {\n try {\n const tags = await ExifReader.load(arrayBuffer);\n \n if (tags[\"ImageDescription\"] && tags[\"ImageDescription\"].description) {\n const description = tags[\"ImageDescription\"].description;\n \n const matches = description.match(/(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)/);\n if (matches) {\n const min = parseFloat(matches[1]);\n const max = parseFloat(matches[2]);\n \n if (!isNaN(min) && !isNaN(max)) {\n this.valueRange = [min, max];\n }\n }\n }\n \n // Then define onload handler\n image.onload = () => {\n URL.revokeObjectURL(objectURL);\n if (this.gl) {\n this.sourceTexture = createTexture(this.gl, this.gl.LINEAR, image);\n if (this.valueRange) {\n this.colormapTexture = createColormap(this.gl, this.color, this.valueRange);\n }\n this.sourceLoaded = true;\n if (this.map) {\n this.map.triggerRepaint();\n }\n }\n };\n\n image.onerror = (err) => {\n URL.revokeObjectURL(objectURL);\n console.error('Error loading source image:', err);\n };\n // Always proceed to load the image, even if EXIF parsing fails\n image.src = objectURL;\n } catch (error) {\n console.warn('Error reading EXIF data:', error);\n // Still proceed with loading the image\n image.src = objectURL;\n }\n })();\n })\n .catch(error => {\n console.error('Error fetching image:', error);\n });\n }\n\n onRemove() {\n // Clean up WebGL resources\n const gl = this.gl;\n if (!gl) return;\n\n // Delete textures\n if (this.sourceTexture) gl.deleteTexture(this.sourceTexture);\n if (this.colormapTexture) gl.deleteTexture(this.colormapTexture);\n\n // Delete buffer\n if (this.vertexBuffer) gl.deleteBuffer(this.vertexBuffer);\n\n // Delete shaders and program\n if (this.program) {\n const shaders = gl.getAttachedShaders(this.program.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.program.program);\n }\n\n // Clear references\n this.sourceTexture = null;\n this.colormapTexture = null;\n this.vertexBuffer = null;\n this.program = null;\n this.gl = null;\n this.map = null;\n this.sourceLoaded = false;\n }\n\n render(gl, matrix) {\n // Only render if source is loaded\n if (!this.sourceLoaded || !this.readyForDisplay) return;\n \n gl.useProgram(this.program.program);\n\n // Set up blending for transparency\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n //gl.blendEquation(gl.FUNC_ADD);\n\n // Set uniforms\n gl.uniformMatrix4fv(this.program.u_matrix, false, matrix);\n gl.uniform4fv(this.program.u_bounds, this.bounds);\n gl.uniform1f(this.program.u_opacity, this.opacity);\n gl.uniform2fv(this.program.u_value_range, this.valueRange);\n\n // Bind textures\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.program.u_image, 0);\n\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, this.colormapTexture);\n gl.uniform1i(this.program.u_colormap, 1);\n\n // Draw quad\n gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);\n gl.enableVertexAttribArray(this.program.a_pos);\n gl.vertexAttribPointer(this.program.a_pos, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.disable(gl.BLEND);\n }\n} "],"names":["createProgram","gl","vertexSource","fragmentSource","program","vertexShader","createShader","VERTEX_SHADER","fragmentShader","FRAGMENT_SHADER","attachShader","includes","transformFeedbackVaryings","SEPARATE_ATTRIBS","linkProgram","getProgramParameter","LINK_STATUS","Error","getProgramInfoLog","wrapper","numAttributes","ACTIVE_ATTRIBUTES","i","attribute","getActiveAttrib","name","getAttribLocation","numUniforms","ACTIVE_UNIFORMS","uniform","getActiveUniform","getUniformLocation","type","source","shader","shaderSource","compileShader","getShaderParameter","COMPILE_STATUS","getShaderInfoLog","createTexture","filter","data","width","height","texture","bindTexture","TEXTURE_2D","texParameteri","TEXTURE_WRAP_S","CLAMP_TO_EDGE","TEXTURE_WRAP_T","TEXTURE_MIN_FILTER","TEXTURE_MAG_FILTER","Uint8Array","texImage2D","RGBA","UNSIGNED_BYTE","Image","createBuffer","buffer","bindBuffer","ARRAY_BUFFER","bufferData","STATIC_DRAW","kphToMph","kph","mpsToMph","mps","ParticleMotion","Evented","constructor","id","color","bounds","particleCount","readyForDisplay","ageThreshold","maxAge","velocityFactor","fadeOpacity","updateInterval","pointSize","trailLength","trailSizeDecay","unit","super","this","renderingMode","sourceLoaded","speedRange","onAdd","map","updateProgram","renderProgram","positions","Float32Array","ages","gridSize","Math","ceil","sqrt","baseX","baseY","floor","gridSpacing","jitterX","random","jitterY","max","min","particleBufferA","particleBufferB","currentBuffer","nextBuffer","ageBufferA","ageBufferB","currentAgeBuffer","nextAgeBuffer","trailOffsets","trailOffsetBuffer","transformFeedback","createTransformFeedback","lastTime","setSource","percentParticleWhenSetSource","image","crossOrigin","fetch","cache","then","response","Promise","all","clone","blob","arrayBuffer","objectURL","URL","createObjectURL","onload","revokeObjectURL","sourceTexture","LINEAR","shouldResetParticles","triggerRepaint","onerror","err","console","warn","tags","ExifReader","load","description","matches","match","min_u","parseFloat","max_u","min_v","max_v","min_speed","max_speed","isNaN","valueRange_u","valueRange_v","colormapTexture","colors","valueRange","sort","a","b","minVal","maxVal","value","lowIndex","length","highIndex","low","high","t","idx","j","round","createColormap","src","error","catch","render","matrix","currentTime","performance","now","colorMask","disable","BLEND","useProgram","uniform1f","u_time","u_speed_factor","uniform4fv","u_bounds","uniform2fv","u_value_range_u","u_value_range_v","u_speed_range","u_age_threshold","u_max_age","u_percent_reset","uniform1i","u_should_reset","activeTexture","TEXTURE0","u_velocity_texture","enableVertexAttribArray","a_position","vertexAttribPointer","FLOAT","a_age","bindTransformFeedback","TRANSFORM_FEEDBACK","bindBufferBase","TRANSFORM_FEEDBACK_BUFFER","beginTransformFeedback","POINTS","drawArrays","endTransformFeedback","bindFramebuffer","FRAMEBUFFER","viewport","canvas","enable","blendFunc","SRC_ALPHA","ONE_MINUS_SRC_ALPHA","uniformMatrix4fv","u_matrix","u_point_size","u_opacity","u_trail_size_decay","TEXTURE1","u_wind_color","a_trail_offset","vertexAttribDivisor","drawArraysInstanced","onRemove","shaders","getAttachedShaders","forEach","deleteShader","deleteProgram","deleteBuffer","deleteTransformFeedback","deleteTexture","SmoothRaster","opacity","vertices","vertexBuffer","NEAREST","u_value_range","u_image","u_colormap","a_pos","TRIANGLES"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/ParticleMotion.js","../src/SmoothRaster.js"],"sourcesContent":["import {Evented} from 'mapbox-gl';\nimport ExifReader from 'exifreader';\n\nconst vertexShader = \n `#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Current position [0,1]\n in float a_age; // Particle age for tracking circular patterns\n out vec2 v_position; // Updated position for transform feedback\n out float v_age; // Updated age for transform feedback\n \n uniform sampler2D u_velocity_texture; // Texture with normalized velocities [0,1]\n uniform mediump vec4 u_bounds;\n uniform mediump float u_speed_factor;\n uniform mediump float u_time;\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump float u_age_threshold; // Age threshold for reset probability\n uniform mediump float u_max_age; // Maximum age before forced reset\n uniform mediump float u_percent_reset; // Percentage of particles to reset on source update\n uniform bool u_should_reset; // Flag to indicate if we should apply the percentage reset\n \n // Random function based on time and position\n float random(vec2 co) {\n float a = 12.9898;\n float b = 78.233;\n float c = 43758.5453;\n float dt = dot(co, vec2(a,b));\n float sn = mod(dt, 3.14);\n return fract(sin(sn) * c + u_time);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n // Function to generate random position within extent\n vec2 generateRandomPosition(vec2 seed) {\n return vec2(\n random(seed + vec2(1.23, 4.56)),\n random(seed + vec2(7.89, 0.12))\n );\n }\n \n // Function to generate a position on one of the boundaries\n vec2 generateBoundaryPosition(vec2 seed) {\n // Choose which boundary (0=left, 1=top, 2=right, 3=bottom)\n float boundary = floor(random(seed) * 4.0);\n \n // Position along the boundary\n float pos = random(seed + vec2(boundary));\n \n // Small offset to prevent immediate out-of-bounds\n float offset = 0.001;\n \n if (boundary < 1.0) { // Left\n return vec2(offset, pos);\n } else if (boundary < 2.0) { // Top\n return vec2(pos, offset);\n } else if (boundary < 3.0) { // Right\n return vec2(1.0 - offset, pos);\n } else { // Bottom\n return vec2(pos, 1.0 - offset);\n }\n }\n \n void main() {\n // Sample normalized velocity from texture [0,1]\n vec4 velocity = texture(u_velocity_texture, a_position);\n \n // Denormalize velocities to actual mph values\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n \n // Calculate wind speed\n float windSpeed = length(vec2(u, v));\n \n // Convert position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], a_position.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - a_position.y);\n \n // Convert wind velocity to degree offsets using actual mph values\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), lat);\n \n // Convert degree offsets back to normalized coordinates\n vec2 normalizedVelocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n // Update position with velocity\n vec2 newPos = a_position + normalizedVelocity * u_speed_factor;\n \n // Reset rules\n bool shouldReset = false;\n \n // Get current age of particle and increment\n float age = a_age + 1.0;\n \n // Reset if out of bounds or very low wind speed\n if (newPos.x < 0.0 || newPos.x > 1.0 || newPos.y < 0.0 || newPos.y > 1.0 || windSpeed < 1.5) {\n shouldReset = true;\n }\n \n // Reset based on age with increasing probability\n if (age > u_age_threshold) {\n float resetProbability = (age - u_age_threshold) / (u_max_age - u_age_threshold);\n if (random(a_position + vec2(u_time * 0.1, age * 0.01)) < resetProbability) {\n shouldReset = true;\n }\n }\n \n // Force reset of very old particles\n if (age > u_max_age) {\n shouldReset = true;\n }\n \n // Additional reset based on percentParticleWhenSetSource if flag is set\n if (!shouldReset && u_should_reset) {\n if (random(a_position + vec2(u_time)) < u_percent_reset) {\n shouldReset = true;\n }\n }\n \n // Handle reset by generating a new position\n if (shouldReset) {\n newPos = generateRandomPosition(a_position + vec2(u_time));\n\n age = 0.0; // Reset age\n }\n \n v_position = newPos;\n v_age = age;\n }\n`;\n\nconst fragmentShader = \n `#version 300 es\n precision mediump float;\n \n uniform sampler2D u_wind_color; // Colormap texture\n uniform sampler2D u_velocity_texture; // Wind velocity texture\n uniform mediump float u_opacity; // Global opacity control\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n uniform mediump vec2 u_speed_range; // Speed range for normalization\n \n in vec2 v_position; // Current position\n out vec4 fragColor; // Output color\n \n void main() {\n // Calculate distance from center for circular particles\n vec2 center = gl_PointCoord - vec2(0.5);\n float dist = length(center);\n \n // Discard pixels outside the circle\n if (dist > 0.5) {\n discard;\n }\n \n // Create soft-edged circular particles\n float edgeFactor = 1.0 - smoothstep(0.45, 0.5, dist);\n \n // Sample wind velocity for coloring\n vec4 velocity = texture(u_velocity_texture, v_position);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocity.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocity.g);\n float speed = length(vec2(u, v));\n float normalizedSpeed = (speed - u_speed_range[0]) / (u_speed_range[1] - u_speed_range[0]);\n \n // Sample color from colormap using normalized speed\n vec4 color = texture(u_wind_color, vec2(normalizedSpeed, 0.5));\n \n // Ensure we have some minimum color intensity\n color.rgb = max(color.rgb, vec3(0.2));\n \n // Combine edge fade with opacity\n float finalAlpha = edgeFactor * u_opacity;\n \n // Output final color with alpha\n fragColor = vec4(color.rgb, finalAlpha);\n }\n`;\n\nconst renderVertexShader = \n `#version 300 es\n precision mediump float;\n \n in vec2 a_position; // Position in [0,1] range\n in float a_trail_offset; // Trail offset (0=main particle, 1,2,3=trail segments)\n \n uniform mediump mat4 u_matrix;\n uniform mediump vec4 u_bounds; // [minX, maxY, maxX, minY]\n uniform mediump float u_point_size; // Base point size\n uniform mediump float u_speed_factor; // Speed multiplier\n uniform mediump float u_trail_size_decay; // Size decay rate for trail particles\n uniform sampler2D u_velocity_texture; // Velocity texture\n uniform mediump vec2 u_value_range_u; // Wind U component range\n uniform mediump vec2 u_value_range_v; // Wind V component range\n \n out vec2 v_position; // Pass position to fragment shader\n // out float v_opacity; // Varying opacity for trail\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n return vec2(x, y);\n }\n \n // Convert position to geographic coordinates\n vec2 positionToGeo(vec2 pos) {\n float lng = mix(u_bounds[0], u_bounds[2], pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - pos.y); // Invert y for correct mapping\n return vec2(lng, lat);\n }\n \n // Convert mph to degrees per frame\n vec2 mphToDegreesPerFrame(vec2 mph, float lat) {\n float kmh_to_degrees = 1.60934 / 111.32; // Convert mph to km/h, then to degrees\n float latScale = cos(lat * 3.14159 / 180.0); // Latitude scaling for longitude\n return vec2(mph.x * kmh_to_degrees / latScale, -mph.y * kmh_to_degrees);\n }\n \n void main() {\n // Trail offset (0 = main particle, >0 = trail segment)\n float trailOffset = a_trail_offset;\n \n // Main particle position from buffer\n vec2 mainPos = a_position;\n \n // Current position is main position for main particle (offset=0)\n vec2 currentPos = mainPos;\n \n // Sample velocity at the main particle's position\n vec4 velocityData = texture(u_velocity_texture, mainPos);\n float u = mix(u_value_range_u[0], u_value_range_u[1], velocityData.r);\n float v = mix(u_value_range_v[0], u_value_range_v[1], velocityData.g);\n \n // Calculate velocity in normalized coordinates\n vec2 geo = positionToGeo(mainPos);\n vec2 degrees = mphToDegreesPerFrame(vec2(u, v), geo.y);\n vec2 velocity = vec2(\n degrees.x / (u_bounds[2] - u_bounds[0]),\n degrees.y / (u_bounds[1] - u_bounds[3])\n );\n \n if (trailOffset > 0.0) {\n // Compute trail position by moving backwards from main particle along its velocity path\n // The strength of the offset is based on trail segment position\n currentPos = mainPos - velocity * u_speed_factor * trailOffset * 1.5;\n }\n \n // Convert normalized position to geographic coordinates\n float lng = mix(u_bounds[0], u_bounds[2], currentPos.x);\n float lat = mix(u_bounds[3], u_bounds[1], 1.0 - currentPos.y);\n \n // Project to Web Mercator\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n \n // Pass position to fragment shader\n v_position = currentPos;\n \n // Compute opacity for trail particles (1.0 for main particle, decreasing for trail)\n // v_opacity = trailOffset == 0.0 ? 1.0 : 1.0 - (trailOffset / float(3));\n \n // Compute point size (decreasing for trail particles)\n float size = trailOffset == 0.0 ? u_point_size : u_point_size * pow(u_trail_size_decay, trailOffset);\n gl_PointSize = size;\n }\n`;\n\nconst updateFragmentShader = \n `#version 300 es\n precision mediump float;\n \n out vec4 fragColor;\n \n void main() {\n // For update step, we don't need to output anything visual\n // We're just using transform feedback to capture the new positions\n fragColor = vec4(0.0, 0.0, 0.0, 0.0);\n }\n`;\n\nfunction createProgram(gl, vertexSource, fragmentSource) {\n const program = gl.createProgram();\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n\n // Specify transform feedback varyings if this is the update program\n if (vertexSource.includes('out vec2 v_position')) {\n // Check if we also have age tracking\n if (vertexSource.includes('out float v_age')) {\n gl.transformFeedbackVaryings(program, ['v_position', 'v_age'], gl.SEPARATE_ATTRIBS);\n } else {\n gl.transformFeedbackVaryings(program, ['v_position'], gl.SEPARATE_ATTRIBS);\n }\n }\n\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(gl.getProgramInfoLog(program));\n }\n\n const wrapper = {program: program};\n\n const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);\n for (let i = 0; i < numAttributes; i++) {\n const attribute = gl.getActiveAttrib(program, i);\n wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);\n }\n const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\n for (let i = 0; i < numUniforms; i++) {\n const uniform = gl.getActiveUniform(program, i);\n wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);\n }\n\n return wrapper;\n}\n\nfunction createShader(gl, type, source) {\n const shader = gl.createShader(type);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(gl.getShaderInfoLog(shader));\n }\n return shader;\n}\n\nfunction createTexture(gl, filter, data, width, height) {\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);\n \n if (data instanceof Uint8Array) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else if (data instanceof Image) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else {\n // For null data (empty texture)\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);\n }\n return texture;\n}\n\nfunction createBuffer(gl, data) {\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);\n return buffer;\n}\n\nfunction createColormap(gl, colors, valueRange) {\n // Sort colors by value\n colors.sort((a, b) => a[0] - b[0]);\n \n // Create a 256x1 texture for the colormap\n const data = new Uint8Array(256 * 4);\n const [minVal, maxVal] = valueRange;\n \n // Fill the colormap texture\n for (let i = 0; i < 256; i++) {\n // Convert texture position [0,255] to actual data value\n const value = minVal + (maxVal - minVal) * (i / 255);\n \n // Find the color stops that bracket this value\n let lowIndex = 0;\n while (lowIndex < colors.length - 1 && colors[lowIndex + 1][0] < value) {\n lowIndex++;\n }\n const highIndex = Math.min(lowIndex + 1, colors.length - 1);\n \n // Interpolate between the two colors\n const low = colors[lowIndex];\n const high = colors[highIndex];\n const t = highIndex > lowIndex ? \n (value - low[0]) / (high[0] - low[0]) : 0;\n \n const idx = i * 4;\n for (let j = 0; j < 3; j++) {\n data[idx + j] = Math.round(low[1][j] + t * (high[1][j] - low[1][j]));\n }\n data[idx + 3] = 255;\n }\n \n return createTexture(gl, gl.LINEAR, data, 256, 1);\n}\n\n// Add unit conversion functions before the class definition\nfunction kphToMph(kph) {\n return kph * 0.621371;\n}\n\nfunction mpsToMph(mps) {\n return mps * 2.23694;\n}\n\nexport default class ParticleMotion extends Evented {\n constructor({id, source, color, bounds, particleCount = 5000, readyForDisplay = false, ageThreshold = 500, maxAge = 1000,\n velocityFactor = 0.05, fadeOpacity = 0.9, updateInterval = 50, pointSize = 5.0, trailLength = 3, trailSizeDecay = 0.8, \n unit = 'mph', cacheOption = 'no-cache', slot}) {\n super();\n \n this.id = id;\n this.type = 'custom';\n this.renderingMode = '2d';\n\n if (slot !== undefined) {\n this.slot = slot;\n }\n \n this.source = source;\n this.color = color;\n this.bounds = bounds;\n this.particleCount = particleCount;\n \n this.sourceLoaded = false;\n this.readyForDisplay = readyForDisplay;\n \n // Particle behavior settings\n this.velocityFactor = velocityFactor; // Speed multiplier for particle motion\n this.fadeOpacity = fadeOpacity; // Global opacity for particles\n this.updateInterval = updateInterval; // Minimum time (ms) between particle updates\n this.pointSize = pointSize; // Size of particles in pixels\n \n // Trail settings\n this.trailLength = trailLength; // Number of trailing particles per main particle\n this.trailSizeDecay = trailSizeDecay; // How quickly the point size decreases for trail particles\n \n // Age-based reset settings\n this.ageThreshold = ageThreshold; // Age threshold before reset probability increases\n this.maxAge = maxAge; // Maximum age before forced reset\n \n // Default speed range in case we can't read from EXIF\n this.speedRange = [0, 100];\n \n this.unit = unit; // Store the unit\n this.cacheOption = cacheOption; // Store the cache option\n }\n\n onAdd(map, gl) {\n this.map = map;\n this.gl = gl;\n\n // Create programs with appropriate fragment shaders\n this.updateProgram = createProgram(gl, vertexShader, updateFragmentShader);\n this.renderProgram = createProgram(gl, renderVertexShader, fragmentShader);\n\n // Initialize particle positions with a uniform grid distribution\n const positions = new Float32Array(this.particleCount * 2);\n const ages = new Float32Array(this.particleCount);\n const gridSize = Math.ceil(Math.sqrt(this.particleCount));\n \n for (let i = 0; i < this.particleCount; i++) {\n const x = i % gridSize;\n const y = Math.floor(i / gridSize);\n \n // Calculate base position in [0,1] range\n const baseX = x / (gridSize - 1);\n const baseY = y / (gridSize - 1);\n \n // Add very small jitter to prevent negative values\n const gridSpacing = 1.0 / (gridSize - 1);\n const jitterX = (Math.random() - 0.5) * 0.25 * gridSpacing;\n const jitterY = (Math.random() - 0.5) * 0.25 * gridSpacing;\n \n // Combine base position with jitter\n positions[i * 2] = Math.max(0, Math.min(1, baseX + jitterX));\n positions[i * 2 + 1] = Math.max(0, Math.min(1, baseY + jitterY));\n \n // Initialize ages to random values to prevent synchronized updates\n ages[i] = Math.floor(Math.random() * 100);\n }\n \n // Create double-buffered particle position buffers (for main particles only)\n this.particleBufferA = createBuffer(gl, positions);\n this.particleBufferB = createBuffer(gl, positions);\n this.currentBuffer = this.particleBufferA;\n this.nextBuffer = this.particleBufferB;\n \n // Create age buffers for tracking particle lifecycles\n this.ageBufferA = createBuffer(gl, ages);\n this.ageBufferB = createBuffer(gl, ages);\n this.currentAgeBuffer = this.ageBufferA;\n this.nextAgeBuffer = this.ageBufferB;\n \n // Create trail offset buffer (for trail rendering)\n // We'll use instanced rendering to draw main particle + trails\n const trailOffsets = new Float32Array(this.trailLength + 1);\n for (let i = 0; i <= this.trailLength; i++) {\n trailOffsets[i] = i; // 0 = main particle, 1,2,3... = trail segments\n }\n this.trailOffsetBuffer = createBuffer(gl, trailOffsets);\n \n // Create transform feedback object\n this.transformFeedback = gl.createTransformFeedback();\n \n // Initialize time for animation\n this.lastTime = 0;\n\n // Load source image\n this.setSource(this.source, 0.0);\n }\n\n setSource(source, percentParticleWhenSetSource = 0.5) {\n if (this.source != source) {\n this.source = source;\n }\n\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n \n fetch(source, {\n cache: this.cacheOption\n })\n .then(response => \n Promise.all([\n response.clone().blob(),\n response.arrayBuffer().then(buffer => new Uint8Array(buffer).buffer)\n ])\n )\n .then(([blob, arrayBuffer]) => {\n const objectURL = URL.createObjectURL(blob);\n\n image.onload = () => {\n URL.revokeObjectURL(objectURL);\n this.sourceTexture = createTexture(this.gl, this.gl.LINEAR, image);\n this.sourceLoaded = true;\n \n // Only apply percentParticleWhenSetSource if this is not the first source set\n if (percentParticleWhenSetSource > 0.0) {\n this.percentParticleWhenSetSource = percentParticleWhenSetSource;\n this.shouldResetParticles = true;\n }\n \n this.map.triggerRepaint();\n };\n\n image.onerror = (err) => {\n console.warn('ParticleMotion: Error loading source image:', err);\n URL.revokeObjectURL(objectURL);\n };\n\n (async () => {\n try {\n const tags = await ExifReader.load(arrayBuffer);\n \n if (tags[\"ImageDescription\"] && tags[\"ImageDescription\"].description) {\n const description = tags[\"ImageDescription\"].description;\n \n const matches = description.match(/(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*);(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*);(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)/);\n if (matches) {\n let min_u = parseFloat(matches[1]);\n let max_u = parseFloat(matches[2]);\n let min_v = parseFloat(matches[3]);\n let max_v = parseFloat(matches[4]);\n let min_speed = parseFloat(matches[5]);\n let max_speed = parseFloat(matches[6]);\n \n // Convert units if necessary\n if (this.unit === 'kph') {\n min_u = kphToMph(min_u);\n max_u = kphToMph(max_u);\n min_v = kphToMph(min_v);\n max_v = kphToMph(max_v);\n min_speed = kphToMph(min_speed);\n max_speed = kphToMph(max_speed);\n } else if (this.unit === 'mps') {\n min_u = mpsToMph(min_u);\n max_u = mpsToMph(max_u);\n min_v = mpsToMph(min_v);\n max_v = mpsToMph(max_v);\n min_speed = mpsToMph(min_speed);\n max_speed = mpsToMph(max_speed);\n }\n \n if (!isNaN(min_u) && !isNaN(max_u) && !isNaN(min_v) && !isNaN(max_v) && !isNaN(min_speed) && !isNaN(max_speed)) {\n this.valueRange_u = [min_u, max_u];\n this.valueRange_v = [min_v, max_v];\n this.speedRange = [min_speed, max_speed];\n \n this.colormapTexture = createColormap(this.gl, this.color, this.speedRange);\n \n image.src = objectURL;\n return;\n }\n }\n }\n \n console.warn('ParticleMotion: No valid value ranges found in EXIF data');\n URL.revokeObjectURL(objectURL);\n \n } catch (error) {\n console.warn('ParticleMotion: Error reading EXIF data:', error);\n URL.revokeObjectURL(objectURL);\n }\n })();\n })\n .catch(error => {\n console.warn('ParticleMotion: Error fetching image:', error);\n });\n }\n\n render(gl, matrix) {\n if (!this.sourceLoaded || !this.readyForDisplay) {\n return;\n }\n\n // Update particle positions with throttling\n const currentTime = performance.now();\n if (!this.lastTime) this.lastTime = currentTime;\n const deltaTime = currentTime - this.lastTime;\n \n // Only update particles if enough time has passed\n const shouldUpdate = deltaTime >= this.updateInterval;\n if (shouldUpdate) {\n this.lastTime = currentTime;\n\n // ---------- UPDATE STEP ----------\n // Prevent rendering during update step\n gl.colorMask(false, false, false, false);\n gl.disable(gl.BLEND);\n \n gl.useProgram(this.updateProgram.program);\n \n // Set uniforms for update\n gl.uniform1f(this.updateProgram.u_time, currentTime / 1000);\n gl.uniform1f(this.updateProgram.u_speed_factor, this.velocityFactor);\n gl.uniform4fv(this.updateProgram.u_bounds, this.bounds);\n gl.uniform2fv(this.updateProgram.u_value_range_u, this.valueRange_u);\n gl.uniform2fv(this.updateProgram.u_value_range_v, this.valueRange_v);\n gl.uniform2fv(this.updateProgram.u_speed_range, this.speedRange);\n gl.uniform1f(this.updateProgram.u_age_threshold, this.ageThreshold);\n gl.uniform1f(this.updateProgram.u_max_age, this.maxAge);\n \n // Set new uniforms for particle reset\n gl.uniform1f(this.updateProgram.u_percent_reset, this.percentParticleWhenSetSource || 0.0);\n gl.uniform1i(this.updateProgram.u_should_reset, this.shouldResetParticles ? 1 : 0);\n // Reset the flag after setting it\n this.shouldResetParticles = false;\n \n // Bind velocity texture\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.updateProgram.u_velocity_texture, 0);\n \n // Bind current particle positions buffer as input\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentBuffer);\n gl.enableVertexAttribArray(this.updateProgram.a_position);\n gl.vertexAttribPointer(this.updateProgram.a_position, 2, gl.FLOAT, false, 0, 0);\n \n // Bind current age buffer as input\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentAgeBuffer);\n gl.enableVertexAttribArray(this.updateProgram.a_age);\n gl.vertexAttribPointer(this.updateProgram.a_age, 1, gl.FLOAT, false, 0, 0);\n \n // Bind transform feedback and next buffers\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, this.transformFeedback);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, this.nextBuffer); // Position\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, this.nextAgeBuffer); // Age\n \n // Begin transform feedback\n gl.beginTransformFeedback(gl.POINTS);\n \n // Draw particles to update positions (but nothing will be rendered due to colorMask)\n gl.drawArrays(gl.POINTS, 0, this.particleCount);\n \n // End transform feedback\n gl.endTransformFeedback();\n \n // Clean up state\n gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);\n gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 1, null);\n \n // Re-enable drawing to color buffer\n gl.colorMask(true, true, true, true);\n \n // Swap buffers\n [this.currentBuffer, this.nextBuffer] = [this.nextBuffer, this.currentBuffer];\n [this.currentAgeBuffer, this.nextAgeBuffer] = [this.nextAgeBuffer, this.currentAgeBuffer];\n }\n \n // ---------- RENDER STEP ----------\n gl.bindFramebuffer(gl.FRAMEBUFFER, null);\n gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);\n \n gl.useProgram(this.renderProgram.program);\n \n // Set up blending for transparency\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n \n // Set uniforms for rendering\n gl.uniformMatrix4fv(this.renderProgram.u_matrix, false, matrix);\n gl.uniform4fv(this.renderProgram.u_bounds, this.bounds);\n gl.uniform1f(this.renderProgram.u_point_size, this.pointSize);\n gl.uniform1f(this.renderProgram.u_opacity, this.fadeOpacity);\n gl.uniform1f(this.renderProgram.u_speed_factor, this.velocityFactor);\n gl.uniform1f(this.renderProgram.u_trail_size_decay, this.trailSizeDecay);\n gl.uniform2fv(this.renderProgram.u_value_range_u, this.valueRange_u);\n gl.uniform2fv(this.renderProgram.u_value_range_v, this.valueRange_v);\n gl.uniform2fv(this.renderProgram.u_speed_range, this.speedRange);\n \n // Bind textures\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.renderProgram.u_velocity_texture, 0);\n \n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, this.colormapTexture);\n gl.uniform1i(this.renderProgram.u_wind_color, 1);\n \n // Bind current particle positions\n gl.bindBuffer(gl.ARRAY_BUFFER, this.currentBuffer);\n gl.enableVertexAttribArray(this.renderProgram.a_position);\n gl.vertexAttribPointer(this.renderProgram.a_position, 2, gl.FLOAT, false, 0, 0);\n \n // Set up instanced rendering for trails\n gl.bindBuffer(gl.ARRAY_BUFFER, this.trailOffsetBuffer);\n gl.enableVertexAttribArray(this.renderProgram.a_trail_offset);\n gl.vertexAttribPointer(this.renderProgram.a_trail_offset, 1, gl.FLOAT, false, 0, 0);\n gl.vertexAttribDivisor(this.renderProgram.a_trail_offset, 1); // This makes it instanced\n \n // Draw trails using instanced rendering\n // Each main particle will be drawn (trailLength+1) times with different offsets\n gl.drawArraysInstanced(gl.POINTS, 0, this.particleCount, this.trailLength + 1);\n \n // Reset vertex attrib divisor\n gl.vertexAttribDivisor(this.renderProgram.a_trail_offset, 0);\n \n gl.disable(gl.BLEND);\n \n // Request next frame\n this.map.triggerRepaint();\n }\n\n onRemove(map, gl) {\n // Clean up WebGL resources\n if (this.updateProgram) {\n const shaders = gl.getAttachedShaders(this.updateProgram.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.updateProgram.program);\n }\n if (this.renderProgram) {\n const shaders = gl.getAttachedShaders(this.renderProgram.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.renderProgram.program);\n }\n\n // Delete buffers\n if (this.particleBufferA) gl.deleteBuffer(this.particleBufferA);\n if (this.particleBufferB) gl.deleteBuffer(this.particleBufferB);\n if (this.ageBufferA) gl.deleteBuffer(this.ageBufferA);\n if (this.ageBufferB) gl.deleteBuffer(this.ageBufferB);\n if (this.trailOffsetBuffer) gl.deleteBuffer(this.trailOffsetBuffer);\n\n // Delete transform feedback\n if (this.transformFeedback) gl.deleteTransformFeedback(this.transformFeedback);\n\n // Delete textures\n if (this.sourceTexture) gl.deleteTexture(this.sourceTexture);\n if (this.colormapTexture) gl.deleteTexture(this.colormapTexture);\n\n // Clear references\n this.particleBufferA = null;\n this.particleBufferB = null;\n this.ageBufferA = null;\n this.ageBufferB = null;\n this.currentBuffer = null;\n this.nextBuffer = null;\n this.currentAgeBuffer = null;\n this.nextAgeBuffer = null;\n this.trailOffsetBuffer = null;\n this.transformFeedback = null;\n this.sourceTexture = null;\n this.colormapTexture = null;\n this.updateProgram = null;\n this.renderProgram = null;\n this.gl = null;\n this.map = null;\n this.sourceLoaded = false;\n }\n} ","import {Evented} from 'mapbox-gl';\nimport ExifReader from 'exifreader';\n\nconst vertexShader = `\n precision mediump float;\n \n attribute vec2 a_pos;\n uniform mat4 u_matrix;\n uniform vec4 u_bounds; // [minX, maxY, maxX, minY]\n \n varying vec2 v_tex_pos;\n \n const float PI = 3.141592653589793;\n \n vec2 latLngToMercator(vec2 lnglat) {\n // Convert lng/lat to Web Mercator coordinates in [0, 1] range\n float x = (lnglat.x + 180.0) / 360.0; // Convert longitude to [0,1]\n \n // Convert latitude to y coordinate using Web Mercator projection\n float latRad = lnglat.y * PI / 180.0;\n float y = 0.5 - (log(tan(PI / 4.0 + latRad / 2.0)) / (2.0 * PI));\n \n // y is already in [0,1] range\n return vec2(x, y);\n }\n \n void main() {\n // Map input position [0,1] to geographic bounds\n float lng = mix(u_bounds[0], u_bounds[2], a_pos.x);\n float lat = mix(u_bounds[3], u_bounds[1], a_pos.y);\n \n // Convert to Web Mercator coordinates in [0,1] range\n vec2 mercator = latLngToMercator(vec2(lng, lat));\n \n // Pass texture coordinates\n v_tex_pos = vec2(a_pos.x, 1.0 - a_pos.y);\n \n // Apply matrix transformation\n gl_Position = u_matrix * vec4(mercator, 0, 1);\n }\n`;\n\nconst fragmentShader = `\n precision mediump float;\n \n uniform sampler2D u_image;\n uniform sampler2D u_colormap;\n uniform float u_opacity;\n uniform vec2 u_value_range; // [min, max] of original values\n \n varying vec2 v_tex_pos;\n \n void main() {\n // Get the R value from the source image\n vec4 pixel = texture2D(u_image, v_tex_pos);\n float normalized = pixel.r;\n \n // Use value as index into colormap\n vec4 color = texture2D(u_colormap, vec2(normalized, 0.5));\n \n // Apply global opacity\n gl_FragColor = vec4(color.rgb, color.a * u_opacity);\n }\n`;\n\nfunction createProgram(gl, vertexSource, fragmentSource) {\n const program = gl.createProgram();\n\n const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);\n const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n throw new Error(gl.getProgramInfoLog(program));\n }\n\n const wrapper = {program: program};\n\n const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);\n for (let i = 0; i < numAttributes; i++) {\n const attribute = gl.getActiveAttrib(program, i);\n wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);\n }\n const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);\n for (let i = 0; i < numUniforms; i++) {\n const uniform = gl.getActiveUniform(program, i);\n wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);\n }\n\n return wrapper;\n}\n\nfunction createShader(gl, type, source) {\n const shader = gl.createShader(type);\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n throw new Error(gl.getShaderInfoLog(shader));\n }\n return shader;\n}\n\nfunction createTexture(gl, filter, data, width, height) {\n const texture = gl.createTexture();\n gl.bindTexture(gl.TEXTURE_2D, texture);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);\n \n if (data instanceof Uint8Array) {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);\n } else {\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);\n }\n return texture;\n}\n\nfunction createBuffer(gl, data) {\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);\n return buffer;\n}\n\nfunction createColormap(gl, colors, valueRange) {\n // Sort colors by value\n colors.sort((a, b) => a[0] - b[0]);\n \n // Create a 256x1 texture for the colormap\n const data = new Uint8Array(256 * 4);\n const [minVal, maxVal] = valueRange;\n \n // Fill the colormap texture\n for (let i = 0; i < 256; i++) {\n // Convert texture position [0,255] to actual data value\n const value = minVal + (maxVal - minVal) * (i / 255);\n \n // Find the color stops that bracket this value\n let lowIndex = 0;\n while (lowIndex < colors.length - 1 && colors[lowIndex + 1][0] < value) {\n lowIndex++;\n }\n const highIndex = Math.min(lowIndex + 1, colors.length - 1);\n \n // Interpolate between the two colors\n const low = colors[lowIndex];\n const high = colors[highIndex];\n const t = highIndex > lowIndex ? \n (value - low[0]) / (high[0] - low[0]) : 0;\n \n const idx = i * 4;\n for (let j = 0; j < 3; j++) {\n data[idx + j] = Math.round(low[1][j] + t * (high[1][j] - low[1][j]));\n }\n\n if (low[1][3] != null && low[1][3] != 255) {\n data[idx + 3] = low[1][3];\n }\n else {\n data[idx + 3] = 255;\n }\n }\n \n return createTexture(gl, gl.NEAREST, data, 256, 1);\n}\n\nexport default class SmoothRaster extends Evented {\n constructor({id, source, color, bounds, opacity = 1.0, readyForDisplay = false, cacheOption = 'no-cache', slot}) {\n super();\n \n this.id = id;\n this.type = 'custom';\n this.renderingMode = '2d';\n\n if (slot !== undefined) {\n this.slot = slot;\n }\n \n this.source = source;\n this.color = color;\n this.opacity = opacity;\n this.bounds = bounds;\n \n this.sourceLoaded = false;\n this.readyForDisplay = readyForDisplay;\n this.cacheOption = cacheOption; // Store the cache option\n }\n\n onAdd(map, gl) {\n this.map = map;\n this.gl = gl;\n\n // Create program\n this.program = createProgram(gl, vertexShader, fragmentShader);\n\n // Create vertex buffer for a full-screen quad\n const vertices = new Float32Array([\n 0, 0,\n 1, 0,\n 0, 1,\n 0, 1,\n 1, 0,\n 1, 1\n ]);\n this.vertexBuffer = createBuffer(gl, vertices);\n\n // Load source image\n this.setSource(this.source);\n }\n\n setSource(source, color = null) {\n if (this.source != source) {\n this.source = source;\n }\n\n if (color != null) {\n this.color = color;\n }\n\n const image = new Image();\n image.crossOrigin = \"anonymous\";\n \n fetch(source, {\n cache: this.cacheOption\n })\n .then(response => \n Promise.all([\n response.clone().blob(),\n response.arrayBuffer().then(buffer => new Uint8Array(buffer).buffer)\n ])\n )\n .then(([blob, arrayBuffer]) => {\n const objectURL = URL.createObjectURL(blob);\n\n // Set default value range if EXIF reading fails\n this.valueRange = [0, 255]; // Default range\n \n (async () => {\n try {\n const tags = await ExifReader.load(arrayBuffer);\n \n if (tags[\"ImageDescription\"] && tags[\"ImageDescription\"].description) {\n const description = tags[\"ImageDescription\"].description;\n \n const matches = description.match(/(-?\\d+\\.?\\d*),(-?\\d+\\.?\\d*)/);\n if (matches) {\n const min = parseFloat(matches[1]);\n const max = parseFloat(matches[2]);\n \n if (!isNaN(min) && !isNaN(max)) {\n this.valueRange = [min, max];\n }\n }\n }\n \n // Then define onload handler\n image.onload = () => {\n URL.revokeObjectURL(objectURL);\n if (this.gl) {\n this.sourceTexture = createTexture(this.gl, this.gl.LINEAR, image);\n if (this.valueRange) {\n this.colormapTexture = createColormap(this.gl, this.color, this.valueRange);\n }\n this.sourceLoaded = true;\n if (this.map) {\n this.map.triggerRepaint();\n }\n }\n };\n\n image.onerror = (err) => {\n URL.revokeObjectURL(objectURL);\n console.error('Error loading source image:', err);\n };\n // Always proceed to load the image, even if EXIF parsing fails\n image.src = objectURL;\n } catch (error) {\n console.warn('Error reading EXIF data:', error);\n // Still proceed with loading the image\n image.src = objectURL;\n }\n })();\n })\n .catch(error => {\n console.error('Error fetching image:', error);\n });\n }\n\n onRemove() {\n // Clean up WebGL resources\n const gl = this.gl;\n if (!gl) return;\n\n // Delete textures\n if (this.sourceTexture) gl.deleteTexture(this.sourceTexture);\n if (this.colormapTexture) gl.deleteTexture(this.colormapTexture);\n\n // Delete buffer\n if (this.vertexBuffer) gl.deleteBuffer(this.vertexBuffer);\n\n // Delete shaders and program\n if (this.program) {\n const shaders = gl.getAttachedShaders(this.program.program);\n if (shaders) {\n shaders.forEach(shader => gl.deleteShader(shader));\n }\n gl.deleteProgram(this.program.program);\n }\n\n // Clear references\n this.sourceTexture = null;\n this.colormapTexture = null;\n this.vertexBuffer = null;\n this.program = null;\n this.gl = null;\n this.map = null;\n this.sourceLoaded = false;\n }\n\n render(gl, matrix) {\n // Only render if source is loaded\n if (!this.sourceLoaded || !this.readyForDisplay) return;\n \n gl.useProgram(this.program.program);\n\n // Set up blending for transparency\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n //gl.blendEquation(gl.FUNC_ADD);\n\n // Set uniforms\n gl.uniformMatrix4fv(this.program.u_matrix, false, matrix);\n gl.uniform4fv(this.program.u_bounds, this.bounds);\n gl.uniform1f(this.program.u_opacity, this.opacity);\n gl.uniform2fv(this.program.u_value_range, this.valueRange);\n\n // Bind textures\n gl.activeTexture(gl.TEXTURE0);\n gl.bindTexture(gl.TEXTURE_2D, this.sourceTexture);\n gl.uniform1i(this.program.u_image, 0);\n\n gl.activeTexture(gl.TEXTURE1);\n gl.bindTexture(gl.TEXTURE_2D, this.colormapTexture);\n gl.uniform1i(this.program.u_colormap, 1);\n\n // Draw quad\n gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);\n gl.enableVertexAttribArray(this.program.a_pos);\n gl.vertexAttribPointer(this.program.a_pos, 2, gl.FLOAT, false, 0, 0);\n gl.drawArrays(gl.TRIANGLES, 0, 6);\n\n gl.disable(gl.BLEND);\n }\n} "],"names":["createProgram","gl","vertexSource","fragmentSource","program","vertexShader","createShader","VERTEX_SHADER","fragmentShader","FRAGMENT_SHADER","attachShader","includes","transformFeedbackVaryings","SEPARATE_ATTRIBS","linkProgram","getProgramParameter","LINK_STATUS","Error","getProgramInfoLog","wrapper","numAttributes","ACTIVE_ATTRIBUTES","i","attribute","getActiveAttrib","name","getAttribLocation","numUniforms","ACTIVE_UNIFORMS","uniform","getActiveUniform","getUniformLocation","type","source","shader","shaderSource","compileShader","getShaderParameter","COMPILE_STATUS","getShaderInfoLog","createTexture","filter","data","width","height","texture","bindTexture","TEXTURE_2D","texParameteri","TEXTURE_WRAP_S","CLAMP_TO_EDGE","TEXTURE_WRAP_T","TEXTURE_MIN_FILTER","TEXTURE_MAG_FILTER","Uint8Array","texImage2D","RGBA","UNSIGNED_BYTE","Image","createBuffer","buffer","bindBuffer","ARRAY_BUFFER","bufferData","STATIC_DRAW","kphToMph","kph","mpsToMph","mps","ParticleMotion","Evented","constructor","id","color","bounds","particleCount","readyForDisplay","ageThreshold","maxAge","velocityFactor","fadeOpacity","updateInterval","pointSize","trailLength","trailSizeDecay","unit","cacheOption","slot","super","this","renderingMode","undefined","sourceLoaded","speedRange","onAdd","map","updateProgram","renderProgram","positions","Float32Array","ages","gridSize","Math","ceil","sqrt","baseX","baseY","floor","gridSpacing","jitterX","random","jitterY","max","min","particleBufferA","particleBufferB","currentBuffer","nextBuffer","ageBufferA","ageBufferB","currentAgeBuffer","nextAgeBuffer","trailOffsets","trailOffsetBuffer","transformFeedback","createTransformFeedback","lastTime","setSource","percentParticleWhenSetSource","image","crossOrigin","fetch","cache","then","response","Promise","all","clone","blob","arrayBuffer","objectURL","URL","createObjectURL","onload","revokeObjectURL","sourceTexture","LINEAR","shouldResetParticles","triggerRepaint","onerror","err","console","warn","tags","ExifReader","load","description","matches","match","min_u","parseFloat","max_u","min_v","max_v","min_speed","max_speed","isNaN","valueRange_u","valueRange_v","colormapTexture","colors","valueRange","sort","a","b","minVal","maxVal","value","lowIndex","length","highIndex","low","high","t","idx","j","round","createColormap","src","error","catch","render","matrix","currentTime","performance","now","colorMask","disable","BLEND","useProgram","uniform1f","u_time","u_speed_factor","uniform4fv","u_bounds","uniform2fv","u_value_range_u","u_value_range_v","u_speed_range","u_age_threshold","u_max_age","u_percent_reset","uniform1i","u_should_reset","activeTexture","TEXTURE0","u_velocity_texture","enableVertexAttribArray","a_position","vertexAttribPointer","FLOAT","a_age","bindTransformFeedback","TRANSFORM_FEEDBACK","bindBufferBase","TRANSFORM_FEEDBACK_BUFFER","beginTransformFeedback","POINTS","drawArrays","endTransformFeedback","bindFramebuffer","FRAMEBUFFER","viewport","canvas","enable","blendFunc","SRC_ALPHA","ONE_MINUS_SRC_ALPHA","uniformMatrix4fv","u_matrix","u_point_size","u_opacity","u_trail_size_decay","TEXTURE1","u_wind_color","a_trail_offset","vertexAttribDivisor","drawArraysInstanced","onRemove","shaders","getAttachedShaders","forEach","deleteShader","deleteProgram","deleteBuffer","deleteTransformFeedback","deleteTexture","SmoothRaster","opacity","vertices","vertexBuffer","NEAREST","u_value_range","u_image","u_colormap","a_pos","TRIANGLES"],"mappings":"8DAwSA,SAASA,EAAcC,EAAIC,EAAcC,GACrC,MAAMC,EAAUH,EAAGD,gBAEbK,EAAeC,EAAaL,EAAIA,EAAGM,cAAeL,GAClDM,EAAiBF,EAAaL,EAAIA,EAAGQ,gBAAiBN,GAiB5D,GAfAF,EAAGS,aAAaN,EAASC,GACzBJ,EAAGS,aAAaN,EAASI,GAGrBN,EAAaS,SAAS,yBAElBT,EAAaS,SAAS,mBACtBV,EAAGW,0BAA0BR,EAAS,CAAC,aAAc,SAAUH,EAAGY,kBAElEZ,EAAGW,0BAA0BR,EAAS,CAAC,cAAeH,EAAGY,mBAIjEZ,EAAGa,YAAYV,IAEVH,EAAGc,oBAAoBX,EAASH,EAAGe,aACpC,MAAM,IAAIC,MAAMhB,EAAGiB,kBAAkBd,IAGzC,MAAMe,EAAU,CAACf,QAASA,GAEpBgB,EAAgBnB,EAAGc,oBAAoBX,EAASH,EAAGoB,mBACzD,IAAK,IAAIC,EAAI,EAAGA,EAAIF,EAAeE,IAAK,CACpC,MAAMC,EAAYtB,EAAGuB,gBAAgBpB,EAASkB,GAC9CH,EAAQI,EAAUE,MAAQxB,EAAGyB,kBAAkBtB,EAASmB,EAAUE,KAC1E,CACI,MAAME,EAAc1B,EAAGc,oBAAoBX,EAASH,EAAG2B,iBACvD,IAAK,IAAIN,EAAI,EAAGA,EAAIK,EAAaL,IAAK,CAClC,MAAMO,EAAU5B,EAAG6B,iBAAiB1B,EAASkB,GAC7CH,EAAQU,EAAQJ,MAAQxB,EAAG8B,mBAAmB3B,EAASyB,EAAQJ,KACvE,CAEI,OAAON,CACX,CAEA,SAASb,EAAaL,EAAI+B,EAAMC,GAC5B,MAAMC,EAASjC,EAAGK,aAAa0B,GAG/B,GAFA/B,EAAGkC,aAAaD,EAAQD,GACxBhC,EAAGmC,cAAcF,IACZjC,EAAGoC,mBAAmBH,EAAQjC,EAAGqC,gBAClC,MAAM,IAAIrB,MAAMhB,EAAGsC,iBAAiBL,IAExC,OAAOA,CACX,CAEA,SAASM,EAAcvC,EAAIwC,EAAQC,EAAMC,EAAOC,GAC5C,MAAMC,EAAU5C,EAAGuC,gBAenB,OAdAvC,EAAG6C,YAAY7C,EAAG8C,WAAYF,GAC9B5C,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGgD,eAAgBhD,EAAGiD,eACtDjD,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGkD,eAAgBlD,EAAGiD,eACtDjD,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGmD,mBAAoBX,GACvDxC,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGoD,mBAAoBZ,GAEnDC,aAAgBY,WAChBrD,EAAGsD,WAAWtD,EAAG8C,WAAY,EAAG9C,EAAGuD,KAAMb,EAAOC,EAAQ,EAAG3C,EAAGuD,KAAMvD,EAAGwD,cAAef,GAC/EA,aAAgBgB,MACvBzD,EAAGsD,WAAWtD,EAAG8C,WAAY,EAAG9C,EAAGuD,KAAMvD,EAAGuD,KAAMvD,EAAGwD,cAAef,GAGpEzC,EAAGsD,WAAWtD,EAAG8C,WAAY,EAAG9C,EAAGuD,KAAMb,EAAOC,EAAQ,EAAG3C,EAAGuD,KAAMvD,EAAGwD,cAAe,MAEnFZ,CACX,CAEA,SAASc,EAAa1D,EAAIyC,GACtB,MAAMkB,EAAS3D,EAAG0D,eAGlB,OAFA1D,EAAG4D,WAAW5D,EAAG6D,aAAcF,GAC/B3D,EAAG8D,WAAW9D,EAAG6D,aAAcpB,EAAMzC,EAAG+D,aACjCJ,CACX,CAuCA,SAASK,EAASC,GACd,MAAa,QAANA,CACX,CAEA,SAASC,EAASC,GACd,OAAa,QAANA,CACX,CAEe,MAAMC,UAAuBC,EACxC,WAAAC,EAAYC,GAACA,EAAEvC,OAAEA,EAAMwC,MAAEA,EAAKC,OAAEA,EAAMC,cAAEA,EAAgB,IAAIC,gBAAEA,GAAkB,EAAKC,aAAEA,EAAe,IAAGC,OAAEA,EAAS,IAAIC,eACpHA,EAAiB,IAAIC,YAAEA,EAAc,GAAGC,eAAEA,EAAiB,GAAEC,UAAEA,EAAY,EAAGC,YAAEA,EAAc,EAACC,eAAEA,EAAiB,GAAGC,KACrHA,EAAO,MAAKC,YAAEA,EAAc,WAAUC,KAAEA,IACxCC,QAEAC,KAAKjB,GAAKA,EACViB,KAAKzD,KAAO,SACZyD,KAAKC,cAAgB,UAERC,IAATJ,IACAE,KAAKF,KAAOA,GAGhBE,KAAKxD,OAASA,EACdwD,KAAKhB,MAAQA,EACbgB,KAAKf,OAASA,EACde,KAAKd,cAAgBA,EAErBc,KAAKG,cAAe,EACpBH,KAAKb,gBAAkBA,EAGvBa,KAAKV,eAAiBA,EACtBU,KAAKT,YAAcA,EACnBS,KAAKR,eAAiBA,EACtBQ,KAAKP,UAAYA,EAGjBO,KAAKN,YAAcA,EACnBM,KAAKL,eAAiBA,EAGtBK,KAAKZ,aAAeA,EACpBY,KAAKX,OAASA,EAGdW,KAAKI,WAAa,CAAC,EAAG,KAEtBJ,KAAKJ,KAAOA,EACZI,KAAKH,YAAcA,CAC3B,CAEI,KAAAQ,CAAMC,EAAK9F,GACPwF,KAAKM,IAAMA,EACXN,KAAKxF,GAAKA,EAGVwF,KAAKO,cAAgBhG,EAAcC,EA9cvC,2tKAwRA,mTAuLIwF,KAAKQ,cAAgBjG,EAAcC,EAtRvC,slIAhDA,u2DAyUI,MAAMiG,EAAY,IAAIC,aAAkC,EAArBV,KAAKd,eAClCyB,EAAO,IAAID,aAAaV,KAAKd,eAC7B0B,EAAWC,KAAKC,KAAKD,KAAKE,KAAKf,KAAKd,gBAE1C,IAAK,IAAIrD,EAAI,EAAGA,EAAImE,KAAKd,cAAerD,IAAK,CACzC,MAIMmF,EAJInF,EAAI+E,GAIKA,EAAW,GACxBK,EAJIJ,KAAKK,MAAMrF,EAAI+E,IAINA,EAAW,GAGxBO,EAAc,GAAOP,EAAW,GAChCQ,EAAkC,KAAvBP,KAAKQ,SAAW,IAAcF,EACzCG,EAAkC,KAAvBT,KAAKQ,SAAW,IAAcF,EAG/CV,EAAc,EAAJ5E,GAASgF,KAAKU,IAAI,EAAGV,KAAKW,IAAI,EAAGR,EAAQI,IACnDX,EAAc,EAAJ5E,EAAQ,GAAKgF,KAAKU,IAAI,EAAGV,KAAKW,IAAI,EAAGP,EAAQK,IAGvDX,EAAK9E,GAAKgF,KAAKK,MAAsB,IAAhBL,KAAKQ,SACtC,CAGQrB,KAAKyB,gBAAkBvD,EAAa1D,EAAIiG,GACxCT,KAAK0B,gBAAkBxD,EAAa1D,EAAIiG,GACxCT,KAAK2B,cAAgB3B,KAAKyB,gBAC1BzB,KAAK4B,WAAa5B,KAAK0B,gBAGvB1B,KAAK6B,WAAa3D,EAAa1D,EAAImG,GACnCX,KAAK8B,WAAa5D,EAAa1D,EAAImG,GACnCX,KAAK+B,iBAAmB/B,KAAK6B,WAC7B7B,KAAKgC,cAAgBhC,KAAK8B,WAI1B,MAAMG,EAAe,IAAIvB,aAAaV,KAAKN,YAAc,GACzD,IAAK,IAAI7D,EAAI,EAAGA,GAAKmE,KAAKN,YAAa7D,IACnCoG,EAAapG,GAAKA,EAEtBmE,KAAKkC,kBAAoBhE,EAAa1D,EAAIyH,GAG1CjC,KAAKmC,kBAAoB3H,EAAG4H,0BAG5BpC,KAAKqC,SAAW,EAGhBrC,KAAKsC,UAAUtC,KAAKxD,OAAQ,EACpC,CAEI,SAAA8F,CAAU9F,EAAQ+F,EAA+B,IACzCvC,KAAKxD,QAAUA,IACfwD,KAAKxD,OAASA,GAGlB,MAAMgG,EAAQ,IAAIvE,MAClBuE,EAAMC,YAAc,YAEpBC,MAAMlG,EAAQ,CACVmG,MAAO3C,KAAKH,cAEX+C,MAAKC,GACFC,QAAQC,IAAI,CACRF,EAASG,QAAQC,OACjBJ,EAASK,cAAcN,MAAKzE,GAAU,IAAIN,WAAWM,GAAQA,aAGpEyE,MAAK,EAAEK,EAAMC,MACV,MAAMC,EAAYC,IAAIC,gBAAgBJ,GAEtCT,EAAMc,OAAS,KACXF,IAAIG,gBAAgBJ,GACpBnD,KAAKwD,cAAgBzG,EAAciD,KAAKxF,GAAIwF,KAAKxF,GAAGiJ,OAAQjB,GAC5DxC,KAAKG,cAAe,EAGhBoC,EAA+B,IAC/BvC,KAAKuC,6BAA+BA,EACpCvC,KAAK0D,sBAAuB,GAGhC1D,KAAKM,IAAIqD,gBAAgB,EAG7BnB,EAAMoB,QAAWC,IACbC,QAAQC,KAAK,8CAA+CF,GAC5DT,IAAIG,gBAAgBJ,EAAU,EAGlC,WACI,IACI,MAAMa,QAAaC,EAAWC,KAAKhB,GAEnC,GAAIc,EAAuB,kBAAKA,EAAuB,iBAAEG,YAAa,CAClE,MAEMC,EAFcJ,EAAuB,iBAAEG,YAEjBE,MAAM,uFAClC,GAAID,EAAS,CACT,IAAIE,EAAQC,WAAWH,EAAQ,IAC3BI,EAAQD,WAAWH,EAAQ,IAC3BK,EAAQF,WAAWH,EAAQ,IAC3BM,EAAQH,WAAWH,EAAQ,IAC3BO,EAAYJ,WAAWH,EAAQ,IAC/BQ,EAAYL,WAAWH,EAAQ,IAmBnC,GAhBkB,QAAdpE,KAAKJ,MACL0E,EAAQ9F,EAAS8F,GACjBE,EAAQhG,EAASgG,GACjBC,EAAQjG,EAASiG,GACjBC,EAAQlG,EAASkG,GACjBC,EAAYnG,EAASmG,GACrBC,EAAYpG,EAASoG,IACA,QAAd5E,KAAKJ,OACZ0E,EAAQ5F,EAAS4F,GACjBE,EAAQ9F,EAAS8F,GACjBC,EAAQ/F,EAAS+F,GACjBC,EAAQhG,EAASgG,GACjBC,EAAYjG,EAASiG,GACrBC,EAAYlG,EAASkG,MAGpBC,MAAMP,IAAWO,MAAML,IAAWK,MAAMJ,IAAWI,MAAMH,IAAWG,MAAMF,IAAeE,MAAMD,IAQhG,OAPA5E,KAAK8E,aAAe,CAACR,EAAOE,GAC5BxE,KAAK+E,aAAe,CAACN,EAAOC,GAC5B1E,KAAKI,WAAa,CAACuE,EAAWC,GAE9B5E,KAAKgF,gBArOzC,SAAwBxK,EAAIyK,EAAQC,GAEhCD,EAAOE,MAAK,CAACC,EAAGC,IAAMD,EAAE,GAAKC,EAAE,KAG/B,MAAMpI,EAAO,IAAIY,WAAW,OACrByH,EAAQC,GAAUL,EAGzB,IAAK,IAAIrJ,EAAI,EAAGA,EAAI,IAAKA,IAAK,CAE1B,MAAM2J,EAAQF,EAA8BzJ,EAAI,KAAxB0J,EAASD,GAGjC,IAAIG,EAAW,EACf,KAAOA,EAAWR,EAAOS,OAAS,GAAKT,EAAOQ,EAAW,GAAG,GAAKD,GAC7DC,IAEJ,MAAME,EAAY9E,KAAKW,IAAIiE,EAAW,EAAGR,EAAOS,OAAS,GAGnDE,EAAMX,EAAOQ,GACbI,EAAOZ,EAAOU,GACdG,EAAIH,EAAYF,GACjBD,EAAQI,EAAI,KAAOC,EAAK,GAAKD,EAAI,IAAM,EAEtCG,EAAU,EAAJlK,EACZ,IAAK,IAAImK,EAAI,EAAGA,EAAI,EAAGA,IACnB/I,EAAK8I,EAAMC,GAAKnF,KAAKoF,MAAML,EAAI,GAAGI,GAAKF,GAAKD,EAAK,GAAGG,GAAKJ,EAAI,GAAGI,KAEpE/I,EAAK8I,EAAM,GAAK,GACxB,CAEI,OAAOhJ,EAAcvC,EAAIA,EAAGiJ,OAAQxG,EAAM,IAAK,EACnD,CAmM2DiJ,CAAelG,KAAKxF,GAAIwF,KAAKhB,MAAOgB,KAAKI,iBAEhEoC,EAAM2D,IAAMhD,EAGhD,CACA,CAEwBW,QAAQC,KAAK,4DACbX,IAAIG,gBAAgBJ,EAEvB,CAAC,MAAOiD,GACLtC,QAAQC,KAAK,2CAA4CqC,GACzDhD,IAAIG,gBAAgBJ,EAC5C,CACiB,EArDD,EAqDI,IAEPkD,OAAMD,IACHtC,QAAQC,KAAK,wCAAyCqC,EAAM,GAE5E,CAEI,MAAAE,CAAO9L,EAAI+L,GACP,IAAKvG,KAAKG,eAAiBH,KAAKb,gBAC5B,OAIJ,MAAMqH,EAAcC,YAAYC,MAC3B1G,KAAKqC,WAAUrC,KAAKqC,SAAWmE,GAClBA,EAAcxG,KAAKqC,UAGHrC,KAAKR,iBAEnCQ,KAAKqC,SAAWmE,EAIhBhM,EAAGmM,WAAU,GAAO,GAAO,GAAO,GAClCnM,EAAGoM,QAAQpM,EAAGqM,OAEdrM,EAAGsM,WAAW9G,KAAKO,cAAc5F,SAGjCH,EAAGuM,UAAU/G,KAAKO,cAAcyG,OAAQR,EAAc,KACtDhM,EAAGuM,UAAU/G,KAAKO,cAAc0G,eAAgBjH,KAAKV,gBACrD9E,EAAG0M,WAAWlH,KAAKO,cAAc4G,SAAUnH,KAAKf,QAChDzE,EAAG4M,WAAWpH,KAAKO,cAAc8G,gBAAiBrH,KAAK8E,cACvDtK,EAAG4M,WAAWpH,KAAKO,cAAc+G,gBAAiBtH,KAAK+E,cACvDvK,EAAG4M,WAAWpH,KAAKO,cAAcgH,cAAevH,KAAKI,YACrD5F,EAAGuM,UAAU/G,KAAKO,cAAciH,gBAAiBxH,KAAKZ,cACtD5E,EAAGuM,UAAU/G,KAAKO,cAAckH,UAAWzH,KAAKX,QAGhD7E,EAAGuM,UAAU/G,KAAKO,cAAcmH,gBAAiB1H,KAAKuC,8BAAgC,GACtF/H,EAAGmN,UAAU3H,KAAKO,cAAcqH,eAAgB5H,KAAK0D,qBAAuB,EAAI,GAEhF1D,KAAK0D,sBAAuB,EAG5BlJ,EAAGqN,cAAcrN,EAAGsN,UACpBtN,EAAG6C,YAAY7C,EAAG8C,WAAY0C,KAAKwD,eACnChJ,EAAGmN,UAAU3H,KAAKO,cAAcwH,mBAAoB,GAGpDvN,EAAG4D,WAAW5D,EAAG6D,aAAc2B,KAAK2B,eACpCnH,EAAGwN,wBAAwBhI,KAAKO,cAAc0H,YAC9CzN,EAAG0N,oBAAoBlI,KAAKO,cAAc0H,WAAY,EAAGzN,EAAG2N,OAAO,EAAO,EAAG,GAG7E3N,EAAG4D,WAAW5D,EAAG6D,aAAc2B,KAAK+B,kBACpCvH,EAAGwN,wBAAwBhI,KAAKO,cAAc6H,OAC9C5N,EAAG0N,oBAAoBlI,KAAKO,cAAc6H,MAAO,EAAG5N,EAAG2N,OAAO,EAAO,EAAG,GAGxE3N,EAAG6N,sBAAsB7N,EAAG8N,mBAAoBtI,KAAKmC,mBACrD3H,EAAG+N,eAAe/N,EAAGgO,0BAA2B,EAAGxI,KAAK4B,YACxDpH,EAAG+N,eAAe/N,EAAGgO,0BAA2B,EAAGxI,KAAKgC,eAGxDxH,EAAGiO,uBAAuBjO,EAAGkO,QAG7BlO,EAAGmO,WAAWnO,EAAGkO,OAAQ,EAAG1I,KAAKd,eAGjC1E,EAAGoO,uBAGHpO,EAAG6N,sBAAsB7N,EAAG8N,mBAAoB,MAChD9N,EAAG+N,eAAe/N,EAAGgO,0BAA2B,EAAG,MACnDhO,EAAG+N,eAAe/N,EAAGgO,0BAA2B,EAAG,MAGnDhO,EAAGmM,WAAU,GAAM,GAAM,GAAM,IAG9B3G,KAAK2B,cAAe3B,KAAK4B,YAAc,CAAC5B,KAAK4B,WAAY5B,KAAK2B,gBAC9D3B,KAAK+B,iBAAkB/B,KAAKgC,eAAiB,CAAChC,KAAKgC,cAAehC,KAAK+B,mBAI5EvH,EAAGqO,gBAAgBrO,EAAGsO,YAAa,MACnCtO,EAAGuO,SAAS,EAAG,EAAGvO,EAAGwO,OAAO9L,MAAO1C,EAAGwO,OAAO7L,QAE7C3C,EAAGsM,WAAW9G,KAAKQ,cAAc7F,SAGjCH,EAAGyO,OAAOzO,EAAGqM,OACbrM,EAAG0O,UAAU1O,EAAG2O,UAAW3O,EAAG4O,qBAG9B5O,EAAG6O,iBAAiBrJ,KAAKQ,cAAc8I,UAAU,EAAO/C,GACxD/L,EAAG0M,WAAWlH,KAAKQ,cAAc2G,SAAUnH,KAAKf,QAChDzE,EAAGuM,UAAU/G,KAAKQ,cAAc+I,aAAcvJ,KAAKP,WACnDjF,EAAGuM,UAAU/G,KAAKQ,cAAcgJ,UAAWxJ,KAAKT,aAChD/E,EAAGuM,UAAU/G,KAAKQ,cAAcyG,eAAgBjH,KAAKV,gBACrD9E,EAAGuM,UAAU/G,KAAKQ,cAAciJ,mBAAoBzJ,KAAKL,gBACzDnF,EAAG4M,WAAWpH,KAAKQ,cAAc6G,gBAAiBrH,KAAK8E,cACvDtK,EAAG4M,WAAWpH,KAAKQ,cAAc8G,gBAAiBtH,KAAK+E,cACvDvK,EAAG4M,WAAWpH,KAAKQ,cAAc+G,cAAevH,KAAKI,YAGrD5F,EAAGqN,cAAcrN,EAAGsN,UACpBtN,EAAG6C,YAAY7C,EAAG8C,WAAY0C,KAAKwD,eACnChJ,EAAGmN,UAAU3H,KAAKQ,cAAcuH,mBAAoB,GAEpDvN,EAAGqN,cAAcrN,EAAGkP,UACpBlP,EAAG6C,YAAY7C,EAAG8C,WAAY0C,KAAKgF,iBACnCxK,EAAGmN,UAAU3H,KAAKQ,cAAcmJ,aAAc,GAG9CnP,EAAG4D,WAAW5D,EAAG6D,aAAc2B,KAAK2B,eACpCnH,EAAGwN,wBAAwBhI,KAAKQ,cAAcyH,YAC9CzN,EAAG0N,oBAAoBlI,KAAKQ,cAAcyH,WAAY,EAAGzN,EAAG2N,OAAO,EAAO,EAAG,GAG7E3N,EAAG4D,WAAW5D,EAAG6D,aAAc2B,KAAKkC,mBACpC1H,EAAGwN,wBAAwBhI,KAAKQ,cAAcoJ,gBAC9CpP,EAAG0N,oBAAoBlI,KAAKQ,cAAcoJ,eAAgB,EAAGpP,EAAG2N,OAAO,EAAO,EAAG,GACjF3N,EAAGqP,oBAAoB7J,KAAKQ,cAAcoJ,eAAgB,GAI1DpP,EAAGsP,oBAAoBtP,EAAGkO,OAAQ,EAAG1I,KAAKd,cAAec,KAAKN,YAAc,GAG5ElF,EAAGqP,oBAAoB7J,KAAKQ,cAAcoJ,eAAgB,GAE1DpP,EAAGoM,QAAQpM,EAAGqM,OAGd7G,KAAKM,IAAIqD,gBACjB,CAEI,QAAAoG,CAASzJ,EAAK9F,GAEV,GAAIwF,KAAKO,cAAe,CACpB,MAAMyJ,EAAUxP,EAAGyP,mBAAmBjK,KAAKO,cAAc5F,SACrDqP,GACAA,EAAQE,SAAQzN,GAAUjC,EAAG2P,aAAa1N,KAE9CjC,EAAG4P,cAAcpK,KAAKO,cAAc5F,QAChD,CACQ,GAAIqF,KAAKQ,cAAe,CACpB,MAAMwJ,EAAUxP,EAAGyP,mBAAmBjK,KAAKQ,cAAc7F,SACrDqP,GACAA,EAAQE,SAAQzN,GAAUjC,EAAG2P,aAAa1N,KAE9CjC,EAAG4P,cAAcpK,KAAKQ,cAAc7F,QAChD,CAGYqF,KAAKyB,iBAAiBjH,EAAG6P,aAAarK,KAAKyB,iBAC3CzB,KAAK0B,iBAAiBlH,EAAG6P,aAAarK,KAAK0B,iBAC3C1B,KAAK6B,YAAYrH,EAAG6P,aAAarK,KAAK6B,YACtC7B,KAAK8B,YAAYtH,EAAG6P,aAAarK,KAAK8B,YACtC9B,KAAKkC,mBAAmB1H,EAAG6P,aAAarK,KAAKkC,mBAG7ClC,KAAKmC,mBAAmB3H,EAAG8P,wBAAwBtK,KAAKmC,mBAGxDnC,KAAKwD,eAAehJ,EAAG+P,cAAcvK,KAAKwD,eAC1CxD,KAAKgF,iBAAiBxK,EAAG+P,cAAcvK,KAAKgF,iBAGhDhF,KAAKyB,gBAAkB,KACvBzB,KAAK0B,gBAAkB,KACvB1B,KAAK6B,WAAa,KAClB7B,KAAK8B,WAAa,KAClB9B,KAAK2B,cAAgB,KACrB3B,KAAK4B,WAAa,KAClB5B,KAAK+B,iBAAmB,KACxB/B,KAAKgC,cAAgB,KACrBhC,KAAKkC,kBAAoB,KACzBlC,KAAKmC,kBAAoB,KACzBnC,KAAKwD,cAAgB,KACrBxD,KAAKgF,gBAAkB,KACvBhF,KAAKO,cAAgB,KACrBP,KAAKQ,cAAgB,KACrBR,KAAKxF,GAAK,KACVwF,KAAKM,IAAM,KACXN,KAAKG,cAAe,CAC5B,ECxsBA,SAAStF,EAAaL,EAAI+B,EAAMC,GAC5B,MAAMC,EAASjC,EAAGK,aAAa0B,GAG/B,GAFA/B,EAAGkC,aAAaD,EAAQD,GACxBhC,EAAGmC,cAAcF,IACZjC,EAAGoC,mBAAmBH,EAAQjC,EAAGqC,gBAClC,MAAM,IAAIrB,MAAMhB,EAAGsC,iBAAiBL,IAExC,OAAOA,CACX,CAEA,SAASM,EAAcvC,EAAIwC,EAAQC,EAAMC,EAAOC,GAC5C,MAAMC,EAAU5C,EAAGuC,gBAYnB,OAXAvC,EAAG6C,YAAY7C,EAAG8C,WAAYF,GAC9B5C,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGgD,eAAgBhD,EAAGiD,eACtDjD,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGkD,eAAgBlD,EAAGiD,eACtDjD,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGmD,mBAAoBX,GACvDxC,EAAG+C,cAAc/C,EAAG8C,WAAY9C,EAAGoD,mBAAoBZ,GAEnDC,aAAgBY,WAChBrD,EAAGsD,WAAWtD,EAAG8C,WAAY,EAAG9C,EAAGuD,KAAMb,EAAOC,EAAQ,EAAG3C,EAAGuD,KAAMvD,EAAGwD,cAAef,GAEtFzC,EAAGsD,WAAWtD,EAAG8C,WAAY,EAAG9C,EAAGuD,KAAMvD,EAAGuD,KAAMvD,EAAGwD,cAAef,GAEjEG,CACX,CAmDe,MAAMoN,UAAqB3L,EACtC,WAAAC,EAAYC,GAACA,EAAEvC,OAAEA,EAAMwC,MAAEA,EAAKC,OAAEA,EAAMwL,QAAEA,EAAU,EAAGtL,gBAAEA,GAAkB,EAAKU,YAAEA,EAAc,WAAUC,KAAEA,IACtGC,QAEAC,KAAKjB,GAAKA,EACViB,KAAKzD,KAAO,SACZyD,KAAKC,cAAgB,UAERC,IAATJ,IACAE,KAAKF,KAAOA,GAGhBE,KAAKxD,OAASA,EACdwD,KAAKhB,MAAQA,EACbgB,KAAKyK,QAAUA,EACfzK,KAAKf,OAASA,EAEde,KAAKG,cAAe,EACpBH,KAAKb,gBAAkBA,EACvBa,KAAKH,YAAcA,CAC3B,CAEI,KAAAQ,CAAMC,EAAK9F,GACPwF,KAAKM,IAAMA,EACXN,KAAKxF,GAAKA,EAGVwF,KAAKrF,QApIb,SAAuBH,EAAIC,EAAcC,GACrC,MAAMC,EAAUH,EAAGD,gBAEbK,EAAeC,EAAaL,EAAIA,EAAGM,cAAeL,GAClDM,EAAiBF,EAAaL,EAAIA,EAAGQ,gBAAiBN,GAM5D,GAJAF,EAAGS,aAAaN,EAASC,GACzBJ,EAAGS,aAAaN,EAASI,GACzBP,EAAGa,YAAYV,IAEVH,EAAGc,oBAAoBX,EAASH,EAAGe,aACpC,MAAM,IAAIC,MAAMhB,EAAGiB,kBAAkBd,IAGzC,MAAMe,EAAU,CAACf,QAASA,GAEpBgB,EAAgBnB,EAAGc,oBAAoBX,EAASH,EAAGoB,mBACzD,IAAK,IAAIC,EAAI,EAAGA,EAAIF,EAAeE,IAAK,CACpC,MAAMC,EAAYtB,EAAGuB,gBAAgBpB,EAASkB,GAC9CH,EAAQI,EAAUE,MAAQxB,EAAGyB,kBAAkBtB,EAASmB,EAAUE,KAC1E,CACI,MAAME,EAAc1B,EAAGc,oBAAoBX,EAASH,EAAG2B,iBACvD,IAAK,IAAIN,EAAI,EAAGA,EAAIK,EAAaL,IAAK,CAClC,MAAMO,EAAU5B,EAAG6B,iBAAiB1B,EAASkB,GAC7CH,EAAQU,EAAQJ,MAAQxB,EAAG8B,mBAAmB3B,EAASyB,EAAQJ,KACvE,CAEI,OAAON,CACX,CAwGuBnB,CAAcC,EAlMhB,yvCAuCE,ioBA8Jf,MAAMkQ,EAAW,IAAIhK,aAAa,CAC9B,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,EACH,EAAG,IAEPV,KAAK2K,aAvFb,SAAsBnQ,EAAIyC,GACtB,MAAMkB,EAAS3D,EAAG0D,eAGlB,OAFA1D,EAAG4D,WAAW5D,EAAG6D,aAAcF,GAC/B3D,EAAG8D,WAAW9D,EAAG6D,aAAcpB,EAAMzC,EAAG+D,aACjCJ,CACX,CAkF4BD,CAAa1D,EAAIkQ,GAGrC1K,KAAKsC,UAAUtC,KAAKxD,OAC5B,CAEI,SAAA8F,CAAU9F,EAAQwC,EAAQ,MAClBgB,KAAKxD,QAAUA,IACfwD,KAAKxD,OAASA,GAGL,MAATwC,IACAgB,KAAKhB,MAAQA,GAGjB,MAAMwD,EAAQ,IAAIvE,MAClBuE,EAAMC,YAAc,YAEpBC,MAAMlG,EAAQ,CACVmG,MAAO3C,KAAKH,cAEX+C,MAAKC,GACFC,QAAQC,IAAI,CACRF,EAASG,QAAQC,OACjBJ,EAASK,cAAcN,MAAKzE,GAAU,IAAIN,WAAWM,GAAQA,aAGpEyE,MAAK,EAAEK,EAAMC,MACV,MAAMC,EAAYC,IAAIC,gBAAgBJ,GAGtCjD,KAAKkF,WAAa,CAAC,EAAG,KAEtB,WACI,IACI,MAAMlB,QAAaC,EAAWC,KAAKhB,GAEnC,GAAIc,EAAuB,kBAAKA,EAAuB,iBAAEG,YAAa,CAClE,MAEMC,EAFcJ,EAAuB,iBAAEG,YAEjBE,MAAM,+BAClC,GAAID,EAAS,CACT,MAAM5C,EAAM+C,WAAWH,EAAQ,IACzB7C,EAAMgD,WAAWH,EAAQ,IAE1BS,MAAMrD,IAASqD,MAAMtD,KACtBvB,KAAKkF,WAAa,CAAC1D,EAAKD,GAE5D,CACA,CAGwBiB,EAAMc,OAAS,KACXF,IAAIG,gBAAgBJ,GAChBnD,KAAKxF,KACLwF,KAAKwD,cAAgBzG,EAAciD,KAAKxF,GAAIwF,KAAKxF,GAAGiJ,OAAQjB,GACxDxC,KAAKkF,aACLlF,KAAKgF,gBAzIzC,SAAwBxK,EAAIyK,EAAQC,GAEhCD,EAAOE,MAAK,CAACC,EAAGC,IAAMD,EAAE,GAAKC,EAAE,KAG/B,MAAMpI,EAAO,IAAIY,WAAW,OACrByH,EAAQC,GAAUL,EAGzB,IAAK,IAAIrJ,EAAI,EAAGA,EAAI,IAAKA,IAAK,CAE1B,MAAM2J,EAAQF,EAA8BzJ,EAAI,KAAxB0J,EAASD,GAGjC,IAAIG,EAAW,EACf,KAAOA,EAAWR,EAAOS,OAAS,GAAKT,EAAOQ,EAAW,GAAG,GAAKD,GAC7DC,IAEJ,MAAME,EAAY9E,KAAKW,IAAIiE,EAAW,EAAGR,EAAOS,OAAS,GAGnDE,EAAMX,EAAOQ,GACbI,EAAOZ,EAAOU,GACdG,EAAIH,EAAYF,GACjBD,EAAQI,EAAI,KAAOC,EAAK,GAAKD,EAAI,IAAM,EAEtCG,EAAU,EAAJlK,EACZ,IAAK,IAAImK,EAAI,EAAGA,EAAI,EAAGA,IACnB/I,EAAK8I,EAAMC,GAAKnF,KAAKoF,MAAML,EAAI,GAAGI,GAAKF,GAAKD,EAAK,GAAGG,GAAKJ,EAAI,GAAGI,KAGnD,MAAbJ,EAAI,GAAG,IAA2B,KAAbA,EAAI,GAAG,GAC5B3I,EAAK8I,EAAM,GAAKH,EAAI,GAAG,GAGvB3I,EAAK8I,EAAM,GAAK,GAE5B,CAEI,OAAOhJ,EAAcvC,EAAIA,EAAGoQ,QAAS3N,EAAM,IAAK,EACpD,CAiG2DiJ,CAAelG,KAAKxF,GAAIwF,KAAKhB,MAAOgB,KAAKkF,aAEpElF,KAAKG,cAAe,EAChBH,KAAKM,KACLN,KAAKM,IAAIqD,iBAE7C,EAGwBnB,EAAMoB,QAAWC,IACbT,IAAIG,gBAAgBJ,GACpBW,QAAQsC,MAAM,8BAA+BvC,EAAI,EAGrDrB,EAAM2D,IAAMhD,CACf,CAAC,MAAOiD,GACLtC,QAAQC,KAAK,2BAA4BqC,GAEzC5D,EAAM2D,IAAMhD,CACpC,CACiB,EA5CD,EA4CI,IAEPkD,OAAMD,IACHtC,QAAQsC,MAAM,wBAAyBA,EAAM,GAE7D,CAEI,QAAA2D,GAEI,MAAMvP,EAAKwF,KAAKxF,GAChB,GAAKA,EAAL,CAUA,GAPIwF,KAAKwD,eAAehJ,EAAG+P,cAAcvK,KAAKwD,eAC1CxD,KAAKgF,iBAAiBxK,EAAG+P,cAAcvK,KAAKgF,iBAG5ChF,KAAK2K,cAAcnQ,EAAG6P,aAAarK,KAAK2K,cAGxC3K,KAAKrF,QAAS,CACd,MAAMqP,EAAUxP,EAAGyP,mBAAmBjK,KAAKrF,QAAQA,SAC/CqP,GACAA,EAAQE,SAAQzN,GAAUjC,EAAG2P,aAAa1N,KAE9CjC,EAAG4P,cAAcpK,KAAKrF,QAAQA,QAC1C,CAGQqF,KAAKwD,cAAgB,KACrBxD,KAAKgF,gBAAkB,KACvBhF,KAAK2K,aAAe,KACpB3K,KAAKrF,QAAU,KACfqF,KAAKxF,GAAK,KACVwF,KAAKM,IAAM,KACXN,KAAKG,cAAe,CAzBX,CA0BjB,CAEI,MAAAmG,CAAO9L,EAAI+L,GAEFvG,KAAKG,cAAiBH,KAAKb,kBAEhC3E,EAAGsM,WAAW9G,KAAKrF,QAAQA,SAG3BH,EAAGyO,OAAOzO,EAAGqM,OACbrM,EAAG0O,UAAU1O,EAAG2O,UAAW3O,EAAG4O,qBAI9B5O,EAAG6O,iBAAiBrJ,KAAKrF,QAAQ2O,UAAU,EAAO/C,GAClD/L,EAAG0M,WAAWlH,KAAKrF,QAAQwM,SAAUnH,KAAKf,QAC1CzE,EAAGuM,UAAU/G,KAAKrF,QAAQ6O,UAAWxJ,KAAKyK,SAC1CjQ,EAAG4M,WAAWpH,KAAKrF,QAAQkQ,cAAe7K,KAAKkF,YAG/C1K,EAAGqN,cAAcrN,EAAGsN,UACpBtN,EAAG6C,YAAY7C,EAAG8C,WAAY0C,KAAKwD,eACnChJ,EAAGmN,UAAU3H,KAAKrF,QAAQmQ,QAAS,GAEnCtQ,EAAGqN,cAAcrN,EAAGkP,UACpBlP,EAAG6C,YAAY7C,EAAG8C,WAAY0C,KAAKgF,iBACnCxK,EAAGmN,UAAU3H,KAAKrF,QAAQoQ,WAAY,GAGtCvQ,EAAG4D,WAAW5D,EAAG6D,aAAc2B,KAAK2K,cACpCnQ,EAAGwN,wBAAwBhI,KAAKrF,QAAQqQ,OACxCxQ,EAAG0N,oBAAoBlI,KAAKrF,QAAQqQ,MAAO,EAAGxQ,EAAG2N,OAAO,EAAO,EAAG,GAClE3N,EAAGmO,WAAWnO,EAAGyQ,UAAW,EAAG,GAE/BzQ,EAAGoM,QAAQpM,EAAGqM,OACtB"}