Skip to content

Instantly share code, notes, and snippets.

@ilblog
Created May 18, 2018 07:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ilblog/11863f97f25a4c2478649f5b618fcc16 to your computer and use it in GitHub Desktop.
Save ilblog/11863f97f25a4c2478649f5b618fcc16 to your computer and use it in GitHub Desktop.
iOS crash code
/*! */
W.define('airgram',['imageMaker','overlays'], (iMaker,overlays) => W.ImageMaker.instance({
colors: iMaker.temp.fillColors,
altN: ['1000h', '950h','925h','900h','850h','800h','700h','600h','500h','400h','300h','200h','150h'],
alt: [ 1000.0, 950.0, 925.0, 900.0, 850.0, 800.0, 700.0, 600.0, 500.0, 400.0, 300.0, 200.0, 150.0 ],
step: 5.0, // isotherm step;
K0: 273.15, // 0°C in Kelvin
KMin: 193, // minimum temp value for scale [K]
colorBg: 'rgba(255,255,255,1)', // airgram background color
colorWind: 'rgba(0, 0, 0, 0.90)', // wind signs color
colorNoWind: 'rgba(0, 0, 0, 0.20)', // NO wind signs color
colorTemp: 'rgba(255, 255, 255, 0.8)', // temp values color
colorLegend: 'rgba(32, 32, 32, 0.50)', // text on border
colorHLines: 'rgba(0, 0, 0, 0.20)', // text on border
// Mouse movement properties
/* mouseHook: false,
el: null,
isShown: false,
_init: function() {
this.boundedMouse = this.onMouseMove.bind( this )
this.debouncedDisplay = _.debounce( this.display.bind(this), 200 )
},
onMouseMove: function(e) {
if( this.isShown ) this.hideInfo()
if( e && e.layerX ) {
this.debouncedDisplay( e.layerX, e.layerY - canvasOffset )
}
console.log( 'offsets: ' + this.el.offsetLeft + ', ' + this.el.offsetTop,
'page: ' + e.pageX + ',' + e.pageY,
'layer: ' + e.layerX + ',' + e.layerY,
'client: ' + e.clientX + ',' + e.clientY,
'screen: ' + e.screenX + ',' + e.screenY)
},
hideInfo: function() {
this.show = false
console.log('HIDE')
},
display: function(x,y) {
this.show = true
var res = this.airgramPoint(x,y)
console.log(x,y,res)
},
addMouseHook: function(el) {
if(!el) return;
this.mouseHook = true;
this.el = el;
this.el.addEventListener('mousemove',this.boundedMouse,true)
},
removeMouseHook: function() {
this.mouseHook = false;
this.el.removeEventListener('mousemove',this.boundedMouse,true)
},*/
lerpColor256( a, b, f ) {
var n = a.length;
var c = [];
for( var i = 0; i < n; ++i ) {
c.push( Math.min( Math.max( 0, Math.round( a[i] + f * (b[i] - a[i]) ) ), 255) );
}
return c;
},
prepareStepColors() {
var a = []
, step = this.step
, fw = 0.3
, n0 = 1 + Math.floor( (this.K0 - this.KMin) / step )
, t = this.K0 - ( step * n0 ) // initial temperature value for table
, t1 = 328.0 // target temp value
, gradient = this.colors
, n = gradient.length
this.tmin_ = t
//t += 2.5
t += 0.5 * step
while( t < t1 ) {
for (let j = 1; j < n; ++j) {
if( t < gradient[j][0] ) {
let f = ( t - gradient[j - 1][0] ) / ( gradient[j][0] - gradient[j - 1][0] )
a.push( this.lerpColor256( this.lerpColor256( gradient[j - 1][1], gradient[j][1], f), [160, 160, 160, 255], fw ) )
break
}
}
t += step
}
this.n0_ = n0 - 1
this.n1_ = n0
this.steps_ = a
},
// @params .. { .., airSegWidth, airHeight, airBorders, ... }
// @json2 .. meteogram API result
// @heigth .. airgram height
// @params.tdWidth .. segment size in normal pixels (NOT scaled by pixel ratio)
render( json2 ) {
if( ! this.steps_ ) this.prepareStepColors();
// var height = this.h * this.canvasRatio
// , segW = this.tdWidth * this.canvasRatio
var height = this.getPixelRatioAdjustedSize( this.h )
, segW = this.getPixelRatioAdjustedSize( this.tdWidth )
var data = json2.data
, nx = data["temp-1000h"].length
, ny = this.altN.length
, n = nx * ny
, tempArray = new Float32Array( n ) // 2D array of temp values
, p = 0
for( let j = 0; j < ny; ++j ) {
let nameT = 'temp-' + this.altN[ j ]
, rowT = data[ nameT ];
for( let i = 0; i < nx; ++i ) {
tempArray[ p++ ] = Number( rowT[i] );
}
}
// render temp gradient and return height lookup table for point function
var hLut = this.renderTemp( nx, ny, segW, height, tempArray );
// render legend before wind marks
this.legend( nx, ny, this.tdWidth, this.h );
// WIND
var windArray = new Float32Array( n + n ) // array of wind [U,V] values
p = 0
for( let j = 0; j < ny; ++j ) {
let name1 = 'wind_u-' + this.altN[ j ]
, name2 = 'wind_v-' + this.altN[ j ]
, row1 = data[ name1 ]
, row2 = data[ name2 ];
for( let i = 0; i < nx; ++i ) {
windArray[ p++ ] = Number( row1[ i ] );
windArray[ p++ ] = Number( row2[ i ] );
}
}
this.renderWind( nx, ny, this.tdWidth, this.h, windArray );
// store data for point function
this.preparePData( nx, ny, this.tdWidth, this.w, this.h, hLut, data, tempArray, windArray );
return this
},
// modify pixels on edges
// @a .. pixel values array
edge( a, d, i ) {
// get core
var m = [ a[i-1-d], a[i-d], a[i+1-d],
a[i-1], a[i], a[i+1],
a[i-1+d], a[i+d], a[i+1+d] ];
var f = 0.0;
if( m[1] != m[7] ) f += 2.0;
if( m[3] != m[5] ) f += 2.0;
if( m[0] != m[6] ) f += 1.0;
if( m[2] != m[8] ) f += 1.0;
if( m[0] != m[2] ) f += 1.0;
if( m[6] != m[8] ) f += 1.0;
var b1 = false, b2 = false;
for( var j = 0; j < m.length; j++ ) {
if( m[j] == this.n0_ ) b1 = true;
if( m[j] == this.n1_ ) b2 = true;
}
if(b1 && b2) {
f = Math.min(2.0 + f * 48.0, 128.0);
} else {
f = Math.max(1.0 - f * 0.03, 0.05);
}
return f;
},
renderTemp( nx, ny, sx, h, a ) {
var ctx = this.ctx
, tAdd = -this.tmin_
, tMul = 1.0 / this.step
var w = nx * sx
, imageData = ctx.createImageData( w, h )
, data = imageData.data
, size = w * h
, dst = new Uint8Array( size )
, hAdd = -150.0
, hMul = (h - 1) / ( 1000.0 - 150.0 )
, y1 = h
, y0
, dx2 = (sx + 1) >> 1
, hli = h + h
, hLut = new Int32Array( hli );
for (let j = 0; j < ny - 1; ++j) {
y0 = y1
y1 = Math.round( (this.alt[j + 1] + hAdd) * hMul )
let j0 = nx * this.clamp0X(j + 2, ny)
, j1 = nx * this.clamp0X(j + 1, ny)
, j2 = nx * this.clamp0X(j + 0, ny)
, j3 = nx * this.clamp0X(j - 1, ny)
, x0 = 0
, x1 = dx2
, dy = y0 - y1
, fy = 1.0 / dy
for (let i = 0; i < nx + 1; ++i) {
let i0 = this.clamp0X(i - 2, nx)
, i1 = this.clamp0X(i - 1, nx)
, i2 = this.clamp0X(i + 0, nx)
, i3 = this.clamp0X(i + 1, nx)
, m = [ a[j0 + i0], a[j0 + i1], a[j0 + i2], a[j0 + i3],
a[j1 + i0], a[j1 + i1], a[j1 + i2], a[j1 + i3],
a[j2 + i0], a[j2 + i1], a[j2 + i2], a[j2 + i3],
a[j3 + i0], a[j3 + i1], a[j3 + i2], a[j3 + i3]
]
, dx = x1 - x0
, fx = 1.0 / dx
, offs = (y1 * w + x0) // top-left corner of renctangle to fill
for (let y = 0; y < dy; ++y) {
if( i == 0 ) { // prepare height LUT for point data
hLut[ --hli ] = j;
hLut[ --hli ] = Math.round( y * fy * 10000 );
//hLut[ --hli ] = [j, y * fy]
}
let o = offs + y * w; // * 4);
for (let x = 0; x < dx && o < dst.length; ++x, o++) {
let c =this.bicubicFiltering( m, fx * x, fy * ( y + 0 ) )
dst[ o ] = Math.floor( (c + tAdd) * tMul )
}
}
x0 = x1
x1 += sx
if( x1 > w ) {
x1 = w
}
}
}
const len = ( data.length - 4 ) // last item
var p = 0
//
// INSIDE THIS LOOPS IT CRASHES!!!!!!! ALWAYS ON DIFFERENT INDEXes
//
for( let j = 0; j < h; j++ ) {
if(p >= len) break
for( let i = 0; i < w; i++ ) {
if(p >= len) break
let o = j * w + i
var br = 1.0
if( (j > 0) && (j < (h - 1)) && (i > 0) && (i < w - 1) ) {
br = this.edge( dst, w, o )
}
var rgb = this.steps_[ dst[o] ] || [0, 0, 0]
if( br < 0.99 ) {
data[p++] = Math.round(rgb[0] * br)
data[p++] = Math.round(rgb[1] * br)
data[p++] = Math.round(rgb[2] * br)
}
else if( br > 2.0 ) {
data[p++] = Math.min(rgb[0] + br, 255)
data[p++] = Math.min(rgb[1] + br, 255)
data[p++] = Math.min(rgb[2] + br, 255)
}
else {
data[p++] = rgb[0]
data[p++] = rgb[1]
data[p++] = rgb[2]
}
data[p++] = 255;
}
}
//
// Batched version (did not helped)
//
/* const maxBatchTime = 50 // Max batch time in ms
const batch = () => {
const limit = Date.now() + maxBatchTime
while( j < h ) {
j++
if(p >= len) break
for( let i = 0; i < w; i++ ) {
if(p >= len) break
let o = j * w + i
var br = 1.0
if( (j > 0) && (j < (h - 1)) && (i > 0) && (i < w - 1) ) {
br = this.edge( dst, w, o )
}
var rgb = this.steps_[ dst[o] ] || [0, 0, 0]
if( br < 0.99 ) {
data[p++] = Math.round(rgb[0] * br)
data[p++] = Math.round(rgb[1] * br)
data[p++] = Math.round(rgb[2] * br)
}
else if( br > 2.0 ) {
data[p++] = Math.min(rgb[0] + br, 255)
data[p++] = Math.min(rgb[1] + br, 255)
data[p++] = Math.min(rgb[2] + br, 255)
}
else {
data[p++] = rgb[0]
data[p++] = rgb[1]
data[p++] = rgb[2]
}
data[p++] = 255;
}
if( !(j % 10) && Date.now() > limit ) {
ctx.putImageData( imageData, 0, 0 );
setTimeout( batch, 250)
return
}
}
ctx.putImageData( imageData, 0, 0 );
}
batch()*/
// temp values
var
nx0 = 2
, nxStep = 4
, v0, v1
let x = sx * nx0;
let dy = 12;
ctx.fillStyle = this.colorTemp;
ctx.textAlign = 'center';
ctx.font = '8px Verdana';
while( nx0 < nx ) {
let i = x;
y1 = 0;
for( let y = 0; y < h - dy / 2; y++ ) {
v0 = dst[i];
if( v1 >= 0 ) {
if( v1 != v0 ) {
if( y - y1 > dy ) {
// render temp value at edge position
ctx.fillText(
this.convertTemp( Math.round( this.tmin_ + Math.max(v0, v1) * this.step ) ) + '°',
x / this.canvasRatio,
( y + dy * 0.35 ) / this.canvasRatio
);
// ctx.fillText( Math.round( this.tmin_ + Math.max(v0, v1) * this.step - this.K0 ) + '°',
// x + borders[0], y + borders[2] + dy * 0.35 ); // Celsius only variant
y1 = y;
}
}
}
v1 = v0;
i += w;
}
nx0 += nxStep;
x += nxStep * sx;
}
return hLut;
},
// s ..size
windMark( ctx, x, y, u, v, s ) {
u = -u;
var d = Math.sqrt( u * u + v * v )
, knotsI = Math.round( d * 0.388768 ) // conversion from m/s to 5-knots multimple
, s1 = s * 0.17
, s2 = s * 0.35;
if( knotsI > 0 ) {
let u0 = u / d
, v0 = v / d
, x1 = u0 * s1 * 0.5 - v0 * s2
, y1 = v0 * s1 * 0.5 + u0 * s2
, dx = -s1 * u0
, dy = -s1 * v0;
ctx.strokeStyle = ctx.fillStyle = this.colorWind;
ctx.beginPath();
ctx.moveTo(x, y);
x += u0 * s;
y += v0 * s;
ctx.lineTo(x, y);
if( knotsI == 1 ) { // shift for 5 kn mark
x += dx;
y += dy;
}
if ( knotsI >= 10 ) {
while( knotsI >= 10 ) { // fat mark (50 kn)
knotsI -= 10;
ctx.lineTo(x + dx + x1, y + dy + y1 );
ctx.lineTo(x + dx, y + dy );
ctx.fill();
x += dx;
y += dy;
}
x += dx;
y += dy;
}
while( knotsI > 0 ) { // thin mark
ctx.moveTo(x, y)
let f = knotsI == 1 ? 0.5 : 1.0 // short or normal mark (5 kn or 10 kn)
ctx.lineTo(x + x1 * f, y + y1 * f )
x += dx; y += dy
knotsI -= 2
}
ctx.stroke()
}
else {
ctx.strokeStyle = ctx.fillStyle = this.colorNoWind
ctx.beginPath();
ctx.arc( x, y, s * 0.07, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.beginPath();
ctx.arc( x, y, s * 0.16, 0, 2 * Math.PI, false);
ctx.stroke();
}
},
//
// Render wind marks
//
renderWind( nx, ny, segW, h, a ) {
var ctx = this.ctx
, hAdd = -150.0
, hMul = (h - 1) / ( 1000.0 - 150.0 )
, dx2 = (segW + 1) >> 1
, s1 = segW * 0.65 // normal wind mark size
, s2 = segW * 0.46 // smaller size for first and last column
, s3 = segW * 0.4
, x, y, u, v, i, o
, altName, size
ctx.lineWidth = 1
for( var j = 0; j < ny; ++j ) {
y = Math.round( ( this.alt[ j ] + hAdd ) * hMul );
altName = this.altN[ j ]
// Skip these levels
if( /925h|150h/.test( altName ) ) continue;
for (i = 0; i < nx; ++i ) {
x = dx2 + segW * i
o = ( j * nx + i ) << 1
u = a[ o ]
v = a[ o + 1 ]
if( /1000h/.test(altName) )
size = s3
else
size = ( (i === 0 && u < 0 ) || ( i === nx - 1 && u > 0 ) ) ? s2 : s1
this.windMark( ctx, x, y, u, v, size );
}
}
},
legend( nx, ny, segW, h ) {
var ctx = this.ctx
, hAdd = -150.0
, hMul = (h - 1) / ( 1000.0 - 150.0 )
, y, i, j, s, x
, d = 2;
ctx.fillStyle = this.colorLegend;
ctx.font = '9px Verdana';
ctx.setLineDash([2, 4]);
ctx.lineWidth = this.canvasRatio;
ctx.strokeStyle = this.colorHLines;
ctx.beginPath()
for( j = 0; j < 2; j++ ) {
ctx.textAlign = j ? 'right' : 'left';
x = j === 0 ? d : this.w - d;
for( i = 0; i < this.altN.length - 1; i++ ) {
s = this.altN[i];
if( s !== '925h' ) {
y = Math.round( (this.alt[i] + hAdd) * hMul );
ctx.fillText( s, x, y + 3 );
if( j && i ) {
ctx.moveTo( 30, y );
ctx.lineTo( this.w - 30, y );
}
}
}
}
ctx.stroke();
ctx.setLineDash([]);
},
// data for airgram point function
pdata: null,
// convert temperature to user units
convertTemp( t ) {
return Math.round( overlays.temp.convertNumber( t ) )
},
// prepare data for point function on airgram
preparePData( nx, ny, segmentWidth, imgWidth, imgHeight, hLut, data, tempArray, windArray ) {
this.nx_ = nx;
this.ny_ = ny;
this.segmentWidth_ = segmentWidth;
this.imgWidth_ = imgWidth;
this.imgHeight_ = imgHeight;
this.hLut_ = hLut;
this.tempArray_ = tempArray;
this.windArray_ = windArray;
// TODO: hr array?
},
// @x, @y .. position inside image including borders (values scaled by canvasRatio)
// returns array [ temp, windU, windV ]
airgramPoint( x, y ) {
var w = this.imgWidth_
, h = this.imgHeight_
, nx = this.nx_
, lerp = this.lerp
, offs
if( x >= 0 && x < w && y >= 0 && y < h ) { // is inside?
offs = y + y
var j = this.hLut_[ offs ]
, fy = 0.0001 * this.hLut_[ offs + 1 ]
, x1 = x - 0.5 * this.segmentWidth_
, i = 0
, fx = 0.0
, ret = []
if( x1 > 0 ) {
fx = x1 / this.segmentWidth_;
i = Math.floor( fx );
if( i >= nx - 1 ) {
i = nx - 2;
fx = 1.0;
}
else {
fx -= i;
}
}
//
offs = j * nx + i;
ret.push( lerp(
lerp( this.tempArray_[ offs ], this.tempArray_[ offs + 1 ], fx ),
lerp( this.tempArray_[ offs + nx ], this.tempArray_[ offs + nx + 1 ], fx ),
fy
) );
offs += offs;
for( var l = 0; l < 2; l++ ) {
ret.push( this.lerp(
lerp( this.windArray_[ offs + l ], this.windArray_[ offs + 2 + l ], fx ),
lerp( this.windArray_[ offs + nx + nx + l ], this.windArray_[ offs + nx + nx + 2 + l ], fx ),
fy
) );
}
return ret;
}
return null // no data or border
}
}));
W.require(['airgram'])
// legend2( nx, ny, segW, h, a, borders ) {
// var ctx = this.ctx
// , b = this.canvasRatio > 1.5
// , hAdd = -150.0
// , hMul = (h - 1) / ( 1000.0 - 150.0 )
// , h2 = 0.5 * this.h
// , y, i, s
// ctx.fillStyle = this.colorLegend;
// ctx.textAlign = 'left';
// ctx.font = (b ? '16' : '8') + 'px Verdana';
// for( i = 0; i < this.altN.length; i++ ) {
// s = this.altN[i];
// if( s !== '925h' ) {
// y = Math.round( (this.alt[i] + hAdd) * hMul ) + borders[2] + ( b ? 5 : 3 )
// ctx.fillText( s, this.w - borders[1], y );
// }
// }
// //
// ctx.translate( h2, h2 );
// ctx.rotate( -0.5 * Math.PI );
// ctx.textAlign = 'center';
// ctx.fillText( 'Temperature (°C), Wind (kt)', 0, borders[0] - h2 - ( b ? 5 : 3 ) );
// ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
// //
// ctx.font = (b ? '14' : '7') + 'px Verdana';
// var hour = 0
// , dh = 3
// , x = borders[ 0 ] + 0.5 * segW
// y = h + borders[ 2 ] + (b ? 18 : 10 );
// for( i = 0; i < nx; i++ ) {
// if( hour >= 24 || hour <= 0 ) {
// hour = 0;
// }
// else {
// ctx.fillText( ( '0' + hour ).slice( -2 ), x, y );
// }
// hour += dh;
// x += segW;
// }
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment