How-to for using mapnik to render osm map tiles using C.
I have a slippy map program written in C++ that I use on a linux laptop to display maps for navigation in remote locations. That program relied on tiles previously archived on the local disk (bulk downloaded); which seemed to make tile server operators grouchy. So I added the capability to render tiles, using mapnik, as needed from osm.pbf data. I found nowhere anything that clearly said how to do this so here is my attempt to document the steps required to make an openstreetmap tile available on disk at the request of a C++ language computer program. I start this example on a virgin computer so as not to miss any install dependencies. Most likely some of the steps below will have already been completed on your machine.
- Install your favorite linux distribution. I used Fedora 20.
I don’t suggest that you need to do this step. - Bring your installation current.
“sudo yum update”
Fedora provides yum. Your distribution probably uses something else.
Reboot. Just in case. - Bring up the postgresql server. Mapnik expects postgre feature data
I am user ‘RB’ that appears below
“sudo yum install postgresql”
“sudo yum install postgresql-server” this creates directory /var/lib/pgsql size 24 B
“sudo yum install postgresql-contrib”
“sudo yum install postgis”
“sudo yum install kdeadmin” To set user parameters
“sudo kuser” Add RB to postgres group and postgres to RB group
“psql –version” returns 9.3.4 so postgre is up
“sudo chmod 770 /var/lib/psql” Maybe ? I doubt this is necessary. Was reversed later.
“sudo chmod 770 /home/RB”
“su postgres”
“postgresql-setup initdb”
“/bin/initdb -U RB -D /usr/local/pgsql/data” This creates directory /usr/local/pgsql size 36K
“postgres -D /usr/local/pgsql/data” Server now running
“ps -A” and see 7 instances of postgres running
“su RB”
“createdb -O RB -U RB gis” ‘gis’ is the name of the database mapnik looks for
“psql -d gis -c ‘CREATE EXTENSION hstore; CREATE EXTENSION postgis;’
“psql gis” See database prompt gis=# \q to exit - Fetch osm style sheets and make osm.xml file
“sudo yum install git”
“sudo yum install carto”
Make a directory named whatever. I use /home/RB/Maps/MapWorking
cd to that directory
“git clone https://github.com/gravitystorm/openstreetmap-carto.git”
This will make directory MapWorking/openstreetmap-carto.
“cd MapWorking/openstreetmap-carto”
“./get-shapefiles.sh” Time consuming
“carto -l project.mml > osm.xml”
This will make file ‘osm.xml’ which you will copy to another directory later. - Fetch osm map data in .pbf format
From http://download.geofabrik.de/ select a geographic area of interest and download the .osm.pbf version. For this exercise I select Nevada and Utah and place the requested data files into an arbitrarily named folder. I use “/home/RB/Maps/MapWorking/OSMfromGeofabrik”. These are 30+ MiB files. - Place osm data into postgre database
This uses the osm2pgsql program. Some examples I have come upon called for the “–slim” option. That will not work if wish to append to your database. Notice the –merc option. We are working with mercator data. The path to your data is shown as ~/
“sudo yum install osm2pgsql”
“su RB”
Hold on tight here
“osm2pgsql --create --cache 1000 --number-processes 2 --hstore --style ~/openstreetmap-carto/openstreetmap-carto.style --multi-geometry --merc ~/OSMfromGeofabrik/nevada-latest.osm.pbf”
“psql gis”
\d shows tables does not include table planet_osm_nodes
SELECT lat, lon FROM planet_osm_nodes ; Shows no points
“osm2pgsql --create --cache 1000 --number-processes 2 --hstore --style ~/openstreetmap-carto/openstreetmap-carto.style --multi-geometry --merc –slim ~/OSMfromGeofabrik/nevada-latest.osm.pbf” Notice –slim option
“psql gis”
\d shows tables now including planet_osm_nodes table
SELECT lat, lon FROM planet_osm_nodes ; Shows mercator points
“osm2pgsql --append --cache 1000 --number-processes 2 --hstore --style ~/openstreetmap-carto/openstreetmap-carto.style --multi-geometry --merc –slim ~/OSMfromGeofabrik/utah-latest.osm.pbf” fails for duplicate key
“osm2pgsql --create --cache 1000 --number-processes 2 --hstore --style ~/openstreetmap-carto/openstreetmap-carto.style --multi-geometry --merc ~/OSMfromGeofabrik/nevada-latest.osm.pbf” Notice –slim dropped
“psql gis”
\d shows tables Still includies planet_osm_nodes table
SELECT lat, lon FROM planet_osm_nodes ; Shows mercator points
“osm2pgsql --append --cache 1000 --number-processes 2 --hstore --style ~/openstreetmap-carto/openstreetmap-carto.style --multi-geometry --merc ~/OSMfromGeofabrik/utah-latest.osm.pbf”
Now completes successfully with Nevada and Utah loaded into postgresql
I don’t know anything about most other parameters except they were there to copy
7. Install mapnik I doubt this was necessary as mapnik was configured and built later on
“sudo yum install mapnik” See folder /usr/lib64/mapnik appear
“sudo yum install mapnik-demo”
“sudo yum install wget”
in directory MapWorking
“wget http://svn.openstreetmap.org/applications/rendering/mapnik/get-coastlines.sh”
“chmod 777 get-coastlines.sh”
“get-coastlines.sh”
In /usr/share/doc/mapnik-demo/python
“python rundemo.py” See in demo.png Ottawa map
8. Install development environment
The goal of this exercise is to use mapnik from a C language program. Kdevelop is used here to build that C program. This is a QT graphics project. The need for installs here may have been discovered in later steps and grouped here for convenience. If I did an install, but it did not fix what I was working on then I do not know if it was necessary.
“sudo yum install kdevelop”
“sudo yum install gcc”
“sudo yum install gcc-c++”
“sudo yum install qt-devel”
“sudo yum install boost-system” Not sure about this one
“sudo yum install boost”
“sudo yum install boost-devel”
“sudo yum install python-devel” Not sure about this one
“sudo yum install libxml2-devel”
“sudo yum install libtiff-devel”
“sudo yum install libpqxx”
“sudo yum install libpqxx-devel”
download mapnik-v2.2.0.tar.bz2and extract to ~/Maps/MapMaker/MapnikBuild
In /MapnikBuild
“./configure” Completes with noted omissions
“make” Time consuming
“sudo make install”
“kdevelop” See kdevelop come up and compile the “hello world” project
9. Bring up the application
The development location will be ~/projects/MapMaker
The execution location will be ~/projects/MapMaker/build
Copy in or create a real project that uses mapnik. When you get your kdevelop project right it will create or recognize the structure above.
Copy the osm.xml file created in Step 4 above to ~/projects/MapMaker/build
edit this file at line 18 to replace fontset-0 ‘DejaVu sans book’ with ‘DejaVu sans oblique’
(I could not find the ‘book’ font anyplace I looked)
In ~/projects/MapMaker/build
“ln -s /home/RB/Maps/MapWorking/openstreetmap-carto/symbols symbols”
With kdevelop build and execute the application disclosed below.
10. The application
This application will cause the file 24920.png to appear in directory /home/RB/Maps/Mapworking. You will recognizs that as part of the Spaghetti Bowl in Reno, Nv.
#include
#include
#include “/home/RB/projects/MapMaker/MapMaker.h”
#define RAD_TO_DEG (360.0 / 2.0 / M_PI)
//
// Driver program (the application)
// Calls interface with zoom level and x/y tile numbers to render and destination
// for the rendered map. Note that ‘typeMap’ is unused here
//
bool MapMakerDo(int zoom, int xTileLon,int yTileLat, QString destPath, QString typeMap);
MapMaker::MapMaker(QApplication *App)
{
int zoom = 16;
int xTileLon = 10961;
int yTileLat = 24920;
QString destPath = “/home/RB/Maps/MapWorking”;
QString typeMap = “openstreetmap”;
MapMakerDo(zoom, xTileLon, yTileLat, destPath, typeMap);
}
//
// The mapnik interface
//
bool MapMakerDo(int zoom, int xTileLon,int yTileLat, QString destPath, QString typeMap)
{
int i;
mapnik::datasource_cache::instance().register_datasources(“/usr/local/lib/mapnik/input”);
i=mapnik::freetype_engine::register_font(“/usr/local/lib/mapnik/fonts/DejaVuSans-Oblique.ttf”); i=mapnik::freetype_engine::register_font(“/usr/local/lib/mapnik/fonts/DejaVuSans-Bold.ttf”);
i=mapnik::freetype_engine::register_fonts(“/usr/local/lib/mapnik/fonts/DejaVuSans-Oblique.ttf”);
i=mapnik::freetype_engine::face_names().size();
double mapWid = 256;
mapnik::Map m(mapWid, mapWid);
mapnik::load_map(m, "osm.xml");
double powZoom = exp2(zoom);
double lon;
double latMax = atan(sinh(M_PI * (1 - 2 * (yTileLat) / powZoom))) * RAD_TO_DEG; //NW corner
double latMin = atan(sinh(M_PI * (1 - 2 * (yTileLat+1) / powZoom))) * RAD_TO_DEG; //SE corner
lon = (xTileLon / powZoom) * 360.0 - 180.0;
double lonDegPerPix = 360.0 / (mapWid * powZoom);
double lonMin = lon;
double lonMax = lon + lonDegPerPix * mapWid;
mapnik::projection merc = mapnik::projection("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over");
mapnik::projection longlat = mapnik::projection("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
mapnik::box2d<double> bbox (lonMin, latMin, lonMax, latMax);
mapnik::proj_transform tr(longlat,merc);
bool ret = tr.forward(bbox); //Alters bbox to merc
m.zoom_to_box(bbox);
mapnik::image_32 buf (m.width(), m.height());
mapnik::agg_renderer<mapnik::image_32> r(m, buf);
r.apply();
QString outFname = destPath + "/" + QString("%1").arg(yTileLat) + ".png"; /destPath must exist
mapnik::save_to_file<mapnik::image_data_32>(buf.data(), outFname.toStdString(), "png");
return(true); //Only return value as path constrained to be present
}
11. PS
This has been posted to both the openstreetmap forum and the mapnik google group Comments posted on either group may add insight to what was presented above.
I was never able to find a source for the ’ DejaVu sans book’ font. Like “DejaVuSans-Book.ttf” Do you know where to look?
Thank you to all who posted the useful examples scattered about the web that I have used here.
-RickBrown in Reno, NV