Creating your own custom maps

The prettymaps Architecture

At least from the outside, prettymaps seems little more than a small blob of well-done glue logic that connects many other Python libraries just for the purpose of drawing maps based on OSM data. The most important of these libraries are OSMnx, Matplotlib, Shapely, and vsketch. Any of these tools deserves a tutorial of its own, but here we only need to know the main features and role of each.

OSMnx [6] can download any combination of layers and other data from the OSM database, allowing the user to select them by place name, placement inside a polygon, distance from a given address, or by geographical coordinates. Once it has fetched the data, OSMnx can also save it to local files in several formats or use it to calculate street orientation, inclination, and many other characteristics. After OSMnx, prettymaps loads functions from Shapely [7] and vsketch [8] to prepare the raw drawing by analyzing and modifying, as requested, any geometric object in the data. Finally, Matplotlib [9] creates and saves the actual image in several formats, with the zoom levels, captions, and visual style specified by the user.

Last but not least, to work without problems, prettymaps also needs IPython [10], the Python command shell for interactive computing in Python and other languages.

Installing All the Pieces

Configuring a Linux system to use prettymaps is not really difficult. However, it took me about 15 minutes, much longer than anything else I have installed on my Ubuntu box in the past year, and I have to say that the installation is pretty hard to document as one linear procedure. The reason is simply that prettymaps needs lots of specific versions of many other libraries, which may or may not be already installed on your system. Above all, depending on your distribution, those packages may not be available as native packages but only through unpredictable combinations of Flatpak, snap, pip, or any of the other systems that have made Linux life much more complicated than it was 10 years ago. The good news is that you almost surely won't have to compile anything from source, and that the whole process should not take more than 15 minutes. For example, on my Ubuntu 21.04 computer, I had to type commands such as:

sudo apt-get install -y ipython3
pip install git+https://github.com/abey79/vsketch#egg=vsketch
sudo pip install prettymaps

to install IPython, vsketch, and prettymaps itself, plus similar commands for the other libraries. In practice, the safest and most portable advice and procedure to get prettymaps up and running on a generic Linux box seems to be this:

  1. 1 Install prettymaps as above, plus any dependencies it may demand right away.
  2. 2 Copy and paste any of the examples from the website into a plain text file called, for example, prettymap-test.py.
  3. 3 Open a terminal and type python3 prettymap-test.py.
  4. 4 If there are any errors, figure out from them which library is still missing. Type How to install <library> on <Linux distribution> in a search engine and follow the instructions.
  5. 5 Repeat the previous step until prettymaps actually generates a map.

A Basic prettymaps Script

As already mentioned, once everything is installed, all you have to do to generate a map with prettymaps is type python3, followed by the prettymaps script file at a command prompt. Figure 1 shows the circular map of Rome that I got when I did that with the code in Listing 1, which comes from one of the examples provided by the developer. I only changed the initial coordinates – and omitted all comments because I am going to explain the code here.

Figure 1: A map of Rome in circular format is easily derived from the prettymaps examples.

Listing 1

Circular Map of Rome

01
02 from IPython import get_ipython
03 ipython = get_ipython()
04
05 if '__IPYTHON__' in globals():
06 ipython.magic('load_ext autoreload')
07 ipython.magic('autoreload 2')
08
09 import sys; sys.path.append('../')
10
11 from prettymaps import *
12 import vsketch
13 import osmnx as ox
14 import matplotlib.font_manager as fm
15 from matplotlib import pyplot as plt
16 from descartes import PolygonPatch
17 from shapely.geometry import *
18 from shapely.affinity import *
19 from shapely.ops import unary_union
20
21 fig, ax = plt.subplots(figsize = (12, 12), constrained_layout = True)
22
23 layers = plot(
24 '41.894988965651585, 12.491488590684028', radius = 10000,
25 ax = ax,
26 layers = {
27 'perimeter': {},
28 'streets': {
29 'custom_filter': '["highway"~"motorway|trunk|primary|secondary|tertiary|residential|service|unclassified|pedestrian|footway"]',
30 'width': {
31 'motorway': 5,
32 'trunk': 5,
33 'primary': 4.5,
34 'secondary': 4,
35 'tertiary': 3.5,
36 'residential': 3,
37 'service': 2,
38 'unclassified': 2,
39 'pedestrian': 2,
40 'footway': 1,
41 }
42 },
43 'building': {'tags': {'building': True, 'landuse': 'construction'}, 'union': False},
44 'water': {'tags': {'natural': ['water', 'bay']}},
45 'green': {'tags': {'landuse': 'grass', 'natural': ['island', 'wood'], 'leisure': 'park'}},
46 'forest': {'tags': {'landuse': 'forest'}},
47 'parking': {'tags': {'amenity': 'parking', 'highway': 'pedestrian', 'man_made': 'pier'}}
48 },
49 drawing_kwargs = {
50 'background': {'fc': '#F2F4CB', 'ec': '#dadbc1', 'hatch': 'ooo...', 'zorder': -1},
51 'perimeter': {'fc': '#F2F4CB', 'ec': '#dadbc1', 'lw': 0, 'hatch': 'ooo...',  'zorder': 0},
52 'green': {'fc': '#D0F1BF', 'ec': '#2F3737', 'lw': 1, 'zorder': 1},
53 'forest': {'fc': '#64B96A', 'ec': '#2F3737', 'lw': 1, 'zorder': 1},
54 'water': {'fc': '#a1e3ff', 'ec': '#2F3737', 'hatch': 'ooo...', 'hatch_c': '#85c9e6', 'lw': 1, 'zorder': 2},
55 'parking': {'fc': '#F2F4CB', 'ec': '#2F3737', 'lw': 1, 'zorder': 3},
56 'streets': {'fc': '#2F3737', 'ec': '#475657', 'alpha': 1, 'lw': 0, 'zorder': 3},
57 'building': {'palette': ['#FFC857', '#E9724C', '#C5283D'], 'ec': '#2F3737', 'lw': .5, 'zorder': 4},
58 },
59
60 osm_credit = {'color': '#2F3737'}
61 )
62
63 plt.savefig('rome-circular.png')
64 plt.savefig('rome-circular.svg')

Listing 1 is quite verbose but much less complicated than it may seem at first sight. The first 20 lines just load all the libraries that prettymaps needs. Line 21 defines the Matplotlib figure that must be created, setting its axes and size.

There are several more options that you may optionally set in this initial part of the script, starting with a title for the whole figure and a rotation and offset, if, for whatever reason, you would like the map rotated at any angle or not at the center of the image. Also important is the "backup" option that allows you to load the results of a previous run of prettymaps to save time. For the corresponding details, please see the prettymaps website [3].

Possibly the core instruction of the whole script, line 24 describes what area of the whole Earth should be represented in the map. In this case, it is a circle with a 10km radius, centered on the latitude and longitude that approximately correspond to the center of Rome.

Finding the coordinates of whatever point on Earth you would like to map is easy. In OpenStreetMap, double-click on the point of interest and select the Share icon from the right toolbar to open a form. Figure 2 shows the form with the coordinates to copy into the prettymaps file. On Google Maps, select the point, right-click on it to open a pop-up window, and copy the coordinates from there (Figure 3).

Figure 2: OpenStreetMap's Share dialog also lists the coordinates of the city center used in Listing 1.
Figure 3: The same coordinates in Figure 2 found with Google Maps.

Important note: The layers one may download with the rest of Listing 1 are so many, and with so many options, that describing them all might fill a book. Therefore, I only highlight the most important features and common characteristics of the corresponding function and suggest you ask for more details either on the prettymaps GitHub page [3] or the corresponding subreddit [11].

After setting the position, shape, and extension of the map, lines 26 to 48 describe which layers should be downloaded from the database and how to select their sub-components if needed. Examples of this capability are the custom_filter of line 29 and the options for buildings, parking, and green areas later on. When applicable, for example, with streets, you may also specify the line width in line 30 that corresponds to any different type of "street."

Lines 49 to 58 specify how to draw and paint each layer, as it is evident when confronting all the sub-parameters of the drawing_kwargs function with the layers listed in the previous block of code. Here, each layer previously called is given a foreground (fc) and edge (ec) color, as well as line width (lw), hatching (i.e., fixed patterns instead of solid colors), alpha transparency, and, when needed, a palette of several colors (see the building parameters in line 57). All these parameters are user-definable, and they are how you give your map your preferred style.

From this point of view, the most confusing part may be deciding which colors to use for each feature. As far as colors are concerned, prettymaps follows the same standard adopted by countless other programs, on and off the web. Each color is described as a combination of three fundamental colors (red, green, and blue) and the intensity of each of them is represented with a two-digit hexadecimal number that can go from 00 to FF. In this format, the string FF0000 means red, 00FF00 is green, and 0000FF is blue. To see the corresponding string for any other color to use it when coloring your own maps, you can use the W3Schools online color picker [12] shown in Figure 4.

Figure 4: Finding the color codes for your maps is easy with tools such as the W3Schools color picker.

Another very important parameter in prettymaps scripts is the so-called zorder, the order in which each layer must be drawn and placed with respect to the others. That's why the background has the lowest zorder (-1 in line 50): By definition, the background must be drawn first and then be covered by the others, one at a time.

The final instructions of Listing 1 specify colors for the rights and proper credit that OSM deserves (line 60) and the name and format of the file or files in which the map should be saved (lines 63 and 64).

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News