node-red-contrib-web-worldmap
Version:
A Node-RED node to provide a web page of a world map for plotting things on.
441 lines (391 loc) • 13 kB
JavaScript
var BufferList = require('bufferlist');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
module.exports = Binary;
module.exports.Binary = Binary; // backwards compatibility
Binary.prototype = new EventEmitter;
function Binary(buffer) {
if (!(this instanceof Binary)) return new Binary(buffer);
var self = this;
this.vars = {};
this.offset = 0;
this.actions = [];
this.parent = null;
// an explicit end loads all the actions before any evaluation happens
this.end = function () {
if (buffer.listeners('write').indexOf(update) < 0) {
buffer.on('write', update);
}
update();
return this;
};
// Signify to the parent that processing should stop.
this.exit = function () {
this.pushAction({
ready : true,
action : function () {
this.actions = [];
if (this.parent) this.parent.actions = [];
},
});
return this;
};
function update () {
var action = self.actions[0];
if (!action) {
buffer.removeListener('write', update);
self.emit('end', self.vars);
}
else if (action.ready.call(self, self.vars)) {
self.actions.shift();
if (action.context == false) {
action.action.call(self, self.vars);
self.end();
}
else {
buffer.removeListener('write', update);
var child = new Binary(buffer);
child.vars = self.vars;
child.parent = self;
child.offset = self.offset;
child.on('end', function () {
self.offset = child.offset;
buffer.on('write', update);
self.end();
});
action.action.call(child, child.vars);
child.end();
}
}
}
this.pushAction = function (action) {
if (!action) throw "Action not specified";
var ready = {
'function' : action.ready,
'boolean' : function () { return action.ready },
}[typeof(action.ready)];
if (!ready) throw "Unknown action.ready type";
this.actions.push({
'action' : action.action,
'ready' : ready,
'context' : action.context || false,
});
};
this.flush = function () {
this.pushAction({
ready : true,
action : function () {
buffer.advance(this.offset);
this.offset = 0;
},
});
return this;
};
this.skip = function (bytes) {
this.pushAction({
ready : true,
action : function () {
this.offset += bytes;
},
});
return this;
};
this.tap = function (f) {
this.pushAction({
ready : true,
context : true,
action : function () {
f.call(this, this.vars);
},
});
return this;
};
this.when = function (v1, v2, f) {
var f1 = typeof(v1) == 'string'
? function (vars) { return lookup(this,v1) }
: function (vars) { return v1 }
;
var f2 = typeof(v2) == 'string'
? function (vars) { return lookup(this,v2) }
: function (vars) { return v2 }
;
return this.tap(function () {
if (f1.call(this,this.vars) == f2.call(this,this.vars)) {
f.call(this, this.vars);
}
});
};
this.unless = function (v1, v2, f) {
var f1 = typeof(v1) == 'string'
? function (vars) { return lookup(this,v1) }
: function (vars) { return v1 }
;
var f2 = typeof(v2) == 'string'
? function (vars) { return lookup(this,v2) }
: function (vars) { return v2 }
;
return this.tap(function () {
if (f1.call(this,this.vars) != f2.call(this,this.vars)) {
f.call(this, this.vars);
}
});
};
this.repeat = function (n, f) {
var nf = typeof(n) == 'string'
? function (vars) { return lookup(this,n) }
: function (vars) { return n }
;
this.pushAction({
ready : true,
context : true,
action : function () {
var nn = nf.call(this, this.vars);
for (var i = 0; i < nn; i++) {
f.call(this, this.vars, i);
}
},
});
return this;
};
this.forever = function (f) {
self.foreverfunc = f;
self.foreveraction = {
ready : true,
context : true,
action : function () {
self.foreverfunc.call(this, this.vars);
self.pushAction(self.foreveraction);
},
};
this.pushAction(self.foreveraction);
return this;
};
// assign immediately
function assign (self, key, value) {
visit(
self, key instanceof Array ? key : [key],
function (v,k) { v[k] = value }
);
}
function lookup (self) {
var args = [].slice.call(arguments, 1);
return visit(self, args, function (v,k) { return v[k] });
}
function visit(self, args, f) {
var keys = args.reduce(function (acc,x) {
return acc.concat(x.split('.'))
},[])
;
var obj = self.vars;
keys.slice(0,-1).forEach(function (k) {
if (!obj[k]) obj[k] = {};
obj = obj[k];
});
return f(obj, keys.slice(-1)[0]);
}
// Assign into a variable. All but the last argument make up the key, which
// may describe a deeply nested address. If the last argument is a:
// * function - assign the variables from the inner chain
// * string - assign from the key name
// * number - assign from this value
this.into = function () {
var args = [].concat.apply([],arguments);
var keys = args.slice(0,-1);
var fv = args.slice(-1)[0];
return this.tap(function (vars) {
if (typeof fv == 'function') {
var topVars = this.vars;
this.vars = {};
fv.call(this, this.vars);
this.pushAction({
ready : true,
action : function () {
var localVars = this.vars;
this.vars = topVars;
assign(this, keys, localVars);
}
});
}
else if (typeof fv == 'string') {
assign(this, keys, lookup(this,fv));
}
else if (typeof fv == 'number') {
assign(this, keys, fv);
}
else {
throw TypeError(
'Last argument to .into must be a string, number, '
+ 'or a function, not a "' + typeof fv + '".'
+ 'Value supplied: ' + util.inspect(fv)
);
}
});
};
function get (opts) {
var into = [].reduce.call(opts.into, function (acc,x) {
return acc.concat(x);
}, []);
this.pushAction({
ready : function () {
return buffer.length - this.offset >= opts.bytes;
},
action : function () {
var data = buffer.join(this.offset, this.offset + opts.bytes);
this.offset += opts.bytes;
var decodeLittleEndian = opts.signed ? decodeLEs : decodeLE;
var decodeBigEndian = opts.signed ? decodeBEs : decodeBE;
assign(this, into,
opts.endian && opts.endian == 'little'
? decodeLittleEndian(data)
: decodeBigEndian(data)
);
},
});
return this;
};
this.getWord8 = function () {
return get.call(
this, { into : arguments, bytes : 1 }
);
};
this.getWord16be = function () {
return get.call(
this, { into : arguments, bytes : 2, endian : 'big' }
);
};
this.getWord16bes = function () {
return get.call(
this, { into : arguments, bytes : 2, endian : 'big', signed : true }
);
}
this.getWord16le = function () {
return get.call(
this, { into : arguments, bytes : 2, endian : 'little' }
);
};
this.getWord16les = function () {
return get.call(
this, { into : arguments, bytes : 2, endian : 'little',
signed : true }
);
}
this.getWord32be = function () {
return get.call(
this, { into : arguments, bytes : 4, endian : 'big' }
);
};
this.getWord32bes = function () {
return get.call(
this, { into : arguments, bytes : 4, endian : 'big',
signed : true }
);
};
this.getWord32le = function () {
return get.call(
this, { into : arguments, bytes : 4, endian : 'little' }
);
};
this.getWord32les = function () {
return get.call(
this, { into : arguments, bytes : 4, endian : 'little',
signed: true }
);
};
this.getWord64be = function () {
return get.call(
this, { into : arguments, bytes : 8, endian : 'big' }
);
};
this.getWord64bes = function () {
return get.call(
this, { into : arguments, bytes : 8, endian : 'big',
signed : true }
);
};
this.getWord64le = function () {
return get.call(
this, { into : arguments, bytes : 8, endian : 'little' }
);
};
this.getWord64les = function () {
return get.call(
this, { into : arguments, bytes : 8, endian : 'little',
signed : true }
);
};
this.getBuffer = function () {
var args = [].concat.apply([],arguments);
// flatten :into so .getBuffer(['foo','bar','baz'],10)
// and .getBuffer('foo','bar','baz',10) both work
var into = args.slice(0,-1).reduce(function f (acc,x) {
return acc.concat(
x instanceof Array ? x.reduce(f) : x.split('.')
);
}, []);
var length = args.slice(-1)[0];
var lengthF;
if (typeof(length) == 'string') {
var s = length;
lengthF = function (vars) { return lookup(this,s) };
}
else if (typeof(length) == 'number') {
var s = length;
lengthF = function (vars) { return s };
}
else if (length instanceof Function) {
lengthF = length;
}
else {
throw TypeError(
'Last argument to getBuffer (length) must be a string, number, '
+ 'or a function, not a "' + typeof(length) + '".'
+ 'Value supplied: ' + util.inspect(length)
);
}
this.pushAction({
ready : function () {
var s = lengthF.call(this,this.vars);
return s && buffer.length - this.offset >= s;
},
action : function () {
var s = lengthF.call(this,this.vars);
var data = buffer.join(this.offset, this.offset + s);
this.offset += s;
assign(this, into, data);
},
});
return this;
};
}
// convert byte strings to little endian numbers
function decodeLE (bytes) {
var acc = 0;
for (var i = 0; i < bytes.length; i++) {
acc += Math.pow(256,i) * bytes[i];
}
return acc;
}
// convert byte strings to big endian numbers
function decodeBE (bytes) {
var acc = 0;
for (var i = 0; i < bytes.length; i++) {
acc += Math.pow(256, bytes.length - i - 1) * bytes[i];
}
return acc;
}
// convert byte strings to signed big endian numbers
function decodeBEs (bytes) {
var val = decodeBE(bytes);
if ((bytes[0]&0x80) == 0x80) {
val -= Math.pow(256, bytes.length);
}
return val;
}
// convert byte strings to signed little endian numbers
function decodeLEs (bytes) {
var val = decodeLE(bytes);
if ((bytes[bytes.length-1]&0x80) == 0x80) {
val -= Math.pow(256, bytes.length);
}
return val;
}