Skip to content

Instantly share code, notes, and snippets.

@AF7TI
Last active December 5, 2020 17:43
Show Gist options
  • Save AF7TI/35b7381c74df1349b28d93241281adc1 to your computer and use it in GitHub Desktop.
Save AF7TI/35b7381c74df1349b28d93241281adc1 to your computer and use it in GitHub Desktop.
JavaScript Service Worker Frame-by-Frame Image Animation and GIF Encoding

About

This code demonstrates practical use of JavaScript Service Workers to cache image files and generate a GIF

Caching images locally with a Service Worker enables higher framerate image animations than is possible with default browser behavior

Images are fetched from mountain webcams every minute and aged out after a day

Keypoints shows result of blob detection on diff of adjacent images - accomplished on backend using OpenCV

Online at animate.af7ti.com

// gif.js 0.2.0 - https://github.com/jnordberg/gif.js
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GIF=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){function EventEmitter(){this._events=this._events||{};this._maxListeners=this._maxListeners||undefined}module.exports=EventEmitter;EventEmitter.EventEmitter=EventEmitter;EventEmitter.prototype._events=undefined;EventEmitter.prototype._maxListeners=undefined;EventEmitter.defaultMaxListeners=10;EventEmitter.prototype.setMaxListeners=function(n){if(!isNumber(n)||n<0||isNaN(n))throw TypeError("n must be a positive number");this._maxListeners=n;return this};EventEmitter.prototype.emit=function(type){var er,handler,len,args,i,listeners;if(!this._events)this._events={};if(type==="error"){if(!this._events.error||isObject(this._events.error)&&!this._events.error.length){er=arguments[1];if(er instanceof Error){throw er}else{var err=new Error('Uncaught, unspecified "error" event. ('+er+")");err.context=er;throw err}}}handler=this._events[type];if(isUndefined(handler))return false;if(isFunction(handler)){switch(arguments.length){case 1:handler.call(this);break;case 2:handler.call(this,arguments[1]);break;case 3:handler.call(this,arguments[1],arguments[2]);break;default:args=Array.prototype.slice.call(arguments,1);handler.apply(this,args)}}else if(isObject(handler)){args=Array.prototype.slice.call(arguments,1);listeners=handler.slice();len=listeners.length;for(i=0;i<len;i++)listeners[i].apply(this,args)}return true};EventEmitter.prototype.addListener=function(type,listener){var m;if(!isFunction(listener))throw TypeError("listener must be a function");if(!this._events)this._events={};if(this._events.newListener)this.emit("newListener",type,isFunction(listener.listener)?listener.listener:listener);if(!this._events[type])this._events[type]=listener;else if(isObject(this._events[type]))this._events[type].push(listener);else this._events[type]=[this._events[type],listener];if(isObject(this._events[type])&&!this._events[type].warned){if(!isUndefined(this._maxListeners)){m=this._maxListeners}else{m=EventEmitter.defaultMaxListeners}if(m&&m>0&&this._events[type].length>m){this._events[type].warned=true;console.error("(node) warning: possible EventEmitter memory "+"leak detected. %d listeners added. "+"Use emitter.setMaxListeners() to increase limit.",this._events[type].length);if(typeof console.trace==="function"){console.trace()}}}return this};EventEmitter.prototype.on=EventEmitter.prototype.addListener;EventEmitter.prototype.once=function(type,listener){if(!isFunction(listener))throw TypeError("listener must be a function");var fired=false;function g(){this.removeListener(type,g);if(!fired){fired=true;listener.apply(this,arguments)}}g.listener=listener;this.on(type,g);return this};EventEmitter.prototype.removeListener=function(type,listener){var list,position,length,i;if(!isFunction(listener))throw TypeError("listener must be a function");if(!this._events||!this._events[type])return this;list=this._events[type];length=list.length;position=-1;if(list===listener||isFunction(list.listener)&&list.listener===listener){delete this._events[type];if(this._events.removeListener)this.emit("removeListener",type,listener)}else if(isObject(list)){for(i=length;i-- >0;){if(list[i]===listener||list[i].listener&&list[i].listener===listener){position=i;break}}if(position<0)return this;if(list.length===1){list.length=0;delete this._events[type]}else{list.splice(position,1)}if(this._events.removeListener)this.emit("removeListener",type,listener)}return this};EventEmitter.prototype.removeAllListeners=function(type){var key,listeners;if(!this._events)return this;if(!this._events.removeListener){if(arguments.length===0)this._events={};else if(this._events[type])delete this._events[type];return this}if(arguments.length===0){for(key in this._events){if(key==="removeListener")continue;this.removeAllListeners(key)}this.removeAllListeners("removeListener");this._events={};return this}listeners=this._events[type];if(isFunction(listeners)){this.removeListener(type,listeners)}else if(listeners){while(listeners.length)this.removeListener(type,listeners[listeners.length-1])}delete this._events[type];return this};EventEmitter.prototype.listeners=function(type){var ret;if(!this._events||!this._events[type])ret=[];else if(isFunction(this._events[type]))ret=[this._events[type]];else ret=this._events[type].slice();return ret};EventEmitter.prototype.listenerCount=function(type){if(this._events){var evlistener=this._events[type];if(isFunction(evlistener))return 1;else if(evlistener)return evlistener.length}return 0};EventEmitter.listenerCount=function(emitter,type){return emitter.listenerCount(type)};function isFunction(arg){return typeof arg==="function"}function isNumber(arg){return typeof arg==="number"}function isObject(arg){return typeof arg==="object"&&arg!==null}function isUndefined(arg){return arg===void 0}},{}],2:[function(require,module,exports){var UA,browser,mode,platform,ua;ua=navigator.userAgent.toLowerCase();platform=navigator.platform.toLowerCase();UA=ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/)||[null,"unknown",0];mode=UA[1]==="ie"&&document.documentMode;browser={name:UA[1]==="version"?UA[3]:UA[1],version:mode||parseFloat(UA[1]==="opera"&&UA[4]?UA[4]:UA[2]),platform:{name:ua.match(/ip(?:ad|od|hone)/)?"ios":(ua.match(/(?:webos|android)/)||platform.match(/mac|win|linux/)||["other"])[0]}};browser[browser.name]=true;browser[browser.name+parseInt(browser.version,10)]=true;browser.platform[browser.platform.name]=true;module.exports=browser},{}],3:[function(require,module,exports){var EventEmitter,GIF,browser,extend=function(child,parent){for(var key in parent){if(hasProp.call(parent,key))child[key]=parent[key]}function ctor(){this.constructor=child}ctor.prototype=parent.prototype;child.prototype=new ctor;child.__super__=parent.prototype;return child},hasProp={}.hasOwnProperty,indexOf=[].indexOf||function(item){for(var i=0,l=this.length;i<l;i++){if(i in this&&this[i]===item)return i}return-1},slice=[].slice;EventEmitter=require("events").EventEmitter;browser=require("./browser.coffee");GIF=function(superClass){var defaults,frameDefaults;extend(GIF,superClass);defaults={workerScript:"gif.worker.js",workers:2,repeat:0,background:"#fff",quality:10,width:null,height:null,transparent:null,debug:false,dither:false};frameDefaults={delay:500,copy:false};function GIF(options){var base,key,value;this.running=false;this.options={};this.frames=[];this.freeWorkers=[];this.activeWorkers=[];this.setOptions(options);for(key in defaults){value=defaults[key];if((base=this.options)[key]==null){base[key]=value}}}GIF.prototype.setOption=function(key,value){this.options[key]=value;if(this._canvas!=null&&(key==="width"||key==="height")){return this._canvas[key]=value}};GIF.prototype.setOptions=function(options){var key,results,value;results=[];for(key in options){if(!hasProp.call(options,key))continue;value=options[key];results.push(this.setOption(key,value))}return results};GIF.prototype.addFrame=function(image,options){var frame,key;if(options==null){options={}}frame={};frame.transparent=this.options.transparent;for(key in frameDefaults){frame[key]=options[key]||frameDefaults[key]}if(this.options.width==null){this.setOption("width",image.width)}if(this.options.height==null){this.setOption("height",image.height)}if(typeof ImageData!=="undefined"&&ImageData!==null&&image instanceof ImageData){frame.data=image.data}else if(typeof CanvasRenderingContext2D!=="undefined"&&CanvasRenderingContext2D!==null&&image instanceof CanvasRenderingContext2D||typeof WebGLRenderingContext!=="undefined"&&WebGLRenderingContext!==null&&image instanceof WebGLRenderingContext){if(options.copy){frame.data=this.getContextData(image)}else{frame.context=image}}else if(image.childNodes!=null){if(options.copy){frame.data=this.getImageData(image)}else{frame.image=image}}else{throw new Error("Invalid image")}return this.frames.push(frame)};GIF.prototype.render=function(){var i,j,numWorkers,ref;if(this.running){throw new Error("Already running")}if(this.options.width==null||this.options.height==null){throw new Error("Width and height must be set prior to rendering")}this.running=true;this.nextFrame=0;this.finishedFrames=0;this.imageParts=function(){var j,ref,results;results=[];for(i=j=0,ref=this.frames.length;0<=ref?j<ref:j>ref;i=0<=ref?++j:--j){results.push(null)}return results}.call(this);numWorkers=this.spawnWorkers();if(this.options.globalPalette===true){this.renderNextFrame()}else{for(i=j=0,ref=numWorkers;0<=ref?j<ref:j>ref;i=0<=ref?++j:--j){this.renderNextFrame()}}this.emit("start");return this.emit("progress",0)};GIF.prototype.abort=function(){var worker;while(true){worker=this.activeWorkers.shift();if(worker==null){break}this.log("killing active worker");worker.terminate()}this.running=false;return this.emit("abort")};GIF.prototype.spawnWorkers=function(){var j,numWorkers,ref,results;numWorkers=Math.min(this.options.workers,this.frames.length);(function(){results=[];for(var j=ref=this.freeWorkers.length;ref<=numWorkers?j<numWorkers:j>numWorkers;ref<=numWorkers?j++:j--){results.push(j)}return results}).apply(this).forEach(function(_this){return function(i){var worker;_this.log("spawning worker "+i);worker=new Worker(_this.options.workerScript);worker.onmessage=function(event){_this.activeWorkers.splice(_this.activeWorkers.indexOf(worker),1);_this.freeWorkers.push(worker);return _this.frameFinished(event.data)};return _this.freeWorkers.push(worker)}}(this));return numWorkers};GIF.prototype.frameFinished=function(frame){var i,j,ref;this.log("frame "+frame.index+" finished - "+this.activeWorkers.length+" active");this.finishedFrames++;this.emit("progress",this.finishedFrames/this.frames.length);this.imageParts[frame.index]=frame;if(this.options.globalPalette===true){this.options.globalPalette=frame.globalPalette;this.log("global palette analyzed");if(this.frames.length>2){for(i=j=1,ref=this.freeWorkers.length;1<=ref?j<ref:j>ref;i=1<=ref?++j:--j){this.renderNextFrame()}}}if(indexOf.call(this.imageParts,null)>=0){return this.renderNextFrame()}else{return this.finishRendering()}};GIF.prototype.finishRendering=function(){var data,frame,i,image,j,k,l,len,len1,len2,len3,offset,page,ref,ref1,ref2;len=0;ref=this.imageParts;for(j=0,len1=ref.length;j<len1;j++){frame=ref[j];len+=(frame.data.length-1)*frame.pageSize+frame.cursor}len+=frame.pageSize-frame.cursor;this.log("rendering finished - filesize "+Math.round(len/1e3)+"kb");data=new Uint8Array(len);offset=0;ref1=this.imageParts;for(k=0,len2=ref1.length;k<len2;k++){frame=ref1[k];ref2=frame.data;for(i=l=0,len3=ref2.length;l<len3;i=++l){page=ref2[i];data.set(page,offset);if(i===frame.data.length-1){offset+=frame.cursor}else{offset+=frame.pageSize}}}image=new Blob([data],{type:"image/gif"});return this.emit("finished",image,data)};GIF.prototype.renderNextFrame=function(){var frame,task,worker;if(this.freeWorkers.length===0){throw new Error("No free workers")}if(this.nextFrame>=this.frames.length){return}frame=this.frames[this.nextFrame++];worker=this.freeWorkers.shift();task=this.getTask(frame);this.log("starting frame "+(task.index+1)+" of "+this.frames.length);this.activeWorkers.push(worker);return worker.postMessage(task)};GIF.prototype.getContextData=function(ctx){return ctx.getImageData(0,0,this.options.width,this.options.height).data};GIF.prototype.getImageData=function(image){var ctx;if(this._canvas==null){this._canvas=document.createElement("canvas");this._canvas.width=this.options.width;this._canvas.height=this.options.height}ctx=this._canvas.getContext("2d");ctx.setFill=this.options.background;ctx.fillRect(0,0,this.options.width,this.options.height);ctx.drawImage(image,0,0);return this.getContextData(ctx)};GIF.prototype.getTask=function(frame){var index,task;index=this.frames.indexOf(frame);task={index:index,last:index===this.frames.length-1,delay:frame.delay,transparent:frame.transparent,width:this.options.width,height:this.options.height,quality:this.options.quality,dither:this.options.dither,globalPalette:this.options.globalPalette,repeat:this.options.repeat,canTransfer:browser.name==="chrome"};if(frame.data!=null){task.data=frame.data}else if(frame.context!=null){task.data=this.getContextData(frame.context)}else if(frame.image!=null){task.data=this.getImageData(frame.image)}else{throw new Error("Invalid frame")}return task};GIF.prototype.log=function(){var args;args=1<=arguments.length?slice.call(arguments,0):[];if(!this.options.debug){return}return console.log.apply(console,args)};return GIF}(EventEmitter);module.exports=GIF},{"./browser.coffee":2,events:1}]},{},[3])(3)});
//# sourceMappingURL=gif.js.map
// gif.worker.js 0.2.0 - https://github.com/jnordberg/gif.js
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){var NeuQuant=require("./TypedNeuQuant.js");var LZWEncoder=require("./LZWEncoder.js");function ByteArray(){this.page=-1;this.pages=[];this.newPage()}ByteArray.pageSize=4096;ByteArray.charMap={};for(var i=0;i<256;i++)ByteArray.charMap[i]=String.fromCharCode(i);ByteArray.prototype.newPage=function(){this.pages[++this.page]=new Uint8Array(ByteArray.pageSize);this.cursor=0};ByteArray.prototype.getData=function(){var rv="";for(var p=0;p<this.pages.length;p++){for(var i=0;i<ByteArray.pageSize;i++){rv+=ByteArray.charMap[this.pages[p][i]]}}return rv};ByteArray.prototype.writeByte=function(val){if(this.cursor>=ByteArray.pageSize)this.newPage();this.pages[this.page][this.cursor++]=val};ByteArray.prototype.writeUTFBytes=function(string){for(var l=string.length,i=0;i<l;i++)this.writeByte(string.charCodeAt(i))};ByteArray.prototype.writeBytes=function(array,offset,length){for(var l=length||array.length,i=offset||0;i<l;i++)this.writeByte(array[i])};function GIFEncoder(width,height){this.width=~~width;this.height=~~height;this.transparent=null;this.transIndex=0;this.repeat=-1;this.delay=0;this.image=null;this.pixels=null;this.indexedPixels=null;this.colorDepth=null;this.colorTab=null;this.neuQuant=null;this.usedEntry=new Array;this.palSize=7;this.dispose=-1;this.firstFrame=true;this.sample=10;this.dither=false;this.globalPalette=false;this.out=new ByteArray}GIFEncoder.prototype.setDelay=function(milliseconds){this.delay=Math.round(milliseconds/10)};GIFEncoder.prototype.setFrameRate=function(fps){this.delay=Math.round(100/fps)};GIFEncoder.prototype.setDispose=function(disposalCode){if(disposalCode>=0)this.dispose=disposalCode};GIFEncoder.prototype.setRepeat=function(repeat){this.repeat=repeat};GIFEncoder.prototype.setTransparent=function(color){this.transparent=color};GIFEncoder.prototype.addFrame=function(imageData){this.image=imageData;this.colorTab=this.globalPalette&&this.globalPalette.slice?this.globalPalette:null;this.getImagePixels();this.analyzePixels();if(this.globalPalette===true)this.globalPalette=this.colorTab;if(this.firstFrame){this.writeLSD();this.writePalette();if(this.repeat>=0){this.writeNetscapeExt()}}this.writeGraphicCtrlExt();this.writeImageDesc();if(!this.firstFrame&&!this.globalPalette)this.writePalette();this.writePixels();this.firstFrame=false};GIFEncoder.prototype.finish=function(){this.out.writeByte(59)};GIFEncoder.prototype.setQuality=function(quality){if(quality<1)quality=1;this.sample=quality};GIFEncoder.prototype.setDither=function(dither){if(dither===true)dither="FloydSteinberg";this.dither=dither};GIFEncoder.prototype.setGlobalPalette=function(palette){this.globalPalette=palette};GIFEncoder.prototype.getGlobalPalette=function(){return this.globalPalette&&this.globalPalette.slice&&this.globalPalette.slice(0)||this.globalPalette};GIFEncoder.prototype.writeHeader=function(){this.out.writeUTFBytes("GIF89a")};GIFEncoder.prototype.analyzePixels=function(){if(!this.colorTab){this.neuQuant=new NeuQuant(this.pixels,this.sample);this.neuQuant.buildColormap();this.colorTab=this.neuQuant.getColormap()}if(this.dither){this.ditherPixels(this.dither.replace("-serpentine",""),this.dither.match(/-serpentine/)!==null)}else{this.indexPixels()}this.pixels=null;this.colorDepth=8;this.palSize=7;if(this.transparent!==null){this.transIndex=this.findClosest(this.transparent,true)}};GIFEncoder.prototype.indexPixels=function(imgq){var nPix=this.pixels.length/3;this.indexedPixels=new Uint8Array(nPix);var k=0;for(var j=0;j<nPix;j++){var index=this.findClosestRGB(this.pixels[k++]&255,this.pixels[k++]&255,this.pixels[k++]&255);this.usedEntry[index]=true;this.indexedPixels[j]=index}};GIFEncoder.prototype.ditherPixels=function(kernel,serpentine){var kernels={FalseFloydSteinberg:[[3/8,1,0],[3/8,0,1],[2/8,1,1]],FloydSteinberg:[[7/16,1,0],[3/16,-1,1],[5/16,0,1],[1/16,1,1]],Stucki:[[8/42,1,0],[4/42,2,0],[2/42,-2,1],[4/42,-1,1],[8/42,0,1],[4/42,1,1],[2/42,2,1],[1/42,-2,2],[2/42,-1,2],[4/42,0,2],[2/42,1,2],[1/42,2,2]],Atkinson:[[1/8,1,0],[1/8,2,0],[1/8,-1,1],[1/8,0,1],[1/8,1,1],[1/8,0,2]]};if(!kernel||!kernels[kernel]){throw"Unknown dithering kernel: "+kernel}var ds=kernels[kernel];var index=0,height=this.height,width=this.width,data=this.pixels;var direction=serpentine?-1:1;this.indexedPixels=new Uint8Array(this.pixels.length/3);for(var y=0;y<height;y++){if(serpentine)direction=direction*-1;for(var x=direction==1?0:width-1,xend=direction==1?width:0;x!==xend;x+=direction){index=y*width+x;var idx=index*3;var r1=data[idx];var g1=data[idx+1];var b1=data[idx+2];idx=this.findClosestRGB(r1,g1,b1);this.usedEntry[idx]=true;this.indexedPixels[index]=idx;idx*=3;var r2=this.colorTab[idx];var g2=this.colorTab[idx+1];var b2=this.colorTab[idx+2];var er=r1-r2;var eg=g1-g2;var eb=b1-b2;for(var i=direction==1?0:ds.length-1,end=direction==1?ds.length:0;i!==end;i+=direction){var x1=ds[i][1];var y1=ds[i][2];if(x1+x>=0&&x1+x<width&&y1+y>=0&&y1+y<height){var d=ds[i][0];idx=index+x1+y1*width;idx*=3;data[idx]=Math.max(0,Math.min(255,data[idx]+er*d));data[idx+1]=Math.max(0,Math.min(255,data[idx+1]+eg*d));data[idx+2]=Math.max(0,Math.min(255,data[idx+2]+eb*d))}}}}};GIFEncoder.prototype.findClosest=function(c,used){return this.findClosestRGB((c&16711680)>>16,(c&65280)>>8,c&255,used)};GIFEncoder.prototype.findClosestRGB=function(r,g,b,used){if(this.colorTab===null)return-1;if(this.neuQuant&&!used){return this.neuQuant.lookupRGB(r,g,b)}var c=b|g<<8|r<<16;var minpos=0;var dmin=256*256*256;var len=this.colorTab.length;for(var i=0,index=0;i<len;index++){var dr=r-(this.colorTab[i++]&255);var dg=g-(this.colorTab[i++]&255);var db=b-(this.colorTab[i++]&255);var d=dr*dr+dg*dg+db*db;if((!used||this.usedEntry[index])&&d<dmin){dmin=d;minpos=index}}return minpos};GIFEncoder.prototype.getImagePixels=function(){var w=this.width;var h=this.height;this.pixels=new Uint8Array(w*h*3);var data=this.image;var srcPos=0;var count=0;for(var i=0;i<h;i++){for(var j=0;j<w;j++){this.pixels[count++]=data[srcPos++];this.pixels[count++]=data[srcPos++];this.pixels[count++]=data[srcPos++];srcPos++}}};GIFEncoder.prototype.writeGraphicCtrlExt=function(){this.out.writeByte(33);this.out.writeByte(249);this.out.writeByte(4);var transp,disp;if(this.transparent===null){transp=0;disp=0}else{transp=1;disp=2}if(this.dispose>=0){disp=dispose&7}disp<<=2;this.out.writeByte(0|disp|0|transp);this.writeShort(this.delay);this.out.writeByte(this.transIndex);this.out.writeByte(0)};GIFEncoder.prototype.writeImageDesc=function(){this.out.writeByte(44);this.writeShort(0);this.writeShort(0);this.writeShort(this.width);this.writeShort(this.height);if(this.firstFrame||this.globalPalette){this.out.writeByte(0)}else{this.out.writeByte(128|0|0|0|this.palSize)}};GIFEncoder.prototype.writeLSD=function(){this.writeShort(this.width);this.writeShort(this.height);this.out.writeByte(128|112|0|this.palSize);this.out.writeByte(0);this.out.writeByte(0)};GIFEncoder.prototype.writeNetscapeExt=function(){this.out.writeByte(33);this.out.writeByte(255);this.out.writeByte(11);this.out.writeUTFBytes("NETSCAPE2.0");this.out.writeByte(3);this.out.writeByte(1);this.writeShort(this.repeat);this.out.writeByte(0)};GIFEncoder.prototype.writePalette=function(){this.out.writeBytes(this.colorTab);var n=3*256-this.colorTab.length;for(var i=0;i<n;i++)this.out.writeByte(0)};GIFEncoder.prototype.writeShort=function(pValue){this.out.writeByte(pValue&255);this.out.writeByte(pValue>>8&255)};GIFEncoder.prototype.writePixels=function(){var enc=new LZWEncoder(this.width,this.height,this.indexedPixels,this.colorDepth);enc.encode(this.out)};GIFEncoder.prototype.stream=function(){return this.out};module.exports=GIFEncoder},{"./LZWEncoder.js":2,"./TypedNeuQuant.js":3}],2:[function(require,module,exports){var EOF=-1;var BITS=12;var HSIZE=5003;var masks=[0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535];function LZWEncoder(width,height,pixels,colorDepth){var initCodeSize=Math.max(2,colorDepth);var accum=new Uint8Array(256);var htab=new Int32Array(HSIZE);var codetab=new Int32Array(HSIZE);var cur_accum,cur_bits=0;var a_count;var free_ent=0;var maxcode;var clear_flg=false;var g_init_bits,ClearCode,EOFCode;function char_out(c,outs){accum[a_count++]=c;if(a_count>=254)flush_char(outs)}function cl_block(outs){cl_hash(HSIZE);free_ent=ClearCode+2;clear_flg=true;output(ClearCode,outs)}function cl_hash(hsize){for(var i=0;i<hsize;++i)htab[i]=-1}function compress(init_bits,outs){var fcode,c,i,ent,disp,hsize_reg,hshift;g_init_bits=init_bits;clear_flg=false;n_bits=g_init_bits;maxcode=MAXCODE(n_bits);ClearCode=1<<init_bits-1;EOFCode=ClearCode+1;free_ent=ClearCode+2;a_count=0;ent=nextPixel();hshift=0;for(fcode=HSIZE;fcode<65536;fcode*=2)++hshift;hshift=8-hshift;hsize_reg=HSIZE;cl_hash(hsize_reg);output(ClearCode,outs);outer_loop:while((c=nextPixel())!=EOF){fcode=(c<<BITS)+ent;i=c<<hshift^ent;if(htab[i]===fcode){ent=codetab[i];continue}else if(htab[i]>=0){disp=hsize_reg-i;if(i===0)disp=1;do{if((i-=disp)<0)i+=hsize_reg;if(htab[i]===fcode){ent=codetab[i];continue outer_loop}}while(htab[i]>=0)}output(ent,outs);ent=c;if(free_ent<1<<BITS){codetab[i]=free_ent++;htab[i]=fcode}else{cl_block(outs)}}output(ent,outs);output(EOFCode,outs)}function encode(outs){outs.writeByte(initCodeSize);remaining=width*height;curPixel=0;compress(initCodeSize+1,outs);outs.writeByte(0)}function flush_char(outs){if(a_count>0){outs.writeByte(a_count);outs.writeBytes(accum,0,a_count);a_count=0}}function MAXCODE(n_bits){return(1<<n_bits)-1}function nextPixel(){if(remaining===0)return EOF;--remaining;var pix=pixels[curPixel++];return pix&255}function output(code,outs){cur_accum&=masks[cur_bits];if(cur_bits>0)cur_accum|=code<<cur_bits;else cur_accum=code;cur_bits+=n_bits;while(cur_bits>=8){char_out(cur_accum&255,outs);cur_accum>>=8;cur_bits-=8}if(free_ent>maxcode||clear_flg){if(clear_flg){maxcode=MAXCODE(n_bits=g_init_bits);clear_flg=false}else{++n_bits;if(n_bits==BITS)maxcode=1<<BITS;else maxcode=MAXCODE(n_bits)}}if(code==EOFCode){while(cur_bits>0){char_out(cur_accum&255,outs);cur_accum>>=8;cur_bits-=8}flush_char(outs)}}this.encode=encode}module.exports=LZWEncoder},{}],3:[function(require,module,exports){var ncycles=100;var netsize=256;var maxnetpos=netsize-1;var netbiasshift=4;var intbiasshift=16;var intbias=1<<intbiasshift;var gammashift=10;var gamma=1<<gammashift;var betashift=10;var beta=intbias>>betashift;var betagamma=intbias<<gammashift-betashift;var initrad=netsize>>3;var radiusbiasshift=6;var radiusbias=1<<radiusbiasshift;var initradius=initrad*radiusbias;var radiusdec=30;var alphabiasshift=10;var initalpha=1<<alphabiasshift;var alphadec;var radbiasshift=8;var radbias=1<<radbiasshift;var alpharadbshift=alphabiasshift+radbiasshift;var alpharadbias=1<<alpharadbshift;var prime1=499;var prime2=491;var prime3=487;var prime4=503;var minpicturebytes=3*prime4;function NeuQuant(pixels,samplefac){var network;var netindex;var bias;var freq;var radpower;function init(){network=[];netindex=new Int32Array(256);bias=new Int32Array(netsize);freq=new Int32Array(netsize);radpower=new Int32Array(netsize>>3);var i,v;for(i=0;i<netsize;i++){v=(i<<netbiasshift+8)/netsize;network[i]=new Float64Array([v,v,v,0]);freq[i]=intbias/netsize;bias[i]=0}}function unbiasnet(){for(var i=0;i<netsize;i++){network[i][0]>>=netbiasshift;network[i][1]>>=netbiasshift;network[i][2]>>=netbiasshift;network[i][3]=i}}function altersingle(alpha,i,b,g,r){network[i][0]-=alpha*(network[i][0]-b)/initalpha;network[i][1]-=alpha*(network[i][1]-g)/initalpha;network[i][2]-=alpha*(network[i][2]-r)/initalpha}function alterneigh(radius,i,b,g,r){var lo=Math.abs(i-radius);var hi=Math.min(i+radius,netsize);var j=i+1;var k=i-1;var m=1;var p,a;while(j<hi||k>lo){a=radpower[m++];if(j<hi){p=network[j++];p[0]-=a*(p[0]-b)/alpharadbias;p[1]-=a*(p[1]-g)/alpharadbias;p[2]-=a*(p[2]-r)/alpharadbias}if(k>lo){p=network[k--];p[0]-=a*(p[0]-b)/alpharadbias;p[1]-=a*(p[1]-g)/alpharadbias;p[2]-=a*(p[2]-r)/alpharadbias}}}function contest(b,g,r){var bestd=~(1<<31);var bestbiasd=bestd;var bestpos=-1;var bestbiaspos=bestpos;var i,n,dist,biasdist,betafreq;for(i=0;i<netsize;i++){n=network[i];dist=Math.abs(n[0]-b)+Math.abs(n[1]-g)+Math.abs(n[2]-r);if(dist<bestd){bestd=dist;bestpos=i}biasdist=dist-(bias[i]>>intbiasshift-netbiasshift);if(biasdist<bestbiasd){bestbiasd=biasdist;bestbiaspos=i}betafreq=freq[i]>>betashift;freq[i]-=betafreq;bias[i]+=betafreq<<gammashift}freq[bestpos]+=beta;bias[bestpos]-=betagamma;return bestbiaspos}function inxbuild(){var i,j,p,q,smallpos,smallval,previouscol=0,startpos=0;for(i=0;i<netsize;i++){p=network[i];smallpos=i;smallval=p[1];for(j=i+1;j<netsize;j++){q=network[j];if(q[1]<smallval){smallpos=j;smallval=q[1]}}q=network[smallpos];if(i!=smallpos){j=q[0];q[0]=p[0];p[0]=j;j=q[1];q[1]=p[1];p[1]=j;j=q[2];q[2]=p[2];p[2]=j;j=q[3];q[3]=p[3];p[3]=j}if(smallval!=previouscol){netindex[previouscol]=startpos+i>>1;for(j=previouscol+1;j<smallval;j++)netindex[j]=i;previouscol=smallval;startpos=i}}netindex[previouscol]=startpos+maxnetpos>>1;for(j=previouscol+1;j<256;j++)netindex[j]=maxnetpos}function inxsearch(b,g,r){var a,p,dist;var bestd=1e3;var best=-1;var i=netindex[g];var j=i-1;while(i<netsize||j>=0){if(i<netsize){p=network[i];dist=p[1]-g;if(dist>=bestd)i=netsize;else{i++;if(dist<0)dist=-dist;a=p[0]-b;if(a<0)a=-a;dist+=a;if(dist<bestd){a=p[2]-r;if(a<0)a=-a;dist+=a;if(dist<bestd){bestd=dist;best=p[3]}}}}if(j>=0){p=network[j];dist=g-p[1];if(dist>=bestd)j=-1;else{j--;if(dist<0)dist=-dist;a=p[0]-b;if(a<0)a=-a;dist+=a;if(dist<bestd){a=p[2]-r;if(a<0)a=-a;dist+=a;if(dist<bestd){bestd=dist;best=p[3]}}}}}return best}function learn(){var i;var lengthcount=pixels.length;var alphadec=30+(samplefac-1)/3;var samplepixels=lengthcount/(3*samplefac);var delta=~~(samplepixels/ncycles);var alpha=initalpha;var radius=initradius;var rad=radius>>radiusbiasshift;if(rad<=1)rad=0;for(i=0;i<rad;i++)radpower[i]=alpha*((rad*rad-i*i)*radbias/(rad*rad));var step;if(lengthcount<minpicturebytes){samplefac=1;step=3}else if(lengthcount%prime1!==0){step=3*prime1}else if(lengthcount%prime2!==0){step=3*prime2}else if(lengthcount%prime3!==0){step=3*prime3}else{step=3*prime4}var b,g,r,j;var pix=0;i=0;while(i<samplepixels){b=(pixels[pix]&255)<<netbiasshift;g=(pixels[pix+1]&255)<<netbiasshift;r=(pixels[pix+2]&255)<<netbiasshift;j=contest(b,g,r);altersingle(alpha,j,b,g,r);if(rad!==0)alterneigh(rad,j,b,g,r);pix+=step;if(pix>=lengthcount)pix-=lengthcount;i++;if(delta===0)delta=1;if(i%delta===0){alpha-=alpha/alphadec;radius-=radius/radiusdec;rad=radius>>radiusbiasshift;if(rad<=1)rad=0;for(j=0;j<rad;j++)radpower[j]=alpha*((rad*rad-j*j)*radbias/(rad*rad))}}}function buildColormap(){init();learn();unbiasnet();inxbuild()}this.buildColormap=buildColormap;function getColormap(){var map=[];var index=[];for(var i=0;i<netsize;i++)index[network[i][3]]=i;var k=0;for(var l=0;l<netsize;l++){var j=index[l];map[k++]=network[j][0];map[k++]=network[j][1];map[k++]=network[j][2]}return map}this.getColormap=getColormap;this.lookupRGB=inxsearch}module.exports=NeuQuant},{}],4:[function(require,module,exports){var GIFEncoder,renderFrame;GIFEncoder=require("./GIFEncoder.js");renderFrame=function(frame){var encoder,page,stream,transfer;encoder=new GIFEncoder(frame.width,frame.height);if(frame.index===0){encoder.writeHeader()}else{encoder.firstFrame=false}encoder.setTransparent(frame.transparent);encoder.setRepeat(frame.repeat);encoder.setDelay(frame.delay);encoder.setQuality(frame.quality);encoder.setDither(frame.dither);encoder.setGlobalPalette(frame.globalPalette);encoder.addFrame(frame.data);if(frame.last){encoder.finish()}if(frame.globalPalette===true){frame.globalPalette=encoder.getGlobalPalette()}stream=encoder.stream();frame.data=stream.pages;frame.cursor=stream.cursor;frame.pageSize=stream.constructor.pageSize;if(frame.canTransfer){transfer=function(){var i,len,ref,results;ref=frame.data;results=[];for(i=0,len=ref.length;i<len;i++){page=ref[i];results.push(page.buffer)}return results}();return self.postMessage(frame,transfer)}else{return self.postMessage(frame)}};self.onmessage=function(event){return renderFrame(event.data)}},{"./GIFEncoder.js":1}]},{},[4]);
//# sourceMappingURL=gif.worker.js.map
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="manifest" href="/manifest.webmanifest" />
<script src="gif.js?v=3"></script>
<style>
body {
text-align: center;
font: .8rem Inconsolata, monospace;
margin: 0px;
padding: 0px;
}
#animation {
height: 84vh;
}
#animation img
{
max-width: 100%;
max-height: 100%;
height: 100%;
margin: auto;
display: block;
}
body {background-color: #A9A9A9;
margin-top: 1px;
margin-bottom: 1px;
}
.dark-mode {
background-color: black;
color: white;
}
#elem_frame_rate, #elem_frames_to_skip {
width: 2em;
}
#rangeText{
position: relative;
bottom: 7px;
}
#info{
position: relative;
width: 150px;
display: inline-block;
}
#bottom{
display: block;
}
.container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.left {
float:left;
display: inline-block;
}
.right {
float:right;
display: inline-block;
}
</style>
</head>
<body>
<div id="animation">
<img id = 'image'/>
</div>
<div id=top><div id="info"></div>
<div style="display: inline-block;">
<input type="radio" id="radio-latest" name="radio-display" value="latest"
checked onclick="setDisplayMode('latest');">
<label for="latest">Latest</label>
</div>
<div style="display: inline-block;">
<input type="radio" id="radio-display" name="radio-display" value="query" onclick="elem_display_changed_to_query();">
<label for="query">Date Range</label>
</div>
</div>
<div id="datediv" style="display: hidden;">
<label for="start-time">Start</label>
<input type="datetime-local" id="start-time"
name="start-time" onchange="query_start_date = this.value">
<label for="end-time">End</label>
<input type="datetime-local" id="end-time"
name="end-time" onchange="query_end_date = this.value">
</div>
<div id="rangediv">
<input type="range" id="range" name="range"
min="0" max="5" oninput="range_changed(parseInt(this.value))" >
<label id="rangeText"></label>
</div>
<div id="bottom">
<select id="elem_image_type" name="elem_image_type" onchange=elem_image_type_changed(this.value)>
<option>keypoints</option>
<option selected>objectKey</option>
</select>
<select id="elem_station" name="elem_station" onchange=elem_station_changed(this.value)>
<option>palmer</option>
<option selected>talkeetna</option>
<option>paradise</option>
<option>bartlett</option>
</select>
<select id="elem_items_to_show" name="elem_items_to_show" onchange=elem_items_to_show_changed(this.value)>
<option selected>5</option>
<option>10</option>
<option>25</option>
<option>50</option>
<option>100</option>
<option>200</option>
<option>300</option>
</select>
<input type="button" value="OK" onclick="okClicked();" />
<input type="checkbox" id="loop" name="loop" onclick="loop=!loop;">
<label for="loop">Loop</label>
<div class="container">
<div class="left"><fieldset>
<input type="number" placeholder="6" step=".1" min="0" max="24" value="6" id="elem_frame_rate" onchange=elem_frame_rate_changed(this.value)>
<label id="">frames/sec</label>
</fieldset>
</div>
<div class="right"><fieldset>
<input type="number" placeholder="1" step="1" min="1" max="60" value="1" id="elem_frames_to_skip" onchange=elem_frames_to_skip_changed(this.value)>
<label id="">mins/frame</label>
</fieldset>
</div>
<input type="button" name="gif_button" value="GIF" onclick="gifClicked();" />
<input type="button" name="theme_button" value="&#128262;" onclick="toggleTheme();" />
</div>
</div>
<script>
const url_pfx = 'https://dvfvh7sumgfb0.cloudfront.net/';
const image_types = ['objectKey', 'keypoints'];
const stations_url = 'https://api.af7ti.com/peakog';
var data;
var data_frames;
var station;
var stations;
var image_type;
var loop=false;
var items_to_show = document.getElementById("elem_items_to_show").value;
var i = parseInt(items_to_show);
var frameRate = 6; //frames per second
var frames_to_skip = 1;
var displayMode; //strings 'latest' or 'query' (date range)
function toggleTheme() {
var element = document.body;
element.classList.toggle("dark-mode");
}
function elem_frames_to_skip_changed(val) {
frames_to_skip = Number(val);
}
function elem_display_changed_to_query() {
setDisplayMode('query');
document.getElementById('bottom').scrollIntoView();
}
function elem_frame_rate_changed(val) {
frameRate = Number(document.getElementById("elem_frame_rate").value);
clearInterval(interval);
}
function updateRange(val) {
console.log(`updateRange: val is ${val}`);
document.getElementById("range").value = val;
i = val;
range_changed(val);
}
function update_elem_image_type(operator) {
//console.log(`update_elem_image_type: val is ${operator}`);
let idx = image_types.indexOf(image_type);
if (operator == '+') {
console.log(`update_elem_image_type: incrementing`);
idx < image_types.length -1 ? idx = idx + 1 : idx = 0;
}
if (operator == '-') {
console.log(`update_elem_image_type: decrementing`);
idx >= 1 ? idx = idx - 1 : idx = image_types.length - 1;
}
val = image_types[idx];
console.log(`update_elem_image_type val is ${val}`);
document.getElementById("elem_image_type").value = val;
elem_image_type_changed(val);
}
function updateLabel(i) {
document.getElementById("rangeText").innerHTML = new Date(data[i].updated).toUTCString();
document.getElementById("rangeText").innerHTML = applyTimeOffset(station, data[i].updated)
}
function range_changed(val) {
//console.log(`range_changed: val is ${val}`);
loop = false;
clearInterval(interval);
document.getElementById("loop").checked = false;
i = val;
changeImage(val);
updateLabel(val);
document.getElementById("info").innerHTML = getWxText(i);
}
function elem_items_to_show_changed(val) {
console.log(`elem_items_to_show_changed fired`);
items_to_show = val;
setRangeMax();
update();
}
function elem_image_type_changed(val) {
console.log(`elem_image_type_changed: val is ${val}`);
clearInterval(interval);
let idx = image_types.indexOf(image_type);
console.log(`elem_image_type_changed: idx is ${idx}`);
image_type = val;
console.log(`elem_image_type_changed: image_type is ${image_type}`);
console.log(`elem_image_type_changed: i is ${i}`);
changeImage(i);
if (loop === true) fetch_loop();
}
function elem_station_changed(val) {
clearInterval(interval);
station = val;
initializeDates();
//getdata();
update();
}
function setRangeMax() {
document.getElementById("range").max = items_to_show -1;
}
function startAnimation(data) {
typeof interval != "undefined" ? clearInterval(interval) : '';
//let data_frames = data.map(d => d[image_type]); //;
//console.log(`startAnimation: i is ${i}`);
var frameCount = data_frames.length;
i = 0;
interval = setInterval(function (d) {
if (i > frameCount-1) {
clearInterval(interval);
if (loop == false) return;
if (loop == true) startAnimation(data);
}
if (i < frameCount) {
document.getElementById("image").src = `${url_pfx}${data_frames[i]}`;
document.getElementById("range").value = i;
document.getElementById("info").innerHTML = getWxText(i);
updateLabel(i);
if (i < frameCount ) i = i + 1;
if (i == frameCount) return;
}
}, 1000/frameRate);
}
async function getdata() {
all_data = [];
data = [];
json = [];
all_data.length = 0;
data.length = 0;
json.length = 0;
if (displayMode == 'latest') url=`https://api.af7ti.com/peakog/query?stationId=${station}&limit=${items_to_show}`;
if (displayMode == 'query') url=`https://api.af7ti.com/peakog/search-between?stationId=${station}&start=${applyTimeOffsetPos(station, query_start_date)}&end=${applyTimeOffsetPos(station,query_end_date)}`;
//console.log(`getdata displayMode is: ${displayMode} url is ${url}`);
fetch(url)
.then(response => response.json())
.then(json => json.reverse())
.then(all_data => {
data = skipFrames(all_data.slice());
document.getElementById("range").max = data.length -1;
fetch_loop()
})
//.then(console.log('getdata done'));
.catch((error) => {
console.log(`getdata error ${error}`);
});
}
function skipFrames(arr) {
let result = arr.slice().filter(function (value, index, ar) {
return (index % frames_to_skip == 0);
} );
return result;
}
function changeImage(val) {
document.getElementById("image").src = `${url_pfx}${data.map(d => d[image_type])[val]}`;
}
async function fetch_loop(i=0) {
if (data.length == 0) return;
//console.log(`fetch_loop: i is ${i}`);
data_frames = data.map(d => d[image_type]);
let l = data.length;
let url = url_pfx + data_frames[i];
//console.log(`fetch_loop start ${i} url is ${url} data length is ${data.length}`);
if (i+1 < l) document.getElementById("info").innerHTML = `Loading ${i}/${l}`;
if (i+1 == l) document.getElementById("info").innerHTML = ``;
if (typeof i == "undefined") return;
fetch(url)
.then(response => response.blob())
//.then(response => console.log(`${i} done`))
.then(response => {
if (i == l -1) {
//sleep(1000);
startAnimation(data);
return;
}
fetch_loop(i+1)
})
.catch((error) => {
console.log(`fetch_loop error ${error}`);
if (i == l -1) {
//sleep(1000);
startAnimation(data);
return;
}
fetch_loop(i+1)
});
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
document.onkeydown = checkKey;
function checkKey(e) {
//make sure nothing like date picker is in focus
if (document.activeElement.tagName != "BODY") return;
e = e || window.event;
if (e.keyCode === 38) {
// up arrow
update_elem_image_type('+')
}
else if (e.keyCode === 40) {
// down arrow
update_elem_image_type('-')
}
else if (e.keyCode === 37) {
// left arrow
i >= 1 ? i = i-1 : i = data.length - 1;
updateRange(i);
}
else if (e.keyCode === 39) {
// right arrow
i < data.length -1 ? i = i+1 : i = 0;
updateRange(i);
}
}
function getdates(){
query_start_date = document.getElementById("start-time").value;
query_end_date = document.getElementById("end-time").value;
}
function update() {
clearInterval(interval);
getdates();
getdata();
}
function setDisplayMode(val){
clearInterval(interval);
console.log(`setDisplayMode clicked: val is ${val}`);
displayMode = val;
if (val == 'latest'){
document.getElementById("datediv").style.display="none";
document.getElementById("elem_items_to_show").style.display="";
items_to_show = document.getElementById("elem_items_to_show").value;
}
if (val == 'query'){
document.getElementById("datediv").style.display="";
document.getElementById("elem_items_to_show").style.display="none";
}
}
async function start() {
loadStations()
.then(res => initializeDates());
document.getElementById("datediv").style.display="none";
displayMode = 'latest';
station = 'talkeetna';
image_type = 'objectKey';
setRangeMax();
getdata();
//initializeDates();
}
function okClicked() {
console.log(`ok clicked`);
update();
}
function gifClicked() {
console.log(`gif clicked`);
newgif();
}
function initializeDates() {
initialstart = new Date(new Date().getTime() - (1 * 60 * 60 * 1000)).toISOString().slice(0,19);
initialend = new Date(new Date().getTime()).toISOString().slice(0,19)
initialstart = applyTimeOffset(station, initialstart);
initialend = applyTimeOffset(station, initialend);
document.getElementById('start-time').value = initialstart;
document.getElementById('end-time').value = initialend;
//document.getElementById('end-time').max = new Date(Date.now()).getTime();
document.getElementById('start-time').min = new Date(new Date().getTime() - (25 * 60 * 60 * 1000)).toISOString().slice(0,19);
}
async function loadStations() {
let response = await fetch(stations_url);
stations = await response.json();
return stations;
}
function calcTimeOffset(val) {
let result = parseInt(stations.find(d => d.stationId == val).timezone);
return result;
}
function applyTimeOffset(station, val){ //given a station and gmt time return local time
let offset = Math.abs(calcTimeOffset(station)) + new Date().getTimezoneOffset()/60;
let dateA = new Date(val).getTime()
let result = new Date(dateA - (offset * 60 * 60 * 1000)).toISOString().slice(0,19);
return result;
}
function applyTimeOffsetPos(station, val){ //given a station and gmt time return local time
let offset = Math.abs(calcTimeOffset(station)) - new Date().getTimezoneOffset()/60;
let dateA = new Date(val).getTime()
let result = new Date(dateA + (offset * 60 * 60 * 1000)).toISOString().slice(0,19);
return result;
}
function getWxText(val) {
//console.log(`getWxText: val is ${val}`);
temp = "";
wind_dir = "";
wind_speed = "";
fractalIndex = "";
if (!isNaN(data[val].wx_temperature)) temp = `${Math.round(data[val].wx_temperature * 9/5 + 32 )}°F`;
if (!isNaN(Number(data[val].wx_windDirection))) wind_dir = windDirection(data[val].wx_windDirection);
if (!isNaN(data[val].wx_windSpeed)) wind_speed = `${Math.round(data[val].wx_windSpeed * 0.621371)}mph`;
if (!isNaN(data[val].fractalIndex)) fractalIndex = `${data[val].fractalIndex}`;
//typeof wind_dir == "undefined" ? wind_dir = "" : "";
//if (isNaN(wind_speed)) wind_speed = "";
//console.log(`temp is ${temp} wind_dir ${wind_dir} is wind_speed is ${wind_speed} `)
return `${temp} ${wind_dir} ${wind_speed} ${fractalIndex}`;
}
function windDirection(val) {
if (typeof val == "undefined" || val == "None") result = "";
if (0.1 <= val && val <= 22) result = '↓';
if (22 <= val && val <= 67) result = '↙';
if (67 <= val && val <= 107) result = '←';
if (107 <= val && val <= 147) result = '↖';
if (147 <= val && val <= 190) result = '↑';
if (190 <= val && val <= 230) result = '↗';
if (230 <= val && val <= 280) result = '→';
if (280 <= val && val <= 320) result = '↘';
if (320 <= val && val <= 360) result = '↓';
if (val == 0) result = '-';
if (typeof result == "undefined" ) result = "";
return result;
}
function imagesizes() {
let w = document.getElementById('image').naturalWidth;
let h = document.getElementById('image').naturalHeight;
return [w,h];
};
appConfig = {
quality: 50
};
async function newgif() {
images = data_frames.map(d => url_pfx + d);
gif_frames = [];
gif = new GIF({
//debug: true,
quality: appConfig.quality,
workers: 4,
//delay: 1000/frameRate
width: imagesizes()[0],
height: imagesizes()[1]
});
gif.on('progress', function(p) {
document.getElementById("info").innerHTML = "GIF " + Math.round(p * 100) + "%" + " Complete";
});
gif.on('finished', function(blob) {
window.open(URL.createObjectURL(blob));
});
images.forEach(function(d) {
let img = new Image();
img.onload=function(){
}
img.crossOrigin = "anonymous";
img.src = d;
gif_frames.push(img);
});
gif_frames.forEach(d => gif.addFrame(d, {delay: 1000/frameRate}));
await sleep(500);
try { gif.render(); }
catch(error) {
console.log(error);
}
}
start();
//Registering your worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./serviceworker.js', {scope: '/'})
.then((reg) => {
// registration worked
console.log('Registration succeeded. Scope is ' + reg.scope);
}).catch((error) => {
// registration failed
console.log('Registration failed with ' + error);
});
}
</script>
</body>
</html>
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v1').then(function(cache) {
console.log('Service worker installation OK');
})
);
});
self.addEventListener('fetch', function(event) {
//console.log('Handling fetch event for', event.request.url);
event.respondWith(
caches.match(event.request).then(function(response) {
//just fetch dont clone api requests
if ( event.request.url.includes('api') ) {
//console.log('not cloning ', event.request.url);
return fetch(event.request).then(function (response) {
return response;
})
}
//just fetch dont clone html requests
if ( event.request.url.includes('.html') ) {
//console.log('not cloning ', event.request.url);
return fetch(event.request).then(function (response) {
return response;
})
}
//just fetch dont clone html requests
if ( event.request.url.includes('animate.af7ti.com') ) {
//console.log('not cloning ', event.request.url);
return fetch(event.request).then(function (response) {
return response;
})
}
if (response !== undefined) {
//console.log('fetch event listener: in cache');
return response;
} else {
//console.log('fetch event listener: cloning - not in cache');
return fetch(event.request).then(function (response) {
// response may be used only once
// we need to save clone to put one copy in cache
// and serve second one
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function (e) {
console.log(`fetch event listener: error ${e}`);
});
}
}));
});
//importScripts('https://animate.af7ti.com/animate.js');
self.addEventListener('periodicsync', (event) => {
console.log(`in periodicsync`);
getdata();
if (event.tag === 'content-sync') {
//console.log(`in periodicsync`);
// See the "Think before you sync" section for
// checks you could perform before syncing.
event.waitUntil(syncContent());
}
// Other logic for different tags as needed.
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment