glad-stream-decoder
Version:
Transform stream to decode GLAD forest alerts
93 lines (81 loc) • 2.95 kB
JavaScript
var Transform = require('readable-stream').Transform
var inherits = require('inherits')
var assign = require('object-assign')
var julian = require('julian')
var colorString = require('color-string')
module.exports = GLADStreamDecoder
var DEFAULT_OPTS = {
color: 'rgb(220, 102, 153)',
since: new Date(2015, 0, 1)
}
var GLAD_START_JULIAN_DAY = julian.toJulianDay(new Date(2015, 0, 1))
/**
* Transforms an encoded rgb image stream (a buffer of r,g,b values in order)
* from GLAD alerts into an rgba image stream with alerts as colored pixels
* `opts.color` and the intensity as the transparency of the pixel.
* @param {object} opts
* @param {string|array} opts.color CSS color string or [red, green, blue] array for color of pixels
* @param {date} opts.since Date object, filters alerts since this date
*/
function GLADStreamDecoder (opts) {
opts = assign({}, DEFAULT_OPTS, opts)
if (opts.since < DEFAULT_OPTS.since) {
opts.since = DEFAULT_OPTS.since
}
this._color = colorString.get.rgb(opts.color)
this._sinceDays = julian.toJulianDay(opts.since) - GLAD_START_JULIAN_DAY
var self = this
this.once('pipe', function (src) {
src.on('format', function (format) {
self._inputFormat = format
})
})
Transform.call(this)
}
inherits(GLADStreamDecoder, Transform)
GLADStreamDecoder.prototype._transform = function (data, encoding, cb) {
if (!this._started) {
if (!this._inputFormat || this._inputFormat.colorSpace !== 'rgb') {
return cb(new Error('Only supports rgb input'))
}
var format = assign({}, this._inputFormat, {colorSpace: 'rgba'})
this.emit('format', format)
this._started = true
}
var res = Buffer.allocUnsafe(data.length / 3 * 4)
var days
var j = 0
for (var i = 0; i < data.length; i += 3) {
res[j++] = this._color[0]
res[j++] = this._color[1]
res[j++] = this._color[2]
// find the total days of the pixel by
// multiplying the red band by 255 and adding
// the green band to that
days = data[i] * 255 + data[i + 1]
if (days > this._sinceDays) {
// Convert the blue band to string, leading
// zeros if it's not currently three digits
// this occurs very rarely; where there's an intensity
// value but no date/confidence for it. Due to bilinear
// resampling
var band3Str = pad(data[i + 2].toString())
// Grab confidence (the first value) from this string
// confidence is stored as 1/2, subtract one to make it 0/1
// var confidence = parseInt(band3Str[0]) - 1
// Grab the raw intensity value from the pixel; ranges from 1 - 55
var rawIntensity = parseInt(band3Str.slice(1, 3))
// Scale the intensity to make it visible
var intensity = Math.min(rawIntensity * 50, 255)
res[j++] = intensity
} else {
res[j++] = 0
}
}
this.push(res)
cb()
}
function pad (num) {
var s = '00' + num
return s.substr(s.length - 3)
}