Compare commits

...

46 Commits

Author SHA1 Message Date
Petr Sloup 54f11a2125 Update package version to 2.2.0 2017-09-01 11:37:18 +02:00
Petr Sloup 598c8c590b Update package dependencies 2017-09-01 11:30:52 +02:00
Petr Sloup 438a18eec1 Merge pull request #207 from tschaub/handle-request-failures
Try to create the appropriate data type on http errors
2017-08-24 09:33:22 +02:00
Tim Schaub 7bdb7afcb9 Reuse the function for creating empty responses 2017-08-23 11:30:05 -04:00
Tim Schaub 2e46700cd9 Try to create the appropriate data type on http errors 2017-08-22 14:17:53 -04:00
Petr Sloup bb09f3df64 Add --rm to docker run instruction to run it more cleanly 2017-07-28 09:54:12 +02:00
Petr Sloup 42f24c2c99 Add concept of data decorator function 2017-07-28 09:53:32 +02:00
Petr Sloup 427a0f0687 Update package version to 2.1.0 2017-07-03 09:41:27 +02:00
Petr Sloup da5ea4b426 Update Mapbox GL JS to v0.38.0 (close #188) 2017-07-03 09:39:55 +02:00
Petr Sloup 2208ff1e24 Merge pull request #187 from aleksejleonov/debian_stretch_nodejs_repo
Use debian stretch nodejs repo
2017-07-03 09:29:29 +02:00
Oleksii Leonov cf521058be Use debian stretch nodejs repo
Dockerfile based on debian:stretch, so better to use native stretch nodejs repo instead of jessie repo.
2017-06-30 00:54:13 +03:00
Petr Sloup a9b38022bb Give xvfb some time to start up before starting the server in docker (#185) 2017-06-29 10:50:02 +02:00
Petr Sloup 654bdda629 Update package version to 2.0.0 2017-06-23 21:45:11 +02:00
Petr Sloup 537313840e Update node version in documentation 2017-06-23 21:44:40 +02:00
Petr Sloup d30f8464b2 Add some packages to travis and Dockerfile 2017-06-23 21:34:59 +02:00
Petr Sloup 698c527e94 Change endpoint URLs (close #154)
- styles at /style/{id}/style.json
- rendered tiles at /style/{id}/{z}/{x}/{y}.{format}
- TileJSONs at /style/{id}.json
2017-06-22 16:37:32 +02:00
Petr Sloup 8007f1386c Add required package for travis and Dockerfile 2017-06-22 16:37:32 +02:00
Petr Sloup 4f2fdf602b Update Dockerfiles to use node v6 2017-06-22 16:37:32 +02:00
Petr Sloup 6d7397647a Update travis script to use node v6 2017-06-22 16:37:32 +02:00
Petr Sloup 8fd7a9b42b Update dependencies to get ready for node v6 2017-06-22 16:37:32 +02:00
Petr Sloup 95470143b6 Merge pull request #179 from tschaub/err
Log errors to stderr and return
2017-06-20 17:29:39 +02:00
Tim Schaub de83021c3d Log errors to stderr and return 2017-06-20 08:09:59 -07:00
Petr Sloup 7de3d8b9c7 Update package version to 1.7.0 2017-05-10 10:32:44 +02:00
Petr Sloup bdc3d20524 Use promises instead of async in font concatenation (remove async dependency) 2017-05-10 10:22:39 +02:00
Petr Sloup 5d93b1d4f9 Add healthcheck endpoint (close #140) 2017-05-10 08:57:51 +02:00
Petr Sloup d30027e992 Modify all serve_* modules to return a Promise (preparation for #140) 2017-05-10 08:56:43 +02:00
Petr Sloup c233d23523 Fix broken static maps bounds overlay (for very large areas) 2017-05-05 16:15:10 +02:00
Petr Sloup 1109c77ec2 Update package version to 1.6.0 2017-05-04 12:07:58 +02:00
Petr Sloup 88cf9b37a9 Revert mapbox-gl-native package update
It broke travis (to be further investigated)
2017-05-04 11:21:57 +02:00
Petr Sloup 2d207f792b Update package dependencies 2017-05-04 11:13:24 +02:00
Petr Sloup d67a57861d Merge pull request #155 from tschaub/reconfigure-on-sighup
Reload configuration on SIGHUP
2017-05-04 11:02:48 +02:00
Petr Sloup 27e9dbfb4e Merge pull request #157 from tschaub/address
Enclose literal IPv6 addresses in brackets
2017-05-04 08:57:17 +02:00
Tim Schaub a199008fa3 Enclose literal IPv6 addresses in brackets 2017-04-28 07:28:49 -07:00
Tim Schaub e88b786073 Reload configuration on SIGHUP 2017-04-28 07:13:34 -07:00
Petr Sloup c03b0a12f8 Merge pull request #156 from tschaub/no-callback
Remove unused callback
2017-04-28 11:04:17 +02:00
Tim Schaub a234041cd1 Remove unused callback 2017-04-26 08:02:18 -07:00
Petr Sloup 49a779970e Merge pull request #150 from pirxpilot/no-cors
add `--cors` option to allow for optional CORS handling
2017-04-18 09:32:11 +02:00
Petr Sloup 9545c2594e Handle scale in query-based static endpoint 2017-04-14 12:05:03 +02:00
Damian Krzeminski 6c23d95feb add --cors option to allow for optional CORS handling
we are using `cors` middleware with default options which works for most
applications, but does not allow for fine tuning (whitelisting origins
etc.)

this change keeps CORS handling as default to preserve compatibility but
also allows for specying `--no-cors` option which makes it possible to
handle CORS in an independent proxy (NGINX, another node app etc.)
2017-04-12 09:43:43 -07:00
Petr Sloup 366380395e Proper error message when metadata are missing in the mbtiles (close #147) 2017-04-11 19:10:40 +02:00
Petr Sloup 8ea665297f Minor fix in style path handling (allow absolute paths) 2017-04-07 18:53:11 +02:00
Petr Sloup f6580c0342 Improved request logging 2017-04-04 18:46:18 +02:00
Petr Sloup 34a139040c Slight docker performance optimization 2017-04-04 18:46:18 +02:00
Petr Sloup 66bea8a42b Merge pull request #142 from somthanat/master
Word correction (Forwaded -> Forwarded)
2017-04-03 15:16:40 +02:00
Petr Sloup 28790fda30 Alternative query-based static endpoint 2017-04-03 15:14:40 +02:00
Somthanat Wongsa 0b16af0084 Word correction (Forwaded -> Forwarded) 2017-03-30 20:16:03 +07:00
24 changed files with 939 additions and 677 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
language: node_js language: node_js
node_js: node_js:
- "4" - "6"
env: env:
- CXX=g++-4.8 - CXX=g++-4.8
addons: addons:
@@ -12,7 +12,7 @@ addons:
before_install: before_install:
- sudo apt-get update -qq - sudo apt-get update -qq
- sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++ - sudo apt-get install -qq libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
- sudo apt-get install -qq xvfb - sudo apt-get install -qq xvfb libgles2-mesa-dev libgbm-dev libxxf86vm-dev
install: install:
- npm install - npm install
- wget -O test_data.zip https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/test_data.zip - wget -O test_data.zip https://github.com/klokantech/tileserver-gl/releases/download/v1.3.0/test_data.zip
+8 -2
View File
@@ -9,10 +9,14 @@ RUN apt-get -qq update \
build-essential \ build-essential \
python \ python \
libcairo2-dev \ libcairo2-dev \
libgles2-mesa-dev \
libgbm-dev \
libllvm3.9 \
libprotobuf-dev \ libprotobuf-dev \
libxxf86vm-dev \
xvfb \ xvfb \
&& echo "deb https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \ && echo "deb https://deb.nodesource.com/node_6.x stretch main" >> /etc/apt/sources.list.d/nodejs.list \
&& echo "deb-src https://deb.nodesource.com/node_4.x jessie main" >> /etc/apt/sources.list.d/nodejs.list \ && echo "deb-src https://deb.nodesource.com/node_6.x stretch main" >> /etc/apt/sources.list.d/nodejs.list \
&& apt-get -qq update \ && apt-get -qq update \
&& DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated install \ && DEBIAN_FRONTEND=noninteractive apt-get -y --allow-unauthenticated install \
nodejs \ nodejs \
@@ -26,5 +30,7 @@ RUN cd /usr/src/app && npm install --production
VOLUME /data VOLUME /data
WORKDIR /data WORKDIR /data
ENV NODE_ENV="production"
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["/usr/src/app/run.sh"] ENTRYPOINT ["/usr/src/app/run.sh"]
+3 -1
View File
@@ -1,4 +1,4 @@
FROM node:4 FROM node:6
MAINTAINER Petr Sloup <petr.sloup@klokantech.com> MAINTAINER Petr Sloup <petr.sloup@klokantech.com>
RUN mkdir -p /usr/src/app RUN mkdir -p /usr/src/app
@@ -8,5 +8,7 @@ RUN cd /usr/src/app && npm install --production
VOLUME /data VOLUME /data
WORKDIR /data WORKDIR /data
ENV NODE_ENV="production"
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["node", "/usr/src/app/", "-p", "80"] ENTRYPOINT ["node", "/usr/src/app/", "-p", "80"]
+1 -1
View File
@@ -34,7 +34,7 @@ Alternatively, you can use the `tileserver-gl-light` package instead, which is p
An alternative to npm to start the packed software easier is to install [Docker](http://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command: An alternative to npm to start the packed software easier is to install [Docker](http://www.docker.com/) on your computer and then run in the directory with the downloaded MBTiles the command:
```bash ```bash
docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl docker run --rm -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl
``` ```
This will download and start a ready to use container on your computer and the maps are going to be available in webbrowser on localhost:8080. This will download and start a ready to use container on your computer and the maps are going to be available in webbrowser on localhost:8080.
+1 -1
View File
@@ -17,4 +17,4 @@ Nginx can be used to add protection via https, password, referrer, IP address re
Running behind a proxy or a load-balancer Running behind a proxy or a load-balancer
========================================= =========================================
If you need to run TileServer GL behind a proxy, make sure the proxy sends ``X-Forwarded-*`` headers to the server (most importantly ``X-Forwarded-Host`` and ``X-Forwaded-Proto``) to ensures the URLs generated inside TileJSON etc. are using the desired domain and protocol. If you need to run TileServer GL behind a proxy, make sure the proxy sends ``X-Forwarded-*`` headers to the server (most importantly ``X-Forwarded-Host`` and ``X-Forwarded-Proto``) to ensures the URLs generated inside TileJSON etc. are using the desired domain and protocol.
+10 -3
View File
@@ -6,18 +6,18 @@ If you visit the server on the configured port (default 8080) you can see your m
Styles Styles
====== ======
* Styles are served at ``/styles/{id}.json`` (+ array at ``/styles.json``) * Styles are served at ``/styles/{id}/style.json`` (+ array at ``/styles.json``)
* Sprites at ``/styles/{id}/sprite[@2x].{format}`` * Sprites at ``/styles/{id}/sprite[@2x].{format}``
* Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf`` * Fonts at ``/fonts/{fontstack}/{start}-{end}.pbf``
Rendered tiles Rendered tiles
============== ==============
* Rendered tiles are served at ``/styles/{id}/rendered/{z}/{x}/{y}[@2x].{format}`` * Rendered tiles are served at ``/styles/{id}/{z}/{x}/{y}[@2x].{format}``
* The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles * The optional ``@2x`` (or ``@3x``, ``@4x``) part can be used to render HiDPI (retina) tiles
* Available formats: ``png``, ``jpg`` (``jpeg``), ``webp`` * Available formats: ``png``, ``jpg`` (``jpeg``), ``webp``
* TileJSON at ``/styles/{id}/rendered.json`` * TileJSON at ``/styles/{id}.json``
* The rendered tiles are not available in the ``tileserver-gl-light`` version. * The rendered tiles are not available in the ``tileserver-gl-light`` version.
@@ -64,3 +64,10 @@ Array of all TileJSONs is at ``/index.json`` (``/rendered.json``; ``/data.json``
List of available fonts List of available fonts
======================= =======================
Array of names of the available fonts is at ``/fonts.json`` Array of names of the available fonts is at ``/fonts.json``
Health check
============
Endpoint reporting health status is at ``/health`` and currently returns:
* ``503`` Starting - for a short period before everything is initialized
* ``200`` OK - when the server is running
+2 -2
View File
@@ -7,7 +7,7 @@ Docker
When running docker image, no special installation is needed -- the docker will automatically download the image if not present. When running docker image, no special installation is needed -- the docker will automatically download the image if not present.
Just run ``docker run -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl``. Just run ``docker run --rm -it -v $(pwd):/data -p 8080:80 klokantech/tileserver-gl``.
Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following: Additional options (see :doc:`/usage`) can be passed to the TileServer GL by appending them to the end of this command. You can, for example, do the following:
@@ -41,7 +41,7 @@ Alternatively, you can use ``tileserver-gl-light`` package instead, which is pur
From source From source
=========== ===========
Make sure you have Node v4 (nvm install 4) and run:: Make sure you have Node v6 (nvm install 6) and run::
npm install npm install
node . node .
+17 -17
View File
@@ -1,6 +1,6 @@
{ {
"name": "tileserver-gl", "name": "tileserver-gl",
"version": "1.5.0", "version": "2.2.0",
"description": "Map tile server for JSON GL styles - vector and server side generated raster tiles", "description": "Map tile server for JSON GL styles - vector and server side generated raster tiles",
"main": "src/main.js", "main": "src/main.js",
"bin": "src/main.js", "bin": "src/main.js",
@@ -13,34 +13,34 @@
}, },
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=4.2.1 <5" "node": ">=6 <7"
}, },
"scripts": { "scripts": {
"test": "mocha test/**.js" "test": "mocha test/**.js"
}, },
"dependencies": { "dependencies": {
"async": "2.2.0", "@mapbox/mapbox-gl-native": "3.5.4",
"advanced-pool": "0.3.2", "@mapbox/mbtiles": "0.9.0",
"@mapbox/sphericalmercator": "1.0.5",
"@mapbox/vector-tile": "1.3.0",
"advanced-pool": "0.3.3",
"base64url": "2.0.0", "base64url": "2.0.0",
"canvas": "1.6.5", "canvas": "1.6.6",
"clone": "2.1.1", "clone": "2.1.1",
"color": "1.0.3", "color": "1.0.3",
"cors": "2.8.2", "cors": "2.8.4",
"express": "4.15.2", "express": "4.15.4",
"glyph-pbf-composite": "0.0.2", "glyph-pbf-composite": "0.0.2",
"handlebars": "4.0.6", "handlebars": "4.0.10",
"mbtiles": "0.9.0", "http-shutdown": "^1.2.0",
"morgan": "1.8.1", "morgan": "1.8.2",
"node-pngquant-native": "1.0.4", "node-pngquant-native": "1.0.5",
"nomnom": "1.8.1", "nomnom": "1.8.1",
"pbf": "3.0.5", "pbf": "3.0.5",
"proj4": "2.4.3", "proj4": "2.4.4",
"request": "2.81.0", "request": "2.81.0",
"sharp": "0.17.2", "sharp": "0.18.2",
"tileserver-gl-styles": "1.1.1", "tileserver-gl-styles": "1.2.0"
"vector-tile": "1.3.0",
"@mapbox/mapbox-gl-native": "3.4.4",
"@mapbox/sphericalmercator": "1.0.5"
}, },
"devDependencies": { "devDependencies": {
"should": "^11.2.0", "should": "^11.2.0",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -25,7 +25,7 @@
{{#each styles}} {{#each styles}}
<div class="item"> <div class="item">
{{#if thumbnail}} {{#if thumbnail}}
<img src="/styles/{{@key}}/rendered/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" /> <img src="/styles/{{@key}}/{{thumbnail}}{{&../key_query}}" alt="{{name}} preview" />
{{else}} {{else}}
<img src="/images/placeholder.png" alt="{{name}} preview" /> <img src="/images/placeholder.png" alt="{{name}} preview" />
{{/if}} {{/if}}
@@ -35,10 +35,10 @@
<p class="services"> <p class="services">
services: services:
{{#if serving_data}} {{#if serving_data}}
<a href="/styles/{{@key}}.json{{&../key_query}}">GL Style</a> <a href="/styles/{{@key}}/style.json{{&../key_query}}">GL Style</a>
{{/if}} {{/if}}
{{#if serving_rendered}} {{#if serving_rendered}}
{{#if serving_data}}| {{/if}}<a href="/styles/{{@key}}/rendered.json{{&../key_query}}">TileJSON</a> {{#if serving_data}}| {{/if}}<a href="/styles/{{@key}}.json{{&../key_query}}">TileJSON</a>
{{/if}} {{/if}}
{{#if wmts_link}} {{#if wmts_link}}
| <a href="{{&wmts_link}}">WMTS</a> | <a href="{{&wmts_link}}">WMTS</a>
+2 -2
View File
@@ -27,12 +27,12 @@
mapboxgl.setRTLTextPlugin('/mapbox-gl-rtl-text.js{{&key_query}}'); mapboxgl.setRTLTextPlugin('/mapbox-gl-rtl-text.js{{&key_query}}');
var map = new mapboxgl.Map({ var map = new mapboxgl.Map({
container: 'map', container: 'map',
style: '/styles/{{id}}.json{{&key_query}}', style: '/styles/{{id}}/style.json{{&key_query}}',
hash: true hash: true
}); });
map.addControl(new mapboxgl.NavigationControl()); map.addControl(new mapboxgl.NavigationControl());
} else { } else {
var map = L.mapbox.map('map', '/styles/{{id}}/rendered.json{{&key_query}}', { zoomControl: false }); var map = L.mapbox.map('map', '/styles/{{id}}.json{{&key_query}}', { zoomControl: false });
new L.Control.Zoom({ position: 'topright' }).addTo(map); new L.Control.Zoom({ position: 'topright' }).addTo(map);
setTimeout(function() { setTimeout(function() {
new L.Hash(map); new L.Hash(map);
+6 -1
View File
@@ -1,3 +1,8 @@
#!/bin/bash #!/bin/bash
start-stop-daemon --start --pidfile ~/xvfb.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset
sleep 1
export DISPLAY=:99.0
cd /data cd /data
xvfb-run -a -e /dev/stdout --server-args="-screen 0 1024x768x24" node /usr/src/app/ -p 80 "$@" node /usr/src/app/ -p 80 "$@"
+13 -2
View File
@@ -6,7 +6,7 @@ var fs = require('fs'),
path = require('path'), path = require('path'),
request = require('request'); request = require('request');
var mbtiles = require('mbtiles'); var mbtiles = require('@mapbox/mbtiles');
var packageJson = require('../package'); var packageJson = require('../package');
@@ -32,6 +32,10 @@ var opts = require('nomnom')
default: 8080, default: 8080,
help: 'Port' help: 'Port'
}) })
.option('cors', {
default: true,
help: 'Enable Cross-origin resource sharing headers'
})
.option('verbose', { .option('verbose', {
abbr: 'V', abbr: 'V',
flag: true, flag: true,
@@ -54,7 +58,8 @@ var startServer = function(configPath, config) {
configPath: configPath, configPath: configPath,
config: config, config: config,
bind: opts.bind, bind: opts.bind,
port: opts.port port: opts.port,
cors: opts.cors
}); });
}; };
@@ -70,6 +75,12 @@ var startWithMBTiles = function(mbtilesFile) {
} }
var instance = new mbtiles(mbtilesFile, function(err) { var instance = new mbtiles(mbtilesFile, function(err) {
instance.getInfo(function(err, info) { instance.getInfo(function(err, info) {
if (err || !info) {
console.log('ERROR: Metadata missing in the MBTiles.');
console.log(' Make sure ' + path.basename(mbtilesFile) +
' is valid MBTiles.');
process.exit(1);
}
var bounds = info.bounds; var bounds = info.bounds;
var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/"); var styleDir = path.resolve(__dirname, "../node_modules/tileserver-gl-styles/");
+33 -14
View File
@@ -6,9 +6,9 @@ var fs = require('fs'),
var clone = require('clone'), var clone = require('clone'),
express = require('express'), express = require('express'),
mbtiles = require('mbtiles'), mbtiles = require('@mapbox/mbtiles'),
pbf = require('pbf'), pbf = require('pbf'),
VectorTile = require('vector-tile').VectorTile; VectorTile = require('@mapbox/vector-tile').VectorTile;
var tileshrinkGl; var tileshrinkGl;
try { try {
@@ -34,20 +34,28 @@ module.exports = function(options, repo, params, id, styles) {
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) { if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
throw Error('Not valid MBTiles file: ' + mbtilesFile); throw Error('Not valid MBTiles file: ' + mbtilesFile);
} }
var source = new mbtiles(mbtilesFile, function(err) { var source;
source.getInfo(function(err, info) { var sourceInfoPromise = new Promise(function(resolve, reject) {
tileJSON['name'] = id; source = new mbtiles(mbtilesFile, function(err) {
tileJSON['format'] = 'pbf'; source.getInfo(function(err, info) {
tileJSON['name'] = id;
tileJSON['format'] = 'pbf';
Object.assign(tileJSON, info); Object.assign(tileJSON, info);
tileJSON['tilejson'] = '2.0.0'; tileJSON['tilejson'] = '2.0.0';
delete tileJSON['filesize']; delete tileJSON['filesize'];
delete tileJSON['mtime']; delete tileJSON['mtime'];
delete tileJSON['scheme']; delete tileJSON['scheme'];
Object.assign(tileJSON, params.tilejson || {}); Object.assign(tileJSON, params.tilejson || {});
utils.fixTileJSONCenter(tileJSON); utils.fixTileJSONCenter(tileJSON);
if (options.dataDecoratorFunc) {
tileJSON = options.dataDecoratorFunc(id, 'tilejson', tileJSON);
}
resolve();
});
}); });
}); });
@@ -110,6 +118,13 @@ module.exports = function(options, repo, params, id, styles) {
//console.log(shrinkers[style].getStats()); //console.log(shrinkers[style].getStats());
} }
} }
if (options.dataDecoratorFunc) {
if (isGzipped) {
data = zlib.unzipSync(data);
isGzipped = false;
}
data = options.dataDecoratorFunc(id, 'data', data, z, x, y);
}
} }
if (format == 'pbf') { if (format == 'pbf') {
headers['Content-Type'] = 'application/x-protobuf'; headers['Content-Type'] = 'application/x-protobuf';
@@ -161,5 +176,9 @@ module.exports = function(options, repo, params, id, styles) {
return res.send(info); return res.send(info);
}); });
return app; return new Promise(function(resolve, reject) {
sourceInfoPromise.then(function() {
resolve(app);
});
});
}; };
+21 -18
View File
@@ -15,16 +15,19 @@ module.exports = function(options, allowedFonts) {
var fontPath = options.paths.fonts; var fontPath = options.paths.fonts;
var existingFonts = {}; var existingFonts = {};
fs.readdir(options.paths.fonts, function(err, files) { var fontListingPromise = new Promise(function(resolve, reject) {
files.forEach(function(file) { fs.readdir(options.paths.fonts, function(err, files) {
fs.stat(path.join(fontPath, file), function(err, stats) { files.forEach(function(file) {
if (!err) { fs.stat(path.join(fontPath, file), function(err, stats) {
if (stats.isDirectory() && if (!err) {
fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) { if (stats.isDirectory() &&
existingFonts[path.basename(file)] = true; fs.existsSync(path.join(fontPath, file, '0-255.pbf'))) {
existingFonts[path.basename(file)] = true;
}
} }
} });
}); });
resolve();
}); });
}); });
@@ -33,19 +36,15 @@ module.exports = function(options, allowedFonts) {
var fontstack = decodeURI(req.params.fontstack); var fontstack = decodeURI(req.params.fontstack);
var range = req.params.range; var range = req.params.range;
return utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts, utils.getFontsPbf(options.serveAllFonts ? null : allowedFonts,
fontPath, fontstack, range, existingFonts, fontPath, fontstack, range, existingFonts).then(function(concated) {
function(err, concated) {
if (err || concated.length === 0) {
console.log(err);
console.log(concated.length);
return res.status(400).send('');
} else {
res.header('Content-type', 'application/x-protobuf'); res.header('Content-type', 'application/x-protobuf');
res.header('Last-Modified', lastModified); res.header('Last-Modified', lastModified);
return res.send(concated); return res.send(concated);
}, function(err) {
return res.status(400).send(err);
} }
}); );
}); });
app.get('/fonts.json', function(req, res, next) { app.get('/fonts.json', function(req, res, next) {
@@ -55,5 +54,9 @@ module.exports = function(options, allowedFonts) {
); );
}); });
return app; return new Promise(function(resolve, reject) {
fontListingPromise.then(function() {
resolve(app);
});
});
}; };
+202 -94
View File
@@ -1,9 +1,9 @@
'use strict'; 'use strict';
var async = require('async'), var advancedPool = require('advanced-pool'),
advancedPool = require('advanced-pool'),
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
url = require('url'),
util = require('util'), util = require('util'),
zlib = require('zlib'); zlib = require('zlib');
@@ -17,7 +17,7 @@ var Canvas = require('canvas'),
express = require('express'), express = require('express'),
mercator = new (require('@mapbox/sphericalmercator'))(), mercator = new (require('@mapbox/sphericalmercator'))(),
mbgl = require('@mapbox/mapbox-gl-native'), mbgl = require('@mapbox/mapbox-gl-native'),
mbtiles = require('mbtiles'), mbtiles = require('@mapbox/mbtiles'),
pngquant = require('node-pngquant-native'), pngquant = require('node-pngquant-native'),
proj4 = require('proj4'), proj4 = require('proj4'),
request = require('request'); request = require('request');
@@ -36,6 +36,68 @@ mbgl.on('message', function(e) {
} }
}); });
/**
* Lookup of sharp output formats by file extension.
*/
var extensionToFormat = {
'.jpg': 'jpeg',
'.jpeg': 'jpeg',
'.png': 'png',
'.webp': 'webp'
};
/**
* Cache of response data by sharp output format and color. Entry for empty
* string is for unknown or unsupported formats.
*/
var cachedEmptyResponses = {
'': new Buffer(0)
};
/**
* Create an appropriate mbgl response for http errors.
* @param {string} format The format (a sharp format or 'pbf').
* @param {string} color The background color (or empty string for transparent).
* @param {Function} callback The mbgl callback.
*/
function createEmptyResponse(format, color, callback) {
if (!format || format === 'pbf') {
callback(null, {data: cachedEmptyResponses['']});
return;
}
if (format === 'jpg') {
format = 'jpeg';
}
if (!color) {
color = 'rgba(255,255,255,0)';
}
var cacheKey = format + ',' + color;
var data = cachedEmptyResponses[cacheKey];
if (data) {
callback(null, {data: data});
return;
}
// create an "empty" response image
var color = new Color(color);
var array = color.array();
var channels = array.length == 4 && format != 'jpeg' ? 4 : 3;
sharp(new Buffer(array), {
raw: {
width: 1,
height: 1,
channels: channels
}
}).toFormat(format).toBuffer(function(err, buffer, info) {
if (!err) {
cachedEmptyResponses[cacheKey] = buffer;
}
callback(null, {data: buffer});
});
}
module.exports = function(options, repo, params, id, dataResolver) { module.exports = function(options, repo, params, id, dataResolver) {
var app = express().disable('x-powered-by'); var app = express().disable('x-powered-by');
@@ -59,15 +121,18 @@ module.exports = function(options, repo, params, id, dataResolver) {
}; };
var existingFonts = {}; var existingFonts = {};
fs.readdir(options.paths.fonts, function(err, files) { var fontListingPromise = new Promise(function(resolve, reject) {
files.forEach(function(file) { fs.readdir(options.paths.fonts, function(err, files) {
fs.stat(path.join(options.paths.fonts, file), function(err, stats) { files.forEach(function(file) {
if (!err) { fs.stat(path.join(options.paths.fonts, file), function(err, stats) {
if (stats.isDirectory()) { if (!err) {
existingFonts[path.basename(file)] = true; if (stats.isDirectory()) {
existingFonts[path.basename(file)] = true;
}
} }
} });
}); });
resolve();
}); });
}); });
@@ -89,13 +154,17 @@ module.exports = function(options, repo, params, id, dataResolver) {
var parts = req.url.split('/'); var parts = req.url.split('/');
var fontstack = unescape(parts[2]); var fontstack = unescape(parts[2]);
var range = parts[3].split('.')[0]; var range = parts[3].split('.')[0];
utils.getFontsPbf(null, options.paths[protocol], fontstack, range, existingFonts, utils.getFontsPbf(
function(err, concated) { null, options.paths[protocol], fontstack, range, existingFonts
callback(err, {data: concated}); ).then(function(concated) {
callback(null, {data: concated});
}, function(err) {
callback(err, {data: null});
}); });
} else if (protocol == 'mbtiles') { } else if (protocol == 'mbtiles') {
var parts = req.url.split('/'); var parts = req.url.split('/');
var source = map.sources[parts[2]]; var sourceId = parts[2];
var source = map.sources[sourceId];
var z = parts[3] | 0, var z = parts[3] | 0,
x = parts[4] | 0, x = parts[4] | 0,
y = parts[5].split('.')[0] | 0, y = parts[5].split('.')[0] | 0,
@@ -103,22 +172,26 @@ module.exports = function(options, repo, params, id, dataResolver) {
source.getTile(z, x, y, function(err, data, headers) { source.getTile(z, x, y, function(err, data, headers) {
if (err) { if (err) {
//console.log('MBTiles error, serving empty', err); //console.log('MBTiles error, serving empty', err);
callback(null, { data: source.emptyTile }); createEmptyResponse(source.format, source.color, callback);
} else { return;
var response = {};
if (headers['Last-Modified']) {
response.modified = new Date(headers['Last-Modified']);
}
if (format == 'pbf') {
response.data = zlib.unzipSync(data);
} else {
response.data = data;
}
callback(null, response);
} }
var response = {};
if (headers['Last-Modified']) {
response.modified = new Date(headers['Last-Modified']);
}
if (format == 'pbf') {
response.data = zlib.unzipSync(data);
if (options.dataDecoratorFunc) {
response.data = options.dataDecoratorFunc(
sourceId, 'data', response.data, z, x, y);
}
} else {
response.data = data;
}
callback(null, response);
}); });
} else if (protocol == 'http' || protocol == 'https') { } else if (protocol == 'http' || protocol == 'https') {
request({ request({
@@ -126,29 +199,28 @@ module.exports = function(options, repo, params, id, dataResolver) {
encoding: null, encoding: null,
gzip: true gzip: true
}, function(err, res, body) { }, function(err, res, body) {
if (err) { var parts = url.parse(req.url);
//console.log('HTTP tile error', err); var extension = path.extname(parts.pathname).toLowerCase();
callback(null, { data: new Buffer(0) }); var format = extensionToFormat[extension] || '';
} else if (res.statusCode == 200) { if (err || res.statusCode < 200 || res.statusCode >= 300) {
var response = {}; // console.log('HTTP error', err || res.statusCode);
createEmptyResponse(format, '', callback);
if (res.headers.modified) { return;
response.modified = new Date(res.headers.modified);
}
if (res.headers.expires) {
response.expires = new Date(res.headers.expires);
}
if (res.headers.etag) {
response.etag = res.headers.etag;
}
response.data = body;
callback(null, response);
} else {
//console.log('HTTP error', JSON.parse(body).message);
callback(null, { data: new Buffer(0) });
} }
var response = {};
if (res.headers.modified) {
response.modified = new Date(res.headers.modified);
}
if (res.headers.expires) {
response.expires = new Date(res.headers.expires);
}
if (res.headers.etag) {
response.etag = res.headers.etag;
}
response.data = body;
callback(null, response);
}); });
} }
} }
@@ -166,7 +238,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
}); });
}; };
var styleJSONPath = path.join(options.paths.styles, styleFile); var styleJSONPath = path.resolve(options.paths.styles, styleFile);
styleJSON = clone(require(styleJSONPath)); styleJSON = clone(require(styleJSONPath));
var httpTester = /^(http(s)?:)?\/\//; var httpTester = /^(http(s)?:)?\/\//;
@@ -218,12 +290,12 @@ module.exports = function(options, repo, params, id, dataResolver) {
} }
mbtilesFile = dataResolver(mbtilesFile); mbtilesFile = dataResolver(mbtilesFile);
if (!mbtilesFile) { if (!mbtilesFile) {
console.log('ERROR: data "' + mbtilesFile + '" not found!'); console.error('ERROR: data "' + mbtilesFile + '" not found!');
process.exit(1); process.exit(1);
} }
} }
queue.push(function(callback) { queue.push(new Promise(function(resolve, reject) {
mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile); mbtilesFile = path.resolve(options.paths.mbtiles, mbtilesFile);
var mbtilesFileStats = fs.statSync(mbtilesFile); var mbtilesFileStats = fs.statSync(mbtilesFile);
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) { if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
@@ -233,6 +305,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
map.sources[name].getInfo(function(err, info) { map.sources[name].getInfo(function(err, info) {
if (err) { if (err) {
console.error(err); console.error(err);
return;
} }
if (!dataProjWGStoInternalWGS && info.proj4) { if (!dataProjWGStoInternalWGS && info.proj4) {
@@ -252,26 +325,11 @@ module.exports = function(options, repo, params, id, dataResolver) {
'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf') 'mbtiles://' + name + '/{z}/{x}/{y}.' + (info.format || 'pbf')
]; ];
delete source.scheme; delete source.scheme;
if (source.format == 'pbf') {
map.sources[name].emptyTile = new Buffer(0); if (options.dataDecoratorFunc) {
} else { source = options.dataDecoratorFunc(name, 'tilejson', source);
var color = new Color(source.color || 'rgba(255,255,255,0)');
var format = source.format;
if (format == 'jpg') {
format = 'jpeg';
}
var array = color.array();
var channels = array.length == 4 && format != 'jpeg' ? 4 : 3;
sharp(new Buffer(array), {
raw: {
width: 1,
height: 1,
channels: channels
}
}).toFormat(format).toBuffer(function(err, buffer, info) {
map.sources[name].emptyTile = buffer;
});
} }
if (!attributionOverride && if (!attributionOverride &&
source.attribution && source.attribution.length > 0) { source.attribution && source.attribution.length > 0) {
if (tileJSON.attribution.length > 0) { if (tileJSON.attribution.length > 0) {
@@ -279,14 +337,14 @@ module.exports = function(options, repo, params, id, dataResolver) {
} }
tileJSON.attribution += source.attribution; tileJSON.attribution += source.attribution;
} }
callback(null); resolve();
}); });
}); });
}); }));
} }
}); });
async.parallel(queue, function(err, results) { var renderersReadyPromise = Promise.all(queue).then(function() {
// TODO: make pool sizes configurable // TODO: make pool sizes configurable
for (var s = 1; s <= maxScaleFactor; s++) { for (var s = 1; s <= maxScaleFactor; s++) {
var minPoolSize = 2; var minPoolSize = 2;
@@ -304,17 +362,19 @@ module.exports = function(options, repo, params, id, dataResolver) {
repo[id] = tileJSON; repo[id] = tileJSON;
var tilePattern = '/rendered/:z(\\d+)/:x(\\d+)/:y(\\d+)' + var tilePattern = '/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+)' +
':scale(' + scalePattern + ')?\.:format([\\w]+)'; ':scale(' + scalePattern + ')?\.:format([\\w]+)';
var respondImage = function(z, lon, lat, bearing, pitch, var respondImage = function(z, lon, lat, bearing, pitch,
width, height, scale, format, res, next, width, height, scale, format, res, next,
opt_overlay) { opt_overlay) {
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06) { if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06 ||
lon != lon || lat != lat) {
return res.status(400).send('Invalid center'); return res.status(400).send('Invalid center');
} }
if (Math.min(width, height) <= 0 || if (Math.min(width, height) <= 0 ||
Math.max(width, height) * scale > (options.maxSize || 2048)) { Math.max(width, height) * scale > (options.maxSize || 2048) ||
width != width || height != height) {
return res.status(400).send('Invalid size'); return res.status(400).send('Invalid size');
} }
if (format == 'png' || format == 'webp') { if (format == 'png' || format == 'webp') {
@@ -341,7 +401,10 @@ module.exports = function(options, repo, params, id, dataResolver) {
} }
renderer.render(params, function(err, data) { renderer.render(params, function(err, data) {
pool.release(renderer); pool.release(renderer);
if (err) console.log(err); if (err) {
console.error(err);
return;
}
var image = sharp(data, { var image = sharp(data, {
raw: { raw: {
@@ -466,9 +529,19 @@ module.exports = function(options, repo, params, id, dataResolver) {
return [px[0] * scale, px[1] * scale]; return [px[0] * scale, px[1] * scale];
}; };
var center = precisePx([x, y], z);
var mapHeight = 512 * (1 << z);
var maxEdge = center[1] + h / 2;
var minEdge = center[1] - h / 2;
if (maxEdge > mapHeight) {
center[1] -= (maxEdge - mapHeight);
} else if (minEdge < 0) {
center[1] -= minEdge;
}
var canvas = new Canvas(scale * w, scale * h); var canvas = new Canvas(scale * w, scale * h);
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
var center = precisePx([x, y], z);
ctx.scale(scale, scale); ctx.scale(scale, scale);
if (bearing) { if (bearing) {
ctx.translate(w / 2, h / 2); ctx.translate(w / 2, h / 2);
@@ -523,7 +596,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
if (options.serveStaticMaps !== false) { if (options.serveStaticMaps !== false) {
var staticPattern = var staticPattern =
'/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' + '/' + id + '/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' +
':scale(' + scalePattern + ')?\.:format([\\w]+)'; ':scale(' + scalePattern + ')?\.:format([\\w]+)';
var centerPattern = var centerPattern =
@@ -564,14 +637,11 @@ module.exports = function(options, repo, params, id, dataResolver) {
res, next, overlay); res, next, overlay);
}); });
var boundsPattern = var serveBounds = function(req, res, next) {
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, boundsPattern), function(req, res, next) {
var raw = req.params.raw; var raw = req.params.raw;
var bbox = [+req.params.minx, +req.params.miny, var bbox = [+req.params.minx, +req.params.miny,
+req.params.maxx, +req.params.maxy]; +req.params.maxx, +req.params.maxy];
var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
var transformer = raw ? var transformer = raw ?
mercator.inverse.bind(mercator) : dataProjWGStoInternalWGS; mercator.inverse.bind(mercator) : dataProjWGStoInternalWGS;
@@ -583,6 +653,7 @@ module.exports = function(options, repo, params, id, dataResolver) {
bbox[1] = minCorner[1]; bbox[1] = minCorner[1];
bbox[2] = maxCorner[0]; bbox[2] = maxCorner[0];
bbox[3] = maxCorner[1]; bbox[3] = maxCorner[1];
center = transformer(center);
} }
var w = req.params.width | 0, var w = req.params.width | 0,
@@ -591,8 +662,8 @@ module.exports = function(options, repo, params, id, dataResolver) {
format = req.params.format; format = req.params.format;
var z = calcZForBBox(bbox, w, h, req.query), var z = calcZForBBox(bbox, w, h, req.query),
x = (bbox[0] + bbox[2]) / 2, x = center[0],
y = (bbox[1] + bbox[3]) / 2, y = center[1],
bearing = 0, bearing = 0,
pitch = 0; pitch = 0;
@@ -601,6 +672,34 @@ module.exports = function(options, repo, params, id, dataResolver) {
path, req.query); path, req.query);
return respondImage(z, x, y, bearing, pitch, w, h, scale, format, return respondImage(z, x, y, bearing, pitch, w, h, scale, format,
res, next, overlay); res, next, overlay);
};
var boundsPattern =
util.format(':minx(%s),:miny(%s),:maxx(%s),:maxy(%s)',
FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN);
app.get(util.format(staticPattern, boundsPattern), serveBounds);
app.get('/' + id + '/static/', function(req, res, next) {
for (var key in req.query) {
req.query[key.toLowerCase()] = req.query[key];
}
req.params.raw = true;
req.params.format = (req.query.format || 'image/png').split('/').pop();
var bbox = (req.query.bbox || '').split(',');
req.params.minx = bbox[0];
req.params.miny = bbox[1];
req.params.maxx = bbox[2];
req.params.maxy = bbox[3];
req.params.width = req.query.width || '256';
req.params.height = req.query.height || '256';
if (req.query.scale) {
req.params.width /= req.query.scale;
req.params.height /= req.query.scale;
req.params.scale = '@' + req.query.scale;
}
return serveBounds(req, res, next);
}); });
var autoPattern = 'auto'; var autoPattern = 'auto';
@@ -630,9 +729,14 @@ module.exports = function(options, repo, params, id, dataResolver) {
bbox[3] = Math.max(bbox[3], pair[1]); bbox[3] = Math.max(bbox[3], pair[1]);
}); });
var bbox_ = mercator.convert(bbox, '900913');
var center = mercator.inverse(
[(bbox_[0] + bbox_[2]) / 2, (bbox_[1] + bbox_[3]) / 2]
);
var z = calcZForBBox(bbox, w, h, req.query), var z = calcZForBBox(bbox, w, h, req.query),
x = (bbox[0] + bbox[2]) / 2, x = center[0],
y = (bbox[1] + bbox[3]) / 2; y = center[1];
var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale,
path, req.query); path, req.query);
@@ -642,12 +746,16 @@ module.exports = function(options, repo, params, id, dataResolver) {
}); });
} }
app.get('/rendered.json', function(req, res, next) { app.get('/' + id + '.json', function(req, res, next) {
var info = clone(tileJSON); var info = clone(tileJSON);
info.tiles = utils.getTileUrls(req, info.tiles, info.tiles = utils.getTileUrls(req, info.tiles,
'styles/' + id + '/rendered', info.format); 'styles/' + id, info.format);
return res.send(info); return res.send(info);
}); });
return app; return new Promise(function(resolve, reject) {
Promise.all([fontListingPromise, renderersReadyPromise]).then(function() {
resolve(app);
});
});
}; };
+3 -3
View File
@@ -10,7 +10,7 @@ var clone = require('clone'),
module.exports = function(options, repo, params, id, reportTiles, reportFont) { module.exports = function(options, repo, params, id, reportTiles, reportFont) {
var app = express().disable('x-powered-by'); var app = express().disable('x-powered-by');
var styleFile = path.join(options.paths.styles, params.style); var styleFile = path.resolve(options.paths.styles, params.style);
var styleJSON = clone(require(styleFile)); var styleJSON = clone(require(styleFile));
Object.keys(styleJSON.sources).forEach(function(name) { Object.keys(styleJSON.sources).forEach(function(name) {
@@ -62,7 +62,7 @@ module.exports = function(options, repo, params, id, reportTiles, reportFont) {
repo[id] = styleJSON; repo[id] = styleJSON;
app.get('/' + id + '.json', function(req, res, next) { app.get('/' + id + '/style.json', function(req, res, next) {
var fixUrl = function(url, opt_nokey, opt_nostyle) { var fixUrl = function(url, opt_nokey, opt_nostyle) {
if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) { if (!url || (typeof url !== 'string') || url.indexOf('local://') !== 0) {
return url; return url;
@@ -117,5 +117,5 @@ module.exports = function(options, repo, params, id, reportTiles, reportFont) {
}); });
}); });
return app; return Promise.resolve(app);
}; };
+117 -52
View File
@@ -10,6 +10,7 @@ var fs = require('fs'),
var base64url = require('base64url'), var base64url = require('base64url'),
clone = require('clone'), clone = require('clone'),
cors = require('cors'), cors = require('cors'),
enableShutdown = require('http-shutdown'),
express = require('express'), express = require('express'),
handlebars = require('handlebars'), handlebars = require('handlebars'),
mercator = new (require('@mapbox/sphericalmercator'))(), mercator = new (require('@mapbox/sphericalmercator'))(),
@@ -28,7 +29,7 @@ if (!isLight) {
serve_rendered = require('./serve_rendered'); serve_rendered = require('./serve_rendered');
} }
module.exports = function(opts, callback) { function start(opts) {
console.log('Starting server'); console.log('Starting server');
var app = express().disable('x-powered-by'), var app = express().disable('x-powered-by'),
@@ -41,10 +42,9 @@ module.exports = function(opts, callback) {
app.enable('trust proxy'); app.enable('trust proxy');
callback = callback || function() {}; if (process.env.NODE_ENV == 'production') {
app.use(morgan('tiny'));
if (process.env.NODE_ENV !== 'production' && } else if (process.env.NODE_ENV !== 'test') {
process.env.NODE_ENV !== 'test') {
app.use(morgan('dev')); app.use(morgan('dev'));
} }
@@ -76,6 +76,8 @@ module.exports = function(opts, callback) {
paths.sprites = path.resolve(paths.root, paths.sprites || ''); paths.sprites = path.resolve(paths.root, paths.sprites || '');
paths.mbtiles = path.resolve(paths.root, paths.mbtiles || ''); paths.mbtiles = path.resolve(paths.root, paths.mbtiles || '');
var startupPromises = [];
var checkPath = function(type) { var checkPath = function(type) {
if (!fs.existsSync(paths[type])) { if (!fs.existsSync(paths[type])) {
console.error('The specified path for "' + type + '" does not exist (' + paths[type] + ').'); console.error('The specified path for "' + type + '" does not exist (' + paths[type] + ').');
@@ -87,9 +89,17 @@ module.exports = function(opts, callback) {
checkPath('sprites'); checkPath('sprites');
checkPath('mbtiles'); checkPath('mbtiles');
if (options.dataDecorator) {
try {
options.dataDecoratorFunc = require(path.resolve(paths.root, options.dataDecorator));
} catch (e) {}
}
var data = clone(config.data || {}); var data = clone(config.data || {});
app.use(cors()); if (opts.cors) {
app.use(cors());
}
Object.keys(config.styles || {}).forEach(function(id) { Object.keys(config.styles || {}).forEach(function(id) {
var item = config.styles[id]; var item = config.styles[id];
@@ -99,7 +109,7 @@ module.exports = function(opts, callback) {
} }
if (item.serve_data !== false) { if (item.serve_data !== false) {
app.use('/styles/', serve_style(options, serving.styles, item, id, startupPromises.push(serve_style(options, serving.styles, item, id,
function(mbtiles, fromData) { function(mbtiles, fromData) {
var dataItemId; var dataItemId;
Object.keys(data).forEach(function(id) { Object.keys(data).forEach(function(id) {
@@ -128,28 +138,38 @@ module.exports = function(opts, callback) {
} }
}, function(font) { }, function(font) {
serving.fonts[font] = true; serving.fonts[font] = true;
}).then(function(sub) {
app.use('/styles/', sub);
})); }));
} }
if (item.serve_rendered !== false) { if (item.serve_rendered !== false) {
if (serve_rendered) { if (serve_rendered) {
app.use('/styles/' + id + '/', startupPromises.push(
serve_rendered(options, serving.rendered, item, id, serve_rendered(options, serving.rendered, item, id,
function(mbtiles) { function(mbtiles) {
var mbtilesFile; var mbtilesFile;
Object.keys(data).forEach(function(id) { Object.keys(data).forEach(function(id) {
if (id == mbtiles) { if (id == mbtiles) {
mbtilesFile = data[id].mbtiles; mbtilesFile = data[id].mbtiles;
}
});
return mbtilesFile;
} }
}); ).then(function(sub) {
return mbtilesFile; app.use('/styles/', sub);
})); })
);
} else { } else {
item.serve_rendered = false; item.serve_rendered = false;
} }
} }
}); });
app.use('/', serve_font(options, serving.fonts)); startupPromises.push(
serve_font(options, serving.fonts).then(function(sub) {
app.use('/', sub);
})
);
Object.keys(data).forEach(function(id) { Object.keys(data).forEach(function(id) {
var item = data[id]; var item = data[id];
@@ -158,7 +178,11 @@ module.exports = function(opts, callback) {
return; return;
} }
app.use('/data/', serve_data(options, serving.data, item, id, serving.styles)); startupPromises.push(
serve_data(options, serving.data, item, id, serving.styles).then(function(sub) {
app.use('/data/', sub);
})
);
}); });
app.get('/styles.json', function(req, res, next) { app.get('/styles.json', function(req, res, next) {
@@ -171,7 +195,7 @@ module.exports = function(opts, callback) {
name: styleJSON.name, name: styleJSON.name,
id: id, id: id,
url: req.protocol + '://' + req.headers.host + url: req.protocol + '://' + req.headers.host +
'/styles/' + id + '.json' + query '/styles/' + id + '/style.json' + query
}); });
}); });
res.send(result); res.send(result);
@@ -182,7 +206,7 @@ module.exports = function(opts, callback) {
var info = clone(serving[type][id]); var info = clone(serving[type][id]);
var path = ''; var path = '';
if (type == 'rendered') { if (type == 'rendered') {
path = 'styles/' + id + '/rendered'; path = 'styles/' + id;
} else { } else {
path = type + '/' + id; path = type + '/' + id;
} }
@@ -219,28 +243,32 @@ module.exports = function(opts, callback) {
templateFile = path.resolve(paths.root, options.frontPage); templateFile = path.resolve(paths.root, options.frontPage);
} }
} }
fs.readFile(templateFile, function(err, content) { startupPromises.push(new Promise(function(resolve, reject) {
if (err) { fs.readFile(templateFile, function(err, content) {
console.error('Template not found:', err); if (err) {
} console.error('Template not found:', err);
var compiled = handlebars.compile(content.toString()); reject(err);
app.use(urlPath, 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; var compiled = handlebars.compile(content.toString());
data['is_light'] = isLight;
data['key_query_part'] = app.use(urlPath, function(req, res, next) {
req.query.key ? 'key=' + req.query.key + '&amp;' : ''; var data = {};
data['key_query'] = req.query.key ? '?key=' + req.query.key : ''; if (dataGetter) {
return res.status(200).send(compiled(data)); 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 + '&amp;' : '';
data['key_query'] = req.query.key ? '?key=' + req.query.key : '';
return res.status(200).send(compiled(data));
});
resolve();
}); });
}); }));
}; };
serveTemplate('/$', 'index', function(req) { serveTemplate('/$', 'index', function(req) {
@@ -266,11 +294,11 @@ module.exports = function(opts, callback) {
var query = req.query.key ? ('?key=' + req.query.key) : ''; var query = req.query.key ? ('?key=' + req.query.key) : '';
style.wmts_link = 'http://wmts.maptiler.com/' + style.wmts_link = 'http://wmts.maptiler.com/' +
base64url('http://' + req.headers.host + base64url('http://' + req.headers.host +
'/styles/' + id + '/rendered.json' + query) + '/wmts'; '/styles/' + id + '.json' + query) + '/wmts';
var tiles = utils.getTileUrls( var tiles = utils.getTileUrls(
req, style.serving_rendered.tiles, req, style.serving_rendered.tiles,
'styles/' + id + '/rendered', style.serving_rendered.format); 'styles/' + id, style.serving_rendered.format);
style.xyz_link = tiles[0]; style.xyz_link = tiles[0];
} }
}); });
@@ -353,20 +381,57 @@ module.exports = function(opts, callback) {
return data; return data;
}); });
var startupComplete = false;
var startupPromise = Promise.all(startupPromises).then(function() {
console.log('Startup complete');
startupComplete = true;
});
app.get('/health', function(req, res, next) {
if (startupComplete) {
return res.status(200).send('OK');
} else {
return res.status(503).send('Starting');
}
});
var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() { var server = app.listen(process.env.PORT || opts.port, process.env.BIND || opts.bind, function() {
console.log('Listening at http://%s:%d/', var address = this.address().address;
this.address().address, this.address().port); if (address.indexOf('::') === 0) {
address = '[' + address + ']'; // literal IPv6 address
return callback(); }
console.log('Listening at http://%s:%d/', address, this.address().port);
}); });
process.on('SIGINT', function() { // add server.shutdown() to gracefully stop serving
process.exit(); enableShutdown(server);
});
setTimeout(callback, 1000);
return { return {
app: app, app: app,
server: server server: server,
startupPromise: startupPromise
}; };
}
module.exports = function(opts) {
var running = start(opts);
process.on('SIGINT', function() {
process.exit();
});
process.on('SIGHUP', function() {
console.log('Stopping server and reloading config');
running.server.shutdown(function() {
for (var key in require.cache) {
delete require.cache[key];
}
var restarted = start(opts);
running.server = restarted.server;
running.app = restarted.app;
});
});
return running;
}; };
+24 -25
View File
@@ -1,7 +1,6 @@
'use strict'; 'use strict';
var async = require('async'), var path = require('path'),
path = require('path'),
fs = require('fs'); fs = require('fs');
var clone = require('clone'), var clone = require('clone'),
@@ -73,47 +72,47 @@ module.exports.fixTileJSONCenter = function(tileJSON) {
} }
}; };
module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, fallbacks, callback) { var getFontPbf = function(allowedFonts, fontPath, name, range, fallbacks) {
var getFontPbf = function(allowedFonts, name, range, callback, fallbacks) { return new Promise(function(resolve, reject) {
if (!allowedFonts || (allowedFonts[name] && fallbacks)) { if (!allowedFonts || (allowedFonts[name] && fallbacks)) {
var filename = path.join(fontPath, name, range + '.pbf'); var filename = path.join(fontPath, name, range + '.pbf');
if (!fallbacks) { if (!fallbacks) {
fallbacks = clone(allowedFonts || {}); fallbacks = clone(allowedFonts || {});
} }
delete fallbacks[name]; delete fallbacks[name];
return fs.readFile(filename, function(err, data) { fs.readFile(filename, function(err, data) {
if (err) { if (err) {
console.error('ERROR: Font not found:', name); console.error('ERROR: Font not found:', name);
if (fallbacks && Object.keys(fallbacks).length) { if (fallbacks && Object.keys(fallbacks).length) {
var fallbackName = Object.keys(fallbacks)[0]; var fallbackName = Object.keys(fallbacks)[0];
console.error('ERROR: Trying to use', fallbackName, 'as a fallback'); console.error('ERROR: Trying to use', fallbackName, 'as a fallback');
delete fallbacks[fallbackName]; delete fallbacks[fallbackName];
return getFontPbf(null, fallbackName, range, callback, fallbacks); getFontPbf(null, fontPath, fallbackName, range, fallbacks).then(resolve, reject);
} else { } else {
return callback(new Error('Font load error: ' + name)); reject('Font load error: ' + name);
} }
} else { } else {
return callback(null, data); resolve(data);
} }
}); });
} else { } else {
return callback(new Error('Font not allowed: ' + name)); reject('Font not allowed: ' + name);
}
};
var fonts = names.split(',');
var queue = [];
fonts.forEach(function(font) {
queue.push(function(callback) {
getFontPbf(allowedFonts, font, range, callback, clone(allowedFonts || fallbacks));
});
});
return async.parallel(queue, function(err, results) {
if (err) {
callback(err, new Buffer([]));
} else {
callback(err, glyphCompose.combine(results));
} }
}); });
}; };
module.exports.getFontsPbf = function(allowedFonts, fontPath, names, range, fallbacks) {
var fonts = names.split(',');
var queue = [];
fonts.forEach(function(font) {
queue.push(
getFontPbf(allowedFonts, fontPath, font, range, clone(allowedFonts || fallbacks))
);
});
return new Promise(function(resolve, reject) {
Promise.all(queue).then(function(values) {
return resolve(glyphCompose.combine(values));
}, reject);
});
};
+9 -1
View File
@@ -38,6 +38,14 @@ var testTileJSON = function(url) {
}; };
describe('Metadata', function() { describe('Metadata', function() {
describe('/health', function() {
it('returns 200', function(done) {
supertest(app)
.get('/health')
.expect(200, done);
});
});
testTileJSONArray('/index.json'); testTileJSONArray('/index.json');
testTileJSONArray('/rendered.json'); testTileJSONArray('/rendered.json');
testTileJSONArray('/data.json'); testTileJSONArray('/data.json');
@@ -63,6 +71,6 @@ describe('Metadata', function() {
}); });
}); });
testTileJSON('/styles/test-style/rendered.json'); testTileJSON('/styles/test-style.json');
testTileJSON('/data/openmaptiles.json'); testTileJSON('/data/openmaptiles.json');
}); });
+1
View File
@@ -12,6 +12,7 @@ before(function() {
}); });
global.app = running.app; global.app = running.app;
global.server = running.server; global.server = running.server;
return running.startupPromise;
}); });
after(function() { after(function() {
+5 -5
View File
@@ -11,12 +11,12 @@ var testIs = function(url, type, status) {
var prefix = 'test-style'; var prefix = 'test-style';
describe('Styles', function() { describe('Styles', function() {
describe('/styles/' + prefix + '.json is valid style', function() { describe('/styles/' + prefix + '/style.json is valid style', function() {
testIs('/styles/' + prefix + '.json', /application\/json/); testIs('/styles/' + prefix + '/style.json', /application\/json/);
it('contains expected properties', function(done) { it('contains expected properties', function(done) {
supertest(app) supertest(app)
.get('/styles/' + prefix + '.json') .get('/styles/' + prefix + '/style.json')
.expect(function(res) { .expect(function(res) {
res.body.version.should.equal(8); res.body.version.should.equal(8);
res.body.name.should.be.String(); res.body.name.should.be.String();
@@ -27,8 +27,8 @@ describe('Styles', function() {
}).end(done); }).end(done);
}); });
}); });
describe('/styles/streets.json is not served', function() { describe('/styles/streets/style.json is not served', function() {
testIs('/styles/streets.json', /./, 404); testIs('/styles/streets/style.json', /./, 404);
}); });
describe('/styles/' + prefix + '/sprite[@2x].{format}', function() { describe('/styles/' + prefix + '/sprite[@2x].{format}', function() {
+1 -1
View File
@@ -1,6 +1,6 @@
var testTile = function(prefix, z, x, y, format, status, scale, type) { var testTile = function(prefix, z, x, y, format, status, scale, type) {
if (scale) y += '@' + scale + 'x'; if (scale) y += '@' + scale + 'x';
var path = '/styles/' + prefix + '/rendered/' + z + '/' + x + '/' + y + '.' + format; var path = '/styles/' + prefix + '/' + z + '/' + x + '/' + y + '.' + format;
it(path + ' returns ' + status, function(done) { it(path + ' returns ' + status, function(done) {
var test = supertest(app).get(path); var test = supertest(app).get(path);
test.expect(status); test.expect(status);