diff --git a/examples/leaflet.html b/examples/leaflet.html new file mode 100644 index 0000000..5919abc --- /dev/null +++ b/examples/leaflet.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index e69de29..0000000 diff --git a/js/pmtiles.js b/js/pmtiles.js index 4a19326..eec54a6 100644 --- a/js/pmtiles.js +++ b/js/pmtiles.js @@ -1,11 +1,140 @@ (function (root, factory) { if (typeof define === 'function' && define.amd) { - define([], factory); + define([], factory) } else if (typeof module === 'object' && module.exports) { - module.exports = factory(); + module.exports = factory() } else { - root.pmtiles = factory(); + root.pmtiles = factory() } }(typeof self !== 'undefined' ? self : this, function () { - return {pmtiles:true}; + const getUint24 = (dataview, pos) => { + return (dataview.getUint16(pos+1,true) << 8) + dataview.getUint8(pos,true); + } + + const getUint48 = (dataview, pos) => { + return (dataview.getUint32(pos+2,true) << 16) + dataview.getUint16(pos,true); + } + + const parseHeader = dataview => { + var magic = dataview.getUint16(0,true) + // assert that the magic number (2 bytes) matches 19792 + var version = dataview.getUint16(2,true) + var json_size = dataview.getUint32(4,true) + var root_entries = dataview.getUint16(8,true) + return {version:version,json_size:json_size,root_entries:root_entries} + } + + const bytesToMap = dataview => { + let m = new Map() + for (var i = 0; i < dataview.byteLength; i+=17) { + var z_raw = dataview.getUint8(i,true) + var z = z_raw & 127 + var is_dir = z_raw >> 7 + var x = getUint24(dataview,i+1) + var y = getUint24(dataview,i+4) + var offset = getUint48(dataview,i+7) + var length = dataview.getUint32(i+13,true) + m.set(z + '_' + x + '_' + y,[offset,length,is_dir]) + } + return m + } + + class PMTiles { + constructor(url,ready) { + this.url = url + this.apex = fetch(this.url,{method:'HEAD',headers:{'Range':'bytes=0-511999'}}).then(resp => { + if (resp.status == 206) { // this does not work on Azure, it returns 200 instead of 206 + console.log("Check succeeded: server supports byte ranges") + return fetch(this.url,{headers:{'Range':'bytes=0-511999'}}).then(resp => { + return resp.arrayBuffer() + }).then(buf => { + const header = parseHeader(new DataView(buf,0,10)) + var dec = new TextDecoder("utf-8") + if (ready) { + ready(JSON.parse(dec.decode(new DataView(buf,10,header.json_size)))) + } + return bytesToMap(new DataView(buf,10+header.json_size,17*header.root_entries)) + }) + } + }) + // TODO make this LRU + this.leaves = new Map() + this.outstanding_requests = new Map() + } + + getLeaf = tilestr => { + return new Promise((resolve,reject) => { + if (this.leaves.has(tilestr)) { + resolve(this.leaves.get(tilestr)) + } else if (this.outstanding_requests.has(tilestr)) { + this.outstanding_requests.get(tilestr).push(resolve) + } else { + this.outstanding_requests.set(tilestr,[]) + this.apex.then(apex_map => { + if (apex_map.has(tilestr)) { + var val = apex_map.get(tilestr) + if (val[2] == 1) { // it is a directory + fetch(this.url, {headers:{'range':'bytes=' + val[0] + '-' + (val[0] + val[1]-1)}}).then(resp => { + return resp.arraybuffer() + }).then(buf => { + var map = bytestomap(buf,val[1]/17) + this.leaves.set(tilestr,map) + resolve(map) + this.outstanding_requests.get(tilestr).foreach(f => { + f(map) + }) + console.log("leaves: ", this.leaves.size) + }) + } + } + }) + } + }) + } + + leafletLayer = options => { + const self = this + var cls = L.GridLayer.extend({ + createTile: function(coords, done){ + var tile = document.createElement('img'); + var error + + self.apex.then(map => { + var strid = coords.z + '_' + coords.x + '_' + coords.y + if (map.has(strid)) { + var val = map.get(strid) + fetch(self.url,{headers:{'Range':'bytes=' + val[0] + '-' + (val[0]+val[1]-1)}}).then(resp => { + return resp.arrayBuffer() + }).then(buf => { + var blob = new Blob( [buf], { type: "image/png" } ); + var imageUrl = window.URL.createObjectURL(blob); + tile.src = imageUrl; + done(error,tile) + }) + } + }) + + return tile; + + }, + + _removeTile: function (key) { + var tile = this._tiles[key] + if (!tile) { return } + tile.el.width = 0 + tile.el.height = 0 + tile.el.deleted = true + L.DomUtil.remove(tile.el) + delete this._tiles[key] + this.fire('tileunload', { + tile: tile.el, + coords: this._keyToTileCoords(key) + }) + }, + }) + return new cls() + } + } + + return {PMTiles:PMTiles} })); \ No newline at end of file