UNPKG

tile-retriever

Version:

Load vector tiles from sources described in MapLibre style documents

1,699 lines (1,396 loc) 60.2 kB
var ieee754 = {}; /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */ ieee754.read = function (buffer, offset, isLE, mLen, nBytes) { var e, m; var eLen = (nBytes * 8) - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var nBits = -7; var i = isLE ? (nBytes - 1) : 0; var d = isLE ? -1 : 1; var s = buffer[offset + i]; i += d; e = s & ((1 << (-nBits)) - 1); s >>= (-nBits); nBits += eLen; for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} m = e & ((1 << (-nBits)) - 1); e >>= (-nBits); nBits += mLen; for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} if (e === 0) { e = 1 - eBias; } else if (e === eMax) { return m ? NaN : ((s ? -1 : 1) * Infinity) } else { m = m + Math.pow(2, mLen); e = e - eBias; } return (s ? -1 : 1) * m * Math.pow(2, e - mLen) }; ieee754.write = function (buffer, value, offset, isLE, mLen, nBytes) { var e, m, c; var eLen = (nBytes * 8) - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0); var i = isLE ? 0 : (nBytes - 1); var d = isLE ? 1 : -1; var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; value = Math.abs(value); if (isNaN(value) || value === Infinity) { m = isNaN(value) ? 1 : 0; e = eMax; } else { e = Math.floor(Math.log(value) / Math.LN2); if (value * (c = Math.pow(2, -e)) < 1) { e--; c *= 2; } if (e + eBias >= 1) { value += rt / c; } else { value += rt * Math.pow(2, 1 - eBias); } if (value * c >= 2) { e++; c /= 2; } if (e + eBias >= eMax) { m = 0; e = eMax; } else if (e + eBias >= 1) { m = ((value * c) - 1) * Math.pow(2, mLen); e = e + eBias; } else { m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); e = 0; } } for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} e = (e << mLen) | m; eLen += mLen; for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} buffer[offset + i - d] |= s * 128; }; function readVarintRemainder(l, s, p) { var buf = p.buf, h, b; b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s); b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s); throw new Error('Expected varint not more than 10 bytes'); } function toNum(low, high, isSigned) { if (isSigned) { return high * 0x100000000 + (low >>> 0); } return ((high >>> 0) * 0x100000000) + (low >>> 0); } function writeBigVarint(val, pbf) { var low, high; if (val >= 0) { low = (val % 0x100000000) | 0; high = (val / 0x100000000) | 0; } else { low = ~(-val % 0x100000000); high = ~(-val / 0x100000000); if (low ^ 0xffffffff) { low = (low + 1) | 0; } else { low = 0; high = (high + 1) | 0; } } if (val >= 0x10000000000000000 || val < -0x10000000000000000) { throw new Error('Given varint doesn\'t fit into 10 bytes'); } pbf.realloc(10); writeBigVarintLow(low, high, pbf); writeBigVarintHigh(high, pbf); } function writeBigVarintLow(low, high, pbf) { pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7; pbf.buf[pbf.pos] = low & 0x7f; } function writeBigVarintHigh(high, pbf) { var lsb = (high & 0x07) << 4; pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return; pbf.buf[pbf.pos++] = high & 0x7f; } // Buffer code below from https://github.com/feross/buffer, MIT-licensed function readUtf8(buf, pos, end) { var str = ''; var i = pos; while (i < end) { var b0 = buf[i]; var c = null; // codepoint var bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1; if (i + bytesPerSequence > end) break; var b1, b2, b3; if (bytesPerSequence === 1) { if (b0 < 0x80) c = b0; } else if (bytesPerSequence === 2) { b1 = buf[i + 1]; if ((b1 & 0xC0) === 0x80) { c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F); if (c <= 0x7F) c = null; } } else if (bytesPerSequence === 3) { b1 = buf[i + 1]; b2 = buf[i + 2]; if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) { c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F); if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) c = null; } } else if (bytesPerSequence === 4) { b1 = buf[i + 1]; b2 = buf[i + 2]; b3 = buf[i + 3]; if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) { c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F); if (c <= 0xFFFF || c >= 0x110000) c = null; } } if (c === null) { c = 0xFFFD; bytesPerSequence = 1; } else if (c > 0xFFFF) { c -= 0x10000; str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800); c = 0xDC00 | c & 0x3FF; } str += String.fromCharCode(c); i += bytesPerSequence; } return str; } function writeUtf8(buf, str, pos) { for (var i = 0, c, lead; i < str.length; i++) { c = str.charCodeAt(i); // code point if (c > 0xD7FF && c < 0xE000) { if (lead) { if (c < 0xDC00) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; lead = c; continue; } else { c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000; lead = null; } } else { if (c > 0xDBFF || (i + 1 === str.length)) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; } else { lead = c; } continue; } } else if (lead) { buf[pos++] = 0xEF; buf[pos++] = 0xBF; buf[pos++] = 0xBD; lead = null; } if (c < 0x80) { buf[pos++] = c; } else { if (c < 0x800) { buf[pos++] = c >> 0x6 | 0xC0; } else { if (c < 0x10000) { buf[pos++] = c >> 0xC | 0xE0; } else { buf[pos++] = c >> 0x12 | 0xF0; buf[pos++] = c >> 0xC & 0x3F | 0x80; } buf[pos++] = c >> 0x6 & 0x3F | 0x80; } buf[pos++] = c & 0x3F | 0x80; } } return pos; } // Buffer code below from https://github.com/feross/buffer, MIT-licensed function readUInt32(buf, pos) { return ((buf[pos]) | (buf[pos + 1] << 8) | (buf[pos + 2] << 16)) + (buf[pos + 3] * 0x1000000); } function writeInt32(buf, val, pos) { buf[pos] = val; buf[pos + 1] = (val >>> 8); buf[pos + 2] = (val >>> 16); buf[pos + 3] = (val >>> 24); } function readInt32(buf, pos) { return ((buf[pos]) | (buf[pos + 1] << 8) | (buf[pos + 2] << 16)) + (buf[pos + 3] << 24); } function Pbf(buf) { this.buf = ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0); this.pos = 0; this.type = 0; this.length = this.buf.length; } Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64 Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32 var SHIFT_LEFT_32 = (1 << 16) * (1 << 16), SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; // Threshold chosen based on both benchmarking and knowledge about browser string // data structures (which currently switch structure types at 12 bytes or more) var TEXT_DECODER_MIN_LENGTH = 12; var utf8TextDecoder = new TextDecoder('utf-8'); Pbf.prototype = { destroy: function() { this.buf = null; }, // === READING ================================================================= readFields: function(readField, result, end) { end = end || this.length; while (this.pos < end) { var val = this.readVarint(), tag = val >> 3, startPos = this.pos; this.type = val & 0x7; readField(tag, result, this); if (this.pos === startPos) this.skip(val); } return result; }, readMessage: function(readField, result) { return this.readFields(readField, result, this.readVarint() + this.pos); }, readFixed32: function() { var val = readUInt32(this.buf, this.pos); this.pos += 4; return val; }, readSFixed32: function() { var val = readInt32(this.buf, this.pos); this.pos += 4; return val; }, // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed) readFixed64: function() { var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; this.pos += 8; return val; }, readSFixed64: function() { var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32; this.pos += 8; return val; }, readFloat: function() { var val = ieee754.read(this.buf, this.pos, true, 23, 4); this.pos += 4; return val; }, readDouble: function() { var val = ieee754.read(this.buf, this.pos, true, 52, 8); this.pos += 8; return val; }, readVarint: function(isSigned) { var buf = this.buf, val, b; b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val; b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val; b = buf[this.pos]; val |= (b & 0x0f) << 28; return readVarintRemainder(val, isSigned, this); }, readVarint64: function() { // for compatibility with v2.0.1 return this.readVarint(true); }, readSVarint: function() { var num = this.readVarint(); return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding }, readBoolean: function() { return Boolean(this.readVarint()); }, readString: function() { var end = this.readVarint() + this.pos; var pos = this.pos; this.pos = end; if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) { // longer strings are fast with the built-in browser TextDecoder API return utf8TextDecoder.decode(this.buf.subarray(pos, end)); } // short strings are fast with our custom implementation return readUtf8(this.buf, pos, end); }, readBytes: function() { var end = this.readVarint() + this.pos, buffer = this.buf.subarray(this.pos, end); this.pos = end; return buffer; }, // verbose for performance reasons; doesn't affect gzipped size readPackedVarint: function(arr = [], isSigned) { if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned)); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readVarint(isSigned)); return arr; }, readPackedSVarint: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readSVarint()); return arr; }, readPackedBoolean: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readBoolean()); return arr; }, readPackedFloat: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readFloat()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readFloat()); return arr; }, readPackedDouble: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readDouble()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readDouble()); return arr; }, readPackedFixed32: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readFixed32()); return arr; }, readPackedSFixed32: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readSFixed32()); return arr; }, readPackedFixed64: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readFixed64()); return arr; }, readPackedSFixed64: function(arr = []) { if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64()); var end = readPackedEnd(this); while (this.pos < end) arr.push(this.readSFixed64()); return arr; }, skip: function(val) { var type = val & 0x7; if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos; else if (type === Pbf.Fixed32) this.pos += 4; else if (type === Pbf.Fixed64) this.pos += 8; else throw new Error('Unimplemented type: ' + type); }, // === WRITING ================================================================= writeTag: function(tag, type) { this.writeVarint((tag << 3) | type); }, realloc: function(min) { var length = this.length || 16; while (length < this.pos + min) length *= 2; if (length !== this.length) { var buf = new Uint8Array(length); buf.set(this.buf); this.buf = buf; this.length = length; } }, finish: function() { this.length = this.pos; this.pos = 0; return this.buf.subarray(0, this.length); }, writeFixed32: function(val) { this.realloc(4); writeInt32(this.buf, val, this.pos); this.pos += 4; }, writeSFixed32: function(val) { this.realloc(4); writeInt32(this.buf, val, this.pos); this.pos += 4; }, writeFixed64: function(val) { this.realloc(8); writeInt32(this.buf, val & -1, this.pos); writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); this.pos += 8; }, writeSFixed64: function(val) { this.realloc(8); writeInt32(this.buf, val & -1, this.pos); writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4); this.pos += 8; }, writeVarint: function(val) { val = +val || 0; if (val > 0xfffffff || val < 0) { writeBigVarint(val, this); return; } this.realloc(4); this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return; this.buf[this.pos++] = (val >>> 7) & 0x7f; }, writeSVarint: function(val) { this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2); }, writeBoolean: function(val) { this.writeVarint(Boolean(val)); }, writeString: function(str) { str = String(str); this.realloc(str.length * 4); this.pos++; // reserve 1 byte for short string length var startPos = this.pos; // write the string directly to the buffer and see how much was written this.pos = writeUtf8(this.buf, str, this.pos); var len = this.pos - startPos; if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position this.pos = startPos - 1; this.writeVarint(len); this.pos += len; }, writeFloat: function(val) { this.realloc(4); ieee754.write(this.buf, val, this.pos, true, 23, 4); this.pos += 4; }, writeDouble: function(val) { this.realloc(8); ieee754.write(this.buf, val, this.pos, true, 52, 8); this.pos += 8; }, writeBytes: function(buffer) { var len = buffer.length; this.writeVarint(len); this.realloc(len); for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i]; }, writeRawMessage: function(fn, obj) { this.pos++; // reserve 1 byte for short message length // write the message directly to the buffer and see how much was written var startPos = this.pos; fn(obj, this); var len = this.pos - startPos; if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position this.pos = startPos - 1; this.writeVarint(len); this.pos += len; }, writeMessage: function(tag, fn, obj) { this.writeTag(tag, Pbf.Bytes); this.writeRawMessage(fn, obj); }, writePackedVarint: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr); }, writePackedSVarint: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr); }, writePackedBoolean: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr); }, writePackedFloat: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr); }, writePackedDouble: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr); }, writePackedFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr); }, writePackedSFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); }, writePackedFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr); }, writePackedSFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); }, writeBytesField: function(tag, buffer) { this.writeTag(tag, Pbf.Bytes); this.writeBytes(buffer); }, writeFixed32Field: function(tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeFixed32(val); }, writeSFixed32Field: function(tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeSFixed32(val); }, writeFixed64Field: function(tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeFixed64(val); }, writeSFixed64Field: function(tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeSFixed64(val); }, writeVarintField: function(tag, val) { this.writeTag(tag, Pbf.Varint); this.writeVarint(val); }, writeSVarintField: function(tag, val) { this.writeTag(tag, Pbf.Varint); this.writeSVarint(val); }, writeStringField: function(tag, str) { this.writeTag(tag, Pbf.Bytes); this.writeString(str); }, writeFloatField: function(tag, val) { this.writeTag(tag, Pbf.Fixed32); this.writeFloat(val); }, writeDoubleField: function(tag, val) { this.writeTag(tag, Pbf.Fixed64); this.writeDouble(val); }, writeBooleanField: function(tag, val) { this.writeVarintField(tag, Boolean(val)); } }; function readPackedEnd(pbf) { return pbf.type === Pbf.Bytes ? pbf.readVarint() + pbf.pos : pbf.pos + 1; } function makeRoomForExtraLength(startPos, len, pbf) { var extraLen = len <= 0x3fff ? 1 : len <= 0x1fffff ? 2 : len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7)); // if 1 byte isn't enough for encoding message length, shift the data to the right pbf.realloc(extraLen); for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i]; } function writePackedVarint(arr, pbf) { arr.forEach(pbf.writeVarint, pbf); } function writePackedSVarint(arr, pbf) { arr.forEach(pbf.writeSVarint, pbf); } function writePackedFloat(arr, pbf) { arr.forEach(pbf.writeFloat, pbf); } function writePackedDouble(arr, pbf) { arr.forEach(pbf.writeDouble, pbf); } function writePackedBoolean(arr, pbf) { arr.forEach(pbf.writeBoolean, pbf); } function writePackedFixed32(arr, pbf) { arr.forEach(pbf.writeFixed32, pbf); } function writePackedSFixed32(arr, pbf) { arr.forEach(pbf.writeSFixed32, pbf); } function writePackedFixed64(arr, pbf) { arr.forEach(pbf.writeFixed64, pbf); } function writePackedSFixed64(arr, pbf) { arr.forEach(pbf.writeSFixed64, pbf); } function classifyRings(rings) { // Classifies an array of rings into polygons with outer rings and holes if (rings.length <= 1) return [rings]; const polygons = []; let polygon, ccw; rings.forEach(ring => { const area = signedArea(ring); if (area === 0) return; if (ccw === undefined) ccw = area < 0; if (ccw === area < 0) { if (polygon) polygons.push(polygon); polygon = [ring]; } else { polygon.push(ring); } }); if (polygon) polygons.push(polygon); return polygons; } function signedArea(ring) { const xmul = (p1, p2) => (p2.x - p1.x) * (p1.y + p2.y); const initialValue = xmul(ring[0], ring[ring.length - 1]); return ring.slice(1) // NOTE: skips ring[0], shifts index .reduce( (sum, p1, i) => sum + xmul(p1, ring[i]), initialValue ); } const types = ["Unknown", "Point", "LineString", "Polygon"]; function toGeoJSON(size, sx = 0, sy = 0) { // Input size is the side length of the (square) area over which the // coordinate space of this tile [0, this.extent] will be rendered. // Input sx, sy is the origin (top left corner) of the output coordinates // within the (size x size) rendered area of the full tile. size = size || this.extent; const scale = size / this.extent; let coords = this.loadGeometry(); let type = types[this.type]; function project(line) { return line.map(p => [p.x * scale - sx, p.y * scale - sy]); } switch (type) { case "Point": coords = project( coords.map(p => p[0]) ); break; case "LineString": coords = coords.map(project); break; case "Polygon": coords = classifyRings(coords); coords = coords.map(polygon => polygon.map(project)); break; } if (coords.length === 1) { coords = coords[0]; } else { type = "Multi" + type; } const result = { type: "Feature", geometry: { type: type, coordinates: coords }, properties: this.properties }; if ("id" in this) result.id = this.id; return result; } function VectorTileFeature(pbf, end, extent, keys, values) { // Public this.properties = {}; this.extent = extent; this.type = 0; // Private this._pbf = pbf; this._geometry = -1; this._keys = keys; this._values = values; pbf.readFields(readFeature, this, end); } function readFeature(tag, feature, pbf) { if (tag == 1) feature.id = pbf.readVarint(); else if (tag == 2) readTag(pbf, feature); else if (tag == 3) feature.type = pbf.readVarint(); else if (tag == 4) feature._geometry = pbf.pos; } function readTag(pbf, feature) { const end = pbf.readVarint() + pbf.pos; const { _keys, _values } = feature; while (pbf.pos < end) { const key = _keys[pbf.readVarint()]; const value = _values[pbf.readVarint()]; feature.properties[key] = value; } } VectorTileFeature.prototype.loadGeometry = function() { const pbf = this._pbf; pbf.pos = this._geometry; const end = pbf.readVarint() + pbf.pos; let cmd = 1; let length = 0; let x = 0; let y = 0; const lines = []; let line; while (pbf.pos < end) { if (length <= 0) { const cmdLen = pbf.readVarint(); cmd = cmdLen & 0x7; length = cmdLen >> 3; } length--; if (cmd === 1 || cmd === 2) { x += pbf.readSVarint(); y += pbf.readSVarint(); if (cmd === 1) { // moveTo if (line) lines.push(line); line = []; } line.push({ x, y }); } else if (cmd === 7) { // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 if (line) line.push({ // closePolygon x: line[0].x, y: line[0].y }); } else { throw Error("unknown command " + cmd); } } if (line) lines.push(line); return lines; }; VectorTileFeature.prototype.bbox = function() { const pbf = this._pbf; pbf.pos = this._geometry; const end = pbf.readVarint() + pbf.pos; let cmd = 1; let length = 0; let x = 0; let y = 0; let x1 = Infinity; let x2 = -Infinity; let y1 = Infinity; let y2 = -Infinity; while (pbf.pos < end) { if (length <= 0) { const cmdLen = pbf.readVarint(); cmd = cmdLen & 0x7; length = cmdLen >> 3; } length--; if (cmd === 1 || cmd === 2) { x += pbf.readSVarint(); y += pbf.readSVarint(); if (x < x1) x1 = x; if (x > x2) x2 = x; if (y < y1) y1 = y; if (y > y2) y2 = y; } else if (cmd !== 7) { throw Error("unknown command " + cmd); } } return [x1, y1, x2, y2]; }; VectorTileFeature.types = types; VectorTileFeature.prototype.toGeoJSON = toGeoJSON; function VectorTileLayer(pbf, end) { // Public this.version = 1; this.name = null; this.extent = 4096; this.length = 0; // Private this._pbf = pbf; this._keys = []; this._values = []; this._features = []; pbf.readFields(readLayer, this, end); this.length = this._features.length; } function readLayer(tag, layer, pbf) { if (tag === 15) layer.version = pbf.readVarint(); else if (tag === 1) layer.name = pbf.readString(); else if (tag === 5) layer.extent = pbf.readVarint(); else if (tag === 2) layer._features.push(pbf.pos); else if (tag === 3) layer._keys.push(pbf.readString()); else if (tag === 4) layer._values.push(readValueMessage(pbf)); } function readValueMessage(pbf) { let value = null; const end = pbf.readVarint() + pbf.pos; while (pbf.pos < end) { const tag = pbf.readVarint() >> 3; value = tag === 1 ? pbf.readString() : tag === 2 ? pbf.readFloat() : tag === 3 ? pbf.readDouble() : tag === 4 ? pbf.readVarint64() : tag === 5 ? pbf.readVarint() : tag === 6 ? pbf.readSVarint() : tag === 7 ? pbf.readBoolean() : null; } return value; } // return feature 'i' from this layer as a 'VectorTileFeature' VectorTileLayer.prototype.feature = function(i) { const { _features, extent, _pbf, _keys, _values } = this; const lastFeature = _features.length - 1; if (i < 0 || i > lastFeature) throw Error("feature index out of bounds"); _pbf.pos = _features[i]; const end = _pbf.readVarint() + _pbf.pos; return new VectorTileFeature(_pbf, end, extent, _keys, _values); }; VectorTileLayer.prototype.toGeoJSON = function(size, sx, sy) { const features = Array.from(Array(this._features.length), (v, i) => { return this.feature(i).toGeoJSON(size, sx, sy); }); return { type: "FeatureCollection", features, extent: this.extent }; }; function VectorTile(pbf, end) { this.layers = pbf.readFields(readTile, {}, end); } function readTile(tag, layers, pbf) { if (tag === 3) { const layer = new VectorTileLayer(pbf, pbf.readVarint() + pbf.pos); if (layer.length) layers[layer.name] = layer; } } function xhrGet(href, type, callback) { const req = new XMLHttpRequest(); req.responseType = type; req.onerror = errHandler; req.onabort = errHandler; req.onload = loadHandler; req.open("get", href); req.send(); function errHandler(e) { return callback(xhrErr("ended with an ", e.type)); } function loadHandler() { const { responseType, status, response } = req; const err = (responseType !== type) ? xhrErr("Expected responseType ", type, ", got ", responseType) : (status !== 200) ? xhrErr("HTTP ", status, " error from ", href) : null; return callback(err, response); } return req; // Request can be aborted via req.abort() } function xhrErr(...strings) { return "XMLHttpRequest: " + strings.join(""); } function initMVT(source) { const getURL = initUrlFunc(source.tiles); // TODO: use VectorTile.extent. Requires changes in dependencies, dependents const size = 512; return function(tileCoords, callback) { const { z, x, y } = tileCoords; const dataHref = getURL(z, x, y); return xhrGet(dataHref, "arraybuffer", parseMVT); function parseMVT(err, data) { if (err) return callback(err, data); const tile = new VectorTile(new Pbf(data)); const json = Object.values(tile.layers) .reduce((d, l) => (d[l.name] = l.toGeoJSON(size), d), {}); callback(null, json); } }; } function initUrlFunc(endpoints) { // Use a different endpoint for each request let index = 0; return function(z, x, y) { index = (index + 1) % endpoints.length; const endpoint = endpoints[index]; return endpoint.replace(/{z}/, z).replace(/{x}/, x).replace(/{y}/, y); }; } // calculate simplification data using optimized Douglas-Peucker algorithm function simplify(coords, first, last, sqTolerance) { var maxSqDist = sqTolerance; var mid = (last - first) >> 1; var minPosToMid = last - first; var index; var ax = coords[first]; var ay = coords[first + 1]; var bx = coords[last]; var by = coords[last + 1]; for (var i = first + 3; i < last; i += 3) { var d = getSqSegDist(coords[i], coords[i + 1], ax, ay, bx, by); if (d > maxSqDist) { index = i; maxSqDist = d; } else if (d === maxSqDist) { // a workaround to ensure we choose a pivot close to the middle of the list, // reducing recursion depth, for certain degenerate inputs // https://github.com/mapbox/geojson-vt/issues/104 var posToMid = Math.abs(i - mid); if (posToMid < minPosToMid) { index = i; minPosToMid = posToMid; } } } if (maxSqDist > sqTolerance) { if (index - first > 3) simplify(coords, first, index, sqTolerance); coords[index + 2] = maxSqDist; if (last - index > 3) simplify(coords, index, last, sqTolerance); } } // square distance from a point to a segment function getSqSegDist(px, py, x, y, bx, by) { var dx = bx - x; var dy = by - y; if (dx !== 0 || dy !== 0) { var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy); if (t > 1) { x = bx; y = by; } else if (t > 0) { x += dx * t; y += dy * t; } } dx = px - x; dy = py - y; return dx * dx + dy * dy; } function createFeature(id, type, geom, tags) { var feature = { id: typeof id === 'undefined' ? null : id, type: type, geometry: geom, tags: tags, minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }; calcBBox(feature); return feature; } function calcBBox(feature) { var geom = feature.geometry; var type = feature.type; if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') { calcLineBBox(feature, geom); } else if (type === 'Polygon' || type === 'MultiLineString') { for (var i = 0; i < geom.length; i++) { calcLineBBox(feature, geom[i]); } } else if (type === 'MultiPolygon') { for (i = 0; i < geom.length; i++) { for (var j = 0; j < geom[i].length; j++) { calcLineBBox(feature, geom[i][j]); } } } } function calcLineBBox(feature, geom) { for (var i = 0; i < geom.length; i += 3) { feature.minX = Math.min(feature.minX, geom[i]); feature.minY = Math.min(feature.minY, geom[i + 1]); feature.maxX = Math.max(feature.maxX, geom[i]); feature.maxY = Math.max(feature.maxY, geom[i + 1]); } } // converts GeoJSON feature into an intermediate projected JSON vector format with simplification data function convert(data, options) { var features = []; if (data.type === 'FeatureCollection') { for (var i = 0; i < data.features.length; i++) { convertFeature(features, data.features[i], options, i); } } else if (data.type === 'Feature') { convertFeature(features, data, options); } else { // single geometry or a geometry collection convertFeature(features, {geometry: data}, options); } return features; } function convertFeature(features, geojson, options, index) { if (!geojson.geometry) return; var coords = geojson.geometry.coordinates; var type = geojson.geometry.type; var tolerance = Math.pow(options.tolerance / ((1 << options.maxZoom) * options.extent), 2); var geometry = []; var id = geojson.id; if (options.promoteId) { id = geojson.properties[options.promoteId]; } else if (options.generateId) { id = index || 0; } if (type === 'Point') { convertPoint(coords, geometry); } else if (type === 'MultiPoint') { for (var i = 0; i < coords.length; i++) { convertPoint(coords[i], geometry); } } else if (type === 'LineString') { convertLine(coords, geometry, tolerance, false); } else if (type === 'MultiLineString') { if (options.lineMetrics) { // explode into linestrings to be able to track metrics for (i = 0; i < coords.length; i++) { geometry = []; convertLine(coords[i], geometry, tolerance, false); features.push(createFeature(id, 'LineString', geometry, geojson.properties)); } return; } else { convertLines(coords, geometry, tolerance, false); } } else if (type === 'Polygon') { convertLines(coords, geometry, tolerance, true); } else if (type === 'MultiPolygon') { for (i = 0; i < coords.length; i++) { var polygon = []; convertLines(coords[i], polygon, tolerance, true); geometry.push(polygon); } } else if (type === 'GeometryCollection') { for (i = 0; i < geojson.geometry.geometries.length; i++) { convertFeature(features, { id: id, geometry: geojson.geometry.geometries[i], properties: geojson.properties }, options, index); } return; } else { throw new Error('Input data is not a valid GeoJSON object.'); } features.push(createFeature(id, type, geometry, geojson.properties)); } function convertPoint(coords, out) { out.push(projectX(coords[0])); out.push(projectY(coords[1])); out.push(0); } function convertLine(ring, out, tolerance, isPolygon) { var x0, y0; var size = 0; for (var j = 0; j < ring.length; j++) { var x = projectX(ring[j][0]); var y = projectY(ring[j][1]); out.push(x); out.push(y); out.push(0); if (j > 0) { if (isPolygon) { size += (x0 * y - x * y0) / 2; // area } else { size += Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)); // length } } x0 = x; y0 = y; } var last = out.length - 3; out[2] = 1; simplify(out, 0, last, tolerance); out[last + 2] = 1; out.size = Math.abs(size); out.start = 0; out.end = out.size; } function convertLines(rings, out, tolerance, isPolygon) { for (var i = 0; i < rings.length; i++) { var geom = []; convertLine(rings[i], geom, tolerance, isPolygon); out.push(geom); } } function projectX(x) { return x / 360 + 0.5; } function projectY(y) { var sin = Math.sin(y * Math.PI / 180); var y2 = 0.5 - 0.25 * Math.log((1 + sin) / (1 - sin)) / Math.PI; return y2 < 0 ? 0 : y2 > 1 ? 1 : y2; } /* clip features between two axis-parallel lines: * | | * ___|___ | / * / | \____|____/ * | | */ function clip(features, scale, k1, k2, axis, minAll, maxAll, options) { k1 /= scale; k2 /= scale; if (minAll >= k1 && maxAll < k2) return features; // trivial accept else if (maxAll < k1 || minAll >= k2) return null; // trivial reject var clipped = []; for (var i = 0; i < features.length; i++) { var feature = features[i]; var geometry = feature.geometry; var type = feature.type; var min = axis === 0 ? feature.minX : feature.minY; var max = axis === 0 ? feature.maxX : feature.maxY; if (min >= k1 && max < k2) { // trivial accept clipped.push(feature); continue; } else if (max < k1 || min >= k2) { // trivial reject continue; } var newGeometry = []; if (type === 'Point' || type === 'MultiPoint') { clipPoints(geometry, newGeometry, k1, k2, axis); } else if (type === 'LineString') { clipLine(geometry, newGeometry, k1, k2, axis, false, options.lineMetrics); } else if (type === 'MultiLineString') { clipLines(geometry, newGeometry, k1, k2, axis, false); } else if (type === 'Polygon') { clipLines(geometry, newGeometry, k1, k2, axis, true); } else if (type === 'MultiPolygon') { for (var j = 0; j < geometry.length; j++) { var polygon = []; clipLines(geometry[j], polygon, k1, k2, axis, true); if (polygon.length) { newGeometry.push(polygon); } } } if (newGeometry.length) { if (options.lineMetrics && type === 'LineString') { for (j = 0; j < newGeometry.length; j++) { clipped.push(createFeature(feature.id, type, newGeometry[j], feature.tags)); } continue; } if (type === 'LineString' || type === 'MultiLineString') { if (newGeometry.length === 1) { type = 'LineString'; newGeometry = newGeometry[0]; } else { type = 'MultiLineString'; } } if (type === 'Point' || type === 'MultiPoint') { type = newGeometry.length === 3 ? 'Point' : 'MultiPoint'; } clipped.push(createFeature(feature.id, type, newGeometry, feature.tags)); } } return clipped.length ? clipped : null; } function clipPoints(geom, newGeom, k1, k2, axis) { for (var i = 0; i < geom.length; i += 3) { var a = geom[i + axis]; if (a >= k1 && a <= k2) { newGeom.push(geom[i]); newGeom.push(geom[i + 1]); newGeom.push(geom[i + 2]); } } } function clipLine(geom, newGeom, k1, k2, axis, isPolygon, trackMetrics) { var slice = newSlice(geom); var intersect = axis === 0 ? intersectX : intersectY; var len = geom.start; var segLen, t; for (var i = 0; i < geom.length - 3; i += 3) { var ax = geom[i]; var ay = geom[i + 1]; var az = geom[i + 2]; var bx = geom[i + 3]; var by = geom[i + 4]; var a = axis === 0 ? ax : ay; var b = axis === 0 ? bx : by; var exited = false; if (trackMetrics) segLen = Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2)); if (a < k1) { // ---|--> | (line enters the clip region from the left) if (b > k1) { t = intersect(slice, ax, ay, bx, by, k1); if (trackMetrics) slice.start = len + segLen * t; } } else if (a > k2) { // | <--|--- (line enters the clip region from the right) if (b < k2) { t = intersect(slice, ax, ay, bx, by, k2); if (trackMetrics) slice.start = len + segLen * t; } } else { addPoint(slice, ax, ay, az); } if (b < k1 && a >= k1) { // <--|--- | or <--|-----|--- (line exits the clip region on the left) t = intersect(slice, ax, ay, bx, by, k1); exited = true; } if (b > k2 && a <= k2) { // | ---|--> or ---|-----|--> (line exits the clip region on the right) t = intersect(slice, ax, ay, bx, by, k2); exited = true; } if (!isPolygon && exited) { if (trackMetrics) slice.end = len + segLen * t; newGeom.push(slice); slice = newSlice(geom); } if (trackMetrics) len += segLen; } // add the last point var last = geom.length - 3; ax = geom[last]; ay = geom[last + 1]; az = geom[last + 2]; a = axis === 0 ? ax : ay; if (a >= k1 && a <= k2) addPoint(slice, ax, ay, az); // close the polygon if its endpoints are not the same after clipping last = slice.length - 3; if (isPolygon && last >= 3 && (slice[last] !== slice[0] || slice[last + 1] !== slice[1])) { addPoint(slice, slice[0], slice[1], slice[2]); } // add the final slice if (slice.length) { newGeom.push(slice); } } function newSlice(line) { var slice = []; slice.size = line.size; slice.start = line.start; slice.end = line.end; return slice; } function clipLines(geom, newGeom, k1, k2, axis, isPolygon) { for (var i = 0; i < geom.length; i++) { clipLine(geom[i], newGeom, k1, k2, axis, isPolygon, false); } } function addPoint(out, x, y, z) { out.push(x); out.push(y); out.push(z); } function intersectX(out, ax, ay, bx, by, x) { var t = (x - ax) / (bx - ax); out.push(x); out.push(ay + (by - ay) * t); out.push(1); return t; } function intersectY(out, ax, ay, bx, by, y) { var t = (y - ay) / (by - ay); out.push(ax + (bx - ax) * t); out.push(y); out.push(1); return t; } function wrap(features, options) { var buffer = options.buffer / options.extent; var merged = features; var left = clip(features, 1, -1 - buffer, buffer, 0, -1, 2, options); // left world copy var right = clip(features, 1, 1 - buffer, 2 + buffer, 0, -1, 2, options); // right world copy if (left || right) { merged = clip(features, 1, -buffer, 1 + buffer, 0, -1, 2, options) || []; // center world copy if (left) merged = shiftFeatureCoords(left, 1).concat(merged); // merge left into center if (right) merged = merged.concat(shiftFeatureCoords(right, -1)); // merge right into center } return merged; } function shiftFeatureCoords(features, offset) { var newFeatures = []; for (var i = 0; i < features.length; i++) { var feature = features[i], type = feature.type; var newGeometry; if (type === 'Point' || type === 'MultiPoint' || type === 'LineString') { newGeometry = shiftCoords(feature.geometry, offset); } else if (type === 'MultiLineString' || type === 'Polygon') { newGeometry = []; for (var j = 0; j < feature.geometry.length; j++) { newGeometry.push(shiftCoords(feature.geometry[j], offset)); } } else if (type === 'MultiPolygon') { newGeometry = []; for (j = 0; j < feature.geometry.length; j++) { var newPolygon = []; for (var k = 0; k < feature.geometry[j].length; k++) { newPolygon.push(shiftCoords(feature.geometry[j][k], offset)); } newGeometry.push(newPolygon); } } newFeatures.push(createFeature(feature.id, type, newGeometry, feature.tags)); } return newFeatures; } function shiftCoords(points, offset) { var newPoints = []; newPoints.size = points.size; if (points.start !== undefined) { newPoints.start = points.start; newPoints.end = points.end; } for (var i = 0; i < points.length; i += 3) { newPoints.push(points[i] + offset, points[i + 1], points[i + 2]); } return newPoints; } // Transforms the coordinates of each feature in the given tile from // mercator-projected space into (extent x extent) tile space. function transformTile(tile, extent) { if (tile.transformed) return tile; var z2 = 1 << tile.z, tx = tile.x, ty = tile.y, i, j, k; for (i = 0; i < tile.features.length; i++) { var feature = tile.features[i], geom = feature.geometry, type = feature.type; feature.geometry = []; if (type === 1) { for (j = 0; j < geom.length; j += 2) { feature.geometry.push(transformPoint(geom[j], geom[j + 1], extent, z2, tx, ty)); } } else { for (j = 0; j < geom.length; j++) { var ring = []; for (k = 0; k < geom[j].length; k += 2) { ring.push(transformPoint(geom[j][k], geom[j][k + 1], extent, z2, tx, ty)); } feature.geometry.push(ring); } } } tile.transformed = true; return tile; } function transformPoint(x, y, extent, z2, tx, ty) { return [ Math.round(extent * (x * z2 - tx)), Math.round(extent * (y * z2 - ty))]; } function createTile(features, z, tx, ty, options) { var tolerance = z === options.maxZoom ? 0 : options.tolerance / ((1 << z) * options.extent); var tile = { features: [], numPoints: 0, numSimplified: 0, numFeatures: 0, source: null, x: tx, y: ty, z: z, transformed: false, minX: 2, minY: 1, maxX: -1, maxY: 0 }; for (var i = 0; i < features.length; i++) { tile.numFeatures++; addFeature(tile, features[i], tolerance, options); var minX = features[i].minX; var minY = features[i].minY; var maxX = features[i].maxX; var maxY = features[i].maxY; if (minX < tile.minX) tile.minX = minX; if (minY < tile.minY) tile.minY = minY; if (maxX > tile.maxX) tile.maxX = maxX; if (maxY > tile.maxY) tile.maxY = maxY; } return tile; } function addFeature(tile, feature, tolerance, options) { var geom = feature.geometry, type = feature.type, simplified = []; if (type === 'Point' || type === 'MultiPoint') { for (var i = 0; i < geom.length; i += 3) { simplified.push(geom[i]); simplified.push(geom[i + 1]); tile.numPoints++; tile.numSimplified++; } } else if (type === 'LineString') { addLine(simplified, geom, tile, tolerance, false, false); } else if (type === 'MultiLineString' || type === 'Polygon') { for (i = 0; i < geom.length; i++) { addLine(simplified, geom[i], tile, tolerance, type === 'Polygon', i === 0); } } else if (type === 'MultiPolygon') { for (var k = 0; k < geom.length; k++) { var polygon = geom[k]; for (i = 0; i < polygon.length; i++) { addLine(simplified, polygon[i], tile, tolerance, true, i === 0); } } } if (simplified.length) { var tags = feature.tags || null; if (type === 'LineString' && options.lineMetrics) { tags = {}; for (var key in feature.tags) tags[key] = feature.tags[key]; tags['mapbox_clip_start'] = geom.start / geom.size; tags['mapbox_clip_end'] = geom.end / geom.size;