From 6bce004193c4f540874565d9693569ba2fca089e Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Sun, 13 Mar 2016 21:23:24 +0100 Subject: [PATCH 1/8] let getJSONFeatures() always return an array This makes the code simpler. --- tile.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tile.js b/tile.js index 61e384b..61bd954 100644 --- a/tile.js +++ b/tile.js @@ -428,15 +428,13 @@ Tile.prototype = client.query(self.getDatabaseQuery(bbox_p), function(err, results) { var content = new Object(); - content.features = new Array(); self.trace('All database queries finished, generating JSON data object.'); content.features = self.getJSONFeatures(results); // catch tiles without data - if (!content.features) + if (content.features.length === 0) { - content.features = new Array(); self.debug('Vector tile contains no data.'); } @@ -695,10 +693,11 @@ Tile.prototype = // converts raw JSON features from database response to objects getJSONFeatures: function(data) { + var features = new Array(); + if (typeof data == undefined || data == null) - return []; + return features; - var features = new Array(); for (var i=0; i Date: Sun, 13 Mar 2016 22:11:14 +0100 Subject: [PATCH 2/8] use hardlinks to empty tile to save inodes and space --- tile.js | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/tile.js b/tile.js index 61bd954..abf6854 100644 --- a/tile.js +++ b/tile.js @@ -778,28 +778,16 @@ Tile.prototype = { self.debug('Bitmap tile empty.'); - fs.readFile('emptytile.png', function(err, data) + fs.link('emptytile.png', filepath + '/' + self.y + '.png', function(err) { - if (err) - { - self.warn('Could not read empty bitmap tile.'); - return process.nextTick(function() - { - callback(true); - }); - } + if (!err) + self.debug('Empty bitmap tile was stored.'); + else + self.debug('Could not link empty bitmap file.'); - fs.writeFile(filepath+'/'+self.y+'.png', data, {mode: 0777}, function(err) + return process.nextTick(function() { - if (!err) - self.debug('Empty bitmap tile was stored.'); - else - self.debug('Could not save empty bitmap file.'); - - return process.nextTick(function() - { - callback(true); - }); + callback(true); }); }); } From ed35b72751c34f144f9f9d03100aba74dfe73c5a Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Sun, 13 Mar 2016 22:26:26 +0100 Subject: [PATCH 3/8] do not mark vector and PNG tiles as executable --- tile.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tile.js b/tile.js index abf6854..37be3f1 100644 --- a/tile.js +++ b/tile.js @@ -156,7 +156,7 @@ Tile.prototype = } self.debug('Created path. Saving vector tile at path: '+filepath+file); - fs.writeFile(filepath+file, JSON.stringify(self.data), {mode: 0777}, function(err) + fs.writeFile(filepath+file, JSON.stringify(self.data), {mode: 0666}, function(err) { if (err) { @@ -490,7 +490,7 @@ Tile.prototype = } self.debug('Saving bitmap tile at path: '+filepath+'/'+self.y+'.png'); - var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0777}); + var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0666}); var stream = image.createPNGStream(); // write PNG data stream @@ -794,7 +794,7 @@ Tile.prototype = else { self.trace('Saving bitmap data...'); - var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0777}); + var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0666}); var stream = image.createPNGStream(); // write PNG data stream From b98e2e445db3e37f510bc3b4cef32a5e2fdc72a3 Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Sun, 13 Mar 2016 22:43:54 +0100 Subject: [PATCH 4/8] use getHeader() in one more place of BitmapTilerequest --- bitmaptilerequest.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bitmaptilerequest.js b/bitmaptilerequest.js index 657add3..9a3a53f 100644 --- a/bitmaptilerequest.js +++ b/bitmaptilerequest.js @@ -141,10 +141,7 @@ BitmapTilerequest.prototype = self.tile.getModifyTime(function(err, mtime) { - var header = { - 'Content-Type': 'image/png', - 'Cache-Control': 'public, max-age=3600' - }; + var header = self.getHeader(); if (!err) header['Last-Modified'] = mtime.toUTCString(); @@ -206,8 +203,6 @@ BitmapTilerequest.prototype = if (expired) header['Cache-Control'] = 'max-age=0'; - else - header['Cache-Control'] = 'public, max-age=3600'; self.tile.trace('Responding bitmap data...'); var stream = image.createPNGStream(); @@ -250,6 +245,7 @@ BitmapTilerequest.prototype = { return { 'Content-Type': 'image/png', + 'Cache-Control': 'public, max-age=3600', 'Server': 'node-tileserver/0.3' }; } From 2251302a5034cd0a4a88ae647f76fd9cca00e08d Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Mon, 14 Mar 2016 00:29:46 +0100 Subject: [PATCH 5/8] explicitely unlink tiles before overwriting them This makes sure e.g. emptytile.png does not accidentially gets overwritten. --- tile.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tile.js b/tile.js index 37be3f1..89fb703 100644 --- a/tile.js +++ b/tile.js @@ -489,8 +489,10 @@ Tile.prototype = return; } - self.debug('Saving bitmap tile at path: '+filepath+'/'+self.y+'.png'); - var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0666}); + var fname = filepath + '/' + self.y + '.png'; + self.debug('Saving bitmap tile at path: ' + fname); + fs.unlinkSync(fname) + var out = fs.createWriteStream(fname, {mode: 0666}); var stream = image.createPNGStream(); // write PNG data stream @@ -773,12 +775,15 @@ Tile.prototype = }); } + var fname = filepath + '/' + self.y + '.png'; + fs.unlinkSync(fname); + // store empty tile if no image could be rendered if (image == null) { self.debug('Bitmap tile empty.'); - fs.link('emptytile.png', filepath + '/' + self.y + '.png', function(err) + fs.link('emptytile.png', fname, function(err) { if (!err) self.debug('Empty bitmap tile was stored.'); @@ -794,7 +799,7 @@ Tile.prototype = else { self.trace('Saving bitmap data...'); - var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0666}); + var out = fs.createWriteStream(fname, {mode: 0666}); var stream = image.createPNGStream(); // write PNG data stream From 4b56c05264290e1f433bf0617041e4865d436cfc Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Tue, 15 Mar 2016 00:38:21 +0100 Subject: [PATCH 6/8] hardlink empty vector tiles to a common file While at it calculate the filename of the new vector file only once. --- emptytile.json | 1 + tile.js | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 emptytile.json diff --git a/emptytile.json b/emptytile.json new file mode 100644 index 0000000..5cb214d --- /dev/null +++ b/emptytile.json @@ -0,0 +1 @@ +{"features":[],"granularity":10} \ No newline at end of file diff --git a/tile.js b/tile.js index 89fb703..4eceefc 100644 --- a/tile.js +++ b/tile.js @@ -140,7 +140,7 @@ Tile.prototype = } var filepath = configuration.vtiledir+'/'+this.z+'/'+this.x+'/'; - var file = this.y+'.json'; + var file = filepath + this.y + '.json'; this.debug('Creating path '+filepath+'...'); var self = this; @@ -155,19 +155,37 @@ Tile.prototype = }); } - self.debug('Created path. Saving vector tile at path: '+filepath+file); - fs.writeFile(filepath+file, JSON.stringify(self.data), {mode: 0666}, function(err) + if (self.data.features.length === 0) + { + self.debug('Created path. Linking empty vector tile to: ' + file); + fs.link('emptytile.json', file, function(err) + { + if (!err) + self.debug('Empty vector tile was stored.'); + else + self.debug('Could not link empty vector file.'); + + return process.nextTick(function() + { + callback(true); + }); + }); + } + + self.debug('Created path. Saving vector tile at path: ' + file); + fs.unlinkSync(file); + fs.writeFile(file, JSON.stringify(self.data), {mode: 0666}, function(err) { if (err) { - self.error('Cannot save vector tile at path: '+filepath+file); + self.error('Cannot save vector tile at path: ' + file); return process.nextTick(function() { callback(err); }); } - self.debug('Saved vector tile at path: '+filepath+file); + self.debug('Saved vector tile at path: ' + file); return process.nextTick(function() { callback(false); From 1d9bb61c2da65e947dd84822b1402d919f87f8fc Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Tue, 15 Mar 2016 22:27:47 +0100 Subject: [PATCH 7/8] factor out BitmapTilerequest.renderCallback() --- bitmaptilerequest.js | 90 +++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/bitmaptilerequest.js b/bitmaptilerequest.js index 9a3a53f..06cff3b 100644 --- a/bitmaptilerequest.js +++ b/bitmaptilerequest.js @@ -34,6 +34,51 @@ BitmapTilerequest = function(self) BitmapTilerequest.prototype = { + // save the rendered image to disk and send it to the client + renderCallback: function(err, image) + { + if (err) + self.tile.debug('Vectortile was empty.'); + self.tile.saveBitmapData(image, function(err) + { + if (err) + { + self.response.writeHead(500, {'Content-Type': 'text/plain'}); + self.response.end(); + self.tile.debug('Empty bitmap tile was responded to the request.'); + self.tile.debug('Finished request.'); + return; + } + + self.tile.getModifyTime(function(err, mtime) + { + var header = self.getHeader(); + + if (!err) + header['Last-Modified'] = mtime.toUTCString(); + + self.tile.trace('Responding bitmap data...'); + var stream = image.createPNGStream(); + self.response.writeHead(200, header); + + // write PNG data stream + stream.on('data', function(data) + { + self.response.write(data); + }); + + // PNG data stream ended + stream.on('end', function() + { + self.response.end(); + self.tile.debug('Bitmap tile was responded to the request.'); + self.tile.debug('Finished request.'); + return; + }); + }); + }); + }, + // serves a bitmap tile getTile: function() { @@ -117,6 +162,7 @@ BitmapTilerequest.prototype = self.abortRequest('Vectortile could not be created. Aborting.'); return; } + self.tile.debug('Vector tile created successfully, saving vector tile...'); self.tile.saveVectorData(function(err) { @@ -124,49 +170,7 @@ BitmapTilerequest.prototype = self.tile.warn('Vector tile could not be saved.'); self.tile.debug('Rendering bitmap tile with style '+self.tile.style); - self.tile.render(function(err, image) - { - if (err) - self.tile.debug('Vectortile was empty.'); - self.tile.saveBitmapData(image, function(err) - { - if (err) - { - self.response.writeHead(500, {'Content-Type': 'text/plain'}); - self.response.end(); - self.tile.debug('Empty bitmap tile was responded to the request.'); - self.tile.debug('Finished request.'); - return; - } - - self.tile.getModifyTime(function(err, mtime) - { - var header = self.getHeader(); - - if (!err) - header['Last-Modified'] = mtime.toUTCString(); - - self.tile.trace('Responding bitmap data...'); - var stream = image.createPNGStream(); - self.response.writeHead(200, header); - - // write PNG data stream - stream.on('data', function(data) - { - self.response.write(data); - }); - - // PNG data stream ended - stream.on('end', function() - { - self.response.end(); - self.tile.debug('Bitmap tile was responded to the request.'); - self.tile.debug('Finished request.'); - return; - }); - }); - }); - }); + self.tile.render(renderCallback); }); }); } From 5a1f60b9257dd36ad0c0c2a114646c2bb7c72d07 Mon Sep 17 00:00:00 2001 From: Rolf Eike Beer Date: Tue, 15 Mar 2016 22:39:42 +0100 Subject: [PATCH 8/8] do not render a bitmap when the vector tile contains no features Simply hardlink the empty tile and serve that. --- bitmaptilerequest.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bitmaptilerequest.js b/bitmaptilerequest.js index 06cff3b..d6f94ec 100644 --- a/bitmaptilerequest.js +++ b/bitmaptilerequest.js @@ -169,8 +169,16 @@ BitmapTilerequest.prototype = if (err) self.tile.warn('Vector tile could not be saved.'); - self.tile.debug('Rendering bitmap tile with style '+self.tile.style); - self.tile.render(renderCallback); + if (self.data.features.length === 0) + { + self.tile.debug('Vector tile without features, serving empty PNG tile for style ' + self.tile.style); + renderCallback(false, null); + } + else + { + self.tile.debug('Rendering bitmap tile with style ' + self.tile.style); + self.tile.render(renderCallback); + } }); }); }