Skip to content

Instantly share code, notes, and snippets.

@kkaefer
Last active August 29, 2015 13:56
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 kkaefer/378bd150fe52de8e74b1 to your computer and use it in GitHub Desktop.
Save kkaefer/378bd150fe52de8e74b1 to your computer and use it in GitHub Desktop.
{
"node": true,
"globalstrict": true
}
'use strict';
module.exports = Geometry;
function Geometry() {
this.values = [];
this.pos = {
x: 0,
y: 0
};
}
Geometry.prototype.encodeCommand = function(cmd, length) {
var cmd_bits = 3;
var cmd_mask = (1 << cmd_bits) - 1;
this.values.push((length << cmd_bits) | (cmd & cmd_mask));
};
Geometry.prototype.encodePoint = function(pt) {
// Compute delta to the previous coordinate.
var dx = pt.x - this.pos.x;
var dy = pt.y - this.pos.y;
// Manual zigzag encoding.
this.values.push((dx << 1) ^ (dx >> 31));
this.values.push((dy << 1) ^ (dy >> 31));
this.pos.x = pt.x;
this.pos.y = pt.y;
};
Geometry.prototype.addPoint = function(point) {
this.encodeCommand(1, 1);
this.encodePoint(point);
};
Geometry.prototype.addPoints = function(points) {
this.encodeCommand(1, points.length);
for (var i = 0; i < points.length; i++) {
this.encodePoint(points[i]);
}
};
Geometry.prototype.addLine = function(line) {
// Encode moveTo first point in the polygon.
this.encodeCommand(1, 1);
this.encodePoint(line[0]);
// Encode lineTo for the rest of the points.
this.encodeCommand(2, line.length - 1);
for (var j = 1; j < line.length; j++) {
this.encodePoint(line[j]);
}
};
Geometry.prototype.addPolygon = function(polygon) {
this.addLine(polygon);
// Encode closepolygon
this.encodeCommand(7, 1);
};
'use strict';
var Protobuf = require('./protobuf.js');
var Geometry = require('./geometry.js');
module.exports = Layer;
function Layer(name, extent) {
this.pbf = new Protobuf();
this.pbf.writeTaggedVarint(15, 1);
this.pbf.writeTaggedString(1, name);
if (extent) {
this.pbf.writeTaggedVarint(5, extent);
}
this.keys = [];
this.values = [];
}
var geom_type = {
unknown: 0,
point: 1,
line: 2,
polygon: 3
};
Layer.prototype.addFeature = function(type, geometry, properties) {
if (!geom_type[type]) {
throw new Error('type must be either point, line or polygon');
}
var feature_pbf = new Protobuf();
if (typeof properties !== 'object') properties = {};
feature_pbf.writeTaggedVarint(3, geom_type[type]);
// Write tags.
var tags = [];
for (var key in properties) {
if (key === 'id') {
feature_pbf.writeTaggedVarint(1, properties.id);
} else {
var key_pos = this.addKey(key);
var val_pos = this.addValue(properties[key]);
tags.push(key_pos, val_pos);
}
}
feature_pbf.writePackedVarints(2, tags);
// Write geometry.
if (!(geometry instanceof Geometry)) {
var value = geometry;
geometry = new Geometry();
if (type == 'point') geometry.addPoint(value);
else if (type == 'line') geometry.addLine(value);
else if (type == 'polygon') geometry.addPolygon(value);
else throw new Error('unknown geometry type');
}
feature_pbf.writePackedVarints(4, geometry.values);
this.pbf.writeMessage(2, feature_pbf);
};
Layer.prototype.addKey = function(key) {
var key_pos = this.keys.indexOf(key);
if (key_pos < 0) {
this.pbf.writeTaggedString(3, key);
key_pos = this.keys.length;
this.keys.push(key);
}
return key_pos;
};
Layer.prototype.addValue = function(value) {
var val_pos = this.values.indexOf(value);
if (val_pos < 0) {
var value_pbf = new Protobuf();
if (typeof value === 'number') {
if (Math.floor(value) === value) {
value_pbf.writeTaggedVarint(4, value);
} else {
value_pbf.writeTaggedDouble(3, value);
}
} else if (typeof value == 'boolean') {
value_pbf.writeTaggedBoolean(7, value);
} else {
value_pbf.writeTaggedString(1, value);
}
this.pbf.writeMessage(4, value_pbf);
val_pos = this.values.length;
this.values.push(value);
}
return val_pos;
};
Layer.prototype.finish = function() {
return this.pbf.finish();
};
'use strict';
module.exports = Protobuf;
function Protobuf() {
this.buf = new Buffer(128);
this.pos = 0;
this.length = this.buf.length;
}
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);
};
'use strict';
var Protobuf = require('./protobuf.js');
var Layer = require('./layer.js');
module.exports = Tile;
function Tile() {
this.pbf = new Protobuf();
}
Tile.prototype.addLayer = function(layer) {
if (!(layer instanceof Layer)) {
throw new Error('layer must be a Layer object');
}
this.pbf.writeMessage(3, layer);
};
Tile.prototype.finish = function() {
return this.pbf.finish();
};
// Protocol Version 1
package llmr.vector;
option optimize_for = LITE_RUNTIME;
enum geom_type {
Unknown = 0;
Point = 1;
LineString = 2;
Polygon = 3;
}
// Variant type encoding
message value {
// Exactly one of these values may be present in a valid message
optional string string_value = 1;
optional float float_value = 2;
optional double double_value = 3;
optional int64 int_value = 4;
optional uint64 uint_value = 5;
optional sint64 sint_value = 6;
optional bool bool_value = 7;
extensions 8 to max;
}
message feature {
optional uint64 id = 1;
// Tags of this feature. Even numbered values refer to the nth
// value in the keys list on the tile message, odd numbered
// values refer to the nth value in the values list on the tile
// message.
repeated uint32 tags = 2 [ packed = true ];
// The type of geometry stored in this feature.
optional geom_type type = 3 [ default = Unknown ];
// Contains a stream of commands and parameters (vertices). The
// repeat count is shifted to the left by 3 bits. This means
// that the command has 3 bits (0-15). The repeat count
// indicates how often this command is to be repeated. Defined
// commands are:
// - MoveTo: 1 (2 parameters follow)
// - LineTo: 2 (2 parameters follow)
// - ClosePath: 15 (no parameters follow)
//
// Ex.: MoveTo(3, 6), LineTo(8, 12), LineTo(20, 34), ClosePath
// Encoded as: [ 3 6 18 5 6 12 22 15 ]
// == command type 15 (ClosePath)
// ===== relative LineTo(+12, +22) == LineTo(20, 34)
// === relative LineTo(+5, +6) == LineTo(8, 12)
// == [00010 010] = command type 2 (LineTo), length 2
// === relative MoveTo(+3, +6)
// = implicit command type 1 (MoveTo), length 1
// Commands are encoded as uint32 varints, vertex parameters are
// encoded as sint32 varints (zigzag). Vertex parameters are
// also encoded as deltas to the previous position. The original
// position is (0,0)
repeated uint32 geometry = 4 [ packed = true ];
// A list of indices to the geometry array that specify a triangulation of
// this geometry. This must only exist if this feature is a polygon.
// These are the valid indices for the example above:
// 0 ==> (3/6)
// 1 ==> (8/12)
// 2 ==> (20/34)
// Indices beyond 2 are invalid, as the total number of vertices is 3.
repeated sint32 triangulation = 5 [ packed = true ];
// The total number of vertices encoded in the geometry field. This is can
// be deduced by manually iterating through the geometry field, but we can
// just as well store the number to avoid the overhead on parsing.
optional uint32 vertex_count = 6;
}
// Stores a glyph with metrics and optional SDF bitmap information.
message glyph {
required uint32 id = 1;
// A signed distance field of the glyph with a border of 3 pixels.
optional bytes bitmap = 2;
// Glyph metrics.
required uint32 width = 3;
required uint32 height = 4;
required sint32 left = 5;
required sint32 top = 6;
required uint32 advance = 7;
}
// Stores font face information and a list of glyphs.
message face {
required string family = 1;
required string style = 2;
repeated glyph glyphs = 5;
}
// Stores the shaping information for the label with a given text
message label {
// The original value ID this shaping information is for.
required uint32 text = 1;
// References the index of the font stack in the layer's fontstack array.
required uint32 stack = 2;
// Parallel arrays of face ID, glyph ID and position.
repeated uint32 faces = 3 [packed = true];
repeated uint32 glyphs = 4 [packed = true];
repeated uint32 x = 5 [packed = true];
repeated uint32 y = 6 [packed = true];
}
message layer {
// Any compliant implementation must first read the version
// number encoded in this message and choose the correct
// implementation for this version number before proceeding to
// decode other parts of this message.
required uint32 version = 15 [ default = 1 ];
required string name = 1;
// The actual features in this tile.
repeated feature features = 2;
// Dictionary encoding for keys
repeated string keys = 3;
// Dictionary encoding for values
repeated value values = 4;
// The bounding box in this tile spans from 0..4095 units
optional uint32 extent = 5 [ default = 4096 ];
// Total vertex count in this layer.
optional uint32 vertex_count = 6;
// Shaping information for labels contained in this tile.
repeated string faces = 7;
repeated label labels = 8;
repeated string stacks = 9;
extensions 16 to max;
}
message tile {
repeated layer layers = 3;
repeated face faces = 4;
extensions 16 to 8191;
}
'use strict';
var Layer = require('./layer.js');
var Tile = require('./tile.js');
var poly_coords = 0;
var tile = new Tile();
var water = new Layer('water');
for (var y = 0; y < 4096; y += 32) {
for (var x = 0; x < 4096; x += 32) {
var poly = [];
poly.push({ x: x, y: y });
// poly.push({ x: x + 8, y: y });
poly.push({ x: x + 10, y: y + 3 });
// poly.push({ x: x + 10, y: y + 8 });
poly.push({ x: x + 12, y: y + 12 });
// poly.push({ x: x + 8, y: y + 10 });
// poly.push({ x: x + 2, y: y + 8 });
poly.push({ x: x + 1, y: y + 3 });
water.addFeature('polygon', poly);
poly_coords += poly.length;
}
}
tile.addLayer(water);
process.stdout.write(tile.finish());
console.warn('poly coords: %d', poly_coords);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment