├── .gitignore ├── package.json ├── README.md ├── yarn.lock └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vector-tiles-generator", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Philippe Auriach", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@mapbox/sphericalmercator": "1.1.0", 13 | "geojson-vt": "3.1.4", 14 | "vt-pbf": "3.1.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vector-tiles-generator 2 | 3 | ## installation 4 | ``` 5 | yarn add vector-tiles-generator 6 | ``` 7 | 8 | ## usage with express 9 | ```JavaScript 10 | var VectorTileGenerator = require('vector-tiles-generator'); 11 | 12 | // initialize once your generator 13 | var vectorTileGenerator = new VectorTileGenerator({ 14 | pgPool: pool // you must provide your own pg-pool 15 | }); 16 | 17 | app.get('/layer/:z/:x/:y.mvt', function(req, res) { 18 | var tile = { 19 | x: parseInt(req.params.x), 20 | y: parseInt(req.params.y), 21 | z: parseInt(req.params.z) 22 | }; 23 | 24 | // nothing before zoom level 9 25 | if(tile.z < 9) { 26 | return res.status(204).send(); // 204 empty status for mapbox 27 | } 28 | 29 | return vectorTileGenerator.get({ 30 | points: `SELECT name, ST_AsGeoJSON(ST_Transform(way, 4326)) as the_geom_geojson 31 | FROM planet_osm_polygon WHERE way && !bbox!` // !bbox! will be replaced 32 | }, tile) 33 | .then(function(result) { 34 | if(!result || result.length === 0) { 35 | return res.status(204).send(); // handle empty status for mapbox 36 | } 37 | return res.send(result); 38 | }); 39 | }); 40 | ``` 41 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@~0.1.0": 6 | version "0.1.0" 7 | resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" 8 | 9 | "@mapbox/sphericalmercator@1.1.0": 10 | version "1.1.0" 11 | resolved "https://registry.yarnpkg.com/@mapbox/sphericalmercator/-/sphericalmercator-1.1.0.tgz#f3b1af042620716a1289fc41e1e97f610823aefe" 12 | 13 | "@mapbox/vector-tile@^1.3.1": 14 | version "1.3.1" 15 | resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" 16 | dependencies: 17 | "@mapbox/point-geometry" "~0.1.0" 18 | 19 | geojson-vt@3.1.4: 20 | version "3.1.4" 21 | resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.1.4.tgz#c8ffefbe3613d3ad2747e963b0b63b9e62ff11b8" 22 | 23 | ieee754@^1.1.6: 24 | version "1.1.12" 25 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" 26 | 27 | pbf@^3.0.5: 28 | version "3.1.0" 29 | resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.1.0.tgz#f70004badcb281761eabb1e76c92f179f08189e9" 30 | dependencies: 31 | ieee754 "^1.1.6" 32 | resolve-protobuf-schema "^2.0.0" 33 | 34 | protocol-buffers-schema@^2.0.2: 35 | version "2.2.0" 36 | resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-2.2.0.tgz#d29c6cd73fb655978fb6989691180db844119f61" 37 | 38 | resolve-protobuf-schema@^2.0.0: 39 | version "2.0.0" 40 | resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.0.0.tgz#e67b062a67f02d11bd6886e70efda788407e0fb4" 41 | dependencies: 42 | protocol-buffers-schema "^2.0.2" 43 | 44 | vt-pbf@3.1.1: 45 | version "3.1.1" 46 | resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.1.tgz#b0f627e39a10ce91d943b898ed2363d21899fb82" 47 | dependencies: 48 | "@mapbox/point-geometry" "0.1.0" 49 | "@mapbox/vector-tile" "^1.3.1" 50 | pbf "^3.0.5" 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var geojsonvt = require('geojson-vt'); 2 | var vtpbf = require('vt-pbf'); 3 | var GeoJSONWrapper = vtpbf.GeoJSONWrapper; 4 | var SphericalMercator = require('@mapbox/sphericalmercator'); 5 | 6 | var VectorTilesGenerator = function(options) { 7 | this.projection = new SphericalMercator({ 8 | size: 256 9 | }); 10 | this.pgPool = options.pgPool; 11 | this.cacheOptions = options.cache; 12 | }; 13 | 14 | VectorTilesGenerator.prototype.tile = function(opts) { 15 | var tile = { 16 | x: opts.x, 17 | y: opts.y, 18 | z: opts.z 19 | }; 20 | tile.bounds = this.projection.bbox(opts.x, opts.y, opts.z, false, '900913'); 21 | tile.bbox = [ 22 | 'ST_SetSRID(', 23 | 'ST_MakeBox2D(', 24 | 'ST_MakePoint(', tile.bounds[0], ', ', tile.bounds[1], '), ', 25 | 'ST_MakePoint(', tile.bounds[2], ', ', tile.bounds[3], ')', 26 | '), ', 27 | '3857', 28 | ')' 29 | ].join(''); 30 | tile.bbox_4326 = 'ST_Transform('+tile.bbox+', 4326)'; 31 | tile.geom_hash = 'Substr(MD5(ST_AsBinary(the_geom)), 1, 10)'; 32 | return tile; 33 | }; 34 | 35 | VectorTilesGenerator.prototype.performQuery = function(sql, tile) { 36 | //replacing patterns between two '!' like !bbox! 37 | var templatePattern = /!([0-9a-zA-Z_\-]+)!/g; 38 | sql = sql.replace(templatePattern, function(match){ 39 | match = match.substr(1, match.length-2); 40 | return tile[match]; 41 | }); 42 | //actually performing query 43 | return this.pgPool.connect() 44 | .then(function(client) { 45 | return client.query(sql) 46 | .then(function(result){ 47 | client.release(); 48 | return result.rows; 49 | }) 50 | .catch(function(e){ 51 | client.release(); 52 | throw e; 53 | }); 54 | }); 55 | }; 56 | 57 | VectorTilesGenerator.prototype.queryResultsToGeoJSON = function(queryResultsRows) { 58 | var features = queryResultsRows.map(function(elt){ 59 | var properties = {}; 60 | for (var attribute in elt) { 61 | if (attribute !== 'the_geom_geojson') { 62 | properties[attribute] = elt[attribute]; 63 | } 64 | } 65 | return { 66 | type: 'Feature', 67 | geometry: JSON.parse(elt.the_geom_geojson), 68 | properties: properties 69 | }; 70 | }); 71 | var geojson = { 72 | type: 'FeatureCollection', 73 | features: features 74 | }; 75 | return Promise.resolve(geojson); 76 | }; 77 | 78 | VectorTilesGenerator.prototype.get = function(queries, opts) { 79 | var self = this; 80 | var tile = this.tile(opts); 81 | return Promise.all(Object.keys(queries).map(function(key){ 82 | var sql = queries[key]; 83 | return self.performQuery(sql, tile) 84 | .then(function(queryResultsRows){ 85 | return self.queryResultsToGeoJSON(queryResultsRows) 86 | .then(function(geojson){ 87 | var govt = geojsonvt(geojson, { 88 | maxZoom: opts.z+1, 89 | indexMaxZoom: opts.z-1 90 | }); 91 | var pbf = govt.getTile(opts.z, opts.x, opts.y); 92 | return { 93 | name: key, 94 | geojson: geojson, 95 | pbf: pbf 96 | }; 97 | }); 98 | }); 99 | })) 100 | .then(function(layers) { 101 | var pbfOptions = {}; 102 | for(var i in layers) { 103 | var layer = layers[i]; 104 | if(layer.pbf){ 105 | //construct the GeoJSONWrapper here, so that we can tell him the version ! 106 | pbfOptions[layer.name] = new GeoJSONWrapper(layer.pbf.features); 107 | pbfOptions[layer.name].name = layer.name; 108 | pbfOptions[layer.name].version = 2; 109 | } 110 | } 111 | if(pbfOptions.length === 0) { 112 | return undefined; 113 | } 114 | // we use fromVectorTileJs instead of fromGeojsonVt because we constructed the GeoJSONWrapper ourselves 115 | var buff = vtpbf.fromVectorTileJs({layers: pbfOptions}); 116 | if(buff) { 117 | buff = new Buffer(buff.buffer); 118 | } 119 | return buff; 120 | }); 121 | }; 122 | 123 | module.exports = VectorTilesGenerator; 124 | --------------------------------------------------------------------------------