diff --git a/.gitignore b/.gitignore index ed787b98..99109a29 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,9 @@ etl/**/*.xls etl/**/*.xlsx etl/**/*.zip +# Cache +app/tilecache/**/*.png +app/tilecache/**/*.mbtiles + # Notes TODO diff --git a/app/src/tiles/cache.js b/app/src/tiles/cache.js new file mode 100644 index 00000000..56deb167 --- /dev/null +++ b/app/src/tiles/cache.js @@ -0,0 +1,72 @@ +/** + * Cache tiles (PNG images generated from database) + * + * Frequency of change: + * - base layer tiles change rarely - on changes to underlying geometry table + * - visualisation layer tiles change frequently - with almost any edit to the buildings table + * + * Cost of generation and storage: + * - low zoom tiles are more expensive to render, containing more features from the database + * - high zoom tiles are cheaper to rerender, and changes are more visible + * - there are many more high zoom tiles than low: 4 tiles at zoom level n+1 for each tile + * at zoom level n + * + */ +import fs from 'fs'; +import path from 'path'; + +// const CACHE_PATH = process.env.CACHE_PATH +const CACHE_PATH = '/home/tom/projects/colouring-london/colouring-london/app/tilecache' + +function get(tileset, z, x, y, cb){ + if (!should_try_cache(tileset, z)) { + cb("Skip cache", null) + return + } + const dir = `${CACHE_PATH}/${tileset}/${z}/${x}` + const fname = `${dir}/${y}.png` + fs.readFile(fname, cb) +} + +function put(im, tileset, z, x, y, cb){ + if (!should_try_cache(tileset, z)) { + cb("Skip cache") + return + } + const dir = `${CACHE_PATH}/${tileset}/${z}/${x}` + const fname = `${dir}/${y}.png` + fs.writeFile(fname, im, 'binary', (err) => { + if (err && err.code === 'ENOENT') { + try { + console.log("trying") + mkdir_recursive(dir); + fs.writeFile(fname, im, 'binary', cb); + } catch (error) { + console.log("mkdir error") + cb(err); + } + } else { + cb(err) + } + }); +} + +// for node >10 we could drop this in favour of fs.mkdir(dir, { recursive: true }, (err) => {}) +function mkdir_recursive(dir) { + const parent = path.dirname(dir); + if (!fs.existsSync(parent)) { + mkdir_recursive(parent) + } + fs.mkdirSync(dir); +} + +function should_try_cache(tileset, z) { + if (tileset === 'base_light' || tileset === 'base_light') { + // cache for higher zoom levels (unlikely to change) + return z <= 15 + } + // else cache for lower zoom levels (change slowly) + return z <= 12 +} + +export { get, put }; diff --git a/app/src/tiles/tileserver.js b/app/src/tiles/tileserver.js index 74fbd5f1..a0773dce 100644 --- a/app/src/tiles/tileserver.js +++ b/app/src/tiles/tileserver.js @@ -4,6 +4,7 @@ */ import express from 'express'; +import { get, put } from './cache'; import { render_tile } from './tile'; import { strictParseInt } from '../parse'; @@ -51,12 +52,27 @@ function handle_tile_request(tileset, req, res) { return {error:'Bad parameter'} } - render_tile(tileset, int_z, int_x, int_y, undefined, function(err, im) { - if (err) throw err + get(tileset, int_z, int_x, int_y, (err, im) => { + if (err) { + render_tile(tileset, int_z, int_x, int_y, undefined, (err, im) => { + if (err) throw err - res.writeHead(200, {'Content-Type': 'image/png'}) - res.end(im) + put(im, tileset, z, x, y, (err) => { + if (err) { + console.error(err) + } + + res.writeHead(200, {'Content-Type': 'image/png'}) + res.end(im) + }) + + }) + } else { + res.writeHead(200, {'Content-Type': 'image/png'}) + res.end(im) + } }) + } function handle_highlight_tile_request(req, res) {