vtt
Version:
Vector Tile Transform
243 lines (206 loc) • 7.4 kB
JavaScript
const pbf = require("pbf");
const stream = require("stream");
const vtt = module.exports = function vtt(){
if (!(this instanceof vtt)) return new vtt(...arguments);
const fn = Array.from(arguments).find(function(arg){ return (typeof arg === "function") });
// data = args.find(function(arg){ return (typeof arg === "object" && Buffer.isBuffer(arg)) });
let buf = [];
return new stream.Transform({
transform: function(chunk, encoding, done) {
buf.push(chunk);
done();
},
flush: function(done) {
const s = this;
if (!fn) return s.emit("error", new Error("Missing Modify Function")), done();
fn(unpack(Buffer.concat(buf)), function(err, data){ // FIXME error?
if (err) return s.emit("error", new Error(err)), s.emit("end"), done();
if (data) s.emit("data", pack(data));
s.emit("end");
done();
});
}
});
};
const unpack = module.exports.unpack = function unpack(buf){
return (new pbf(buf)).readFields(function(tag, layers, pb){
if (tag === 0x3) {
const layer = {
version: 1,
name: null,
extent: 4096,
features: [],
keys: [],
values: [],
};
pb.readFields(function(tag, l, pb) {
switch (tag) {
case 0xf: l.version = pb.readVarint(); break;
case 0x1: l.name = pb.readString(); break;
case 0x5: l.extent = pb.readVarint(); break;
case 0x2:
const feature = { type: 0, properties: [], geometry: -1 };
pb.readFields(function(tag, f, pb) {
let end;
switch (tag) {
case 0x1: f.id = pb.readVarint(); break;
case 0x3: f.type = pb.readVarint(); break;
case 0x2: // read properties
end = pb.readVarint() + pb.pos;
while (pb.pos < end) f.properties.push([ pb.readVarint(), pb.readVarint() ]);
break;
case 0x4: // read geometry
f.geometry = [];
end = pb.readVarint() + pb.pos;
let cmd = 1;
let len = 0;
let x = 0;
let y = 0;
let ring = [];
let inst;
while (pb.pos < end) {
if (len-- <= 0) inst = pb.readVarint(), cmd = inst & 0x7, len = (inst >> 3) - 1;
switch (cmd) {
case 0x1: // moveto; new ring
if (ring.length > 0) f.geometry.push(ring), ring = [];
case 0x2: // lineto, fill ring
ring.push([ x += pb.readSVarint(), y += pb.readSVarint() ]);
break;
case 0x7: // close, close polygon
if (ring.length > 0) ring.push([ ring[0][0], ring[0][1] ]); // close polygon
break;
default:
throw new Error("unknown command "+cmd);
break;
}
}
if (ring.length > 0) f.geometry.push(ring);
break;
}
}, feature, pb.readVarint()+pb.pos);
l.features.push(feature);
break;
case 0x3: l.keys.push(pb.readString()); break;
case 0x4:
let end = (pb.readVarint()+pb.pos);
while (pb.pos < end) {
switch (pb.readVarint() >> 3) {
case 0x1: l.values.push(pb.readString()); break;
case 0x2: l.values.push(pb.readFloat()); break;
case 0x3: l.values.push(pb.readDouble()); break;
case 0x4: l.values.push(pb.readVarint64()); break; // deprecated
case 0x5: l.values.push(pb.readVarint()); break;
case 0x6: l.values.push(pb.readSVarint()); break;
case 0x7: l.values.push(pb.readBoolean()); break;
};
};
break;
};
}, layer, pb.readVarint()+pb.pos);
// map keys and values
layer.features = layer.features.map(function(f){
return f.properties = f.properties.reduce(function(p,v){
return p[layer.keys[v[0]]]=layer.values[v[1]],p;
},{}), f;
});
layers.push(layer);
}
}, []);
};
const pack = module.exports.pack = function js2pbf(tile){
const pb = new pbf();
tile.map(function(layer){
// destruct properties
const keys = {};
const values = {};
let kidx = 0;
let vidx = 0;
layer.features = layer.features.map(function(feature){
feature.properties = Object.entries(feature.properties).filter(function([ k, v ]){
return (v === null || typeof v !== undefined);
}).map(function([ k, v ]){
// create a unique string for value, good enough™
const vk = (typeof v)+":"+v.toString();
if (!keys.hasOwnProperty(k)) keys[k] = kidx++;
if (!values.hasOwnProperty(vk)) values[vk] = [ vidx++, v ];
return [ keys[k], values[vk][0] ];
});
return feature;
});
// flatten keys and values
layer.keys = Object.entries(keys).reduce(function(l, [ k, v ]){ return l[v]=k,l },[]);
layer.values = Object.entries(values).reduce(function(l, [ k, v ]){ return l[v[0]]=v[1],l },[]);
return layer;
}).forEach(function(layer){
// construct protobuf
pb.writeMessage(3, function(l, p){
// write version, name and extent
pb.writeVarintField(15, layer.version || 1);
pb.writeStringField(1, layer.name || "");
pb.writeVarintField(5, layer.extent || 4096);
// write keys
layer.keys.forEach(function(k){
pb.writeStringField(3, k);
});
// write values
layer.values.forEach(function(v){
pb.writeMessage(4, function(v, pb){
switch (typeof v) {
case "string": pb.writeStringField(1, v); break;
case "boolean": pb.writeBooleanField(7, v); break;
case "number":
if (v % 1 !== 0) return pb.writeDoubleField(3, v); // in js all floats are doubles regardless
if (v < 0) return pb.writeSVarintField(6, v);
pb.writeVarintField(5, v);
break;
case "bigint":
pb.writeVarintField(4, v);
break;
default:
throw new Error("Property value has invalid type: "+(typeof v));
break;
}
}, v);
});
// write features
layer.features.forEach(function(feature){
// write feature
pb.writeMessage(2, function(){
// write id and type
if (feature.id !== undefined) pb.writeVarintField(1, feature.id);
pb.writeVarintField(3, feature.type);
// write properties
pb.writeMessage(2, function(properties,pb){
feature.properties.forEach(function(property){
pb.writeVarint(property[0]); // key-id
pb.writeVarint(property[1]); // value-id
});
}, feature.properties);
// write geometry — https://github.com/mapbox/vector-tile-spec/blob/master/2.1/README.md#43-geometry-encoding
pb.writeMessage(4, function(feature, pb){
let x = 0, y = 0;
feature.geometry.forEach(function(ring){
let dx = 0, dy = 0;
// write length 1 and MoveTo first coordinate pair
pb.writeVarint((1<<3)+0x1);
pb.writeSVarint(dx = ring[0][0] - x);
pb.writeSVarint(dy = ring[0][1] - y);
if (feature.type > 0x1) { // only lines and polygons
let len = ring.length+2-feature.type; // omit last coordinate pair for polygons
pb.writeVarint(((len-1)<<3)+0x2); // length (without first element) and LineTo
for (let i = 1; i < len; i++) { // write remaining coordinates, start with second coordinate
pb.writeSVarint(dx = ring[i][0] - (x += dx));
pb.writeSVarint(dy = ring[i][1] - (y += dy));
};
// in case of more rings:
x += dx, y += dy;
};
if (feature.type === 0x3) pb.writeVarint((1<<3)+0x7); // ClosePath for polygons
});
}, feature);
}, feature);
});
}, layer);
});
return pb.finish();
};