Initial implementation of path rendering

This commit is contained in:
Petr Sloup 2016-06-24 11:45:17 +02:00
parent 9a21382984
commit 01bff86c6d
4 changed files with 100 additions and 4 deletions

View file

@ -11,6 +11,7 @@ addons:
- g++-4.8 - g++-4.8
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 xvfb - sudo apt-get install -qq xvfb
install: install:
- npm install - npm install

View file

@ -6,6 +6,7 @@ RUN apt-get -qq update \
curl \ curl \
build-essential \ build-essential \
python \ python \
libcairo2-dev \
xvfb \ xvfb \
&& curl -sL https://deb.nodesource.com/setup_4.x | bash - \ && curl -sL https://deb.nodesource.com/setup_4.x | bash - \
&& apt-get -y install nodejs \ && apt-get -y install nodejs \

View file

@ -17,6 +17,7 @@
"dependencies": { "dependencies": {
"async": "1.5.2", "async": "1.5.2",
"advanced-pool": "0.3.2", "advanced-pool": "0.3.2",
"canvas": "1.4.0",
"clone": "1.0.2", "clone": "1.0.2",
"color": "0.11.2", "color": "0.11.2",
"cors": "2.7.1", "cors": "2.7.1",

View file

@ -8,14 +8,18 @@ var async = require('async'),
util = require('util'), util = require('util'),
zlib = require('zlib'); zlib = require('zlib');
var clone = require('clone'), // sharp has to be required before node-canvas
// see https://github.com/lovell/sharp/issues/371
var sharp = require('sharp');
var Canvas = require('canvas'),
clone = require('clone'),
Color = require('color'), Color = require('color'),
express = require('express'), express = require('express'),
mercator = new (require('sphericalmercator'))(), mercator = new (require('sphericalmercator'))(),
mbgl = require('mapbox-gl-native'), mbgl = require('mapbox-gl-native'),
mbtiles = require('mbtiles'), mbtiles = require('mbtiles'),
request = require('request'), request = require('request');
sharp = require('sharp');
var utils = require('./utils'); var utils = require('./utils');
@ -219,7 +223,8 @@ module.exports = function(options, repo, params, id) {
':scale(' + SCALE_PATTERN + ')?\.:format([\\w]+)'; ':scale(' + SCALE_PATTERN + ')?\.: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) {
if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06) { if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06) {
return res.status(400).send('Invalid center'); return res.status(400).send('Invalid center');
} }
@ -266,6 +271,10 @@ module.exports = function(options, repo, params, id) {
image.resize(width * scale, height * scale); image.resize(width * scale, height * scale);
} }
if (opt_overlay) {
image.overlayWith(opt_overlay);
}
image.toFormat(format); image.toFormat(format);
var formatEncoding = (params.formatEncoding || {})[format] || var formatEncoding = (params.formatEncoding || {})[format] ||
@ -354,6 +363,90 @@ module.exports = function(options, repo, params, id) {
return respondImage(z, x, y, 0, 0, w, h, scale, format, res, next); return respondImage(z, x, y, 0, 0, w, h, scale, format, res, next);
}); });
var pathPattern = 'path/:width(\\d+)x:height(\\d+)';
app.get(util.format(staticPattern, pathPattern), function(req, res, next) {
var pathParts = (req.query.path || '').split('|');
var path = [];
pathParts.forEach(function(pair) {
var pairParts = pair.split(',');
if (pairParts.length == 2) {
if (req.query.latlng == '1' || req.query.latlng == 'true') {
path.push([+(pairParts[1]), +(pairParts[0])]);
} else {
path.push([+(pairParts[0]), +(pairParts[1])]);
}
}
});
if (path.length < 2) {
return res.status(400).send('Invalid path');
}
var w = req.params.width | 0,
h = req.params.height | 0,
scale = getScale(req.params.scale),
format = req.params.format;
var bbox = [Infinity, Infinity, -Infinity, -Infinity];
path.forEach(function(pair) {
bbox[0] = Math.min(bbox[0], pair[0]);
bbox[1] = Math.min(bbox[1], pair[1]);
bbox[2] = Math.max(bbox[2], pair[0]);
bbox[3] = Math.max(bbox[3], pair[1]);
});
var z = 20,
x = (bbox[0] + bbox[2]) / 2,
y = (bbox[1] + bbox[3]) / 2;
var padding = req.query.padding !== undefined ?
parseFloat(req.query.padding) : 0.1;
var minCorner = mercator.px([bbox[0], bbox[3]], z),
maxCorner = mercator.px([bbox[2], bbox[1]], z);
while ((((maxCorner[0] - minCorner[0]) * (1 + 2 * padding) > w) ||
((maxCorner[1] - minCorner[1]) * (1 + 2 * padding) > h)) && z > 0) {
z--;
minCorner[0] /= 2;
minCorner[1] /= 2;
maxCorner[0] /= 2;
maxCorner[1] /= 2;
}
var precisePx = function(ll, zoom) {
var px = mercator.px(ll, 20);
var scale = Math.pow(2, zoom - 20);
return [px[0] * scale, px[1] * scale];
};
var canvas = new Canvas(scale * w, scale * h);
var ctx = canvas.getContext('2d');
var center = precisePx([x, y], z);
ctx.scale(scale, scale);
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
var lineWidth = req.query.width !== undefined ?
parseFloat(req.query.width) : 1;
ctx.lineWidth = lineWidth;
ctx.strokeStyle = req.query.stroke || 'rgba(0,64,255,0.7)';
ctx.fillStyle = req.query.fill || 'rgba(255,255,255,0.4)';
ctx.beginPath();
path.forEach(function(pair) {
var px = precisePx(pair, z);
ctx.lineTo(px[0], px[1]);
});
if (path[0][0] == path[path.length - 1][0] &&
path[0][1] == path[path.length - 1][1]) {
ctx.closePath();
}
ctx.fill();
if (lineWidth > 0) {
ctx.stroke();
}
return respondImage(z, x, y, 0, 0, w, h, scale, format, res, next,
canvas.toBuffer());
});
app.get('/rendered.json', function(req, res, next) { app.get('/rendered.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,