From 3e84297b3066702b1a1ccc5082d609ac344d799f Mon Sep 17 00:00:00 2001 From: Dimosthenis Kaponis Date: Tue, 1 May 2018 20:00:05 +0300 Subject: [PATCH] WiP. Replace image markers resolution-independent Canvas versions. --- .eslintrc.json | 11 + README.md | 2 +- package.json | 15 +- public/resources/images/dropoff-marker.png | Bin 474 -> 0 bytes public/resources/images/pickup-marker.png | Bin 486 -> 0 bytes src/serve_rendered.js | 336 +++++++++++---------- 6 files changed, 194 insertions(+), 170 deletions(-) create mode 100644 .eslintrc.json delete mode 100644 public/resources/images/dropoff-marker.png delete mode 100644 public/resources/images/pickup-marker.png diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..08e6489 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "env": { + "node": true, + "es6": true + } +} \ No newline at end of file diff --git a/README.md b/README.md index ef312ad..3b0d048 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This fork adds features used by BEAT, including: * Marker support in the static (rendered) maps * Prometheus compatible `/metrics` endpoint -* Improved `/health/` endpoint +* Improved `/health` endpoint ## Documentation diff --git a/package.json b/package.json index 3d07070..ff83eb3 100644 --- a/package.json +++ b/package.json @@ -36,17 +36,24 @@ "http-shutdown": "^1.2.0", "morgan": "1.9.0", "nomnom": "1.8.1", - "npm": "^5.7.1", + "npm": "^5.8.0", "pbf": "3.0.5", "proj4": "2.4.4", + "prom-client": "11.0.0", "request": "2.83.0", "sharp": "^0.20.0", - "tileserver-gl-styles": "1.2.0", - "prom-client": "11.0.0" + "tileserver-gl-styles": "1.2.0" }, "devDependencies": { + "eslint-config-standard": "^11.0.0", + "eslint-plugin-import": "^2.11.0", + "eslint-plugin-node": "^6.0.1", + "eslint-plugin-promise": "^3.7.0", + "eslint-plugin-standard": "^3.1.0", + "mocha": "^3.2.0", "should": "^11.2.0", "mocha": "^3.2.0", - "supertest": "^3.0.0" + "supertest": "^3.0.0", + "eslint": "^4.19.1" } } diff --git a/public/resources/images/dropoff-marker.png b/public/resources/images/dropoff-marker.png deleted file mode 100644 index 4ba2e9131dac7eb13030540cbdc373214f3fb778..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 474 zcmV<00VV#4P)Px$lu1NER5%f(Rk2C~K@fd=xs(#n&Q2o%ApyaBfnY09NIJj3#wKEEZE9OTAc6)z zz{WO$7(o!CT}m-nhPcjKGsoVUQ!m+OXXnj(x4SbtXE0@K+HQW>d#wSsW`U|PSZ0{D z7#N@nm`vI>JSM-ENhA@RGuY$tjZ47e%hP|3nb3$W zd8dW)>Uzj*N+y3zK#ZBP;D)FbvNTd+U`J!To!#K$`rd^s>}+81U<)RnOQG0W$T~|w zo!I;NZ3L^mbhsV)e1lqn7OfActvk~-=V?C0sTI|aI_I4Oxy(VD2=6R8ruj66S|Q)2 zoV}z@e`W$W^q86yKgPGIW!4@p>K3-`1GCx&rV+4fuqbC0QJyPo(F)jtBr~(UVO5iv z8WSzRwIF%m9-hq@P)Px$ph-kQR5%f}RliF@Q5623rIF@R87MS488q1v3H=Kelz%~6Lj+-yv`9u(WbPQWspA>Fc4x;v*r|5gnE8(j?xNy&R&iS6_+Rv6M# zZ-jl5)BNcG6!}AxXf)y!l&hhJ$KysOvkzT=G?wgz8;y3HhY~nuz?>>5o!&z-xdXr7 zXRKH(O2J7K3QwwVa@rJo82MnyGpY#rWx?Qr#Z|4oBNSSp2=Y0pk= 300) { - // console.log('HTTP error', err || res.statusCode); - createEmptyResponse(format, '', callback); - return; - } + var parts = url.parse(req.url); + var extension = path.extname(parts.pathname).toLowerCase(); + var format = extensionToFormat[extension] || ''; + if (err || res.statusCode < 200 || res.statusCode >= 300) { + // console.log('HTTP error', err || res.statusCode); + createEmptyResponse(format, '', callback); + return; + } - 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; - } + 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); + response.data = body; + callback(null, response); }); } } @@ -257,33 +237,33 @@ module.exports = function(options, repo, params, id, dataResolver) { var httpTester = /^(http(s)?:)?\/\//; if (styleJSON.sprite && !httpTester.test(styleJSON.sprite)) { styleJSON.sprite = 'sprites://' + - styleJSON.sprite - .replace('{style}', path.basename(styleFile, '.json')) - .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath))); + styleJSON.sprite + .replace('{style}', path.basename(styleFile, '.json')) + .replace('{styleJsonFolder}', path.relative(options.paths.sprites, path.dirname(styleJSONPath))); } if (styleJSON.glyphs && !httpTester.test(styleJSON.glyphs)) { styleJSON.glyphs = 'fonts://' + styleJSON.glyphs; } var markerImages = []; - var markerImageNames = ['pickup','dropoff']; + var markerImageNames = ['pickup', 'dropoff']; var markerLoadPromise = new Promise(function(resolveCallback, rejectCallback) { - markerImageNames.forEach(function(imageName){ - fs.readFile(path.join(__dirname, "../public/resources/images/") + imageName + '-marker.png', function(err, fileData) { + markerImageNames.forEach(function(imageName) { + fs.readFile(path.join(__dirname, "../public/resources/images/") + imageName + '-marker.png', function(err, fileData) { - if (err) { - rejectCallback(err); - } + if (err) { + rejectCallback(err); + } - var mkrImage = new Canvas.Image(); - mkrImage.src = fileData; - markerImages.push(mkrImage); - }); + var mkrImage = new Canvas.Image(); + mkrImage.src = fileData; + markerImages.push(mkrImage); }); - resolveCallback(); + }); + resolveCallback(); }); var tileJSON = { @@ -314,7 +294,7 @@ module.exports = function(options, repo, params, id, dataResolver) { var mbtilesFile = url.substring('mbtiles://'.length); var fromData = mbtilesFile[0] == '{' && - mbtilesFile[mbtilesFile.length - 1] == '}'; + mbtilesFile[mbtilesFile.length - 1] == '}'; if (fromData) { mbtilesFile = mbtilesFile.substr(1, mbtilesFile.length - 2); @@ -365,7 +345,7 @@ module.exports = function(options, repo, params, id, dataResolver) { } if (!attributionOverride && - source.attribution && source.attribution.length > 0) { + source.attribution && source.attribution.length > 0) { if (tileJSON.attribution.length > 0) { tileJSON.attribution += '; '; } @@ -394,25 +374,27 @@ module.exports = function(options, repo, params, id, dataResolver) { repo[id] = tileJSON; 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, - width, height, scale, format, res, next, - opt_overlay) { + width, height, scale, format, res, next, + opt_overlay) { if (Math.abs(lon) > 180 || Math.abs(lat) > 85.06 || - lon != lon || lat != lat) { + lon != lon || lat != lat) { return res.status(400).send('Invalid center'); } if (Math.min(width, height) <= 0 || - Math.max(width, height) * scale > (options.maxSize || 2048) || - width != width || height != height) { + Math.max(width, height) * scale > (options.maxSize || 2048) || + width != width || height != height) { return res.status(400).send('Invalid size'); } - if (format == 'png' || format == 'webp') { - } else if (format == 'jpg' || format == 'jpeg') { - format = 'jpeg'; - } else { + + let formatIndex = ['jpg', 'jpeg', 'png', 'webp'].indexOf(format); + + if (formatIndex == -1) { return res.status(400).send('Invalid format'); + } else if (formatIndex < 2) { + format = 'jpeg'; } var pool = map.renderers[scale]; @@ -468,14 +450,14 @@ module.exports = function(options, repo, params, id, dataResolver) { } var formatQuality = (params.formatQuality || {})[format] || - (options.formatQuality || {})[format]; + (options.formatQuality || {})[format]; if (format == 'png') { - image.png({adaptiveFiltering: false}); + image.png({ adaptiveFiltering: false }); } else if (format == 'jpeg') { - image.jpeg({quality: formatQuality || 80}); + image.jpeg({ quality: formatQuality || 80 }); } else if (format == 'webp') { - image.webp({quality: formatQuality || 90}); + image.webp({ quality: formatQuality || 90 }); } image.toBuffer(function(err, buffer, info) { if (!buffer) { @@ -501,12 +483,12 @@ module.exports = function(options, repo, params, id, dataResolver) { } var z = req.params.z | 0, - x = req.params.x | 0, - y = req.params.y | 0, - scale = getScale(req.params.scale), - format = req.params.format; + x = req.params.x | 0, + y = req.params.y | 0, + scale = getScale(req.params.scale), + format = req.params.format; if (z < 0 || x < 0 || y < 0 || - z > 20 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) { + z > 20 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) { return res.status(404).send('Out of bounds'); } var tileSize = 256; @@ -515,7 +497,7 @@ module.exports = function(options, repo, params, id, dataResolver) { ((y + 0.5) / (1 << z)) * (256 << z) ], z); return respondImage(z, tileCenter[0], tileCenter[1], 0, 0, - tileSize, tileSize, scale, format, res, next); + tileSize, tileSize, scale, format, res, next); }); var extractPathFromQuery = function(query, transformer) { @@ -539,8 +521,40 @@ module.exports = function(options, repo, params, id, dataResolver) { return path; }; + var drawMarker = function(ctx, coordinates, scale, outerColour = "rgb(0,0,0)", innerColour = "rgb(255,255,255)", outerRadius = markerSize, innerRadius = markerSize * 0.35) { + + [outerRadius, innerRadius, coordinates[0], coordinates[1]].map(console.log); + + outerRadius = parseInt(outerRadius); + innerRadius = parseInt(innerRadius); + let x = parseInt(coordinates[0]); + let y = parseInt(coordinates[1]); + + let validParams = [outerRadius, innerRadius, x, y].reduce(function(acc, element) { + if (isNaN(element)) { + console.log("element: " + element + " is invalid."); + } + return acc && !isNaN(element); + }, true); + + if (!validParams) { + console.log("invalid parameters!"); + } + // outer circle. + ctx.beginPath(); + ctx.arc(x, y, outerRadius, 0, 2 * Math.PI, false); + ctx.fillStyle = outerColour; + ctx.fill(); + + // inner circle. + ctx.beginPath(); + ctx.arc(x, y, innerRadius, 0, 2 * Math.PI, false); + ctx.fillStyle = innerColour; + ctx.fill(); + } + var renderOverlay = function(z, x, y, bearing, pitch, w, h, scale, - path, query) { + path, query) { if (!path || path.length < 2) { return null; } @@ -573,7 +587,7 @@ module.exports = function(options, repo, params, id, dataResolver) { ctx.translate(-center[0] + w / 2, -center[1] + h / 2); } var lineWidth = query.width !== undefined ? - parseFloat(query.width) : 1; + parseFloat(query.width) : 1; ctx.lineWidth = lineWidth; ctx.strokeStyle = query.stroke || 'rgba(0,64,255,0.7)'; ctx.fillStyle = query.fill || 'rgba(255,255,255,0.4)'; @@ -583,7 +597,7 @@ module.exports = function(options, repo, params, id, dataResolver) { ctx.lineTo(px[0], px[1]); }); if (path[0][0] == path[path.length - 1][0] && - path[0][1] == path[path.length - 1][1]) { + path[0][1] == path[path.length - 1][1]) { ctx.closePath(); } ctx.fill(); @@ -592,17 +606,9 @@ module.exports = function(options, repo, params, id, dataResolver) { } if (query.showMarkers && query.showMarkers == 1) { - // Add the markers, if requested to do so. - - var markers = [ - [markerImages[0], precisePx(path[0],z).map(function(loc,idx){ return (idx ==1)? loc - markerSize/2: loc - markerSize/2;})], - [markerImages[1], precisePx(path[path.length-1],z).map(function(loc,idx){ return (idx == 1)?loc - markerSize/2: loc - markerSize/2;})] - ]; - - markers.forEach(function(imgSpec){ - var coordinates = imgSpec[1]; - ctx.drawImage(imgSpec[0], coordinates[0], coordinates[1], markerSize, markerSize); - }); + // Add the markers, if requested to do so. + drawMarker(ctx,precisePx(path[path.length-1],z),scale, "rgba(179, 0, 0, 0.7)"); + drawMarker(ctx,precisePx(path[0],z),scale, "rgba(0, 151, 25, 0.7)"); } return canvas.toBuffer(); @@ -612,10 +618,10 @@ module.exports = function(options, repo, params, id, dataResolver) { var z = 25; var padding = query.padding !== undefined ? - parseFloat(query.padding) : 0.1; + parseFloat(query.padding) : 0.1; var minCorner = mercator.px([bbox[0], bbox[3]], z), - maxCorner = mercator.px([bbox[2], bbox[1]], z); + maxCorner = mercator.px([bbox[2], bbox[1]], z); var w_ = w / (1 + 2 * padding); var h_ = h / (1 + 2 * padding); @@ -631,25 +637,25 @@ module.exports = function(options, repo, params, id, dataResolver) { if (options.serveStaticMaps !== false) { var staticPattern = - '/' + id + '/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' + - ':scale(' + scalePattern + ')?\.:format([\\w]+)'; + '/' + id + '/static/:raw(raw)?/%s/:width(\\d+)x:height(\\d+)' + + ':scale(' + scalePattern + ')?.:format([\\w]+)'; var centerPattern = - util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?', - FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, - FLOAT_PATTERN, FLOAT_PATTERN); + util.format(':x(%s),:y(%s),:z(%s)(@:bearing(%s)(,:pitch(%s))?)?', + FLOAT_PATTERN, FLOAT_PATTERN, FLOAT_PATTERN, + FLOAT_PATTERN, FLOAT_PATTERN); app.get(util.format(staticPattern, centerPattern), function(req, res, next) { var raw = req.params.raw; var z = +req.params.z, - x = +req.params.x, - y = +req.params.y, - bearing = +(req.params.bearing || '0'), - pitch = +(req.params.pitch || '0'), - w = req.params.width | 0, - h = req.params.height | 0, - scale = getScale(req.params.scale), - format = req.params.format; + x = +req.params.x, + y = +req.params.y, + bearing = +(req.params.bearing || '0'), + pitch = +(req.params.pitch || '0'), + w = req.params.width | 0, + h = req.params.height | 0, + scale = getScale(req.params.scale), + format = req.params.format; if (z < 0) { return res.status(400).send('Invalid zoom'); @@ -666,16 +672,16 @@ module.exports = function(options, repo, params, id, dataResolver) { var path = extractPathFromQuery(req.query, transformer); var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, - path, req.query); + path, req.query); return respondImage(z, x, y, bearing, pitch, w, h, scale, format, - res, next, overlay); + res, next, overlay); }); var serveBounds = function(req, res, next) { var raw = req.params.raw; 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 ? @@ -692,26 +698,26 @@ module.exports = function(options, repo, params, id, dataResolver) { } var w = req.params.width | 0, - h = req.params.height | 0, - scale = getScale(req.params.scale), - format = req.params.format; + h = req.params.height | 0, + scale = getScale(req.params.scale), + format = req.params.format; var z = calcZForBBox(bbox, w, h, req.query), - x = center[0], - y = center[1], - bearing = 0, - pitch = 0; + x = center[0], + y = center[1], + bearing = 0, + pitch = 0; var path = extractPathFromQuery(req.query, transformer); var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, - path, req.query); + path, req.query); 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); + 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); @@ -742,11 +748,11 @@ module.exports = function(options, repo, params, id, dataResolver) { app.get(util.format(staticPattern, autoPattern), function(req, res, next) { var raw = req.params.raw; var w = req.params.width | 0, - h = req.params.height | 0, - bearing = 0, - pitch = 0, - scale = getScale(req.params.scale), - format = req.params.format; + h = req.params.height | 0, + bearing = 0, + pitch = 0, + scale = getScale(req.params.scale), + format = req.params.format; var transformer = raw ? mercator.inverse.bind(mercator) : dataProjWGStoInternalWGS; @@ -770,21 +776,21 @@ module.exports = function(options, repo, params, id, dataResolver) { ); var z = calcZForBBox(bbox, w, h, req.query), - x = center[0], - y = center[1]; + x = center[0], + y = center[1]; var overlay = renderOverlay(z, x, y, bearing, pitch, w, h, scale, - path, req.query); + path, req.query); return respondImage(z, x, y, bearing, pitch, w, h, scale, format, - res, next, overlay); + res, next, overlay); }); } app.get('/' + id + '.json', function(req, res, next) { var info = clone(tileJSON); info.tiles = utils.getTileUrls(req, info.tiles, - 'styles/' + id, info.format); + 'styles/' + id, info.format); return res.send(info); });