nautilytics
7/15/2015 - 7:07 PM

node-mapnik .pbf tile-server example

node-mapnik .pbf tile-server example

var config = require('../../config/database')
    , _ = require('lodash-compat')
    , mercator = require('./../../geo_utils/sphericalmercator')
    , geographic = require('./../../geo_utils/geographic')
    , crypto = require("crypto")
    , zlib = require('zlib')
    , mapnik = require('mapnik')
    , utils = require('./../../config/utils');

if (mapnik.register_default_input_plugins)
    mapnik.register_default_input_plugins();

var _geometry_field = 'geom'
    , _table = 'us_county_500k'
    , _fields = 'gid';

var createPostGISConnectionDetails = function (table) {
    return _.defaults({table: table, geometry_field: _geometry_field}, config.postGIS);
};

module.exports = function (req, res) {

    var bbox = mercator.xyz_to_envelope(+params.x, +params.y, +params.z, false);

    // Create map
    var map = new mapnik.Map(256, 256, mercator.proj4);
    map.bufferSize = 10; // amount of edging provided for each tile rendered

    // Create layer
    var layer = new mapnik.Layer('tile', geographic.proj4);
    var fieldTable = '(SELECT ' + [_geometry_field, _fields].join(',') + ' FROM ' + _table + ') AS "' + _table + '"';
    layer.datasource = new mapnik.Datasource(new createPostGISConnectionDetails(fieldTable));
    layer.styles = ['default'];

    map.add_layer(layer);

    var opts = {};

    // Use tolerance of 32 for zoom levels below max
    var _maxzoom = 14;
    opts.tolerance = +params.z < _maxzoom ? 32 : 0;

    res.setHeader('Content-Type', 'application/x-protobuf');

    map.extent = bbox;
    opts.buffer_size = map.bufferSize;

    map.render(new mapnik.VectorTile(+params.z, +params.x, +params.y), opts, function (err, image) {

        if (err || !image) {
            res.removeHeader('Content-Encoding');
            res.writeHead(500, {
                'Content-Type': 'application/x-protobuf'
            });
            res.end();
            return;
        }

        // Fake empty RGBA
        image.isSolid(function (err, solid, key) {
            if (err) {
                res.writeHead(500, {
                    'Content-Type': 'text/plain'
                });

                res.end(err.message);
                return;
            }
            // Solid handling.
            var done = function (err, buffer) {
                if (err) {
                    res.writeHead(500, {
                        'Content-Type': 'text/plain'
                    });

                    res.end(err.message);
                    return;
                }

                if (solid === false) {

                    res.send(buffer); // return response
                    return;
                }

                // Empty tiles are equivalent to no tile.
                if (!key) {
                    res.removeHeader('Content-Encoding');
                    res.writeHead(404, {
                        'Content-Type': 'application/octet-stream'
                    });

                    res.end(); //new Buffer('Tile is blank or does not exist', "utf-8")
                    return;
                }

                // Fake a hex code by md5ing the key.
                var mockrgb = crypto.createHash('md5').update(buffer).digest('hex').substr(0, 6);
                buffer.solid = [parseInt(mockrgb.substr(0, 2), 16), parseInt(mockrgb.substr(2, 2), 16), parseInt(mockrgb.substr(4, 2), 16), 1].join(',');
                res.send(buffer);

            };

            //Compress
            res.setHeader('content-encoding', 'gzip');
            zlib.gzip(image.getData(), done);
        });
    });
};