|
| 1 | +(function (global, factory) { |
| 2 | + typeof exports === 'object' && typeof module !== 'undefined' ? factory() : |
| 3 | + typeof define === 'function' && define.amd ? define(factory) : |
| 4 | + (factory()); |
| 5 | +}(this, (function () { 'use strict'; |
| 6 | + |
| 7 | +function createShader(gl, type, source) { |
| 8 | + var shader = gl.createShader(type); |
| 9 | + gl.shaderSource(shader, source); |
| 10 | + |
| 11 | + gl.compileShader(shader); |
| 12 | + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { |
| 13 | + throw new Error(gl.getShaderInfoLog(shader)); |
| 14 | + } |
| 15 | + |
| 16 | + return shader; |
| 17 | +} |
| 18 | + |
| 19 | +function createProgram(gl, vertexSource, fragmentSource) { |
| 20 | + var program = gl.createProgram(); |
| 21 | + |
| 22 | + var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource); |
| 23 | + var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource); |
| 24 | + |
| 25 | + gl.attachShader(program, vertexShader); |
| 26 | + gl.attachShader(program, fragmentShader); |
| 27 | + |
| 28 | + gl.linkProgram(program); |
| 29 | + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { |
| 30 | + throw new Error(gl.getProgramInfoLog(program)); |
| 31 | + } |
| 32 | + |
| 33 | + var wrapper = {program: program}; |
| 34 | + |
| 35 | + var numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); |
| 36 | + for (var i = 0; i < numAttributes; i++) { |
| 37 | + var attribute = gl.getActiveAttrib(program, i); |
| 38 | + wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name); |
| 39 | + } |
| 40 | + var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); |
| 41 | + for (i = 0; i < numUniforms; i++) { |
| 42 | + var uniform = gl.getActiveUniform(program, i); |
| 43 | + wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name); |
| 44 | + } |
| 45 | + |
| 46 | + return wrapper; |
| 47 | +} |
| 48 | + |
| 49 | +function createTexture(gl, filter, data, width, height) { |
| 50 | + var texture = gl.createTexture(); |
| 51 | + gl.bindTexture(gl.TEXTURE_2D, texture); |
| 52 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
| 53 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
| 54 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); |
| 55 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); |
| 56 | + if (data instanceof Uint8Array) { |
| 57 | + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data); |
| 58 | + } else { |
| 59 | + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); |
| 60 | + } |
| 61 | + gl.bindTexture(gl.TEXTURE_2D, null); |
| 62 | + return texture; |
| 63 | +} |
| 64 | + |
| 65 | +function bindTexture(gl, texture, unit) { |
| 66 | + gl.activeTexture(gl.TEXTURE0 + unit); |
| 67 | + gl.bindTexture(gl.TEXTURE_2D, texture); |
| 68 | +} |
| 69 | + |
| 70 | +function createBuffer(gl, data) { |
| 71 | + var buffer = gl.createBuffer(); |
| 72 | + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); |
| 73 | + gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); |
| 74 | + return buffer; |
| 75 | +} |
| 76 | + |
| 77 | +function bindAttribute(gl, buffer, attribute, numComponents) { |
| 78 | + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); |
| 79 | + gl.enableVertexAttribArray(attribute); |
| 80 | + gl.vertexAttribPointer(attribute, numComponents, gl.FLOAT, false, 0, 0); |
| 81 | +} |
| 82 | + |
| 83 | +function bindFramebuffer(gl, framebuffer, texture) { |
| 84 | + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); |
| 85 | + if (texture) { |
| 86 | + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +var drawVert = "precision mediump float;\n\nattribute float a_index;\n\nuniform sampler2D u_particles;\nuniform float u_particles_res;\n\nvarying vec2 v_particle_pos;\n\nvoid main() {\n vec4 color = texture2D(u_particles, vec2(\n fract(a_index / u_particles_res),\n floor(a_index / u_particles_res) / u_particles_res));\n\n // decode current particle position from the pixel's RGBA value\n v_particle_pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a);\n\n gl_PointSize = 1.0;\n gl_Position = vec4(2.0 * v_particle_pos.x - 1.0, 1.0 - 2.0 * v_particle_pos.y, 0, 1);\n}\n"; |
| 91 | + |
| 92 | +var drawFrag = "precision mediump float;\n\nuniform sampler2D u_wind;\nuniform vec2 u_wind_min;\nuniform vec2 u_wind_max;\nuniform sampler2D u_color_ramp;\n\nvarying vec2 v_particle_pos;\n\nvoid main() {\n vec2 velocity = mix(u_wind_min, u_wind_max, texture2D(u_wind, v_particle_pos).rg);\n float speed_t = length(velocity) / length(u_wind_max);\n\n // color ramp is encoded in a 16x16 texture\n vec2 ramp_pos = vec2(\n fract(16.0 * speed_t),\n floor(16.0 * speed_t) / 16.0);\n\n gl_FragColor = texture2D(u_color_ramp, ramp_pos);\n}\n"; |
| 93 | + |
| 94 | +var quadVert = "precision mediump float;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_tex_pos;\n\nvoid main() {\n v_tex_pos = a_pos;\n gl_Position = vec4(1.0 - 2.0 * a_pos, 0, 1);\n}\n"; |
| 95 | + |
| 96 | +var screenFrag = "precision mediump float;\n\nuniform sampler2D u_screen;\nuniform float u_opacity;\n\nvarying vec2 v_tex_pos;\n\nvoid main() {\n vec4 color = texture2D(u_screen, 1.0 - v_tex_pos);\n // a hack to guarantee opacity fade out even with a value close to 1.0\n gl_FragColor = vec4(floor(255.0 * color * u_opacity) / 255.0);\n}\n"; |
| 97 | + |
| 98 | +var updateFrag = "precision highp float;\n\nuniform sampler2D u_particles;\nuniform sampler2D u_wind;\nuniform vec2 u_wind_res;\nuniform vec2 u_wind_min;\nuniform vec2 u_wind_max;\nuniform float u_rand_seed;\nuniform float u_speed_factor;\nuniform float u_drop_rate;\nuniform float u_drop_rate_bump;\n\nvarying vec2 v_tex_pos;\n\n// pseudo-random generator\nconst vec3 rand_constants = vec3(12.9898, 78.233, 4375.85453);\nfloat rand(const vec2 co) {\n float t = dot(rand_constants.xy, co);\n return fract(sin(t) * (rand_constants.z + t));\n}\n\n// wind speed lookup; use manual bilinear filtering based on 4 adjacent pixels for smooth interpolation\nvec2 lookup_wind(const vec2 uv) {\n // return texture2D(u_wind, uv).rg; // lower-res hardware filtering\n vec2 px = 1.0 / u_wind_res;\n vec2 vc = (floor(uv * u_wind_res)) * px;\n vec2 f = fract(uv * u_wind_res);\n vec2 tl = texture2D(u_wind, vc).rg;\n vec2 tr = texture2D(u_wind, vc + vec2(px.x, 0)).rg;\n vec2 bl = texture2D(u_wind, vc + vec2(0, px.y)).rg;\n vec2 br = texture2D(u_wind, vc + px).rg;\n return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);\n}\n\nvoid main() {\n vec4 color = texture2D(u_particles, v_tex_pos);\n vec2 pos = vec2(\n color.r / 255.0 + color.b,\n color.g / 255.0 + color.a); // decode particle position from pixel RGBA\n\n vec2 velocity = mix(u_wind_min, u_wind_max, lookup_wind(pos));\n float speed_t = length(velocity) / length(u_wind_max);\n\n // take EPSG:4236 distortion into account for calculating where the particle moved\n float distortion = max(0.1, cos(radians(pos.y * 180.0 - 90.0)));\n vec2 offset = vec2(velocity.x / distortion, -velocity.y) * 0.0001 * u_speed_factor;\n\n // a random seed to use for the particle drop\n vec2 seed = (pos + v_tex_pos) * u_rand_seed;\n\n // drop rate is a chance a particle will restart at random position, to avoid degeneration\n float dropRate = u_drop_rate + speed_t * u_drop_rate_bump;\n float drop = step(1.0 - dropRate, rand(seed));\n pos = mix(fract(1.0 + pos + offset), vec2(rand(seed + 1.3), rand(seed + 2.1)), drop);\n\n // encode the new particle position back into RGBA\n gl_FragColor = vec4(\n fract(pos * 255.0),\n floor(pos * 255.0) / 255.0);\n}\n"; |
| 99 | + |
| 100 | +var defaultRampColors = { |
| 101 | + 0.0: '#3288bd', |
| 102 | + 0.1: '#66c2a5', |
| 103 | + 0.2: '#abdda4', |
| 104 | + 0.3: '#e6f598', |
| 105 | + 0.4: '#fee08b', |
| 106 | + 0.5: '#fdae61', |
| 107 | + 0.6: '#f46d43', |
| 108 | + 1.0: '#d53e4f' |
| 109 | +}; |
| 110 | + |
| 111 | +class WindGL { |
| 112 | + constructor(gl) { |
| 113 | + this.gl = gl; |
| 114 | + |
| 115 | + this.fadeOpacity = 0.996; // how fast the particle trails fade on each frame |
| 116 | + this.speedFactor = 0.25; // how fast the particles move |
| 117 | + this.dropRate = 0.003; // how often the particles move to a random place |
| 118 | + this.dropRateBump = 0.01; // drop rate increase relative to individual particle speed |
| 119 | + |
| 120 | + this.drawProgram = createProgram(gl, drawVert, drawFrag); |
| 121 | + this.screenProgram = createProgram(gl, quadVert, screenFrag); |
| 122 | + this.updateProgram = createProgram(gl, quadVert, updateFrag); |
| 123 | + |
| 124 | + this.quadBuffer = createBuffer(gl, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1])); |
| 125 | + this.framebuffer = gl.createFramebuffer(); |
| 126 | + |
| 127 | + this.setColorRamp(defaultRampColors); |
| 128 | + this.resize(); |
| 129 | + } |
| 130 | + |
| 131 | + resize() { |
| 132 | + var gl = this.gl; |
| 133 | + var emptyPixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4); |
| 134 | + // screen textures to hold the drawn screen for the previous and the current frame |
| 135 | + this.backgroundTexture = createTexture(gl, gl.NEAREST, emptyPixels, gl.canvas.width, gl.canvas.height); |
| 136 | + this.screenTexture = createTexture(gl, gl.NEAREST, emptyPixels, gl.canvas.width, gl.canvas.height); |
| 137 | + } |
| 138 | + |
| 139 | + setColorRamp(colors) { |
| 140 | + // lookup texture for colorizing the particles according to their speed |
| 141 | + this.colorRampTexture = createTexture(this.gl, this.gl.LINEAR, getColorRamp(colors), 16, 16); |
| 142 | + } |
| 143 | + |
| 144 | + set numParticles(numParticles) { |
| 145 | + var gl = this.gl; |
| 146 | + |
| 147 | + // we create a square texture where each pixel will hold a particle position encoded as RGBA |
| 148 | + var particleRes = this.particleStateResolution = Math.ceil(Math.sqrt(numParticles)); |
| 149 | + this._numParticles = particleRes * particleRes; |
| 150 | + |
| 151 | + var particleState = new Uint8Array(this._numParticles * 4); |
| 152 | + for (var i = 0; i < particleState.length; i++) { |
| 153 | + particleState[i] = Math.floor(Math.random() * 256); // randomize the initial particle positions |
| 154 | + } |
| 155 | + // textures to hold the particle state for the current and the next frame |
| 156 | + this.particleStateTexture0 = createTexture(gl, gl.NEAREST, particleState, particleRes, particleRes); |
| 157 | + this.particleStateTexture1 = createTexture(gl, gl.NEAREST, particleState, particleRes, particleRes); |
| 158 | + |
| 159 | + var particleIndices = new Float32Array(this._numParticles); |
| 160 | + for (i = 0; i < this._numParticles; i++) particleIndices[i] = i; |
| 161 | + this.particleIndexBuffer = createBuffer(gl, particleIndices); |
| 162 | + } |
| 163 | + get numParticles() { |
| 164 | + return this._numParticles; |
| 165 | + } |
| 166 | + |
| 167 | + setWind(windData) { |
| 168 | + this.windData = windData; |
| 169 | + this.windTexture = createTexture(this.gl, this.gl.LINEAR, windData.image); |
| 170 | + } |
| 171 | + |
| 172 | + draw() { |
| 173 | + var gl = this.gl; |
| 174 | + gl.disable(gl.DEPTH_TEST); |
| 175 | + gl.disable(gl.STENCIL_TEST); |
| 176 | + |
| 177 | + bindTexture(gl, this.windTexture, 0); |
| 178 | + bindTexture(gl, this.particleStateTexture0, 1); |
| 179 | + |
| 180 | + this.drawScreen(); |
| 181 | + this.updateParticles(); |
| 182 | + } |
| 183 | + |
| 184 | + drawScreen() { |
| 185 | + var gl = this.gl; |
| 186 | + // draw the screen into a temporary framebuffer to retain it as the background on the next frame |
| 187 | + bindFramebuffer(gl, this.framebuffer, this.screenTexture); |
| 188 | + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); |
| 189 | + |
| 190 | + this.drawTexture(this.backgroundTexture, this.fadeOpacity); |
| 191 | + this.drawParticles(); |
| 192 | + |
| 193 | + bindFramebuffer(gl, null); |
| 194 | + // enable blending to support drawing on top of an existing background (e.g. a map) |
| 195 | + gl.enable(gl.BLEND); |
| 196 | + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); |
| 197 | + this.drawTexture(this.screenTexture, 1.0); |
| 198 | + gl.disable(gl.BLEND); |
| 199 | + |
| 200 | + // save the current screen as the background for the next frame |
| 201 | + var temp = this.backgroundTexture; |
| 202 | + this.backgroundTexture = this.screenTexture; |
| 203 | + this.screenTexture = temp; |
| 204 | + } |
| 205 | + |
| 206 | + drawTexture(texture, opacity) { |
| 207 | + var gl = this.gl; |
| 208 | + var program = this.screenProgram; |
| 209 | + gl.useProgram(program.program); |
| 210 | + |
| 211 | + bindAttribute(gl, this.quadBuffer, program.a_pos, 2); |
| 212 | + bindTexture(gl, texture, 2); |
| 213 | + gl.uniform1i(program.u_screen, 2); |
| 214 | + gl.uniform1f(program.u_opacity, opacity); |
| 215 | + |
| 216 | + gl.drawArrays(gl.TRIANGLES, 0, 6); |
| 217 | + } |
| 218 | + |
| 219 | + drawParticles() { |
| 220 | + var gl = this.gl; |
| 221 | + var program = this.drawProgram; |
| 222 | + gl.useProgram(program.program); |
| 223 | + |
| 224 | + bindAttribute(gl, this.particleIndexBuffer, program.a_index, 1); |
| 225 | + bindTexture(gl, this.colorRampTexture, 2); |
| 226 | + |
| 227 | + gl.uniform1i(program.u_wind, 0); |
| 228 | + gl.uniform1i(program.u_particles, 1); |
| 229 | + gl.uniform1i(program.u_color_ramp, 2); |
| 230 | + |
| 231 | + gl.uniform1f(program.u_particles_res, this.particleStateResolution); |
| 232 | + gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); |
| 233 | + gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); |
| 234 | + |
| 235 | + gl.drawArrays(gl.POINTS, 0, this._numParticles); |
| 236 | + } |
| 237 | + |
| 238 | + updateParticles() { |
| 239 | + var gl = this.gl; |
| 240 | + bindFramebuffer(gl, this.framebuffer, this.particleStateTexture1); |
| 241 | + gl.viewport(0, 0, this.particleStateResolution, this.particleStateResolution); |
| 242 | + |
| 243 | + var program = this.updateProgram; |
| 244 | + gl.useProgram(program.program); |
| 245 | + |
| 246 | + bindAttribute(gl, this.quadBuffer, program.a_pos, 2); |
| 247 | + |
| 248 | + gl.uniform1i(program.u_wind, 0); |
| 249 | + gl.uniform1i(program.u_particles, 1); |
| 250 | + |
| 251 | + gl.uniform1f(program.u_rand_seed, Math.random()); |
| 252 | + gl.uniform2f(program.u_wind_res, this.windData.width, this.windData.height); |
| 253 | + gl.uniform2f(program.u_wind_min, this.windData.uMin, this.windData.vMin); |
| 254 | + gl.uniform2f(program.u_wind_max, this.windData.uMax, this.windData.vMax); |
| 255 | + gl.uniform1f(program.u_speed_factor, this.speedFactor); |
| 256 | + gl.uniform1f(program.u_drop_rate, this.dropRate); |
| 257 | + gl.uniform1f(program.u_drop_rate_bump, this.dropRateBump); |
| 258 | + |
| 259 | + gl.drawArrays(gl.TRIANGLES, 0, 6); |
| 260 | + |
| 261 | + // swap the particle state textures so the new one becomes the current one |
| 262 | + var temp = this.particleStateTexture0; |
| 263 | + this.particleStateTexture0 = this.particleStateTexture1; |
| 264 | + this.particleStateTexture1 = temp; |
| 265 | + } |
| 266 | +} |
| 267 | + |
| 268 | +function getColorRamp(colors) { |
| 269 | + var canvas = document.createElement('canvas'); |
| 270 | + var ctx = canvas.getContext('2d'); |
| 271 | + |
| 272 | + canvas.width = 256; |
| 273 | + canvas.height = 1; |
| 274 | + |
| 275 | + var gradient = ctx.createLinearGradient(0, 0, 256, 0); |
| 276 | + for (var stop in colors) { |
| 277 | + gradient.addColorStop(+stop, colors[stop]); |
| 278 | + } |
| 279 | + |
| 280 | + ctx.fillStyle = gradient; |
| 281 | + ctx.fillRect(0, 0, 256, 1); |
| 282 | + |
| 283 | + return new Uint8Array(ctx.getImageData(0, 0, 256, 1).data); |
| 284 | +} |
| 285 | + |
| 286 | +var canvas = document.getElementById('canvas'); |
| 287 | + |
| 288 | +var pxRatio = Math.max(Math.floor(window.devicePixelRatio) || 1, 2); |
| 289 | +canvas.width = canvas.clientWidth; |
| 290 | +canvas.height = canvas.clientHeight; |
| 291 | + |
| 292 | +var gl = canvas.getContext('webgl', {antialiasing: false}); |
| 293 | + |
| 294 | +var wind = window.wind = new WindGL(gl); |
| 295 | +wind.numParticles = 65536; |
| 296 | + |
| 297 | +function frame() { |
| 298 | + if (wind.windData) { |
| 299 | + wind.draw(); |
| 300 | + } |
| 301 | + requestAnimationFrame(frame); |
| 302 | +} |
| 303 | +frame(); |
| 304 | + |
| 305 | +var gui = new dat.GUI(); |
| 306 | +gui.add(wind, 'numParticles', 1024, 589824); |
| 307 | +gui.add(wind, 'fadeOpacity', 0.96, 0.999).step(0.001).updateDisplay(); |
| 308 | +gui.add(wind, 'speedFactor', 0.05, 1.0); |
| 309 | +gui.add(wind, 'dropRate', 0, 0.1); |
| 310 | +gui.add(wind, 'dropRateBump', 0, 0.2); |
| 311 | + |
| 312 | +var windFiles = { |
| 313 | + 0: '2016112000', |
| 314 | + 6: '2016112006', |
| 315 | + 12: '2016112012', |
| 316 | + 18: '2016112018', |
| 317 | + 24: '2016112100', |
| 318 | + 30: '2016112106', |
| 319 | + 36: '2016112112', |
| 320 | + 42: '2016112118', |
| 321 | + 48: '2016112200' |
| 322 | +}; |
| 323 | + |
| 324 | +var meta = { |
| 325 | + '2016-11-20+h': 0, |
| 326 | + 'retina resolution': false, |
| 327 | + 'github.com/mapbox/webgl-wind': function () { |
| 328 | + window.location = 'https://github.com/mapbox/webgl-wind'; |
| 329 | + } |
| 330 | +}; |
| 331 | +gui.add(meta, '2016-11-20+h', 0, 48, 6).onFinishChange(updateWind); |
| 332 | +if (pxRatio !== 1) { |
| 333 | + gui.add(meta, 'retina resolution').onFinishChange(updateRetina); |
| 334 | +} |
| 335 | +gui.add(meta, 'github.com/mapbox/webgl-wind'); |
| 336 | +updateWind(0); |
| 337 | + |
| 338 | +function updateRetina() { |
| 339 | + var ratio = meta['retina resolution'] ? pxRatio : 1; |
| 340 | + canvas.width = canvas.clientWidth * ratio; |
| 341 | + canvas.height = canvas.clientHeight * ratio; |
| 342 | + wind.resize(); |
| 343 | +} |
| 344 | + |
| 345 | +getJSON('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_110m_coastline.geojson', function (data) { |
| 346 | + var canvas = document.getElementById('coastline'); |
| 347 | + canvas.width = canvas.clientWidth * pxRatio; |
| 348 | + canvas.height = canvas.clientHeight * pxRatio; |
| 349 | + |
| 350 | + var ctx = canvas.getContext('2d'); |
| 351 | + ctx.lineWidth = pxRatio; |
| 352 | + ctx.lineJoin = ctx.lineCap = 'round'; |
| 353 | + ctx.strokeStyle = 'white'; |
| 354 | + ctx.beginPath(); |
| 355 | + |
| 356 | + for (var i = 0; i < data.features.length; i++) { |
| 357 | + var line = data.features[i].geometry.coordinates; |
| 358 | + for (var j = 0; j < line.length; j++) { |
| 359 | + ctx[j ? 'lineTo' : 'moveTo']( |
| 360 | + (line[j][0] + 180) * canvas.width / 360, |
| 361 | + (-line[j][1] + 90) * canvas.height / 180); |
| 362 | + } |
| 363 | + } |
| 364 | + ctx.stroke(); |
| 365 | +}); |
| 366 | + |
| 367 | +function updateWind(name) { |
| 368 | + getJSON('wind/' + windFiles[name] + '.json', function (windData) { |
| 369 | + var windImage = new Image(); |
| 370 | + windData.image = windImage; |
| 371 | + windImage.src = 'wind/' + windFiles[name] + '.png'; |
| 372 | + windImage.onload = function () { |
| 373 | + wind.setWind(windData); |
| 374 | + }; |
| 375 | + }); |
| 376 | +} |
| 377 | + |
| 378 | +function getJSON(url, callback) { |
| 379 | + var xhr = new XMLHttpRequest(); |
| 380 | + xhr.responseType = 'json'; |
| 381 | + xhr.open('get', url, true); |
| 382 | + xhr.onload = function () { |
| 383 | + if (xhr.status >= 200 && xhr.status < 300) { |
| 384 | + callback(xhr.response); |
| 385 | + } else { |
| 386 | + throw new Error(xhr.statusText); |
| 387 | + } |
| 388 | + }; |
| 389 | + xhr.send(); |
| 390 | +} |
| 391 | + |
| 392 | +}))); |
0 commit comments