Using QGIS to create a custom map
11 minutes read | 2311 words by Ruben BerenguelI have ve always loved maps. I guess it goes well with liking drawing and maths, there’s a deep connection (the pun is intended) between the subjects. As a map lover, when we decided to relocate to a somewhat more countryside town, I wanted better-looking maps to wander around the area. I checked government-issued maps, but they were either too large (scale 1:25000) or didn’t show the area I was interested (for the 1:10000 maps.) Thus I did what any other nerd would do: roll my own map as I wanted it.
The end result looks like this (downscaled to 20%)
The real map is 1.4 × 1.2 metres
Getting the data
My first idea was to follow the steps of Dominik Schwarz in his quest for printing a huge map of the world, and harvest images from Google Maps and get away with it. But one of the fundamental parts of my map was supposed to be having all walking routes visible and available. Google Maps won’t show that, or at least not everything. Specially among vines or forests. What’s the point of living close to the Pantà de Foix (the Foix Dam) and not being able to walk there?
Hence, I turned to our trusty government, and in particular to the Catalan Cartographic and Geologic Institute (from now on the ICC, which is the Catalan acronym.) They have a powerful online map application, with good quality satellite imagery, elevation data, toponymy… All is available online. And a free download. You only need to register, and seem to be limited to one download at a time. No big issue, although I needed to get 16 satellite tiles.
There is a huge amount of data available for download. Since my knowledge of technical map stuff ended at knowing that the technical term for what I wanted to do is GIS , I wasn’t really sure what files I needed and what I could do with them on a Mac without having to pay for a dedicated app.
The first step was finding a free app that could “do maps.” After some Googling it was pretty clear it had to be QGIS (formerly known as Quantum GIS) which is free, open-source and available cross-platform. It was quite straightforward to install on Mac, just needing some extra libraries to work with the satellite image files I was using. Once I had it, the real “nightmare” started, where I had to decide what kind of files I needed and what they were good for. This is where ICC helped, since they have documentation for all their file types, not only explaining what they have but also what they are useful for.
For a starter I downloaded all satellite imagery with a scale of 1:2500, generated on 2014. Recent enough. This was the easy one:
- Download each file
- Unzip it
- In
QGIS
,Layer→Add
Layer→Add Raster Layer
(orShift-Cmd-R
)
Since the files are already geolocated data (the format is SID
, which is why I
needed a special plugin to handle it) in just a few minutes I had a satellite
view of the area, automatically placed and multi-scale. Not bad for a few
minutes of downloading and goofing around.
Then the real work started: I wanted street names, road names and features. How? I’m pretty sure there are many ways, but the one I found most straightforward was:
- Download Topography-base maps, with a scale of 1:2500 in
SHP
(Shapefile) format - In
QGIS
,Layer→Add Layer→Add Vector Layer
(or Shift-Cmd-V) - Select full folder, Arc/Info binary format and browse to it
- Select the files you really want
ICC provides a lot of files for each map tile at that resolution. Lots of stuff I didn’t really want. I chose only toponymy, elevation lines, hydrography and roads. Why?
- I needed toponymy layers to set labels to features
- Elevation looks cool (even if I disabled it for the final generated map)
- With hydrography I could change the colour from the satellite imagery to a more bluish tone in water areas (since the Foix dam is quite greenish)
- Roads was the main reason I was doing this
Adding some text
Once I had everything in place I wondered: Where are the labels?
- Double-click a toponymy layer
Labels→Label this layer with…
Here some of the huge power of QGIS started to appear. Most “variables” in the
map can be controlled via either fields in the data layer OR functions applied
to these fields. So, I could easily label by a data column, like TEXT_TOP
(which had the common name of the feature, like Pantà de Foix
.) But I also
could set the size of the font to the one defined in the datafile. And I could
even choose the font according to the feature type (or in my case, the font
defined in the data file: the data provided by the ICC had font size and font
family for the features) with a function.
CASE
WHEN "FONTNAME" LIKE 'Times New Roman' THEN 'Georgia'
WHEN "FONTNAME" LIKE'Arial' THEN 'Futura'
WHEN "FONTNAME" LIKE 'Arial Narrow' THEN 'Futura'
WHEN "FONTNAME" LIKE 'Courier New' THEN 'Inconsolata'
END
The language seems to be QGIS specific, but has SQL-like LIKE
. Single quotes
denote strings, double quotes denote fields, just like in SQL. Important to
remember, since it’s very easy to mix one with the other without realising. I
set all labels to show, even colliding labels. I didn’t want to miss a castle or
church because it was too close to a large street. I’d rather have overlapping
labels in this case.
Then I decided I wanted something more: I wanted icons to show what each feature was. Icons like what you have in a normal map, where churches are indicated by a cross and castles by a tower. The layer I was working with (the toponymy layer) only had type codes for its features, so I couldn’t really tell what a 23 was. Church? Castle? Farm?
Symbols…? Labels…?
To do this, I needed to download an auxiliary file (which the ICC already told
me about when I clicked ShapeFile
as type of download) having the list of code
together with human-readable definitions. It is loaded as a single-file
ShapeFile
layer, but has no “visible data.” Instead you need some magic.
- Open a toponymy layer
- Select
Joins
- Add a join layer with
codis_topo
(the source, in this case the layer I just added) and selecting as join field the type code (in this case,CODI_TOP
) - I disabled caching and added a custom name to the newly created column, the join
With this at least I’d know, since the type was now human-readable. I still didn’t know how to add icons, but I could edit the name and add a letter to denote what it is (or I could even add some text.) The labelling function is then
CASE
WHEN "coditopo_03ca_CONCEPTE" LIKE '%capella%' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%monestir%' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%monestir%' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%tedral%' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Església' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Convent' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Temple' THEN CONCAT( '† ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Arc' THEN CONCAT( 'fl ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%ducte' THEN CONCAT( 'fl ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Font, bullidor, deu' THEN CONCAT( 'ƒ ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%toll,%' THEN CONCAT( '≈ ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%astell,%' THEN CONCAT( 'Ħ ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Talaia, torre de defensa, torre de guaita' THEN CONCAT( 'Ħ ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%rica (en%' THEN CONCAT( 'Ħ ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Muralla' THEN CONCAT( '= ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'GR (Sender de Gran Recorregut)' THEN CONCAT( 'GR: ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'PR (Sender de Petit Recorregut)' THEN CONCAT( 'PR: ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Pou%' THEN CONCAT( 'ů ', "TEXT_TOP" )
WHEN "coditopo_03ca_CONCEPTE" LIKE '%pont%' THEN CONCAT( 'π ', "TEXT_TOP" )
ELSE "TEXT_TOP"
END
This, basically adds a weird symbol to the text, so, for instance, bridges have
a π
symbol in front. Not great, but I actually printed it like this for a
starter and it looks pretty decent. Here you can see a zoom in and the general
view:
In the zoom you can see the glyphs for a fountain (Font d’Horta and Font de Mas Carlús), a church (Sant Pere) an aqueduct (de les Aigües de Vilanova i la Geltrú) and a bridge (de la font d’Horta.) This was nice but non-optimal. I wanted symbols! So, how do you get symbols if you have a toponymy layer with lines, not feature points?
Symbols
First, duplicate the layers holding the labels. It can be done without duplicating, but it introduces a layer of complication for the human… So, if your computer doesn’t mind the extra layers, it is easier for you as human to have a separate layer for the labels and a layer for the icons.
Once you have duplicated the layer, change its style to rule-based, and add marks following rules with the add sign. For instance, this is the rule I use for Castell (castle):
CASE
WHEN "coditopo_03ca_CONCEPTE" LIKE 'Castell,%' THEN 1
ELSE 0
END
with SVG marker svg/fortress2.svg
NOTE: if you are using only one condition, for rule-based markers you can just keep it at
"coditopo_03ca_CONCEPTE" LIKE 'Castell,%'
since it will just return 0 or 1 as needed. You’ll only need CASE when you have several types going to the same marker. Since I just copy-pasted most of my code I didn’t mind the extra cruft. Thanks to Nathan Woodrow, one of QGIS' developers for pointing it out on twitter.
To add this as image you need to select Marker
, then SVG marker
. I found
very nice SVG icons here, so I use this opportunity
as credit (since the printed map is just for my personal use.) And indeed, you
need to add as many rules as icons you have. That’s life. A GIS professional
probably knows other ways, I’m just a newbie. Once you have all the rules you
want, you can copy-paste them in all the layers. Actually, I think all this map
business would have been easier merging all vector layers before doing anything
else, but since I didn’t do it from the beginning it was hard to do it now. You
can find how to merge vector layers in this StackOverflow
question.
After adding markers you’ll need to tweak them. I did one part in SVG (adding stroke to the shapes and changing colours directy in the file so I didn’t need to bother in QGIS) and some others relating to placement (middle point and then changing size and baseline of the icon.) I also did some changes editing the file directly in Emacs, since QGIS files are just XML and replacing is much faster without clicking anything, once you know how the fields are named. The end result? Here you can see the same area as above with icons.
Nice, don’t you think?
So, how do you print it?
This is actually very very easy. To print you need a New print composer
from
the Project
menu. This is a set of rules to print some map. So, you choose the
paper size and then add pieces to the layout. The basics are a map, a scale and
some text, or at least this is the bare minimum I wanted.
Once you have the map area you can change scale, drag around and change many things, it’s awesome. I decided to use 1:10000 since fitting the whole map in the map area I chose was 1:9556 and I’d rather have a round number and lose some map around the borders. Ready to export to PDF and bring to a printing place. You can also generate a JPG file, but beware of trying to open a 176 Mb with an iPad (even if it is an Air.) It won’t zoom. On the other hand, for my Mac it was much easier to open and zoom/navigate a huge JPG rather than a huge PDF file. Also keep in mind large maps take a long time to export: if you are making tests disable as many layers as you can really manage.
Since I wanted a portable version of the map to have on my iPad Air, I decided to split it using ImageMagick in 9 tiles I could easily zoom in any app. IM has the nice “crop” feature that can generate tiles, like:
convert -crop 4x3 MEGAMAP.jpg megamap/tiles%03d.jpg
The problem is that this takes way, way longer than expected, because the image
is too big. The fastest way is instead to use the stream
command to generate
raw RGB data and convert that to JPG. So, repeating 9 times stuff like:
stream -extract 5566x3937+11132+7874 MEGAMAP.jpg t9.rgb
convert -depth 8 -size 5566x3937 rgb:t9.rgb t9.jpeg
Since I only needed to do it this time I didn’t bother to code it as a bash for
loop or anything. Whereas convert
was taking more than 12 hours to do
anything, stream took 3 minutes, counting the time I needed to compute the pixel
shifts and the conversions. And probably even the time it took me to create a
index.html
with the pieces to zoom.
What’s the takeaway of this post? Doing maps is fun, and using QGIS is not that hard as it may seem. And yes, I have an awesome big map now.