350 lines
11 KiB
JavaScript
350 lines
11 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
|
|
process.env.UV_THREADPOOL_SIZE =
|
|
Math.ceil(Math.max(4, require('os').cpus().length * 1.5));
|
|
|
|
var fs = require('fs'),
|
|
path = require('path');
|
|
|
|
var base64url = require('base64url'),
|
|
clone = require('clone'),
|
|
cors = require('cors'),
|
|
express = require('express'),
|
|
handlebars = require('handlebars'),
|
|
mercator = new (require('sphericalmercator'))(),
|
|
morgan = require('morgan');
|
|
|
|
var packageJson = require('../package'),
|
|
serve_font = require('./serve_font'),
|
|
serve_rendered = null,
|
|
serve_style = require('./serve_style'),
|
|
serve_data = require('./serve_data'),
|
|
utils = require('./utils');
|
|
|
|
var isLight = packageJson.name.slice(-6) == '-light';
|
|
if (!isLight) {
|
|
// do not require `serve_rendered` in the light package
|
|
serve_rendered = require('./serve_rendered');
|
|
}
|
|
|
|
module.exports = function(opts, callback) {
|
|
console.log('Starting server');
|
|
|
|
var app = express().disable('x-powered-by'),
|
|
serving = {
|
|
styles: {},
|
|
rendered: {},
|
|
data: {},
|
|
fonts: { // default fonts, always expose these (if they exist)
|
|
'Open Sans Regular': true,
|
|
'Arial Unicode MS Regular': true
|
|
}
|
|
};
|
|
|
|
app.enable('trust proxy');
|
|
|
|
callback = callback || function() {};
|
|
|
|
if (process.env.NODE_ENV !== 'production' &&
|
|
process.env.NODE_ENV !== 'test') {
|
|
app.use(morgan('dev'));
|
|
}
|
|
|
|
var config = opts.config || null;
|
|
var configPath = null;
|
|
if (opts.configPath) {
|
|
configPath = path.resolve(opts.configPath);
|
|
try {
|
|
config = clone(require(configPath));
|
|
} catch (e) {
|
|
console.log('ERROR: Config file not found or invalid!');
|
|
console.log(' See README.md for instructions and sample data.');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
if (!config) {
|
|
console.log('ERROR: No config file not specified!');
|
|
process.exit(1);
|
|
}
|
|
|
|
var options = config.options || {};
|
|
var paths = options.paths || {};
|
|
options.paths = paths;
|
|
paths.root = path.resolve(
|
|
configPath ? path.dirname(configPath) : process.cwd(),
|
|
paths.root || '');
|
|
paths.styles = path.resolve(paths.root, paths.styles || '');
|
|
paths.fonts = path.resolve(paths.root, paths.fonts || '');
|
|
paths.sprites = path.resolve(paths.root, paths.sprites || '');
|
|
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
|
|
|
|
var data = clone(config.data || {});
|
|
|
|
app.use(cors());
|
|
|
|
Object.keys(config.styles || {}).forEach(function(id) {
|
|
var item = config.styles[id];
|
|
if (!item.style || item.style.length == 0) {
|
|
console.log('Missing "style" property for ' + id);
|
|
return;
|
|
}
|
|
|
|
if (item.serve_data !== false) {
|
|
app.use('/styles/', serve_style(options, serving.styles, item, id,
|
|
function(mbtiles, fromData) {
|
|
var dataItemId;
|
|
Object.keys(data).forEach(function(id) {
|
|
if (fromData) {
|
|
if (id == mbtiles) {
|
|
dataItemId = id;
|
|
}
|
|
} else {
|
|
if (data[id].mbtiles == mbtiles) {
|
|
dataItemId = id;
|
|
}
|
|
}
|
|
});
|
|
if (dataItemId) { // mbtiles exist in the data config
|
|
return dataItemId;
|
|
} else if (fromData) {
|
|
console.log('ERROR: data "' + mbtiles + '" not found!');
|
|
process.exit(1);
|
|
} else {
|
|
var id = mbtiles.substr(0, mbtiles.lastIndexOf('.')) || mbtiles;
|
|
while (data[id]) id += '_';
|
|
data[id] = {
|
|
'mbtiles': mbtiles
|
|
};
|
|
return id;
|
|
}
|
|
}, function(font) {
|
|
serving.fonts[font] = true;
|
|
}));
|
|
}
|
|
if (item.serve_rendered !== false) {
|
|
if (serve_rendered) {
|
|
app.use('/styles/' + id + '/',
|
|
serve_rendered(options, serving.rendered, item, id,
|
|
function(mbtiles) {
|
|
var mbtilesFile;
|
|
Object.keys(data).forEach(function(id) {
|
|
if (id == mbtiles) {
|
|
mbtilesFile = data[id].mbtiles;
|
|
}
|
|
});
|
|
return mbtilesFile;
|
|
}));
|
|
} else {
|
|
item.serve_rendered = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (Object.keys(serving.styles).length > 0) {
|
|
// serve fonts only if serving some styles
|
|
app.use('/fonts/', serve_font(options, serving.fonts));
|
|
}
|
|
|
|
Object.keys(data).forEach(function(id) {
|
|
var item = data[id];
|
|
if (!item.mbtiles || item.mbtiles.length == 0) {
|
|
console.log('Missing "mbtiles" property for ' + id);
|
|
return;
|
|
}
|
|
|
|
app.use('/data/', serve_data(options, serving.data, item, id, serving.styles));
|
|
});
|
|
|
|
app.get('/styles.json', function(req, res, next) {
|
|
var result = [];
|
|
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
|
Object.keys(serving.styles).forEach(function(id) {
|
|
var styleJSON = serving.styles[id];
|
|
result.push({
|
|
version: styleJSON.version,
|
|
name: styleJSON.name,
|
|
id: id,
|
|
url: req.protocol + '://' + req.headers.host +
|
|
'/styles/' + id + '.json' + query
|
|
});
|
|
});
|
|
res.send(result);
|
|
});
|
|
|
|
var addTileJSONs = function(arr, req, type) {
|
|
Object.keys(serving[type]).forEach(function(id) {
|
|
var info = clone(serving[type][id]);
|
|
var path = '';
|
|
if (type == 'rendered') {
|
|
path = 'styles/' + id + '/rendered';
|
|
} else {
|
|
path = type + '/' + id;
|
|
}
|
|
info.tiles = utils.getTileUrls(req, info.tiles, path, info.format);
|
|
arr.push(info);
|
|
});
|
|
return arr;
|
|
};
|
|
|
|
app.get('/rendered.json', function(req, res, next) {
|
|
res.send(addTileJSONs([], req, 'rendered'));
|
|
});
|
|
app.get('/data.json', function(req, res, next) {
|
|
res.send(addTileJSONs([], req, 'data'));
|
|
});
|
|
app.get('/index.json', function(req, res, next) {
|
|
res.send(addTileJSONs(addTileJSONs([], req, 'rendered'), req, 'data'));
|
|
});
|
|
|
|
//------------------------------------
|
|
// serve web presentations
|
|
app.use('/', express.static(path.join(__dirname, '../public/resources')));
|
|
|
|
var templates = path.join(__dirname, '../public/templates');
|
|
var serveTemplate = function(path, template, dataGetter) {
|
|
fs.readFile(templates + '/' + template + '.tmpl', function(err, content) {
|
|
if (err) {
|
|
console.log('Template not found:', err);
|
|
}
|
|
var compiled = handlebars.compile(content.toString());
|
|
|
|
app.use(path, function(req, res, next) {
|
|
var data = {};
|
|
if (dataGetter) {
|
|
data = dataGetter(req);
|
|
if (!data) {
|
|
return res.status(404).send('Not found');
|
|
}
|
|
}
|
|
data['server_version'] = packageJson.name + ' v' + packageJson.version;
|
|
data['is_light'] = isLight;
|
|
data['key_query_part'] =
|
|
req.query.key ? 'key=' + req.query.key + '&' : '';
|
|
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
|
|
return res.status(200).send(compiled(data));
|
|
});
|
|
});
|
|
};
|
|
|
|
serveTemplate('/$', 'index', function(req) {
|
|
var styles = clone(config.styles || {});
|
|
Object.keys(styles).forEach(function(id) {
|
|
var style = styles[id];
|
|
style.name = (serving.styles[id] || serving.rendered[id] || {}).name;
|
|
style.serving_data = serving.styles[id];
|
|
style.serving_rendered = serving.rendered[id];
|
|
if (style.serving_rendered) {
|
|
var center = style.serving_rendered.center;
|
|
if (center) {
|
|
style.viewer_hash = '#' + center[2] + '/' +
|
|
center[1].toFixed(5) + '/' +
|
|
center[0].toFixed(5);
|
|
|
|
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
style.thumbnail = center[2] + '/' +
|
|
Math.floor(centerPx[0] / 256) + '/' +
|
|
Math.floor(centerPx[1] / 256) + '.png';
|
|
}
|
|
|
|
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
|
style.wmts_link = 'http://wmts.maptiler.com/' +
|
|
base64url('http://' + req.headers.host +
|
|
'/styles/' + id + '/rendered.json' + query) + '/wmts';
|
|
|
|
var tiles = utils.getTileUrls(
|
|
req, style.serving_rendered.tiles,
|
|
'styles/' + id + '/rendered', style.serving_rendered.format);
|
|
style.xyz_link = tiles[0];
|
|
}
|
|
});
|
|
var data = clone(serving.data || {});
|
|
Object.keys(data).forEach(function(id) {
|
|
var data_ = data[id];
|
|
var center = data_.center;
|
|
if (center) {
|
|
data_.viewer_hash = '#' + center[2] + '/' +
|
|
center[1].toFixed(5) + '/' +
|
|
center[0].toFixed(5);
|
|
}
|
|
data_.is_vector = data_.format == 'pbf';
|
|
if (!data_.is_vector) {
|
|
if (center) {
|
|
var centerPx = mercator.px([center[0], center[1]], center[2]);
|
|
data_.thumbnail = center[2] + '/' +
|
|
Math.floor(centerPx[0] / 256) + '/' +
|
|
Math.floor(centerPx[1] / 256) + '.' + data_.format;
|
|
}
|
|
|
|
var query = req.query.key ? ('?key=' + req.query.key) : '';
|
|
data_.wmts_link = 'http://wmts.maptiler.com/' +
|
|
base64url('http://' + req.headers.host +
|
|
'/data/' + id + '.json' + query) + '/wmts';
|
|
|
|
var tiles = utils.getTileUrls(
|
|
req, data_.tiles, 'data/' + id, data_.format);
|
|
data_.xyz_link = tiles[0];
|
|
}
|
|
if (data_.filesize) {
|
|
var suffix = 'kB';
|
|
var size = parseInt(data_.filesize, 10) / 1024;
|
|
if (size > 1024) {
|
|
suffix = 'MB';
|
|
size /= 1024;
|
|
}
|
|
if (size > 1024) {
|
|
suffix = 'GB';
|
|
size /= 1024;
|
|
}
|
|
data_.formatted_filesize = size.toFixed(2) + ' ' + suffix;
|
|
}
|
|
});
|
|
return {
|
|
styles: styles,
|
|
data: data
|
|
};
|
|
});
|
|
|
|
serveTemplate('/styles/:id/$', 'viewer', function(req) {
|
|
var id = req.params.id;
|
|
var style = clone((config.styles || {})[id]);
|
|
if (!style) {
|
|
return null;
|
|
}
|
|
style.id = id;
|
|
style.name = (serving.styles[id] || serving.rendered[id]).name;
|
|
style.serving_data = serving.styles[id];
|
|
style.serving_rendered = serving.rendered[id];
|
|
return style;
|
|
});
|
|
|
|
/*
|
|
app.use('/rendered/:id/$', function(req, res, next) {
|
|
return res.redirect(301, '/styles/' + req.params.id + '/');
|
|
});
|
|
*/
|
|
|
|
serveTemplate('/data/:id/$', 'data', function(req) {
|
|
var id = req.params.id;
|
|
var data = clone(serving.data[id]);
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
data.id = id;
|
|
data.is_vector = data.format == 'pbf';
|
|
return data;
|
|
});
|
|
|
|
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
|
|
console.log('Listening at http://%s:%d/',
|
|
this.address().address, this.address().port);
|
|
|
|
return callback();
|
|
});
|
|
|
|
setTimeout(callback, 1000);
|
|
return {
|
|
app: app,
|
|
server: server
|
|
};
|
|
};
|