Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use hardlinks for empty bitmap tiles #107

Merged
merged 8 commits into from
Jun 17, 2016
104 changes: 56 additions & 48 deletions bitmaptilerequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -117,59 +162,23 @@ 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)
{
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(function(err, image)
if (self.data.features.length === 0)
{
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 = {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=3600'
};

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.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);
}
});
});
}
Expand Down Expand Up @@ -206,8 +215,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();
Expand Down Expand Up @@ -250,6 +257,7 @@ BitmapTilerequest.prototype =
{
return {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=3600',
'Server': 'node-tileserver/0.3'
};
}
Expand Down
1 change: 1 addition & 0 deletions emptytile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"features":[],"granularity":10}
74 changes: 42 additions & 32 deletions tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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: 0777}, 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);
Expand Down Expand Up @@ -428,15 +446,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.');
}

Expand Down Expand Up @@ -491,8 +507,10 @@ Tile.prototype =
return;
}

self.debug('Saving bitmap tile at path: '+filepath+'/'+self.y+'.png');
var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0777});
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
Expand Down Expand Up @@ -695,10 +713,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<data.rows.length; i++)
{
// catch JSON parsing errors
Expand Down Expand Up @@ -774,40 +793,31 @@ 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.readFile('emptytile.png', function(err, data)
fs.link('emptytile.png', fname, 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);
});
});
}
else
{
self.trace('Saving bitmap data...');
var out = fs.createWriteStream(filepath+'/'+self.y+'.png', {mode: 0777});
var out = fs.createWriteStream(fname, {mode: 0666});
var stream = image.createPNGStream();

// write PNG data stream
Expand Down