Skip to content
This repository has been archived by the owner on Jan 31, 2023. It is now read-only.

Commit

Permalink
implement tile cache
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvinabrokwa committed Sep 28, 2017
1 parent 799618f commit 44aa7d9
Show file tree
Hide file tree
Showing 6 changed files with 323 additions and 142 deletions.
243 changes: 140 additions & 103 deletions Leaflet.VectorTiles.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import Tile from './tile';
import TileCache from './tile_cache';
import Feature from './feature';
import { VectorTile } from '@mapbox/vector-tile';
import Pbf from 'pbf';


/**
* Ensure that the tile is within the normal range
* Checks whether the given tile coordinates is within the normal range
*
* @param {Object} coords
* @param {number} coords.x
* @param {number} coords.y
* @param {number} coords.z
* @returns {boolean} true if the coordinates correspond to a valid tile
*/
function tileIsValid(coords) {
if (coords.x < 0 || coords.y < 0 || coords.z < 0) {
Expand Down Expand Up @@ -92,6 +99,9 @@ L.VectorTiles = L.GridLayer.extend({
// mark a tile for destruction in case it is unloaded before it loads
this._toDestroy = {};

// tile cache
this._tileCache = new TileCache(50, this._debug);

// mark a tile as loaded
// this is needed because if a tile is unloaded before its finished loading
// we need to wait for it to finish loading before we can clean up
Expand Down Expand Up @@ -164,11 +174,9 @@ L.VectorTiles = L.GridLayer.extend({
},

/**
* This method:
* - fetches the data for the tile
* - adds all of its features to the map
* - adds its features to the internal data structure
* - inserts its features into the a spatial tree
* This method fetches vector tile data from the network and creates a tile
* in the event that the tile is cached and the HTTP request returns a 304, it
* uses the tile from the cache
*
* @param {Object} coords
* @param {Function} done
Expand Down Expand Up @@ -198,120 +206,148 @@ L.VectorTiles = L.GridLayer.extend({
return;
}

const tile = new Tile(coords.x, coords.y, coords.z);
let tile = this._tileCache.get(tileKey);

if (!tile) {
tile = new Tile(coords.x, coords.y, coords.z);
}

this._vectorTiles[tileKey] = tile;

// fetch vector tile data for this tile
const url = L.Util.template(this._url, coords);
fetch(url)
.then(res => res.blob())
.then(blob => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => {
resolve(new VectorTile(new Pbf(reader.result)));
}
reader.readAsArrayBuffer(blob);
});
})
.then(vtTile => {
for (const vtLayerName in vtTile.layers) {
// break out if this tile has already be unloaded
if (this._toDestroy[tileKey]) {
if (this._debug) {
console.log('Tile', coords, 'stopped while loading');
}
break;
}

const vtLayer = vtTile.layers[vtLayerName];

for (let j = 0; j < vtLayer.length; j++) {
// break out if this tile has already be unloaded
if (this._toDestroy[tileKey]) {
if (this._debug) {
console.log('Tile', coords, 'stopped while loading');
}
break;
}

const vtFeature = vtLayer.feature(j);

const geojson = vtFeature.toGeoJSON(coords.x, coords.y, coords.z);
const id = this.options.getFeatureId(geojson);
const layer = this._geojsonToLayer(geojson);
if (!layer) {
// unsupported geometry type
continue;
}

// create the Feature
const feature = new Feature(id, geojson, layer);

// add it to the tile
tile.addFeature(feature);
const headers = new Headers();
if (tile.timeCreated) {
headers.append('If-Modified-Since', tile.timeCreated);
}
fetch(url, { headers })
.then(res => {
// use cached tile
if (res.status == '304') {
// add tile to FeatureGroup to add to map
tile.addTo(this._featureGroup);
return;
}

// calculate its style and if its visible
const style = {};
let onMap = true;
let prop;
// record time that tile was retrieved
tile.timeCreated = new Date().getTime();

// property based styles
for (prop in geojson.properties) {
// apply style from options
if (prop in this.options.style
&& geojson.properties[prop] in this.options.style[prop]) {
Object.assign(style, this.options.style[prop][geojson.properties[prop]]);
// parse new vector tile
res.blob()
.then(blob => {
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => {
resolve(new VectorTile(new Pbf(reader.result)));
}

// apply style modifications
if (prop in this._propertyStyles
&& geojson.properties[prop] in this._propertyStyles[prop]) {
Object.assign(style, this._propertyStyles[prop][geojson.properties[prop]]);
reader.readAsArrayBuffer(blob);
});
})
.then(function parseVectorTile(vtTile) {
for (const vtLayerName in vtTile.layers) {
// break out if this tile has already be unloaded
if (this._toDestroy[tileKey]) {
if (this._debug) {
console.log('Tile', coords, 'stopped while loading');
}
break;
}

// put on map based on property
if (prop in this._propertyOnMap
&& geojson.properties[prop] in this._propertyOnMap[prop]) {
onMap = this._propertyOnMap[prop][geojson.properties[prop]];
const vtLayer = vtTile.layers[vtLayerName];

for (let j = 0; j < vtLayer.length; j++) {
// break out if this tile has already be unloaded
if (this._toDestroy[tileKey]) {
if (this._debug) {
console.log('Tile', coords, 'stopped while loading');
}
break;
}

const vtFeature = vtLayer.feature(j);

const geojson = vtFeature.toGeoJSON(coords.x, coords.y, coords.z);
const id = this.options.getFeatureId(geojson);
const layer = this._geojsonToLayer(geojson);
if (!layer) {
// unsupported geometry type
continue;
}

// create the Feature
const feature = new Feature(id, geojson, layer);

// add it to the tile
tile.addFeature(feature);

// calculate its style and if its visible
const style = {};
let onMap = true;
let prop;

// property based styles
for (prop in geojson.properties) {
// apply style from options
if (prop in this.options.style
&& geojson.properties[prop] in this.options.style[prop]) {
Object.assign(style, this.options.style[prop][geojson.properties[prop]]);
}

// apply style modifications
if (prop in this._propertyStyles
&& geojson.properties[prop] in this._propertyStyles[prop]) {
Object.assign(style, this._propertyStyles[prop][geojson.properties[prop]]);
}

// put on map based on property
if (prop in this._propertyOnMap
&& geojson.properties[prop] in this._propertyOnMap[prop]) {
onMap = this._propertyOnMap[prop][geojson.properties[prop]];
}
}

// feature based styles
if (id in this._featureStyles) {
Object.assign(style, this._featureStyles[id]);
}

feature.setStyle(style);

// feature based on map
if (id in this._featureOnMap) {
onMap = this._featureOnMap[id];
}

feature.putOnMap(onMap);
}
}

// feature based styles
if (id in this._featureStyles) {
Object.assign(style, this._featureStyles[id]);
}

feature.setStyle(style);
if (!this._toDestroy[tileKey]) {
// called when all features have been added to the tile
tile.init();

// feature based on map
if (id in this._featureOnMap) {
onMap = this._featureOnMap[id];
// add the featureGroup of this tile to the map
tile.addTo(this._featureGroup);
}

feature.putOnMap(onMap);
}
}

if (!this._toDestroy[tileKey]) {
// called when all features have been added to the tile
tile.init();

// add the featureGroup of this tile to the map
tile.featureGroup.addTo(this._featureGroup);
}
// mark tile as loaded
tile.markAsLoaded();

// mark tile as loaded
tile.markAsLoaded();
// cache the tile
this._tileCache.put(tileKey, tile);

if (this._debug) {
console.log('tile', coords, 'loaded');
}
if (this._debug) {
console.log('tile loaded:', coords, tile.featureGroup.getLayers().length, ' features');
}

// the tile has ~actually~ loaded
// the `tileload` event doesn't fire when `tileunload` fires first
// but in our case we still need to be finished loading to clean up
this.fire('vt_tileload', { coords });
// the tile has ~actually~ loaded
// the `tileload` event doesn't fire when `tileunload` fires first
// but in our case we still need to be finished loading to clean up
this.fire('vt_tileload', { coords });
}.bind(this))
.catch(err => {
console.log(err);
});
})
.catch(err => {
console.log(err);
Expand All @@ -330,9 +366,10 @@ L.VectorTiles = L.GridLayer.extend({
console.log("destroying tile:", coords);
}
const tileKey = this._tileCoordsToKey(coords);
const tile = this._vectorTiles[tileKey];

// remove this tile's FeatureGroup from the map
this._featureGroup.removeLayer(this._vectorTiles[tileKey].featureGroup);
tile.removeFrom(this._featureGroup);

// delete the tile's data
delete this._vectorTiles[tileKey];
Expand Down
2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<title>Leaflet.VectorTiles Test Page</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet@1.2.0/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.2.0/leaflet-src.js"></script>
<script src="http://localhost:9966/dist/leaflet.vector-tiles.js"></script>
<style>
html, body, input { font-family: Helvetica; }
Expand Down
3 changes: 2 additions & 1 deletion example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ function main(geojson) {
lat: 0,
lng: 0
},
zoom: 2
zoom: 2,
preferCanvas: true,
});

//L.tileLayer('http://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
Expand Down
5 changes: 4 additions & 1 deletion example/vt_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@ const pointTileIndex = geojsonvt(pointGeoj, {
const emptyFeatCollection = featureCollection([]);

app.get('/:z/:x/:y', (req, res) => {
if (req.get('If-Modified-Since')) {
return res.status(304).send();
}
const [x, y, z] = [+req.params.x, +req.params.y, +req.params.z];
const countries = countryTileIndex.getTile(z, x, y) || emptyFeatCollection;
const points = pointTileIndex.getTile(z, x, y) || emptyFeatCollection;
const buff = vtpbf.fromGeojsonVt({ countries, points });
res.send(buff);
res.status(200).send(buff);
});

app.get('/geojson/:type', (req, res) => {
Expand Down
Loading

0 comments on commit 44aa7d9

Please sign in to comment.