mapnik / node-mapnik

Bindings to mapnik for node.js
http://mapnik.org/documentation/node-mapnik
BSD 3-Clause "New" or "Revised" License
533 stars 165 forks source link

Why mapnik doesnt return properties of pbf layer #935

Closed jadelmag closed 4 years ago

jadelmag commented 4 years ago

Hi, I'm using mapik to create a my own tile server. I'm using postgressql and postgis. I process a geosjon file and store it in a table with next scheme:

( table_id SERIAL, properties jsonb not null, geom geometry(GeometryZ,4326), primary key (table_id)), ok?

It render perfectly but when I click on a feature only returns table_id. why dont return properties? Because is stored as jsonb? Mapnik have not an option to load properties from jsonb??

Here is my code:

/** CONSTANTS **/
const TILE_SIZE = 256;
const PROJECTION_STRING = '+init=epsg:3857';

/** LIBRARIES **/
var zlib = require('zlib');
var express = require('express');
var mapnik = require('mapnik');
var Promise = require('promise');
var SphericalMercator = require('sphericalmercator');

var mercator = new SphericalMercator({
    size: TILE_SIZE //tile size
});

mapnik.register_default_input_plugins();

var app = express();

app.get('/:namelayer/:z/:x/:y.pbf', (req, res, next) => {
    var options = {
        x: parseInt(req.params.x),
        y: parseInt(req.params.y),
        z: parseInt(req.params.z),
        layerName: req.params.namelayer
    };

    makeVectorTile(options, sql).then( (vectorTile) => {
        zlib.deflate(vectorTile, (err, data) => {
            if (err) {
                return res.status(500).send(err.message);
            }

            res.setHeader('Content-Encoding', 'deflate');
            res.setHeader('Content-Type', 'application/x-protobuf');
            res.setHeader('Access-Control-Allow-Origin', '*');
            return res.send(data);
        });
    });
});

function makeVectorTile(options, sql) {

    var extent = mercator.bbox(options.x, options.y, options.z, false, '3857');
    var map = new mapnik.Map(TILE_SIZE, TILE_SIZE, PROJECTION_STRING);
    map.extent = extent;

    var layer = new mapnik.Layer(options.layerName);
    layer.datasource = new mapnik.Datasource({
        type: process.env.DB_TYPE,
        dbname: process.env.DB_DATABASE,
        table: options.layerName,
        user: process.env.DB_USER,
        password: process.env.DB_PASSWORD
    });

    layer.styles = ['default'];
    map.add_layer(layer);

    return new Promise( (resolve, reject) => {
        var vtile = new mapnik.VectorTile(parseInt(options.z), parseInt(options.x), parseInt(options.y));
        map.render(vtile, function (err, vtile) {
            if (err) {
                return reject(err);
            }
            console.log(`${vtile.getData().length} KB`);
            resolve(vtile.getData());
        });
    });
};

module.exports = app;
jadelmag commented 4 years ago

I have create using postgresql request to get properties from jsonb. I hope will be usefull for mapnik.

/** CONSTANTS **/
const TILE_SIZE = 256;
const PROJECTION_STRING = '+init=epsg:3857';

/** LIBRARIES **/
var zlib = require('zlib');
var express = require('express');
var mapnik = require('mapnik');
var Promise = require('promise');
var SphericalMercator = require('sphericalmercator');

const { pool } = require('../postgressql/config');

var mercator = new SphericalMercator({
    size: TILE_SIZE
});

mapnik.register_default_input_plugins();

var app = express();

app.get('/:namelayer/:z/:x/:y.pbf', (req, res, next) => {
    var options = {
        x: parseInt(req.params.x),
        y: parseInt(req.params.y),
        z: parseInt(req.params.z),
        layerName: req.params.namelayer
    };

    const sql = `select json_data.key, jsonb_typeof(json_data.value) from ${options.layerName} x, jsonb_each(x.properties) as json_data group by key, jsonb_typeof order by key, jsonb_typeof;`
    try {
        pool.query(sql, (error, results) => {
            if (error) {
                return res.status(500).json({
                    ok: false,
                    message: error
                });
            }
            const response = (results && results.rows && results.rows.length > 0 && results.rows[0]) ? results.rows.slice() : [];
            const sql = generateSQL(options, response);

            makeVectorTile(options, sql).then( (vectorTile) => {
                zlib.deflate(vectorTile, (err, data) => {
                    if (err) {
                        return res.status(500).send(err.message);
                    }

                    res.setHeader('Content-Encoding', 'deflate');
                    res.setHeader('Content-Type', 'application/x-protobuf');
                    res.setHeader('Access-Control-Allow-Origin', '*');
                    return res.send(data);
                });
            });
        });
    } catch (e) {
        res.status(404).send({
            error: e.toString(),
        });
    }
});

function generateSQL(options, response) {

    if (response.length === 0) {
        return `select table_id, geom from ${options.layerName}`;
    } else {
        let sql = "";
        response.forEach( data => {
            sql += (data.jsonb_typeof === 'null' || data.jsonb_typeof === null) ? 
                ` properties->>'${data.key}' as ${data.key},` : 
                ` cast (properties->>'${data.key}' as ${getDataBaseType(data.jsonb_typeof)}) as ${data.key},`;
        });
        sql = `select table_id, ${sql} geom from ${options.layerName}`;
        return sql;
    }
};

function getDataBaseType(typedb) {

    switch(typedb) {
        case 'string': return 'text';
        case 'number': return 'decimal';
        default: return;
    }

};

function makeVectorTile(options, sql) {

    var extent = mercator.bbox(options.x, options.y, options.z, false, '3857');
    var map = new mapnik.Map(TILE_SIZE, TILE_SIZE, PROJECTION_STRING);
    map.extent = extent;

    var layer = new mapnik.Layer(options.layerName);
    layer.datasource = new mapnik.Datasource({
        type: process.env.DB_TYPE,
        dbname: process.env.DB_DATABASE,
        table: `(${sql}) as tile`,
        user: process.env.DB_USER,
        password: process.env.DB_PASSWORD
    });

    layer.styles = ['default'];
    map.add_layer(layer);

    return new Promise( (resolve, reject) => {
        var vtile = new mapnik.VectorTile(parseInt(options.z), parseInt(options.x), parseInt(options.y));
        map.render(vtile, function (err, vtile) {
            if (err) {
                return reject(err);
            }
            resolve(vtile.getData());
        });
    });
};

module.exports = app;