├── .gitignore ├── LICENSE ├── README.md ├── config.json ├── geojsons └── test.geojson ├── index.js ├── package-lock.json ├── package.json ├── screenshots └── .gitkeep └── src ├── index.html └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | screenshots/*.png 2 | node_modules 3 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 webkid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leaflet Mapshot 2 | 3 | A script for generating automated screenshots of a local Leaflet map. We used it to create the video in this article https://interaktiv.morgenpost.de/mauerzeichnen-auswertung/. 4 | 5 | ## How does it work? 6 | 7 | When you run the script it uses [`puppeteer`](https://github.com/GoogleChrome/puppeteer) to make screenshots for every GeoJSON file that is under `geojsons`. 8 | You can adjust the `config.json` for changing `tileLayer`, `zoom` and `center`. 9 | 10 | ## Usage 11 | 12 | ``` 13 | npm start 14 | ``` 15 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "center": [52.52, 13.4], 3 | "zoom": 10, 4 | "tileLayerUrl": "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}.png" 5 | } -------------------------------------------------------------------------------- /geojsons/test.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "properties": {}, 4 | "geometry": { 5 | "type": "Polygon", 6 | "coordinates": [ 7 | [ 8 | [ 9 | 13.2879638671875, 10 | 52.452661893987695 11 | ], 12 | [ 13 | 13.524169921874998, 14 | 52.452661893987695 15 | ], 16 | [ 17 | 13.524169921874998, 18 | 52.55464616298482 19 | ], 20 | [ 21 | 13.2879638671875, 22 | 52.55464616298482 23 | ], 24 | [ 25 | 13.2879638671875, 26 | 52.452661893987695 27 | ] 28 | ] 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./src'); -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geojson-image-creator", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "agent-base": { 8 | "version": "4.2.0", 9 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", 10 | "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", 11 | "requires": { 12 | "es6-promisify": "5.0.0" 13 | } 14 | }, 15 | "async-limiter": { 16 | "version": "1.0.0", 17 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 18 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 19 | }, 20 | "balanced-match": { 21 | "version": "1.0.0", 22 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 23 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 24 | }, 25 | "brace-expansion": { 26 | "version": "1.1.8", 27 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 28 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 29 | "requires": { 30 | "balanced-match": "1.0.0", 31 | "concat-map": "0.0.1" 32 | } 33 | }, 34 | "concat-map": { 35 | "version": "0.0.1", 36 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 37 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 38 | }, 39 | "concat-stream": { 40 | "version": "1.6.0", 41 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 42 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", 43 | "requires": { 44 | "inherits": "2.0.3", 45 | "readable-stream": "2.3.3", 46 | "typedarray": "0.0.6" 47 | } 48 | }, 49 | "core-util-is": { 50 | "version": "1.0.2", 51 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 52 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 53 | }, 54 | "debug": { 55 | "version": "2.6.9", 56 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 57 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 58 | "requires": { 59 | "ms": "2.0.0" 60 | } 61 | }, 62 | "es6-promise": { 63 | "version": "4.2.4", 64 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", 65 | "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" 66 | }, 67 | "es6-promisify": { 68 | "version": "5.0.0", 69 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 70 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 71 | "requires": { 72 | "es6-promise": "4.2.4" 73 | } 74 | }, 75 | "extract-zip": { 76 | "version": "1.6.6", 77 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz", 78 | "integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=", 79 | "requires": { 80 | "concat-stream": "1.6.0", 81 | "debug": "2.6.9", 82 | "mkdirp": "0.5.0", 83 | "yauzl": "2.4.1" 84 | } 85 | }, 86 | "fd-slicer": { 87 | "version": "1.0.1", 88 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", 89 | "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", 90 | "requires": { 91 | "pend": "1.2.0" 92 | } 93 | }, 94 | "fs.realpath": { 95 | "version": "1.0.0", 96 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 97 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 98 | }, 99 | "glob": { 100 | "version": "7.1.2", 101 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 102 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 103 | "requires": { 104 | "fs.realpath": "1.0.0", 105 | "inflight": "1.0.6", 106 | "inherits": "2.0.3", 107 | "minimatch": "3.0.4", 108 | "once": "1.4.0", 109 | "path-is-absolute": "1.0.1" 110 | } 111 | }, 112 | "https-proxy-agent": { 113 | "version": "2.1.1", 114 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.1.1.tgz", 115 | "integrity": "sha512-LK6tQUR/VOkTI6ygAfWUKKP95I+e6M1h7N3PncGu1CATHCnex+CAv9ttR0lbHu1Uk2PXm/WoAHFo6JCGwMjVMw==", 116 | "requires": { 117 | "agent-base": "4.2.0", 118 | "debug": "3.1.0" 119 | }, 120 | "dependencies": { 121 | "debug": { 122 | "version": "3.1.0", 123 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 124 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 125 | "requires": { 126 | "ms": "2.0.0" 127 | } 128 | } 129 | } 130 | }, 131 | "inflight": { 132 | "version": "1.0.6", 133 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 134 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 135 | "requires": { 136 | "once": "1.4.0", 137 | "wrappy": "1.0.2" 138 | } 139 | }, 140 | "inherits": { 141 | "version": "2.0.3", 142 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 143 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 144 | }, 145 | "isarray": { 146 | "version": "1.0.0", 147 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 148 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 149 | }, 150 | "mime": { 151 | "version": "1.6.0", 152 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 153 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 154 | }, 155 | "minimatch": { 156 | "version": "3.0.4", 157 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 158 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 159 | "requires": { 160 | "brace-expansion": "1.1.8" 161 | } 162 | }, 163 | "minimist": { 164 | "version": "0.0.8", 165 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 166 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 167 | }, 168 | "mkdirp": { 169 | "version": "0.5.0", 170 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", 171 | "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", 172 | "requires": { 173 | "minimist": "0.0.8" 174 | } 175 | }, 176 | "ms": { 177 | "version": "2.0.0", 178 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 179 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 180 | }, 181 | "once": { 182 | "version": "1.4.0", 183 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 184 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 185 | "requires": { 186 | "wrappy": "1.0.2" 187 | } 188 | }, 189 | "path-is-absolute": { 190 | "version": "1.0.1", 191 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 192 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 193 | }, 194 | "pend": { 195 | "version": "1.2.0", 196 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", 197 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" 198 | }, 199 | "process-nextick-args": { 200 | "version": "1.0.7", 201 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 202 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 203 | }, 204 | "progress": { 205 | "version": "2.0.0", 206 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", 207 | "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=" 208 | }, 209 | "proxy-from-env": { 210 | "version": "1.0.0", 211 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", 212 | "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=" 213 | }, 214 | "puppeteer": { 215 | "version": "1.0.0", 216 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.0.0.tgz", 217 | "integrity": "sha512-e00NMdUL32YhBcua9OkVXHgyDEMBWJhDXkYNv0pyKRU1Z1OrsRm5zCpppAdxAsBI+/MJBspFNfOUZuZ24qPGMQ==", 218 | "requires": { 219 | "debug": "2.6.9", 220 | "extract-zip": "1.6.6", 221 | "https-proxy-agent": "2.1.1", 222 | "mime": "1.6.0", 223 | "progress": "2.0.0", 224 | "proxy-from-env": "1.0.0", 225 | "rimraf": "2.6.2", 226 | "ws": "3.3.3" 227 | } 228 | }, 229 | "readable-stream": { 230 | "version": "2.3.3", 231 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 232 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 233 | "requires": { 234 | "core-util-is": "1.0.2", 235 | "inherits": "2.0.3", 236 | "isarray": "1.0.0", 237 | "process-nextick-args": "1.0.7", 238 | "safe-buffer": "5.1.1", 239 | "string_decoder": "1.0.3", 240 | "util-deprecate": "1.0.2" 241 | } 242 | }, 243 | "rimraf": { 244 | "version": "2.6.2", 245 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 246 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 247 | "requires": { 248 | "glob": "7.1.2" 249 | } 250 | }, 251 | "safe-buffer": { 252 | "version": "5.1.1", 253 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 254 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 255 | }, 256 | "string_decoder": { 257 | "version": "1.0.3", 258 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 259 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 260 | "requires": { 261 | "safe-buffer": "5.1.1" 262 | } 263 | }, 264 | "typedarray": { 265 | "version": "0.0.6", 266 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 267 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 268 | }, 269 | "ultron": { 270 | "version": "1.1.1", 271 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 272 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 273 | }, 274 | "util-deprecate": { 275 | "version": "1.0.2", 276 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 277 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 278 | }, 279 | "wrappy": { 280 | "version": "1.0.2", 281 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 282 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 283 | }, 284 | "ws": { 285 | "version": "3.3.3", 286 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 287 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 288 | "requires": { 289 | "async-limiter": "1.0.0", 290 | "safe-buffer": "5.1.1", 291 | "ultron": "1.1.1" 292 | } 293 | }, 294 | "yauzl": { 295 | "version": "2.4.1", 296 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", 297 | "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", 298 | "requires": { 299 | "fd-slicer": "1.0.1" 300 | } 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geojson-image-creator", 3 | "version": "1.0.0", 4 | "description": "A Node.js script for automatically creating screenshots from Leaflet maps with Geojson layers", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "moklick@webkid.io", 11 | "license": "MIT", 12 | "dependencies": { 13 | "puppeteer": "^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /screenshots/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbkd/leaflet-mapshot/de30246741010a4b993e7b90125e374c8197da6d/screenshots/.gitkeep -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Leaflet Geojson Image Creator 8 | 9 | 10 | 21 | 22 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const puppeteer = require('puppeteer'); 4 | const { promisify } = require('util'); 5 | 6 | const config = require('../config.json'); 7 | const readDirAsync = promisify(fs.readdir); 8 | const readFileAsync = promisify(fs.readFile); 9 | 10 | const geojsonPath = path.resolve(__dirname, '..', 'geojsons'); 11 | const screenshotPath = path.resolve(__dirname, '..', 'screenshots'); 12 | const htmlPath = `file://${__dirname}/index.html`; 13 | 14 | start(); 15 | 16 | async function start() { 17 | const geojsonPaths = await readDirAsync(geojsonPath); 18 | const browser = await puppeteer.launch(); 19 | const page = await browser.newPage(); 20 | 21 | console.log(`found ${geojsonPaths.length} geojson files at ${geojsonPath}`); 22 | 23 | await page.goto(htmlPath); 24 | await page.evaluate(initMap, config); 25 | 26 | for (let [index, filePath] of geojsonPaths.entries()) { 27 | const geojsonRaw = await readFileAsync(`${geojsonPath}/${filePath}`); 28 | const geojson = JSON.parse(geojsonRaw); 29 | 30 | await page.evaluate(addGeojsonLayer, geojson); 31 | await page.screenshot({ path: `${screenshotPath}/geojson_${index}.png` }); 32 | console.log(`make screenshot for: ${filePath}`); 33 | } 34 | 35 | await browser.close(); 36 | } 37 | 38 | function initMap(config) { 39 | return new Promise((yep, nope) => { 40 | const map = L.map('map').setView(config.center, config.zoom); 41 | const tileLayer = L.tileLayer(config.tileLayerUrl).addTo(map); 42 | 43 | window.map = map; 44 | 45 | tileLayer.on('load', yep); 46 | }); 47 | } 48 | 49 | function addGeojsonLayer(geojson) { 50 | if (typeof geojsonLayer !== 'undefined') { 51 | geojsonLayer.clearLayers(); 52 | } 53 | geojsonLayer = L.geoJSON(geojson).addTo(map); 54 | } --------------------------------------------------------------------------------