Renderizado con Mapnik

En el planteamiento que presenté en el primer artículo sobre OSM y PgRouting no estaba incluido el tema del renderizado, pero tras indagar el tema de Mapnik me he dado cuenta de las magníficas posibilidades que ofrece este renderizador, de ahí que haga un inciso para introducir esta cuestión.

Mapnik es un toolkit en C++ para renderizar varios orígenes de datos, que tiene bindings para Python. La calidad de este paquete, a mi juicio, está por encima de otros renderizadores como Osmarender o Kosmos. Profundizar en él me ha permitido aprender algo de Python, que nunca viene mal y sumergirme en las entrañas de la edición cartográfica. En cualquiera de los casos, los resultados estarán limitados a nuestra destreza para la composición de mapas, pero nunca a las limitaciones del programa.

Mapnik entiende los siguientes orígenes de datos:

  • Shapefiles
  • PostGIS
  • Raster (generalmente TIFF, aunque si se compila Mapnik con GDAL podemos ampliar el espectro de formatos)

OBJETIVO:

En muchas de nuestras aplicaciones web incluimos una layer del proveedor OSM, utilizando OpenLayers, Mapfish u otras. El resultado es fácil de implementar, pero tiene el problema de depender del estado de salud del servidor oficial de www.openstreetmap.org. Además, puede que en un momento dado nos planteemos utilizar nuestra propia simbolización, cambiando los colores de las vias, o añadiendo unos iconos diferentes a los oficiales Aquí nos planteamos crear un TMS (Tile Map Service), o lo que es lo mismo, un conjunto de imágenes de mapa (tiles) de reducido tamaño (256 * 256) , personalizadas con nuestros criterios de simbolización gráfica, que están ordenadas en carpetas, cada una de las cuales recibe el nombre del nivel de zoom utilizado, que oscila entre 1 y 18, siguiendo la estructura del “mapnik slippy map“, que es una forma de ordenar las tiles en función de la proyección utilizada (Mercator).

REFERENCIAS WEB:

DESARROLLO:

  1. Compilación de librerías necesarias (Mapnik y osm2pgsql)
  2. Creación de una geodatabase con datos OSM utilizando Osm2Pgsql
  3. Configuración de la plantilla de mapa (XML mapfile)
  4. Pruebas de renderizado
  5. Creación del TMS
  6. Implementación del TMS en OpenLayers

COMPILACIÓN DE LIBRERÍAS:

Entrando en faena vamos a compilar el paquete Mapnik, que como no podía ser de otra manera tiene una dependencia insalvable, BOOST, que ya vimos en el anterior artículo. No obstante indico rápidamente su compilación:

COMPILACIÓN DE BOOST

1. Añadir dependencias. Según la distro necesitaremos una u otra (python-devel, freetype-devel, etc). En este paso vamos a incluir una librería que me ha dado varios quebraderos de cabeza: EXPAT. No sólo es necesaria instalarla, sino que además debemos de indicar algunas variables de configuración. Empezamos por su instalación desde la paquetería

Ubuntu: $  sudo apt-get install libexpat1-dev

$ sudo apt-get install libltdl7-dev libpng12-dev libtiff4-dev libicu-dev libboost-regex-dev libboost-iostreams-dev libboost-filesystem-dev libboost-thread-dev libboost-python1.34.1 libboost-python-dev libfreetype6-dev libcairo2-dev libcairomm-1.0-dev libboost-program-options-dev python-cairo-dev libboost-serialization-dev imagemagick

Centos: $ yum install expat expat-devel.x86_64

NOTA: Fíjate que para expat le indico específicamente la plataformas de 64 bits. Es importante indicarla. Si ponemos “yum install expat-devel” instalará las dos, y nos proporcionará problemas en el futuro (por ejemplo compilando nuestra querida GDAL). Indícale cual es tu plataforma (x86_64 / i386)

2. Define variables para expat:

<br>export EXPAT_INCLUDE=/usr/include<br>export EXPAT_LIBPATH=/usr/lib<br>

3. Descargar la version 1.39 y descomprimir en /usr/local/

mkdir /usr/local/include/boost
wget http://downloads.sourceforge.net/project/boost/boost/1.39.0/boost_1_39_0.tar.bz2
tar -xjvf boost_1_39_0.tar.bz2<br>cd boost_1_39_0

4. Compilar:

./bootstrap.sh --prefix=/usr/local/
./bjam install --includedir=/usr/local/include/boost/

Como root:

/sbin/ldconfig

Este comando es mano de santo para que los compiladores encuentren las librerías.
COMPILAR MAPNIK
Para compilar en Ubuntu os recomiendo seguir las indicaciones de este enlace. Para Centos estos son los pasos a seguir:

cd /home/<usuario>/compilar

1. Descargar y descomprimir la fuente de Mapnik  (versión 0.6.1)

wget http://download.berlios.de/mapnik/mapnik-0.6.1.tar.bz2
tar -xjvf mapnik-0.6.1.tar.bz2
cd mapnik-0.6.1<br>

2. Compilar:

python scons/scons.py configure
python scons/scons.py BOOST_INCLUDES=/usr/local/include/boost/boost-1_39/ BOOST_LIBS=/usr/local/lib BOOST_TOOLKIT=gcc41

Como root:

python scons/scons.py BOOST_INCLUDES=/usr/local/include/boost/boost-1_39/ BOOST_LIBS=/usr/local/lib BOOST_TOOLKIT=gcc41 install

3. Actualizar rutas de librerías (como root):

/sbin/ldconfig

4. Comprobación de la instalación:

Las fuentes contienen una demo para comprobar la instalación. Se trata de un script en Python que tras su ejecución llama a algunos shapefiles y genera unas imágenes en PNG. También lo hará en otros formatos (PDF, PS, etc.)  sí has compilado Mapnik con Cairo y PyCairo. En Ubuntu es muy fácil ya que estas librerías se encuentran en el repositorio, pero en Centos he sido incapaz de utilizarlas (si alguno lo consigue su comentario será de agradecer).

Mapnik utiliza la librería AGG para crear las imágenes, siendo ésta la librería de uso por defecto, aunque también puede utilizar Cairo.
1. Cambia al directorio de pruebas

cd <fuente>/demo/python
python rundemo.py

Obtendrás este mensaje:

Skipping cairo examples as Pycairo not available
3 maps have been rendered in the current directory:
- demo.png
- demo256.png
- demo.jpg
Have a look!

Bueno, pues ya está. Si las imágenes se han generado Mapnik funciona. Sí te da algun error es que te falta alguna librería. Otra manera de ver que errores hay es utilizando la propia consola de python. Escribe lo que está en negrita:

jose@jose:~/compilar$ python

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41)

[GCC 4.3.3] on linux2Type “help”, “copyright”, “credits” or “license” for more information.
>>> import mapnik
>>> exit()

Sí tras escribir “import mapnik” obtienes algunas líneas de error es que algo te falta.  De lo contrario el intérprete de Python entiende que la llamada a la librería funciona y no devuelve nada.
CREACIÓN DE UNA GEODATABASE CON EL PLANET DE OSM

Nuestro objetivo es ahora crear una geodatabase de PostGIS con datos procedente de un planet.osm. Con ello,  conseguiremos tener un repositorio de datos cuyo único fin es ser utilizado por Mapnik para renderizar tiles, y no para otras funciones como la de routing.  Por esta razón utilizamos “osm2pgsql”. Esta herramienta suele formar parte de los repositorios de las distros, por lo que sólo necesitas:

Ubuntu: sudo apt-get install osm2pgsql
Centos: yum install osm2pgsql

Compilación de osm2pgsql:

<br>svn co http://svn.openstreetmap.org/applications/utils/export/osm2pgsql/<br>cd osm2pgsql<br>make<br>

Crear una geodatabase con datos de OSM es una labor sencilla, pero ten en cuenta que OSM cambia constantemente, por lo que la validez de tus datos tienen una caducidad. Por esta razón es importante que articulemos algún sistema para que cada cierto tiempo la base de datos de OSM se actualice sólo. Por ello te recomiendo que personalices el siguiente script en bash que te facilitará la labor:

#!/bin/sh
echo "Script en Bash para actualizar un planet.osm en una base de datos OSM con Postgis v. 1.4."
echo "Borrando spain.osm.bz2"
rm spain.osm.bz2
echo "Descargando fichero osm de Spain desde Cloudmade.com"
wget http://downloads.cloudmade.com/europe/spain/spain.osm.bz2
echo "Descomprimiendo"
bzip2 -d spain.osm.bz2
echo "Borrando bbdd osm"
dropdb -h localhost -U postgres osm
echo "Creando bbdd osm"
createdb -U postgres -E UTF8 osm
echo "Instalar lenguaje pl/pgsql y Postgis en bbdd osm"
createlang -U postgres plpgsql osm
psql -U postgres -f /usr/share/pgsql/contrib/postgis.sql osm
psql -U postgres -f /usr/share/pgsql/contrib/spatial_ref_sys.sql osm
echo "Añadir datos de spain.osm a la bbdd osm"
echo "Tiempo aproximado: 11 minutos"
osm2pgsql --slim -H localhost -U postgres -d osm ./spain.osm
echo "fin"

NOTA: Es muy importante utilizar el parámetro “slim” en osm2pgsql, puesto que permite introducir datos directamente a la base de datos, lo que resulta muy útil con planet.osm de mucho peso. Si no se usa, cargará en memoria todo el osm hasta que ocupe toda la memoria de tu ordenador. Contenido de la geodatabase

psql -h localhost -U postgres osm
Welcome to psql 8.3.8 (server 8.3.7), the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

osm=# \d
                     List of relations
 Schema |            Name            |   Type   |  Owner
--------+----------------------------+----------+----------
 public | geometry_columns           | table    | postgres
 public | planet_osm_line            | table    | postgres
 public | planet_osm_line_pid_seq    | sequence | postgres
 public | planet_osm_nodes           | table    | postgres
 public | planet_osm_point           | table    | postgres
 public | planet_osm_point_pid_seq   | sequence | postgres
 public | planet_osm_polygon         | table    | postgres
 public | planet_osm_polygon_pid_seq | sequence | postgres
 public | planet_osm_rels            | table    | postgres
 public | planet_osm_roads           | table    | postgres
 public | planet_osm_roads_pid_seq   | sequence | postgres
 public | planet_osm_ways            | table    | postgres
 public | spatial_ref_sys            | table    | postgres
(13 rows)

INTRODUCCIÓN A MAPNIK CON PYTHON
Como ya he dicho, no soy un especialista en este lenguaje, aunque su sintaxis me parece muy clara para entenderla sin problemas.
Lo primero que debemos de tener en cuenta es que el diagrama de flujo de Mapnik exige para su uso de un script en Python (o desde la misma consola de Python) donde se aprecien las siguientes fases:

1. Cargar los paquetes (bindings) de Mapnik para Python
2. Indicar la fuente de datos (datasource) de cada layer a representar
3. Definir la simbología de cada layer
4. Indicar los parámetros de la imagen a crear: tamaño, nombre del fichero

Veamos el ejemplo más sencillo: Crear una imagen a partir de un shapefile

#!/usr/bin/env python
# Fichero: hola_mundo.py
# Cargamos los bindings de mapnik
import mapnik
# Definimos un objeto mapa, con un tamaño (en píxeles) y una proyección según PROJ4
m = mapnik.Map(600,300,'+proj=latlong +datum=WGS84')
# Indicamos el color de fondo
m.background = mapnik.Color('steelblue')
# Creamos un estilo. Un estilo es un tipo de objeto que puede almacenar una o varias reglas (rules)
# para la representación cartográfica
s = mapnik.Style()
# Creamos un objeto rule
r=mapnik.Rule()
# Aplicamos parámetros a esa regla. En este caso:
# 1. relleno del polígono
# 2. simbología de la línea (outline)
r.symbols.append(mapnik.PolygonSymbolizer(mapnik.Color('#f2eff9')))
r.symbols.append(mapnik.LineSymbolizer(mapnik.Color('rgb(50%,50%,50%)'),0.1))
# asociamos la "rule" (r) al estilo (s)
s.rules.append(r)
# Asignamos un nombre al estilo
m.append_style('My Style',s)
# Creamos un objeto layer de mapnik: un nombre y una projección. En este caso la geodésica mundial EPSG:4326
lyr = mapnik.Layer('world','+proj=latlong +datum=WGS84')
# Definimos el origen de datos
lyr.datasource = mapnik.Shapefile(file='/Users/path/to/your/data/world_borders')
# Asociamos a la layer el estilo que hemos creado
lyr.styles.append('My Style')
# Asociamos la layer al objeto mapa (m)
m.layers.append(lyr)
# Indicamos que se renderizará el total de la extensión del shapefile
m.zoom_to_box(lyr.envelope())
# Renderizamos la imagen: indicando el objeto mapa (m) y el nombre del fichero de imagen a crear
mapnik.render_to_file(m,'world.png', 'png')
# Si queremos que los parámetros indicados en el objeto mapa sean serializados a un fichero
# mapfile (XML) sólo tenemos que añadir esta linea
mapnik.save_map(m,'mi_mapfile.xml')

Fíjate en la línea que hace referencia a la proyección a utilizar. ¿De donde saco estos parámetros? En esta página puedes encontrar los parámetros necesarios: http://spatialreference.org/ref/epsg/. Veamos algunos ejemplos:

Para ejecutar este script sólo tienes que indicarle que es un ejecutable:

<br>chmod +x hola_mundo.py<br>

Y ahora lo ejecutamos:

<br>python hola_mundo.py<br>

Resultado:

hola munco con Mapnik
hola mundo con Mapnik

Mapfile de Mapnik
Como ves, con muy pocas líneas tenemos una imagen creada con Mapnik, pero, ¿porque no separamos la lógica de representación y los orígenes de datos en un fichero de mapa (mapfile)? Posiblemente este nos recuerda a más de uno los famosos “mapfile” de MapServer, y realmente bastante se parecen a ellos, pero esta vez la lógica está en un fichero XML. Manos a la obra. Primero generamos el script de Python sin referencia alguna a la simbología y los datasources:

#!/usr/bin/env python
import mapnik
mapfile = 'hola_mundo.xml'
map_output = 'hola_mundo.png'
m = mapnik.Map(600, 300)
mapnik.load_map(m, mapfile)
bbox = mapnik.Envelope(mapnik.Coord(-180.0, -90.0), mapnik.Coord(180.0, 90.0))
m.zoom_to_box(bbox)
mapnik.render_to_file(m, map_output)

Fíjate que en la tercera línea llamamos a un fichero de mapa. Se trata de un fichero XML con una estructura definida, donde se indican los datasources (layers) y su representación (styles y rules dentro de ella)

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Map>
<Map bgcolor="steelblue" srs="+proj=latlong +datum=WGS84">
  <Style name="My Style">
    <Rule>
      <PolygonSymbolizer>
        <CssParameter name="fill">#f2eff9</CssParameter>
      </PolygonSymbolizer>
      <LineSymbolizer>
        <CssParameter name="stroke">rgb(50%,50%,50%)</CssParameter>
        <CssParameter name="stroke-width">0.1</CssParameter>
      </LineSymbolizer>
    </Rule>
  </Style><
  <Layer name="world" srs="+proj=latlong +datum=WGS84">
    <StyleName>My Style</StyleName>
    <Datasource>
      <Parameter name="type">shape</Parameter>
      <Parameter name="file"><ruta al shapefile, pero sin extensión .shp></Parameter>
    </Datasource>
  </Layer>
</Map>

En resumen tenemos:
- Fondo de mapa
- Style –> Rule –> Simbología
- Layer –> Proyección –> DataSource –> Style

Estos dos ejemplos utilizan shapefile. Para indicar un datasource PostGIS hay que especificar estas líneas

a) En el caso del fichero Python sin XML

# proyección UTM ED-50 (España) --> EPSG 23030
lyr = mapnik.Layer('spain','+proj=utm +zone=30 +ellps=intl +units=m +no_defs')
lyr = mapnik.Layer('Layer de PostGIS')
lyr.datasource = mapnik.PostGIS(host='localhost',user='postgres',password='',dbname='hispania',table='provincias')
lyr.styles.append('Mi estilo')

b) En el caso de un XML este sería el contenido de la parte referente a la layer

<Layer name="provincias" status="on" srs="+proj=utm +zone=30 +ellps=intl +units=m +no_defs">
    <StyleName>mi estilo</StyleName>
    <Datasource>
      <Parameter name="type">postgis</Parameter>
      <Parameter name="host">localhost</Parameter>
      <Parameter name="user">postgres</Parameter>
      <Parameter name="password"></Parameter>
      <Parameter name="dbname">hispania</Parameter>
      <Parameter name="table">provincias</Parameter>
      <Parameter name="estimate_extent">false</Parameter>
      <Parameter name="extent">-510000.0,3800000.0,1200000.0,4900000.0</Parameter>
    </Datasource>
</Layer>

El parámetro table (provincias) podemos sustituir el nombre de una tabla geométrica por una consulta SQL encerrada entre paréntesis:
(select * from provincias)

Resultado:
hola spain

Enlazando con lo último, en el momento en que definimos en una “layer” un “datasource”, sea un shapefile o PostGIS, con su correspondiente “style”, tenemos a disposición de las “rules” todas las columnas o campos del datasource, que pueden ser utilizados como filtros, lo que resulta muy util para la edición de cartografía temática.
Fichero: mapa_spain_xml2.py

#!/usr/bin/env python
import mapnik
mapfile = 'provincias2.xml'
map_output = 'hola_spain_xml2.png'
m = mapnik.Map(600, 600)
mapnik.load_map(m, mapfile)
bbox = mapnik.Envelope(mapnik.Coord(-510000.0, 3800000.0), mapnik.Coord(1200000.0, 4900000.0))
m.zoom_to_box(bbox)
mapnik.render_to_file(m, map_output)

Ahora el fichero
Fichero provincias2.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE Map>
<Map bgcolor="steelblue" srs="+proj=utm +zone=30 +ellps=intl +units=m +no_defs">
  <Style name="mi estilo provincias">
    <Rule>
      <PolygonSymbolizer>
        <CssParameter name="fill">#f2eff9</CssParameter>
      </PolygonSymbolizer>
      <LineSymbolizer>
        <CssParameter name="stroke">rgb(50%,50%,50%)</CssParameter>
        <CssParameter name="stroke-width">0.1</CssParameter>
      </LineSymbolizer>
    </Rule>
  </Style>
  <Style name="mi estilo vias">
<!-- autopistas -->
    <Rule>
      <Filter>[level] = 41</Filter>
      <LineSymbolizer>
        <CssParameter name="stroke">#506077</CssParameter>
        <CssParameter name="stroke-width">4</CssParameter>
      </LineSymbolizer>
      <TextSymbolizer name="codigo" face_name="DejaVu Sans Book" size="11" fill="#000" halo_radius="1" spacing="400" placement="line" />
    </Rule>
<!-- nacionales -->
    <Rule>
      <Filter>[level] = 40</Filter>
      <LineSymbolizer>
        <CssParameter name="stroke">#a37b48</CssParameter>
        <CssParameter name="stroke-width">2</CssParameter>
       </LineSymbolizer>
    </Rule>
  </Style>
<Layer name="provincias" status="on" srs="+proj=utm +zone=30 +ellps=intl +units=m +no_defs">
    <StyleName>mi estilo provincias</StyleName>
    <Datasource>
      <Parameter name="type">postgis</Parameter>
      <Parameter name="host">localhost</Parameter>
      <Parameter name="user">postgres</Parameter>
      <Parameter name="password"></Parameter>
      <Parameter name="dbname">hispania</Parameter>
      <Parameter name="table">provincias</Parameter>
      <Parameter name="estimate_extent">false</Parameter>
      <Parameter name="extent">-510000.0,3800000.0,1200000.0,4900000.0</Parameter>
    </Datasource><br></Layer>
<Layer name="vias" status="on" srs="+proj=utm +zone=30 +ellps=intl +units=m +no_defs">
    <StyleName>mi estilo vias</StyleName>
    <Datasource>
      <Parameter name="type">postgis</Parameter>
      <Parameter name="host">localhost</Parameter>
      <Parameter name="user">postgres</Parameter>
      <Parameter name="password"></Parameter>
      <Parameter name="dbname">hispania</Parameter>
      <Parameter name="table">vias</Parameter>
      <Parameter name="estimate_extent">false</Parameter>
      <Parameter name="extent">-510000.0,3800000.0,1200000.0,4900000.0</Parameter>
    </Datasource>
</Layer>
</Map>

Este mapfile consta de:
- 2 datasources
- provincias –> asociado al style “mi estilo provincias”
- vias –> asociado al style “mi estilo vias”
- 2 styles
- “mi estilo provincias” –> con una sola “rule”
- “mi estilo vias” –> con 2 “rules”
–> Autopistas: Filtro: “[level] = 40″, Texto: ver TextSymbolizer
–> Nacionales: [level] = 40

Resultado:

hola spain 2

Este artículo es el primero de una serie formada por:

  • Renderizado con Mapnik (este post)
  • Mapnik y OpenStreetMap
  • Thumbnails con Mapnik y OpenStreetMap
  • Uso de TMS de OpenStreetMap con OpenLayers
  • TMS de OpenStreetMap con Mod_tile
VN:F [1.7.7_1013]
Rating: 10.0/10 (4 votes cast)
Renderizado con Mapnik10.0104
  • Share/Bookmark
  1. A lot of specialists say that mortgage loans help a lot of people to live their own way, just because they are able to feel free to buy needed stuff. Furthermore, some banks give student loan for different classes of people.

    UN:F [1.7.7_1013]
    Rating: 0.0/5 (0 votes cast)
  1. October 22nd, 2009
  2. October 22nd, 2009
  3. October 22nd, 2009