Saltar a contenido

Cómo servir datos dinámicos

Partiremos de unos datos con los distritos, barrios y secciones censales de Barcelona. Los datos están en datos/bcn_geodata.sql.

Note

El origen de los datos es Ajuntament de Barcelona CC-by (http://w20.bcn.cat/cartobcn/). Martín González los convirtió a GeoJSON (https://github.com/martgnz/bcn-geodata). Para el taller los hemos convertido de GeoJSON a PostGIS.

Cargarlos en PostGIS:

1
2
cd ~/Desktop/taller-vt
psql -U user < datos/bcn_geodata.sql

Se creará una BDD bcn_geodata con las tablas barrios, distritos y seccion_censal. Se puede acceder a esta BDD con el usuario user, password user.

Una vez con los datos cargados en PostGIS, usaremos tegola para levantar un servidor de teselas vectoriales partir de una BDD PostGIS.

Instalando Tegola

Tegola consiste en un solo fichero ejecutable. La instalación consiste en descargarse un zip, descomprimirlo, y copiar el fichero ejecutable resultante en una ruta localizable:

1
2
3
wget https://github.com/go-spatial/tegola/releases/download/v0.6.3/tegola_linux_amd64.zip
unzip tegola_linux_amd64.zip
sudo mv tegola_linux_amd64 /usr/local/bin/tegola

Comprobar que puede ejecutarse tegola correctamente, y borrar el zip:

1
2
tegola -h # comprobar instalacion
rm tegola_linux_amd64.zip

Configurando Tegola

Tegola necesita de un fichero de configuración en formato toml para funcionar. En él estableceremos los parámetros de conexión con la BDD y las características de las capas que queremos servir.

Creemos pues un directorio llamado tegola, y en él, un fichero llamado config.toml.

1
2
3
mkdir tegola
cd tegola
touch config.toml

Lo primero es definir una sección con el puerto que usaremos para levantar el servicio:

1
2
[webserver]
port = ":8083"

A continuación definimos la conexión con la BDD en una sección providers. Dentro del provider, definimos las capas de datos que queremos obtener de la BDD. En nuestro caso son muy sencillas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[[providers]]
name = "postgis"
type = "postgis"
host = "localhost"
port = 5432
database = "bcn_geodata"
user = "user"
password = "user"

    [[providers.layers]]
    name = "distritos"
    tablename = "distritos"

    [[providers.layers]]
    name = "barrios"
    tablename = "barrios"

    [[providers.layers]]
    name = "seccion_censal"
    tablename = "seccion_censal"

En estas providers.layers también se podrían seleccionar qué columnas queremos obtener, o incluso indicar una consulta SQL para generar la capa, haciendo JOIN de varias tablas, aplicando operaciones espaciales, etc. En nuestro caso, servimos las tablas tal cual.

Finalmente, creamos una sección maps, que consiste en un nombre, y una serie de capas de información:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[[maps]]
name = "bcn_geodata"
attribution = "CC-by Ajuntament de Barcelona"
center = [2.175, 41.39, 13.0]
bounds = [1.898, 41.246, 2.312, 41.533]

    [[maps.layers]]
    provider_layer = "postgis.seccion_censal"
    dont_simplify = true
    min_zoom = 11
    max_zoom = 16

    [[maps.layers]]
    provider_layer = "postgis.barrios"
    dont_simplify = true
    min_zoom = 10
    max_zoom = 16

    [[maps.layers]]
    provider_layer = "postgis.distritos"
    dont_simplify = true
    min_zoom = 0
    max_zoom = 16

En estas maps.layers se indican parámetros para la generación de las VT, como el rango de niveles de zoom en las que serán visibles, si se simplificará o no la geometría.

En la configuración también existe la posibilidad de activar una caché en disco, en s3 o en redis, así como comandos para invalidar partes de la caché. En nuestro caso, no hemos usado caché.

Arrancando tegola

Arrancamos el servicio desde el directorio donde hayamos guardato config.toml:

1
2
~/Desktop/taller-vt/tegola
tegola serve

Y abrimos http://localhost:8083 :

Tegola

Activando "Inspect Features" podremos ver los atributos de cada elemento.

Las teselas están disponibles en: http://localhost:8083/maps/bcn_geodata/{z}/{x}/{y}.pbf

Incorporando los datos en el visor de Barcelona

Sustituiremos el contenido de map.on('load', ...), y donde antes agregábamos los edificios de catastro en 3D, ahora agregaremos las secciones censales. El código es:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    map.on('load', function() {
        map.addSource('bcn_geodata', {
            "type": "vector",
            "tiles": ["http://localhost:8083/maps/bcn_geodata/{z}/{x}/{y}.pbf"]
        });

        map.addLayer({
            "id": "seccion_censal",
            "source": "bcn_geodata",
            "source-layer": "seccion_censal",
            "type": "fill",
            "paint": {
                "fill-opacity": 0.6,
                "fill-color": "#fcc",
                "fill-outline-color": "#000"
            }
        });
    });

Sección Censal 1

Temático avanzado

Expresiones matemáticas

En las tablas tenemos los campos de población divididos en hombres (homes) y mujeres (dones), así como el área en m² de cada sección. Con estos datos, podemos calcular la densidad de población en habitantes/km² según la fórmula:

1
densidad = (homes+dones)/(area/1000000)

El estilo de mapbox-gl permite expresar operaciones matemáticas usando expresiones, de la forma:

  • ["+", ["get", "homes"], ["get", "dones"]] para sumar la población de hombres y de mujeres
  • ["/", ["get", "area"], 1000000] para pasar el área de m² a km²

Y, combinando ambas, obtenemos la densidad de población en habitantes/km²:

1
2
3
4
["/",
    ["+", ["get", "homes"], ["get", "dones"]],
    ["/", ["get", "area"], 1000000]
]

Rampas de color

Así mismo, las expresiones de los estilos de mapbox-gl nos permiten aplicar un número discreto de valores de salida a rangos de entrada. Las definiciones del manual sin un ejemplo concreto son algo difíciles de leer:

1
2
3
4
5
6
["step",
    input: number,
    stop_output_0: OutputType,
    stop_input_1: number, stop_output_1: OutputType,
    stop_input_n: number, stop_output_n: OutputType, ...
]: OutputType  

Pero supongamos que queremos aplicar esta expresión para asignar tres colores en función de rangos de densidad (< 10.000 hab/km², de 10.000 a 25.000 hab/km², y > 25.000 hab/km²), la expresión sería:

1
2
3
4
5
6
["step",
    ["get", "densidad"],
    "<Color_1>",
    10000, "<Color_2>",
    25000, "<Color_3>"
]

Más sencillo de lo que parecía.

Combinando ambas expresiones

Podemos combinar ambas expresiones: "calcula la densidad y luego asigna un color en función de su valor"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
["step",
    ["/",
        ["+", ["get", "homes"], ["get", "dones"]],
        ["/", ["get", "area"], 1000000]
    ],
    "#3d5941",
    17703, "#778868",
    29678, "#b5b991",
    37617, "#f6edbd",
    46016, "#edbb8a",
    54401, "#de8a5a",
    66570, "#ca562c"
]

Tip

Los valores de corte para los diferentes colore se han obtenido calculando los cuantiles con PostgreSQL:

1
2
3
4
5
6
7
SELECT
ntile,
CAST(min(densitat) AS INTEGER)  AS minAmount,
CAST(max(densitat) AS INTEGER)  AS maxAmount
FROM (SELECT (homes+dones)/(area/1000000) as densitat, ntile(7) OVER (ORDER BY (homes+dones)/(area/1000000)) AS ntile FROM seccion_censal) x
GROUP BY ntile
ORDER BY ntile;

Y la rampa de color y sus valores RGB de cada color se han obtenido de carto-colors, que a su vez están inspirados en ColorBrewer.

Aplicada a la propiedad fill-color del estilo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    map.addLayer({
        "id": "seccion_censal",
        "source": "bcn_geodata",
        "source-layer": "seccion_censal",
        "type": "fill",
        "paint": {
            "fill-opacity": 0.75,
            "fill-color": ["step",
                ["/",
                    ["+", ["get", "homes"], ["get", "dones"]],
                    ["/", ["get", "area"], 1000000]
                ],
                "#3d5941",
                17703, "#778868",
                29678, "#b5b991",
                37617, "#f6edbd",
                46016, "#edbb8a",
                54401, "#de8a5a",
                66570, "#ca562c"
            ],
            "fill-outline-color": "#000"
        }
    });

Obtendremos:

Sección Censal 2

Ejercicio extra: Añadir extrusión 3D

Aplicar una extrusión a cada sección censal de altura proporcional a su densidad de población:

Sección Censal 3