diff options
author | Konstantin Käfer <github@kkaefer.com> | 2014-01-08 12:10:46 +0100 |
---|---|---|
committer | Konstantin Käfer <github@kkaefer.com> | 2014-01-08 12:10:46 +0100 |
commit | 4f8556a6b80c41df8f8842a36ce13274c27b9ceb (patch) | |
tree | 51ead2bbe1e83bc7ba6d2fdcb355e131d1b1871b /bin | |
parent | 528eaf475e084508cb2d2d26df7d4136f1eb5e04 (diff) | |
download | qtlocation-mapboxgl-4f8556a6b80c41df8f8842a36ce13274c27b9ceb.tar.gz |
add protobuf encoding of style
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/convert-style.js | 140 | ||||
-rw-r--r-- | bin/protobuf.js | 312 | ||||
-rw-r--r-- | bin/style.js | 127 |
3 files changed, 579 insertions, 0 deletions
diff --git a/bin/convert-style.js b/bin/convert-style.js new file mode 100755 index 0000000000..aeabbfe86a --- /dev/null +++ b/bin/convert-style.js @@ -0,0 +1,140 @@ +#!/usr/bin/env node + +var style = require('./style.js'); +var Protobuf = require('./protobuf.js'); + +var fs = require('fs'); + +var pbf = new Protobuf(); + +// enum +var bucket_type = { + fill: 1, + line: 2, + point: 3 +}; + +// enum +var cap_type = { + round: 1, +} + +// enum +var join_type = { + butt: 1, + bevel: 2 +} + +function createBucket(bucket, name) { + var pbf = new Protobuf(); + pbf.writeTaggedString(1 /* name */, name); + pbf.writeTaggedVarint(2 /* type */, bucket_type[bucket.type]); + + pbf.writeTaggedString(3 /* source_name */, bucket.datasource); + pbf.writeTaggedString(4 /* source_layer */, bucket.layer); + if (bucket.field) { + pbf.writeTaggedString(5 /* source_field */, bucket.field); + var values = Array.isArray(bucket.value) ? bucket.value : [bucket.value]; + for (var i = 0; i < values.length; i++) { + pbf.writeTaggedString(6 /* source_values */, values[i]); + } + } + if (bucket.cap) { + pbf.writeTaggedVarint(7 /* cap */, cap_type[bucket.cap]); + } + if (bucket.join) { + pbf.writeTaggedVarint(8 /* join */, join_type[bucket.join]); + } + + // TODO: font stuff + + return pbf; +} + +function createStructure(structure) { + var pbf = new Protobuf(); + pbf.writeTaggedString(1 /* name */, structure.name); + if (structure.bucket) { + pbf.writeTaggedString(2 /* bucket_name */, structure.bucket); + } else if (structure.layers) { + for (var i = 0; i < structure.layers.length; i++) { + pbf.writeMessage(3 /* child_layers */, createStructure(structure.layers[i])); + } + } + return pbf; +} + +function createWidth(width) { + var pbf = new Protobuf(); + var values = []; + if (Array.isArray(width)) { + pbf.writeTaggedString(1 /* scaling */, width[0]); + for (var i = 1; i < width.length; i++) { + if (width[0] === 'stops') { + values.push(width[i].z, width[i].val); + } else { + values.push(width[i]); + } + } + } else { + values.push(width); + } + pbf.writePackedFloats(2 /* values */, values); + + return pbf; +} + +function createLayer(layer, name) { + var pbf = new Protobuf(); + pbf.writeTaggedString(1 /* layer_name */, name); + + if ('color' in layer) { + var color = layer.color.match(/^#([0-9a-f]{6})$/i); + if (!color) { + console.warn('invalid color'); + } else { + pbf.writeTaggedUInt32(2 /* color */, parseInt(color[1] + 'ff', 16)); + } + } + + if ('antialias' in layer) { + pbf.writeTaggedBoolean(3 /* antialias */, layer.antialias); + } + + if ('width' in layer) { + pbf.writeMessage(4 /* width */, createWidth(layer.width)); + } + + return pbf; +} + + +function createClass(klass) { + var pbf = new Protobuf(); + pbf.writeTaggedString(1 /* name */, klass.name); + for (var name in klass.layers) { + pbf.writeMessage(2 /* layers */, createLayer(klass.layers[name], name)); + } + return pbf; +} + + + + + +for (var name in style.buckets) { + var bucket = style.buckets[name]; + pbf.writeMessage(1 /* buckets */, createBucket(bucket, name)); +} + +for (var i = 0; i < style.structure.length; i++) { + var structure = style.structure[i]; + pbf.writeMessage(2 /* structure */, createStructure(structure)); +} + +for (var i = 0; i < style.classes.length; i++) { + var klass = style.classes[i]; + pbf.writeMessage(3 /* classes */, createClass(klass)); +} + +process.stdout.write(pbf.finish()); diff --git a/bin/protobuf.js b/bin/protobuf.js new file mode 100644 index 0000000000..fb181c548f --- /dev/null +++ b/bin/protobuf.js @@ -0,0 +1,312 @@ +module.exports = Protobuf; +function Protobuf(buf, pos) { + if (Buffer.isBuffer(buf)) { + this.buf = buf; + } else { + this.buf = new Buffer(buf || 128); + } + this.pos = pos || 0; + this.length = this.buf.length; +} + +// === READING ================================================================= + +Protobuf.prototype.readUInt32 = function() { + var val = this.readUInt32LE(this.pos); + this.pos += 4; + return val; +}; + +Protobuf.prototype.readUInt64 = function() { + var val = this.readUInt64LE(this.pos); + this.pos += 4; + return val; +}; + +Protobuf.prototype.readVarint = function() { + // TODO: bounds checking + var pos = this.pos; + if (this.buf[pos] <= 0x7f) { + this.pos++; + return this.buf[pos]; + } else if (this.buf[pos + 1] <= 0x7f) { + this.pos += 2; + return (this.buf[pos] & 0x7f) | (this.buf[pos + 1] << 7); + } else if (this.buf[pos + 2] <= 0x7f) { + this.pos += 3; + return (this.buf[pos] & 0x7f) | (this.buf[pos + 1] & 0x7f) << 7 | (this.buf[pos + 2]) << 14; + } else if (this.buf[pos + 3] <= 0x7f) { + this.pos += 4; + return (this.buf[pos] & 0x7f) | (this.buf[pos + 1] & 0x7f) << 7 | (this.buf[pos + 2] & 0x7f) << 14 | (this.buf[pos + 3]) << 21; + } else if (this.buf[pos + 4] <= 0x7f) { + this.pos += 5; + return ((this.buf[pos] & 0x7f) | (this.buf[pos + 1] & 0x7f) << 7 | (this.buf[pos + 2] & 0x7f) << 14 | (this.buf[pos + 3]) << 21) + (this.buf[pos + 4] * 268435456); + } else { + throw new Error("TODO: Handle 6+ byte varints"); + } +}; + +Protobuf.prototype.readSVarint = function() { + var num = this.readVarint(); + if (num > 2147483647) throw new Error('TODO: Handle numbers >= 2^30'); + // zigzag encoding + return ((num >> 1) ^ -(num & 1)); +}; + +Protobuf.prototype.readString = function() { + var bytes = this.readVarint(); + // Node.js + var str = this.buf.toString('utf8', this.pos, this.pos + bytes); + // Client side JS code: + // // TODO: bounds checking + // var chr = String.fromCharCode; + // var b = this.buf; + // var p = this.pos; + // var end = this.pos; + // var str = ''; + // while (p < end) { + // if (b[p] <= 0x7F) str += chr(b[p++]); + // else if (b[p] <= 0xBF) throw new Error('Invalid UTF-8 codepoint: ' + b[p]); + // else if (b[p] <= 0xDF) str += chr((b[p++] & 0x1F) << 6 | (b[p++] & 0x3F)); + // else if (b[p] <= 0xEF) str += chr((b[p++] & 0x1F) << 12 | (b[p++] & 0x3F) << 6 | (b[p++] & 0x3F)); + // else if (b[p] <= 0xF7) p += 4; // We can't handle these codepoints in JS, so skip. + // else if (b[p] <= 0xFB) p += 5; + // else if (b[p] <= 0xFD) p += 6; + // else throw new Error('Invalid UTF-8 codepoint: ' + b[p]); + // } + this.pos += bytes; + return str; +}; + +Protobuf.prototype.readPacked = function(type) { + // TODO: bounds checking + var bytes = this.readVarint(); + var end = this.pos + bytes; + var array = []; + while (this.pos < end) { + array.push(this['read' + type]()); + } + return array; +}; + +Protobuf.prototype.skip = function(val) { + // TODO: bounds checking + var type = val & 0x7; + switch (type) { + /* varint */ case 0: while (this.buf[this.pos++] > 0x7f); break; + /* 64 bit */ case 1: this.pos += 8; break; + /* length */ case 2: var bytes = this.readVarint(); this.pos += bytes; break; + /* 32 bit */ case 5: this.pos += 4; break; + default: throw new Error('Unimplemented type: ' + type); + } +}; + +// Generic read function +Protobuf.prototype.read = function(spec, end) { + var ref, tag; + var obj = {}; + for (tag in spec) { + ref = spec[tag]; + if ('default' in ref) { + obj[ref.name] = ref['default']; + } + + if ('repeated' in ref) { + obj[ref.name] = []; + } + } + + var begin = this.pos; + if (typeof end == 'undefined') { + var bytes = this.readVarint(); + end = this.pos + bytes; + } + + while (this.pos < end) { + var val = this.readVarint(); + tag = val >> 3; + ref = spec[tag]; + if (ref) { + if (ref.packed) { + obj[ref.name] = this.readPacked(ref.type); + } + else if (ref.repeated) { + if (!obj[ref.name]) obj[ref.name] = []; + obj[ref.name].push(this['read' + ref.type]()); + } else { + obj[ref.name] = this['read' + ref.type](); + } + } else { + this.skip(val); + } + } + + if (this._debug) { + obj._begin = begin; + obj._end = end; + obj._bytes = obj._end - obj._begin; + } + + for (tag in spec) { + ref = spec[tag]; + if (!(ref.name in obj) && ('required' in ref)) { + throw new Error('Field ' + ref.name + ' is required'); + } + } + + return obj; +}; + +// === WRITING ================================================================= + +Protobuf.Varint = 0; +Protobuf.Int64 = 1; +Protobuf.Message = 2; +Protobuf.String = 2; +Protobuf.Packed = 2; +Protobuf.Int32 = 5; + +Protobuf.prototype.writeTag = function(tag, type) { + this.writeVarint((tag << 3) | type); +}; + +Protobuf.prototype.writeUInt32 = function(val) { + while (this.length < this.pos + 4) this.realloc(); + this.buf.writeUInt32LE(val, this.pos); + this.pos += 4; +}; + +Protobuf.prototype.writeTaggedUInt32 = function(tag, val) { + this.writeTag(tag, Protobuf.Int32); + this.writeUInt32(val); +}; + +Protobuf.prototype.writeVarint = function(val) { + val = Number(val); + if (isNaN(val)) { + val = 0; + } + + if (val <= 0x7f) { + while (this.length < this.pos + 1) this.realloc(); + this.buf[this.pos++] = val; + } else if (val <= 0x3fff) { + while (this.length < this.pos + 2) this.realloc(); + this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f); + this.buf[this.pos++] = 0x00 | ((val >>> 7) & 0x7f); + } else if (val <= 0x1ffffff) { + while (this.length < this.pos + 3) this.realloc(); + this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f); + this.buf[this.pos++] = 0x80 | ((val >>> 7) & 0x7f); + this.buf[this.pos++] = 0x00 | ((val >>> 14) & 0x7f); + } else if (val <= 0xfffffff) { + while (this.length < this.pos + 4) this.realloc(); + this.buf[this.pos++] = 0x80 | ((val >>> 0) & 0x7f); + this.buf[this.pos++] = 0x80 | ((val >>> 7) & 0x7f); + this.buf[this.pos++] = 0x80 | ((val >>> 14) & 0x7f); + this.buf[this.pos++] = 0x00 | ((val >>> 21) & 0x7f); + } else { + throw new Error("TODO: Handle 5+ byte varints (" + val + ")"); + } +}; + +Protobuf.prototype.writeTaggedVarint = function(tag, val) { + this.writeTag(tag, Protobuf.Varint); + this.writeVarint(val); +}; + +Protobuf.prototype.writeBoolean = function(val) { + this.writeVarint(Boolean(val)); +}; + +Protobuf.prototype.writeTaggedBoolean = function(tag, val) { + this.writeTaggedVarint(tag, Boolean(val)); +}; + +Protobuf.prototype.writeString = function(str) { + str = String(str); + var bytes = Buffer.byteLength(str); + this.writeVarint(bytes); + while (this.length < this.pos + bytes) this.realloc(); + this.buf.write(str, this.pos); + this.pos += bytes; +}; + +Protobuf.prototype.writeTaggedString = function(tag, str) { + this.writeTag(tag, Protobuf.String); + this.writeString(str); +}; + +Protobuf.prototype.writeFloat = function(val) { + while (this.length < this.pos + 4) this.realloc(); + this.buf.writeFloatLE(val, this.pos); + this.pos += 4; +}; + +Protobuf.prototype.writeTaggedFloat = function(tag, val) { + this.writeTag(tag, Protobuf.Int32); + this.writeFloat(val); +}; + +Protobuf.prototype.writeDouble = function(val) { + while (this.length < this.pos + 8) this.realloc(); + this.buf.writeDoubleLE(val, this.pos); + this.pos += 8; +}; + +Protobuf.prototype.writeTaggedDouble = function(tag, val) { + this.writeTag(tag, Protobuf.Int64); + this.writeDouble(val); +}; + +Protobuf.prototype.writeBuffer = function(buffer) { + var bytes = buffer.length; + this.writeVarint(bytes); + while (this.length < this.pos + bytes) this.realloc(); + buffer.copy(this.buf, this.pos); + this.pos += bytes; +}; + +Protobuf.prototype.writeMessage = function(tag, protobuf) { + var buffer = protobuf.finish(); + this.writeTag(tag, Protobuf.Message); + this.writeBuffer(buffer); +}; + +Protobuf.prototype.writeRepeated = function(type, tag, items) { + for (var i = 0; i < items.length; i++) { + this['write' + type](tag, items[i]); + } +}; + +Protobuf.prototype.writePacked = function(type, tag, items) { + if (!items.length) return; + + var message = new Protobuf(); + for (var i = 0; i < items.length; i++) { + message['write' + type](items[i]); + } + var data = message.finish(); + + this.writeTag(tag, Protobuf.Packed); + this.writeBuffer(data); +}; + +Protobuf.prototype.writePackedVarints = function(tag, items) { + this.writePacked('Varint', tag, items); +}; + +Protobuf.prototype.writePackedFloats = function(tag, items) { + this.writePacked('Float', tag, items); +}; + +Protobuf.prototype.realloc = function() { + var buf = new Buffer(this.buf.length * 2); + this.buf.copy(buf); + this.buf = buf; + this.length = this.buf.length; +}; + +Protobuf.prototype.finish = function() { + return this.buf.slice(0, this.pos); +}; diff --git a/bin/style.js b/bin/style.js new file mode 100644 index 0000000000..d218e125c1 --- /dev/null +++ b/bin/style.js @@ -0,0 +1,127 @@ +"use strict"; + +module.exports = { + "buckets": { + "water": { + "datasource": "streets", + "layer": "water", + "type": "fill" + }, + "road_large": { + "datasource": "streets", + "layer": "road", "field": "class", "value": ["motorway", "main"], + "type": "line", "cap": "round", "join": "bevel" + }, + "road_regular": { + "datasource": "streets", + "layer": "road", "field": "class", "value": "street", + "type": "line", "cap": "round", "join": "bevel" + }, + "road_limited": { + "datasource": "streets", + "layer": "road", "field": "class", "value": "street_limited", + "type": "line", "cap": "round", "join": "bevel" + }, + "park": { + "datasource": "streets", + "layer": "landuse", "field": "class", "value": "park", + "type": "fill" + }, + "wood": { + "datasource": "streets", + "layer": "landuse", "field": "class", "value": "wood", + "type": "fill" + }, + "school": { + "datasource": "streets", + "layer": "landuse", "field": "class", "value": "school", + "type": "fill" + }, + "cemetery": { + "datasource": "streets", + "layer": "landuse", "field": "class", "value": "cemetery", + "type": "fill" + }, + "industrial": { + "datasource": "streets", + "layer": "landuse", "field": "class", "value": "industrial", + "type": "fill" + }, + "building": { + "datasource": "streets", + "layer": "building", + "type": "fill" + }, + "alcohol": { + "datasource": "streets", + "layer": "poi_label", + "field": "type", + "value": ["Alcohol"], + "type": "point" + } + }, + "sprite": "img/maki-sprite", + "structure": [ + { "name": "park", "bucket": "park" }, + { "name": "wood", "bucket": "wood" }, + { "name": "water", "bucket": "water" }, + { "name": "road_limited", "bucket": "road_limited" }, + { "name": "road_regular", "bucket": "road_regular" }, + { "name": "road_large", "bucket": "road_large" }, + { "name": "alcohol", "bucket": "alcohol" } + ], + "classes": [ + { + "name": "default", + "layers": { + "background": { + "color": "#FFFFFF" + }, + "park": { + "color": "#c8df9f", + "antialias": true + }, + "wood": { + "color": "#33AA66", + "antialias": true + }, + "water": { + "color": "#73b6e6", + "antialias": true + }, + "road_limited": { + "color": "#BBBBBB", + "width": [ + "stops", + { z: 0, val: 1 }, + { z: 20, val: 1 } + ] + }, + "road_regular": { + "color": "#999999", + "width": [ + "stops", + { z: 0, val: 0.5 }, + { z: 13, val: 0.5 }, + { z: 16, val: 2 }, + { z: 20, val: 32 } + ], + }, + "road_large": { + "color": "#666666", + "width": [ + "stops", + { z: 0, val: 0.5 }, + { z: 11, val: 0.5 }, + { z: 13, val: 1 }, + { z: 16, val: 4 }, + { z: 20, val: 64 } + ], + }, + "alcohol": { + "image": "alcohol-shop" + } + } + } + ] +}; |