# Maps à la carte
Those who follow my Mastodon account will know that I have a thing for bicycle trips. The bicycle is a technology that gave humans immense freedom back when it appears, and it remains one of my most favourite ways to meet the world on my terms.
Sometimes unrestricted freedom is the best, and sometimes we need to settle on a direction. And the best thing balancing freedom and directions on a bike is a map.
Maps are typically made for a group of people. Thanks to OpenStreetMap you can take a look at a map drawn for everyone, for the cyclist, or even for a biker and hiker. There's a service for generating maps ready for printing, too. However close they come, there are no easy tools to make a map for *me*, according to *my* preferences, showing things *I* need to plan my cycling.
> Yes, I'd like a map made, please. For cycling. Make the asphalt local roads thickest. Motorways? Skip them. Actually, draw them the same way as fences. And canals. Mark footpaths too, please. And information posts, and tourist information points. While we're at it, mark monuments in a different color. Oh, and don't forget about bike repair shops, public water sources and grocery stores. Make them red! Mountain springs? Well, only if there aren't any wells nearby...
– me, ordering a cycling map in my imagination
Thankfully, I'm a problem solver, and I know a solution must exist.
## Playing with crayons
If only I could draw the map on the computer like I drew fictional maps with crayons as a kid. A stroke there, a pictogram there, each takes two seconds total.
The maps I mentioned before use some sort of styles to generate the differently colored pictures out of the same data. The services are made for serving maps, rather than editing them, though. Tools like Mapnik require lots of set up, and even after it's done, editing the styles presumably isn't so comfortable.
What tools do the serious style designers use? Such a tool would put the ease of changing the looks at the forefront, even sacrificing speed…
*A while later*
Well, I found it. The project at the core of it is understaffed, and written in JavaScript, but works amazingly well. It's called TileMill, it's open source, and the maintainers are looking for fresh contributors!
Here's how to set up everything and render a map of your region.
## TileMill
As a first step, I recommend creating a separate user on your machine. The project uses NPM, and that wil leave lots of cruft in your home directory. In addition, we use the dreaded curl-to-shell pattern, which better be contained away from valuable data.
I'm using Fedora 37 as the base system. Fedora doesn't ship with nvm, and the required version of npm doesn't work, so let's fix this:
```
curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash
source ~/.bashrc
```
Now, install some needed packages:
```
sudo dnf install git wget osm2pgsql postgis postgresql-server postgresql-contrib vim
```
Then follow the installation instructions with some changes:
```
git clone https://github.com/tilemill-project/tilemill
cd tilemill
nvm install lts/carbon
nvm use v8.17.0
npm install # this will take a while
npm start
```
Ignore the rest of the installation instructions. Now we have an empty instance of TileMill running on http://localhost:20009. Sadly, have no idea how to change the ports :( Anyway, navigate there with your browser and start a new project. Uncheck "default data", we'll use our own.
You'll see an empty project. Now that we have the renderer running, let's change tracks and prepare some data.
## OpenStreetMap Data
You won't draw a map if you don't know anything about your area. Thankfully, collecting geographical data is what the OpenStreetMap project is best at. Don't be fooled, the map is only a side thing. It should really be called OpenStreetData.
Let's get the data of region that interests us from Geofabrik. I'll choose Münster for demonstration purposes: Germany is *dense* with all kinds of detailed data added by volunteers, and Münster in particular is famous for its bike-friendliness. Now download the .osm.pbf file:
```
https://download.geofabrik.de/europe/germany/nordrhein-westfalen/muenster-regbez-latest.osm.pbf
```
TileMill does not support loading it directly, but it supports something better:connecting directly to a geographic database. The cost of that is that we need to set up the database ourselves.
A while back, we installed the necessary packages: Postgresql with Postgis. Now it's time to configure them.
```
sudo postgresql-setup --initdb --unit postgresql
```
**CAUTION**: it's possible that I messed up the access controls here. An attacker on the same network might be able to get in your (data)base and kill your landmarks.
Edit the file `/var/lib/pgsql/data/pg_hba.conf
` to widen permissions. Afterwards, the relevant part should look more like this:
```
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
# IPv6 local connections:
host all all ::1/128 trust
```
Create the database "osm", to keep our geographical data.
```
systemctl restart postgresql
psql -U postgres -c "create database osm;"
psql -U postgres -d osm -c 'CREATE EXTENSION postgis;'
osm2pgsql -c -G -U postgres -d osm ./muenster-regbez-latest.osm.pbf # you can list additional files here!
```
This will take a moment, but if it succeeds, your data is now safely stored. Test your network access:
```
psql -h 127.0.0.1 -p 5432 -U postgres -d osm
```
If this succeeds, then TileMill will be able to access the database, too.
## My own map
Go back to the browser, and find the "layers" icon. It's shown on the picture:
Add a new layer, select PostGIS, and give it "dbname=osm host=localhost port=5432 user=postgres" in the "Connection" field. Write "highway" in "Class". Finally, enter the query in "Table or subquery":
```
(select * from planet_osm_line where highway!='') as lines
```
We're almost there. Now try this style:
```
Map {
background-color: #b8dee6;
}
#highway {
line-color: #808080;
line-width: 1.0;
}
```
Enter it in `map.mss
`, and press "Save" (or Ctrl+S). Suddenly…
What is that? Zoom in, please.
Now, this looks like the communication network in the region!
## Come on, paint my world
I'm not going to give you a hand-holding here, but I'll leave you with a couple useful tips.
-
TileMill's documentation is quite decent for educating you how to style things.
-
Remember that your database holds 3 types of objects: points, lines, and polygons. Those don't have to be the same as in OSM, and, in fact, I don't know how relations are represented, if at all.
-
You can filter objects using CSS styles, like this:
```
#highways[highway='path'][bicycle='permit'] { foo; }
```
- But dedicated layers are faster at filtering data:
```
(select * from planet_osm_line where highway='path' and bicycle='permit') as bikes
```
- Take a good look at your database with `
psql -h 127.0.0.1 -p 5432 -U postgres -d osm
`.
Tables:
```
osm=# select * from # I pressed tab here
geography_columns pg_toast. planet_osm_roads
geometry_columns planet_osm_line public.
information_schema. planet_osm_point spatial_ref_sys
pg_catalog. planet_osm_polygon
```
Tag values:
```
osm=# select distinct highway from planet_osm_line where highway!='';
highway
----------------
trunk
road
disused
footway
cycleway
services
secondary
traffic_island
tertiary
abandoned
```
Example data (caution, long lines):
```
select * from planet_osm_roads limit 5;
```
- Use more layers!
## Printing
This application is not perfect for printing. It doesn't have a built-in rose of winds, nor a scale, and you're stuck with the Web Mercator projection. QGIS is way better in that respect, but also creating styles in QGIS is a lot more painful.
In a pinch, however, it's sufficient, and exporting is easy, too. The export menu is on the top-right. Make sure to aim at 600pixels per 2.54cm, and to select a zoom level that makes things readable.
That's it! Have fun with your new set of crayons!
## Example
The picture in the first section is from a live demo I performed at the local hackspace. Here's the style for it:
```
Map {
background-color: #fff;
}
#lines {
line-color: #808080;
line-width: 0.0;
[waterway='stream'] {
line-color: #aaf;
line-width: 1.0;
}
[waterway='river'] {
line-color: #aaf;
line-width: 2.0;
}
[highway='path'] {
[bicycle='permit'],
[bicycle='yes'],
[bicycle='designated'],
[bicycle='official'],
[bicycle='permissive'],
[bicycle='use_sidepath'] {
line-color: #444;
line-width: 1.0;
}
}
//[bicycle='permit'],
//[bicycle='yes'],
[bicycle='designated'],
[bicycle='official'],
//[bicycle='permissive'],
[bicycle='use_sidepath'],
[highway='cycleway']{
line-color: #444;
line-width: 2.0;
}
[highway='track'][surface='asphalt'][bicycle!='no'],
[highway='service'],
[highway='unclassified'],
[highway='residential'],
[highway='tertiary'] {
line-width: 1.0;
}
}
#lines {
line-color: #808080;
line-width: 0.0;
[surface='asphalt'] {
//[bicycle='permit'],
//[bicycle='yes'],
[bicycle='designated'],
[bicycle='official'],
//[bicycle='permissive'],
[bicycle='use_sidepath'],
[highway='cycleway']{
line-width: 2.0;
}
}
}
#roads {
line-color: #808080;
line-width: 0.0;/*
[highway='secondary'] {
line-width: 2.0;
}
*/
[highway='primary'],
[highway='trunk'],
[highway='motorway'] {
line-color: #d8c1b1;
line-width: 2.0;
}
}
#toiletten {
[amenity='toilets'] {
marker-width: 6;
marker-fill: #070;
marker-line-width: 0;
}
[amenity='drinking_water'] {
marker-width: 6;
marker-fill: #700;
marker-line-width: 0;
}
}
#rathaus {
[amenity='townhall'] {
polygon-fill: #f00
}
}
#towns {
[place='village'] {
text-name: [name];
text-fill: #f00;
text-face-name: 'Droid Sans Regular';
}
[place='town'],
[place='city']{
text-name: [name];
text-fill: #f00;
text-face-name: 'Droid Sans Regular';
text-size: 20;
}
}
```