tilestrata-postgismvt
Version:
A TileStrata plugin for reading Mapbox Vector Tiles from a PostGIS database
129 lines (117 loc) • 4.2 kB
JavaScript
var pg = require('pg');
const SQL = require('sql-template-strings');
module.exports = function(options) {
var pgPool;
var lyr;
/**
* Initializes the layer config and the PostgreSQL datasource.
*
* @param {TileServer} server
* @param {function} callback(err, fn)
* @return {void}
*/
function initialize(server, callback) {
lyr = options.lyr;
pgPool = new pg.Pool(options.pgConfig);
pgPool.on('error', function (err, client) {
console.error(err.message, err.stack);
var err = new Error('idle client error');
err.statusCode = 500;
callback(err);
});
if ((lyr.mode === 'cluster' || lyr.mode === 'cluster_fields' || (typeof lyr.mode === 'function')) && lyr.type != 'circle') {
var err = new Error('Clustering and mode functions can only be used in conjunction with point data');
err.statusCode = 422;
callback(err);
} else {
callback(null);
}
}
/**
* Creates a tile and returns the result as a Mapbox Vector Tile,
* plus the headers that should accompany it.
*
* @param {TileServer} server
* @param {TileRequest} tile
* @param {function} callback(err, buffer, headers)
* @return {void}
*/
function serveMVT(server, tile, callback) {
if (tile.z < lyr.minZoom || tile.z > lyr.maxZoom) {
err = new Error('Request out of zoom level bounds');
err.statusCode = 204;
return callback(err);
}
var fields = lyr.fields ? ', ' + lyr.fields.split(' ') : '';
var clip_geom = (lyr.buffer > 0) ? true : false;
var resolution = (typeof lyr.resolution === 'function') ? lyr.resolution(server, tile) : lyr.resolution;
var mode = (typeof lyr.mode === 'function') ? lyr.mode(server, tile) : lyr.mode;
var query;
switch (mode) {
case "cluster":
var agg_q_name = 'mvt_geo';
query = `
SELECT ST_AsMVT('${tile.layer}', ${resolution}, 'geom', q) AS mvt FROM (
WITH ${agg_q_name} AS (
SELECT 1 cnt, ST_AsMVTGeom(ST_Transform(${lyr.table}.${lyr.geometry}, 3857), TileBBox(${tile.z}, ${tile.x}, ${tile.y}, 3857), ${resolution}, ${lyr.buffer}, ${clip_geom}) geom
FROM ${lyr.table}
WHERE ST_Intersects(TileBBox(${tile.z}, ${tile.x}, ${tile.y}, ${lyr.srid}), ${lyr.table}.${lyr.geometry})
)
SELECT COUNT(${agg_q_name}.cnt), ${agg_q_name}.geom
FROM ${agg_q_name}
GROUP BY ${agg_q_name}.geom
) AS q
`;
break;
case "cluster_fields":
var agg_q_name = 'mvt_geo';
var fieldsAgg = '';
if (lyr.fields) {
lyr.fields.split(' ').forEach(function(field) {
fieldsAgg += ', string_agg('+agg_q_name+'.' + field + '::text, \',\') AS ' + field;
});
}
query = `
SELECT ST_AsMVT('${tile.layer}', ${resolution}, 'geom', q) AS mvt FROM (
WITH ${agg_q_name} AS (
SELECT 1 cnt, ST_AsMVTGeom(ST_Transform(${lyr.table}.${lyr.geometry}, 3857), TileBBox(${tile.z}, ${tile.x}, ${tile.y}, 3857), ${resolution}, ${lyr.buffer}, ${clip_geom}) geom ${fields}
FROM ${lyr.table}
WHERE ST_Intersects(TileBBox(${tile.z}, ${tile.x}, ${tile.y}, ${lyr.srid}), ${lyr.table}.${lyr.geometry})
)
SELECT COUNT(${agg_q_name}.cnt), ${agg_q_name}.geom ${fieldsAgg}
FROM ${agg_q_name}
GROUP BY ${agg_q_name}.geom
) AS q
`;
break;
default:
query = `
SELECT ST_AsMVT('${tile.layer}', ${resolution}, 'geom', q) AS mvt FROM (
SELECT ST_AsMVTGeom(ST_Transform(${lyr.table}.${lyr.geometry}, 3857), TileBBox(${tile.z}, ${tile.x}, ${tile.y}, 3857), ${resolution}, ${lyr.buffer}, ${clip_geom}) geom ${fields}
FROM ${lyr.table}
WHERE ST_Intersects(TileBBox(${tile.z}, ${tile.x}, ${tile.y}, ${lyr.srid}), ${lyr.table}.${lyr.geometry})
) AS q
`;
break;
}
pgPool.query(query, function(err, result) {
if (err) {
console.log(query, err.message, err.stack)
var err = new Error('An error occurred');
err.statusCode = 500;
return callback(err);
}
if (!result.rows[0].mvt) {
err = new Error('No data');
err.statusCode = 204;
return callback(err);
}
callback(null, result.rows[0].mvt, {'Content-Type': 'application/x-protobuf'});
});
}
return {
name: 'postgismvt',
init: initialize,
serve: serveMVT
};
};