User Tools

Site Tools


installation_guides:tileserver_on_raspberry_pi_4

Installing a tile server on a Raspberry Pi 4

This text describes how to set up a tile server on a Raspberry Pi 4 with 4 Gigabytes of RAM. It deals with OpenStreetMap.org-based styles as well as with OpenStreetMap.de-based styles and addresses a couple of issues that I encountered while actually setting up the software. Be prepared to run into issues - changes to OSM software are frequent so part of the recipe is very likely alread outdated when you are using it.

A demonstration of the layers available on my Raspberry Pi 4 can be found at https://mapintosh.de/myOsm/ - use the layer selector button at the upper right of the map.

Default OSM style

Install Prerequisites

Make sure that the Ubuntu system is up to date, then install the packages required for a tile server. The commands below should install everything required - including recent version of node, npm and yarn. Issuing apt update twice is in order as you add package sources

sudo apt update
sudo apt upgrade
sudo apt install curl
curl -sL https://deb.nodesource.com/setup_12.x | sudo bash -
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update
sudo apt install apache2 apache2-dev autoconf build-essential bzip2 cmake     \
  fonts-noto fonts-noto-hinted g++ gcc gdal-bin git-core libagg-dev           \
  libboost-all-dev libboost-dev libboost-filesystem-dev libboost-system-dev   \
  libbz2-dev libcairo-dev libcairomm-1.0-dev libexpat1-dev libfreetype6-dev   \
  libgdal-dev libgeos-dev libgeos++-dev libgeotiff-epsg libicu-dev            \
  liblua5.1-dev libpq-dev libmapnik-dev libpq-dev                             \
  libproj-dev libprotobuf-c-dev libtiff5-dev libtool libxml2-dev lua5.1 make  \
  mapnik-utils munin munin-node nodejs osm2pgsql postgis postgresql-11        \
  postgresql-11-postgis-2.5 postgresql-11-postgis-2.5-scripts                 \
  postgresql-contrib protobuf-c-compiler python-mapnik tar ttf-unifont unzip  \
  wget yarn zlib1g-dev
sudo npm install -g carto

Configure PostgreSQL and PostGIS

Now create a PostGIS database

sudo -u postgres -i
createuser renderman
createdb -E UTF8 -O renderman gis

Remain logged in as user postgres start psql and do the following:

\c gis
CREATE EXTENSION postgis;
CREATE EXTENSION hstore;
ALTER TABLE geometry_columns OWNER TO renderman;
ALTER TABLE spatial_ref_sys OWNER TO renderman;
\q

Now log out user postgres.

Add Rendering User

Add a user for rendering and make it a sudoer:

sudo adduser renderman
sudo usermod -aG sudo renderman

Check Mapnik

Check if mapnik has been installed correctly:

python
>>> import mapnik
>>>

If the import statement results in no complaint, everything is in order and you can quit Python by entering

quit()

Get, Build, and Install ''mod_tile''

To install mod_tile do the following:

cd ~/src
git clone https://github.com/SomeoneElseOSM/mod_tile.git
cd mod_tile
./autogen.sh
./configure
make -j 4
sudo make install
sudo make install-mod_tile
sudo ldconfig

Note that out of the box mod_tile will not allow you to have more than 10 different XML configurations, i.e. it will not allow you to serve more than 10 different styles. Ten different styles may sound excessive but I added this section after running into the limit.

WARNING: If you already have an existing config in /usr/local/etc/renderd.conf be sure to make a backup copy of it before installing a modded mod_tile. I learned the hard way that will be relentlessly replaced by the default content. Fortunately I stubbornly sticked to a strict naming scheme (all XML files are named mapnik.xml and the directory name is identical to the URI) so I managed to rapidly restore the contents.

Fixing the issue is easy, just change the limit that is hard-coded in includes/render_config.h

git diff includes/render_config.h
diff --git a/includes/render_config.h b/includes/render_config.h
index 997348f..57792d3 100644
--- a/includes/render_config.h
+++ b/includes/render_config.h
@@ -32,7 +32,7 @@
 // The XML configuration used if one is not provided
 #define XMLCONFIG_DEFAULT "default"
 // Maximum number of configurations that mod tile will allow
-#define XMLCONFIGS_MAX 10
+#define XMLCONFIGS_MAX 100
  // Mapnik input plugins (will need to adjust for 32 bit libs)
 #define MAPNIK_PLUGINS "/usr/local/lib64/mapnik/input"

Obtain Standard Style

To obtain the standard style use

cd ~/src
git clone https://github.com/gravitystorm/openstreetmap-carto.git
cd openstreetmap-carto/

Now convert the carto project into something that Mapnik can understand:

carto project.mml > mapnik.xml

Executing this command may take some time.

Download Data

There are a number of sources for data. We'll use bbbike.org to obtain the data for the city of Bonn.

  mkdir ~/src/osm-data
  cd ~/src/osm-data
  curl -O https://download.bbbike.org/osm/bbbike/Bonn/Bonn.osm.pbf

Import Data

To import the data just downloaded use

osm2pgsql -d gis                                                           \
  -C 1000                                                                  \
  --create                                                                 \
  -d gis                                                                   \
  -G                                                                       \
  --hstore                                                                 \
  --number-processes 4                                                     \
  --slim                                                                   \
  -S ~/src/openstreetmap-carto/openstreetmap-carto.style                   \
  --tag-transform-script ~/src/openstreetmap-carto/openstreetmap-carto.lua \
  ~/src/osm-data/Bonn.osm.pbf

Note that -C controls how much memory is allocated and –number-processes sets the number of cores to be used.

Create indexes

For performance create indexes

psql -d gis -f ~/src/openstreetmap-carto/indexes.sql

Download Shapefiles

For rendering you need some shapefiles like low resolution country boundaries and the like (altogether some 750 MB)

cd ~/src/openstreetmap-carto/
./scripts/get-shapefiles.py

Configure Renderd

To configure renderd edit /usr/local/etc/renderd.conf. Some lines may need changes, namely

XML=/home/jburgess/osm/svn.openstreetmap.org/applications/rendering/mapnik/osm-local.xml

to the actual location of the XML file, with the above that should read

XML=/home/renderman/src/openstreetmap-carto/mapnik.xml

and

URI=/osm_tiles/

if you want a different name.

A word of advice: Please confirm that plugins_dir points to the directory that actually contains the plugins, in my case

plugins_dir=/usr/lib/mapnik/3.0/input

The directory is the correct one if it contains a number of files ending in .input

Here's how my config looks renderd.conf looks like:

[renderd]
num_threads=4
tile_dir=/var/lib/mod_tile
stats_file=/var/run/renderd/renderd.stats
 
[mapnik]
plugins_dir=/usr/lib/mapnik/3.0/input/
font_dir=/usr/share/fonts/truetype
font_dir_recurse=1
 
[osm]
URI=/osm/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/openstreetmap-carto/mapnik.xml
HOST=penpendede.hopto.org
TILESIZE=256

Configure Apache

To configure apache first create two directories

sudo mkdir /var/lib/mod_tile /run/renderd
sudo chown renderman /var/lib/mod_tile /run/renderd

To enable mod_tile you first need a config file so that Apache can load the module.

sudo sh -c "echo 'LoadModule tile_module /usr/lib/apache2/modules/mod_tile.so' \
  >> /etc/apache2/conf-available/mod_tile.conf"

Once it exists use

sudo a2enconf mod_tile

to enable the module but don't run systemctl reload apache2 as suggested.

Edit /etc/apache2/sites-available/000-default.conf (or what other config you are using) and between ServerAdmin and DocumentRoot add

LoadTileConfigFile /usr/local/etc/renderd.conf
ModTileRenderdSocketName /run/renderd/renderd.sock
# Timeout before giving up for a tile to be rendered
ModTileRequestTimeout 0
# Timeout before giving up for a tile to be rendered that is otherwise missing
ModTileMissingRequestTimeout 30

when done with this reload apache twice.

sudo service apache2 reload
sudo service apache2 reload

Run ''renderd'' for the Very First Time

Time to check if everything is set up properly. Run renderd in the foreground using

renderd -f -c /usr/local/etc/renderd.conf

Now point your browser to

http://localhost/osm_tiles/0/0/0.png

(you of course need to use the correcthostname) and if everything works out fine you see the world.

If something goes wrong check the output of renderd for hints on the reason why.

Run ''renderd'' in the Background

Apply the following changes to ~/src/mod_tile/debian/renderd.init (and don't worry, you can always use git checkout to revert mishaps)

Firstly change

RUNASUSER=www-data

to

RUNASUSER=renderman

secondly

DAEMON=/usr/bin/$NAME

to

DAEMON=/usr/local/bin/$NAME

thirdly

DAEMON_ARGS=""

to

DAEMON_ARGS="-c /usr/local/etc/renderd.conf"

forthly

PIDSOCKDIR=/var/run/$NAME

to

PIDSOCKDIR=/run/$NAME

and finally

[ -r /etc/default/$NAME ] && . /etc/default/$NAME

to

[ -r /usr/local/etc/$NAME ] && . /usr/local/etc/$NAME

After the block in which variables are initialized add

[ -d "$PIDSOCKDIR" ] || mkdir -p $PIDSOCKDIR && chown $RUNASUSER $PIDSOCKDIR

then copy the file to /etc/init.d/renderd and make that copy executable:

sudo cp ~/src/mod_tile/debian/renderd.init /etc/init.d/renderd
sudo chmod u+x /etc/init.d/renderd

and create /lib/systemd/system/renderd.service containing

[Unit]
Description=renderd
After=multi-user.target
Requires=multi-user.target
 
[Service]
Type=simple
User=root
PIDFile=/run/renderd/renderd.pid
ExecStart=/etc/init.d/renderd start
 
[Install]
WantedBy=multi-user.target

make sure that /run/renderd exists and (including its contents) is owned by renderman

sudo test -d /run/renderd || sudo mkdir /run/renderd
sudo chown -R renderman /run/renderd

and finally

sudo systemctl start renderd
sudo systemctl enable renderd
sudo systemctl restart apache2

Annuntio vobis gaudium magnum: habemus tile server. Let's set up a small testing page for it. First we find out the region covered by our data, then we use Leaflet to show the map.

Obtain Bounding box

The following method for obtaining the bounding box of a region works for virtually any region known to OSM.

To obtain the bounding box of Bonn (i.e. the range of longitudes and latitudes that covers all of Bonn) visit http://boundingbox.klokantech.com/ and search for Bonn, Germany (or whatever region you want. Then go to the bottom of the page and in the pull-down menu under Copy & Paste select CSV. This should result in something like 7.022535,50.632691,7.210677,50.774423.

For our purposes 7.022,50.632,7.211,50.775 is sufficient which means that the longitude ranges from 7.022 to 7.211 degrees East and from 50.632 to 50.775 degrees North.

Create a Map Using Leaflet

Under /var/www/html/ create the subdirectory my_osm_map. In it create style.css containing

* {
  box-sizing: border-box;
}
body {
  bottom: 0;
  left: 0;
  margin: 0;
  padding: 0;
  position: absolute;
  right: 0;
  top: 0;
}
.title {
  font-family: sans-serif;
  font-size: 24px;
  font-weight: bold;
  height: 48px;
  line-height: 48px;
  margin: 0;
  text-align: center;
  width: 100%;
}
.content {
  height: calc( 100% - 48px );
  width: 100%;
}

and index.html containing

<!DOCTYPE html>
<html>
  <head>
    <title>OSM Map of Bonn</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css" />
    <link
      rel="stylesheet"
      href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"
      integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
      crossorigin=""/>
    <script
      src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"
      integrity="sha512-QVftwZFqvtRNi0ZyCtsznlKSWOStnDORoefr1enyq5mVL4tmKB3S/EnC3rRJcxCPavG10IcrVGSmPh6Qw5lwrg=="
      crossorigin=""></script>
  </head>
  <body>
    <div class="title">OSM Map of Bonn</div>
    <div class="content" id="mapid"></div>
    <script>
      var mymap = L.map('mapid').fitBounds([
        [50.632, 7.022],
        [50.775, 7.211]
      ]);
      L.tileLayer('http://localhost/osm_tiles/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution:
          'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
          '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
        id:
          'mapbox.streets'
      }).addTo(mymap);
    </script>
  </body>
</html>

When done visit [http://localhost/my_osm_map](http://localhost/my_osm_map). You should see a nice little page that (after some delay whilst the tiles are being rendered) displays an OSM style map of Bonn.

At this point nothing more remains to be done - unless you want or need the map off the grid.

Going Offline

The only dependence on remote resources currently is leaflet that is fetched from the online source unpkg.com. To go offline we need a local copy of leaflet. We'll use npm that has already been installed to obtain carto.

Go to /var/www/html/my_osm_map and issue

bash
npm install leaflet
cp node_modules/leaflet/dist/leaflet.js .
cp node_modules/leaflet/dist/leaflet.css .
cp -R node_modules/leaflet/dist/images .

then change

<link
  rel="stylesheet"
  href="https://unpkg.com/leaflet@1.4.0/dist/leaflet.css"
  integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA=="
  crossorigin=""/>
<script
  src="https://unpkg.com/leaflet@1.4.0/dist/leaflet.js"
  integrity="sha512-QVftwZFqvtRNi0ZyCtsznlKSWOStnDORoefr1enyq5mVL4tmKB3S/EnC3rRJcxCPavG10IcrVGSmPh6Qw5lwrg=="
  crossorigin=""></script>

to

<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>

and reload the page. That's all there is to it.

BEWARE: ANYTHING BEYOND THIS POINT IS NOT READY FOR USE!!!!!!

Man spricht Deutsch (German spoken)

Lasciate ogne speranza, voi ch'intrate

I will assume that you have followed the above tutorial but want to have the German style as well.

The individuals behind the German OSM style came up with the very ingenious idea of using a different database layout for their style.

While the tile server is already up and running, more work needs to be done to support German. This includes setting up a fitting database.

Firstly we need KAKASI, a simple Kanji/Kana/Romaji converter.

sudo apt install kakasi

As we want to install the libraries for the C/C++ stored procedures to be used, we need some Debian development stuff

sudo apt install devscripts equivs

That will likely install tons of packages.

Installing ''mapnik-german-l10n''

As renderman:

cd ~/src/
git clone https://github.com/giggls/mapnik-german-l10n.git
cd mapnik-german-l10n

There is an INSTALL.md that explains the installation process, that covers the basics but in my opinion requires an experienced Linux navigator to circumnavigate the cliffs. Now that we have mapnik-german-l10n we need to install it. For this some packages need to be installed:

sudo pip3 install pythainlp
sudo apt install postgresql-plpython3-11

Note: You need to install the version of postgresql-plpython3 that matches your postgres version.

Make sure that the postgres version you are using actually is listed in debian/control and debian/control.ini (under Build-Depends, this is not always the case), then issue

sudo mk-build-deps -i debian/control

When this is done build the deb package and install it (please note that the deb package is created in the parent directory):

make deb
for file in ../postgresql*osml10n*.deb; do sudo apt install $file; done

Then, just to be safe (actually only needed if the above causes an error):

sudo apt install -f
sudo apt update
sudo apt upgrade

Preparing the Database

To start with we'll add a new database named osm. So sudo -u postgres psql and enter

CREATE DATABASE osm WITH OWNER renderman;
\q

Exit, then sudo -u postgres psql -d osm and enter

CREATE EXTENSION hstore;
CREATE EXTENSION postgis;
CREATE EXTENSION unaccent;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION osml10n;
CREATE EXTENSION osml10n CASCADE;
CREATE EXTENSION osml10n_thai_transcript CASCADE;
\q

Preparing Python

Make sure that all required Python packages are installed issue:

pip install pyyaml lxml mapnik nik4

Note that nik4 is not actually needed for tile rendering but it is extremely cool: It can be used to render a map of your liking, see https://github.com/Zverik/Nik4

Obtaining German Style

Clone the German style from github:

cd ~/src
git clone git://github.com/giggls/openstreetmap-carto-de.git
cd openstreetmap-carto-de

Import the data:

osm2pgsql                     \
  -d osm                      \
  -G                          \
  --hstore                    \
  --hstore-match-only         \
  --number-processes 4        \
  -p planet_osm_hstore        \
  --style hstore-only.style   \
  ~/src/osm-data/Bonn.osm.pbf

Get the required shapefiles

./scripts/get-shapefiles.py

Oberpfaffenhofen, we've had a problem..

Pardon me? Oh, well, Oberpfaffenhofen is the location of Deutsches Raumfahrt-Kontrollzentrum, the German space mission control center and the heading as a whole referns to the infamous Apollo 13 accident.

It turns out that the current version of openstreetmap-carto-de is broken but fortunately not beyond repair. You can find a patch at https://mg.guelker.eu/articles/2020/01/01/openstreetmap-printkarten/ but I recommend not using it for two reasons:

  1. it modifies certain files that do no have anything to do with the issue
  2. a large part of the patch is simply used to remove files - something a single rm command can do in a much clearer

manner

  1. it fixes the compatibility issue but breaks rendering at low zoom levels

I suggest that as renderman you change directory to ~/src/openstreetmap-carto-de put the patch below into a file named fix.patch and then issue the following commands:

rm project.mml.d/lakes-z[0-9] project.mml.d/ocean-z[0-6] project.mml.d/rivers-z[0-6]
patch -p1 < fix.patch 

Patch

My version of the that as far as I can tell solves the issue without breaking any rendering is available in the ZIP file openstreetmap-carto-de-fix.zip, the fix contains this:

--- a/project.mml.d/order.txt
+++ b/project.mml.d/order.txt
@@ -8,28 +8,7 @@ water-lines-casing
 water-lines-low-zoom
 springs
 water-lines
-rivers-z0
-rivers-z1
-rivers-z2
-rivers-z3
-rivers-z4
-rivers-z5
-rivers-z6
-lakes-z0
-lakes-z1
-lakes-z2
-lakes-z3
-lakes-z4
-lakes-z5
-lakes-z6
 water-areas
-ocean-z0
-ocean-z1
-ocean-z2
-ocean-z3
-ocean-z4
-ocean-z5
-ocean-z6
 ocean-lz
 ocean
 landcover-area-symbols
--- a/project.mml	2020-05-21 19:49:19.247102843 +0200
+++ b/project.mml	2020-05-21 19:48:44.677720976 +0200
@@ -152,7 +152,7 @@
       file: data/world_boundaries/builtup_area.shp
       type: shape
     properties:
-      minzoom: 8
+      minzoom: 0
       maxzoom: 9
   - id: landcover-line
     geometry: linestring
@@ -174,7 +174,7 @@
       file: data/antarctica-icesheet-polygons-3857/icesheet_polygons.shp
       type: shape
     properties:
-      minzoom: 5
+      minzoom: 0
   - id: water-lines-casing
     geometry: linestring
     <<: *extents
@@ -259,144 +259,6 @@
         ) AS water_lines
     properties:
       minzoom: 12
-  - id: rivers-z0
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z0.shp"
-      type: "shape"
-    properties:
-      maxzoom: 0
-    advanced: {}
-  - id: rivers-z1
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z1.shp"
-      type: "shape"
-    properties:
-      minzoom: 1
-      maxzoom: 1
-    advanced: {}
-  - id: rivers-z2
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z2.shp"
-      type: "shape"
-    properties:
-      minzoom: 2
-      maxzoom: 2
-    advanced: {}
-  - id: rivers-z3
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z3.shp"
-      type: "shape"
-    properties:
-      minzoom: 3
-      maxzoom: 3
-    advanced: {}
-  - id: rivers-z4
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z4.shp"
-      type: "shape"
-    properties:
-      minzoom: 4
-      maxzoom: 4
-    advanced: {}
-  - id: rivers-z5
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z5.shp"
-      type: "shape"
-    properties:
-      minzoom: 5
-      maxzoom: 5
-    advanced: {}
-  - id: rivers-z6
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/river-polygons-reduced-3857/river_reduced_z6.shp"
-      type: "shape"
-    properties:
-      minzoom: 6
-      maxzoom: 6
-    advanced: {}
-  - id: lakes-z0
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z0.shp"
-      type: "shape"
-    properties:
-      maxzoom: 0
-    advanced: {}
-  - id: lakes-z1
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z1.shp"
-      type: "shape"
-    properties:
-      minzoom: 1
-      maxzoom: 1
-    advanced: {}
-  - id: lakes-z2
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z2.shp"
-      type: "shape"
-    properties:
-      minzoom: 2
-      maxzoom: 2
-    advanced: {}
-  - id: lakes-z3
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z3.shp"
-      type: "shape"
-    properties:
-      minzoom: 3
-      maxzoom: 3
-    advanced: {}
-  - id: lakes-z4
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z4.shp"
-      type: "shape"
-    properties:
-      minzoom: 4
-      maxzoom: 4
-    advanced: {}
-  - id: lakes-z5
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z5.shp"
-      type: "shape"
-    properties:
-      minzoom: 5
-      maxzoom: 5
-    advanced: {}
-  - id: lakes-z6
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/lakes-polygons-reduced-3857/lakes_reduced_z6.shp"
-      type: "shape"
-    properties:
-      minzoom: 6
-      maxzoom: 6
-    advanced: {}
   - id: water-areas
     geometry: polygon
     <<: *extents
@@ -427,75 +289,6 @@
     properties:
       cache-features: true
       minzoom: 0
-  - id: ocean-z0
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z0.shp"
-      type: "shape"
-    properties:
-      maxzoom: 0
-    advanced: {}
-  - id: ocean-z1
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z1.shp"
-      type: "shape"
-    properties:
-      minzoom: 1
-      maxzoom: 1
-    advanced: {}
-  - id: ocean-z2
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z2.shp"
-      type: "shape"
-    properties:
-      minzoom: 2
-      maxzoom: 2
-    advanced: {}
-  - id: ocean-z3
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z3.shp"
-      type: "shape"
-    properties:
-      minzoom: 3
-      maxzoom: 3
-    advanced: {}
-  - id: ocean-z4
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z4.shp"
-      type: "shape"
-    properties:
-      minzoom: 4
-      maxzoom: 4
-    advanced: {}
-  - id: ocean-z5
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z5.shp"
-      type: "shape"
-    properties:
-      minzoom: 5
-      maxzoom: 5
-    advanced: {}
-  - id: ocean-z6
-    geometry: "polygon"
-    <<: *extents
-    Datasource:
-      file: "data/ocean-polygons-reduced-3857/ocean_reduced_z6.shp"
-      type: "shape"
-    properties:
-      minzoom: 6
-      maxzoom: 6
-    advanced: {}
   - id: ocean-lz
     geometry: polygon
     <<: *extents
@@ -503,7 +296,7 @@
       file: data/simplified-water-polygons-split-3857/simplified_water_polygons.shp
       type: shape
     properties:
-      minzoom: 7
+      minzoom: 0
       maxzoom: 9
   - id: ocean
     geometry: polygon
@@ -550,7 +343,7 @@
       file: data/antarctica-icesheet-outlines-3857/icesheet_outlines.shp
       type: shape
     properties:
-      minzoom: 5
+      minzoom: 0
   - id: water-barriers-line
     geometry: linestring
     <<: *extents
@@ -1356,7 +1149,7 @@
       file: data/ne_110m_admin_0_boundary_lines_land/ne_110m_admin_0_boundary_lines_land.shp
       type: shape
     properties:
-      minzoom: 1
+      minzoom: 0
       maxzoom: 3
   - id: admin-low-zoom
     geometry: linestring

Generate the required views

Now that the SQL scripts have been fixed use

psql -d osm -f osm_tag2num.sql
psql -d osm -f views_osmde/view-line.sql
psql -d osm -f views_osmde/view-point.sql
psql -d osm -f views_osmde/view-polygon.sql
psql -d osm -f views_osmde/view-roads.sql

and create indexes to speed things up

psql -d osm -f indexes-hstore.sql

Creating the German style

In the same manner as you did for the default style enter

cd ~/src/openstreetmap-carto-de/
carto project.mml > mapnik.xml

With both the osm and osm-de style my renderd.conf now looks like this:

[renderd]
num_threads=4
tile_dir=/var/lib/mod_tile
stats_file=/var/run/renderd/renderd.stats
 
[mapnik]
plugins_dir=/usr/lib/mapnik/3.0/input/
font_dir=/usr/share/fonts/truetype
font_dir_recurse=1
 
[osm]
URI=/osm/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/openstreetmap-carto/mapnik.xml
HOST=penpendede.hopto.org
TILESIZE=256
 
[osm-de]
URI=/osm-de/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/openstreetmap-carto-de/mapnik.xml
HOST=penpendede.hopto.org
TILESIZE=256

Leaflet map with both osm and osm-de style

With some small changes you can modify the web page presenting the osm style into one presenting both both the osm and the osm-de style. Simply change index.html like this:

<!DOCTYPE html>
<html>
  <head>
    <title>OSM Map of Bonn</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="leaflet.css">
    <script src="leaflet.js"></script>
  </head>
  <body>
    <div class="title">OSM Map of Bonn</div>
    <div class="content" id="mapid"></div>
    <script>
      var osm = L.tileLayer('https://penpendede.hopto.org/osm/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution:
          'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
          '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
      })
      var osmDe = L.tileLayer('https://penpendede.hopto.org/osm-de/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution:
          'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
          '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
      })
 
      var map = L.map('mapid', {
        layers: [osm, osmDe]
      }).fitBounds([
        [50.632, 7.022],
        [50.775, 7.211]
      ]);
 
      var baseMaps = {
        "osm": osm,
        "osmDe": osmDe
      }
      var overlayMaps = { }
 
      L.control.layers(baseMaps, overlayMaps).addTo(map)
 
    </script>
  </body>
</html>

OpenRailwayMap

Despite its name, the OpenStreetMap databases contains tons on information on railways. https://www.openrailwaymap.org turns this data into overlay maps. If you already have a tile server with the default OSM style up and running it is quite easy to reproduce those maps.

Getting the Style

As user renderman issue:

cd ~/src
git clone https://github.com/OpenRailwayMap/OpenRailwayMap-CartoCSS.git

Creating Views, Pre-calculating Station Label Ranking

The map style accesses the database through a couple of views. In addition, it requires a few custom functions and a precomputed station label ranking. To run the required SQL scripts do the following:

cd ~/src/OpenRailwayMap-CartoCSS
psql -d gis -f sql/osm_carto_views.sql
psql -d gis -f sql/functions.sql
psql -d gis -f sql/get_station_importance.sql

Create the style files

To create the style files use

carto project.mml > standard.xml
carto maxspeed.mml > maxspeed.xml
carto signals.mml > signals.xml

Add the New Styles to Renderd

To add the styles to renderd you need to first add them to /usr/local/etc/renderd.conf; it should look similar to this:

[renderd]
num_threads=4
tile_dir=/var/lib/mod_tile
stats_file=/var/run/renderd/renderd.stats
 
[mapnik]
plugins_dir=/usr/lib/mapnik/3.0/input/
font_dir=/usr/share/fonts/truetype
font_dir_recurse=1
 
[osm]
URI=/osm/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/openstreetmap-carto/mapnik.xml
HOST=penpendede.hopto.org
TILESIZE=256
 
[osm-de]
URI=/osm-de/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/openstreetmap-carto-de/mapnik.xml
HOST=penpendede.hopto.org
TILESIZE=256
 
[train]
URI=/train/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/OpenRailwayMap-CartoCSS/standard.xml
HOST=penpendede.hopto.org
TILESIZE=256
 
[train-maxspeed]
URI=/train-maxspeed/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/OpenRailwayMap-CartoCSS/maxspeed.xml
HOST=penpendede.hopto.org
TILESIZE=256
 
[train-signals]
URI=/train-signals/
TILEDIR=/var/lib/mod_tile
XML=/home/renderman/src/OpenRailwayMap-CartoCSS/signals.xml
HOST=penpendede.hopto.org
TILESIZE=256

When done restart renderd and apache:

sudo service renderd restart
sudo service apache2 restart

Add the Train Layers to the Map

While it would be entirely possible to use the new layers as base maps I prefer using them as overlay maps. The new index.html looks like this:

renderman@penpendede:~/src $ more /var/www/html/myOsm/index.html 
<!DOCTYPE html>
<html>
  <head>
    <title>OSM Map of Bonn</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css" />
    <link rel="stylesheet" href="leaflet.css">
    <script src="leaflet.js"></script>
  </head>
  <body>
    <div class="title">OSM Map of Bonn</div>
    <div class="content" id="mapid"></div>
    <script>
      var baseMaps = {
        'osm': L.tileLayer('https://penpendede.hopto.org/osm/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution:
            'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
            '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
        }),
        osmDe: L.tileLayer('https://penpendede.hopto.org/osm-de/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution:
            'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
            '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
        })
      }
      var overlayMaps = {
        'train': L.tileLayer('https://penpendede.hopto.org/train/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution:
            'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
            '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
        }),
        'train-maxspeed': L.tileLayer('https://penpendede.hopto.org/train-maxspeed/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution:
            'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
            '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
        }),
        'train-signals': L.tileLayer('https://penpendede.hopto.org/train-signals/{z}/{x}/{y}.png', {
          maxZoom: 18,
          attribution:
            'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
            '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
        })
      }
 
      var map = L.map('mapid', {
        layers: [baseMaps['osmDe']]
      }).fitBounds([
        [50.632, 7.022],
        [50.775, 7.211]
      ])
 
      L.control.layers(baseMaps, overlayMaps).addTo(map)
    </script>
  </body>
</html>

Tips and Tricks

Less Shapefiles

You might have noticed that the shapefiles used are huge compared with everything else that consists a certain OSM style. So if you need a lot of different styles at once you waste dozens of gigs for identical copies.

Consider having a separate directory with all the shape file that are used and simply replace the data subdirectory of openstreetmap-carto, openstreetmap-carto-de, … by a symbolic link to that directory. Do not replace the individual files with a symbolic link as that won't work (at least it did not work when I tried). Some tools seem incapable of following symbolic links to files.

Pre-Rendering

To pre-render the metatiles I use a script (I named it populate_bonn.sh that calls render_list with the appropriate parameters for Bonn:

cmd=render_list
socket=/run/renderd/renderd.sock
 
$cmd -m $map -a -z  0 -Z  0 -x      0 -X      0 -y     0 -Y     0 -f -n 4 -s $socket
$cmd -m $map -a -z  1 -Z  1 -x      1 -X      1 -y     0 -Y     0 -f -n 4 -s $socket
$cmd -m $map -a -z  2 -Z  2 -x      2 -X      2 -y     1 -Y     1 -f -n 4 -s $socket
$cmd -m $map -a -z  3 -Z  3 -x      4 -X      4 -y     2 -Y     2 -f -n 4 -s $socket
$cmd -m $map -a -z  4 -Z  4 -x      8 -X      8 -y     5 -Y     5 -f -n 4 -s $socket
$cmd -m $map -a -z  5 -Z  5 -x     16 -X     16 -y    10 -Y    10 -f -n 4 -s $socket
$cmd -m $map -a -z  6 -Z  6 -x     33 -X     33 -y    21 -Y    21 -f -n 4 -s $socket
$cmd -m $map -a -z  7 -Z  7 -x     66 -X     66 -y    42 -Y    43 -f -n 4 -s $socket
$cmd -m $map -a -z  8 -Z  8 -x    132 -X    133 -y    85 -Y    86 -f -n 4 -s $socket
$cmd -m $map -a -z  9 -Z  9 -x    265 -X    266 -y   171 -Y   172 -f -n 4 -s $socket
$cmd -m $map -a -z 10 -Z 10 -x    531 -X    532 -y   343 -Y   344 -f -n 4 -s $socket
$cmd -m $map -a -z 11 -Z 11 -x   1063 -X   1065 -y   687 -Y   688 -f -n 4 -s $socket
$cmd -m $map -a -z 12 -Z 12 -x   2127 -X   2130 -y  1375 -Y  1377 -f -n 4 -s $socket
$cmd -m $map -a -z 13 -Z 13 -x   4255 -X   4260 -y  2750 -Y  2755 -f -n 4 -s $socket
$cmd -m $map -a -z 14 -Z 14 -x   8511 -X   8520 -y  5501 -Y  5511 -f -n 4 -s $socket
$cmd -m $map -a -z 15 -Z 15 -x  17023 -X  17040 -y 11002 -Y 11022 -f -n 4 -s $socket
$cmd -m $map -a -z 16 -Z 16 -x  34046 -X  34080 -y 22005 -Y 22045 -f -n 4 -s $socket
$cmd -m $map -a -z 17 -Z 17 -x  68092 -X  68161 -y 44010 -Y 44091 -f -n 4 -s $socket
$cmd -m $map -a -z 18 -Z 18 -x 136185 -X 136322 -y 88020 -Y 88183 -f -n 4 -s $socket

To use it for a different region you need to modify the values provided for x, y, X, and Y accordingly.

You may wonder where the value for the variable $map comes from. Well, I call the script in the following manner:

map=osm ./populate_bonn.sh

This means that the script is called with the variable $map set to osm - it will not set the environment variable $map - the assignment only effects the command at hand.

installation_guides/tileserver_on_raspberry_pi_4.txt · Last modified: 2020/05/27 19:57 by admin