13. Demo : Creating a Map#
To apply the concepts we have covered, we will develop a program that reads and processes a data file, then uses a library to display the data visually. For this exercise, we will use public data from Line 16 of the Grand Paris Express.
As we did with the secret number game, we will build our project incrementally. It is essential to break the project down into manageable functions that we will implement and test step by step.
13.1. Overall Analysis#
The first step is to examine our input data and the library we will use to create the map. Once we understand these components, we can design the pathway to connect the two.
13.1.1. Input File#
We have a file named gpe16.csv
. This is a CSV file, a spreadsheet-like text file where each row represents an object and each column represents an attribute of that object. For example, in our case, each row in the file represents a station, with columns containing information about the station’s latitude, longitude, identifier, and label. The values in the columns are separated by commas, and the file (in this case) does not include a header. Below are the first few lines of our file.
2.2021300102601757, 48.8724809534261, 74, "Rueil - Suresnes 'Mont-Valérien'"
2.237688709843252, 48.890854792161804, 77, "La Défense"
2.2720135095825587, 48.914987386561855, 79, "Bois-Colombes"
2.3664686037937646, 48.76021877294793, 81, "M.I.N. Porte de Thiais"
2.161383743060809, 48.72993425338691, 82, "CEA Saint-Aubin"
2.200958583207987, 48.715364195739205, 83, "Palaiseau"
2.462741944257064, 48.94553031004862, 87, "Le Blanc-Mesnil"
2.258466492245461, 48.725144279073305, 89, "Massy - Palaiseau"
2.173294535262749, 48.71215046782793, 90, "Orsay - Gif"
2.362272557455038, 48.728004709002676, 95, "Aéroport d'Orly"
...
Using the file manipulation techniques we’ve learned, reading and analyzing this data should not pose a challenge. The simplest approach is to create a dictionary for each station to group the attributes together. The keys will be "lat"
, "lon"
, "id"
and "label"
, corresponding to the station’s latitude, longitude, identifier, and label, respectively. All the station dictionaries will then be stored in a list.
13.1.2. The Folium Library#
For visualization, we will use the Folium library. Folium allows us to generate interactive maps with markers that can be displayed in a web browser as an HTML file. The library’s documentation is available online, and by reviewing it, we can identify the features we need.
Notably, the Folium library can be easily installed using pip
. By typing the command pip install folium
in a terminal, you can install the library. Once installed, you can import it into your project with the line import folium
. The documentation highlights three functions that will be particularly useful for our project:
The function folium.Map
creates and returns a map object. It takes two primary arguments:
A tuple of two values representing the longitude and latitude (in that order).
An optional
zoom_start
parameter, which is an integer that controls the initial zoom level of the map. For example, to create a map centered on Paris, you could use the following code:
paris = folium.Map((48.85381285231611, 2.3449980166801163), zoom_start=13)
The function folium.Marker
creates a marker that can be placed on the map. It accepts two named parameters:
location: A tuple of latitude and longitude coordinates.
popup: A string that will be displayed when the marker is clicked. For example, to create a marker at the center of the map, you can write
center = folium.Marker(
location=(lat, lon),
popup="center of Paris",
)
The marker object has a method (see Method) called add_to
that allows you to add it to a map.
center.add_to(paris)
Once your map is ready, you can save it to an HTML file using the save
method. This method takes the path of the output file as a parameter.
paris.save("test.html")
13.1.3. Roadmap#
To progress systematically, we can break the project down into the following steps.
line_to_tuple
Function: Create a function that takes a line from the CSV file as a parameter and converts it into a list representing a station.read_stations
Function: Create a function that takes the path of a CSV file as a parameter, opens the file, converts each line into a tuple, and returns a list of tuple.add_markers
Function: Create a function that takes a map object and a list of tuple as parameters, and adds markers for all stations on the map.main
Function: Create a main function that brings all the code together and runs the program.
This structured approach will allow us to tackle each part of the project in a manageable way, testing as we go to ensure everything works as expected before moving on to the next step.
13.2. Implementation#
13.2.1. Converting a line into tuple#
Starting with a line we want to convert it into a tuple. We will start by isolating a single line of data from the CSV file. For example:
line = '''2.2021300102601757, 48.8724809534261, 74, "Rueil - Suresnes 'Mont-Valérien"'''
The data in this line is separated by commas, so we can use the split
method to divide the string into individual components. The split
method will break the string at each comma and return a list of strings.
parts = line.split(',')
After splitting, parts would look like this:
['2.2021300102601757', ' 48.8724809534261', ' 74', ' "Rueil - Suresnes \'Mont-Valérien"']
We convert the latitude and longitude to float
, and the ID to int
, because they represent numeric data. The label might have extra spaces or quotation marks that need to be cleaned up. We’ll strip any leading or trailing whitespace and remove the surrounding quotes from the label.
lat = float(parts[0])
lon = float(parts[1])
station_id = int(parts[2])
label = parts[3].strip().strip('"')
Now that we have clean and properly-typed data, we can create the tuple.
def line_to_tuple(line):
parts = line.split(",")
station = (
float(parts[1]),
float(parts[0]),
int(parts[2]),
parts[3].strip().strip('"')
)
return station
line_to_tuple('''2.2021300102601757, 48.8724809534261, 74, "Rueil - Suresnes 'Mont-Valérien'"''')
(48.8724809534261, 2.2021300102601757, 74, "Rueil - Suresnes 'Mont-Valérien'")
13.2.2. Reading all stations#
The function read_stations(data_file)
begins by initializing an empty list named stations. This list will serve as a container to accumulate the data that we extract from each line of the file. We then read all lines of file with a classical approach. The encoding=”utf8” argument ensures that the file is read with the UTF-8 character encoding, which is common for text files.
def read_stations(data_file):
stations = []
file = None
try :
file = open(data_file, 'r', encoding="utf8")
for line in file:
stations.append( line_to_tuple(line) )
except FileNotFoundError:
print("The file does not exist.")
except IOError:
print("Error opening or reading the file.")
finally:
if file:
file.close()
return stations
stations = read_stations("gpe16.csv")
print(len(stations))
print(stations[0])
69
(48.8724809534261, 2.2021300102601757, 74, "Rueil - Suresnes 'Mont-Valérien'")
13.2.3. Adding the markers#
The function for adding markers is not very complex. It takes a map that has been created and a list of stations (in the form of dictionaries) as parameters. We simply iterate over the list of stations to add the markers. This function does not return anything because it modifies the map. Here is a proposed implementation.
Note: map
is a built-in function. We recommend using a different variable name.
import folium
def add_markers(m, stations):
for s in stations :
marker = folium.Marker(
location=(s[0], s[1]),
popup=f'Station #{s[2]} : {s[3]}'
)
marker.add_to(m)
m = folium.Map((48.85381285231611, 2.3449980166801163), zoom_start=10)
add_markers(m, stations)
m
13.2.4. Every thing together#
Now we can regroup every code in a clean main
function and add the save instruction. It’s the entry point of the program.
If it looks quite easy, it’s because we have break the problem into simple pieces. It’s mandatory.
import folium
def line_to_tuple(line):
parts = line.split(",")
station = (
float(parts[1]),
float(parts[0]),
int(parts[2]),
parts[3].strip().strip('"')
)
return station
def read_stations(data_file):
stations = []
file = None
try :
file = open(data_file, 'r', encoding="utf8")
for line in file:
stations.append( line_to_tuple(line) )
except FileNotFoundError:
print("The file does not exist.")
except IOError:
print("Error opening or reading the file.")
finally:
if file:
file.close()
return stations
def add_markers(m, stations):
for s in stations :
marker = folium.Marker(
location=(s[0], s[1]),
popup=f'Station #{s[2]} : {s[3]}'
)
marker.add_to(m)
def main():
stations = read_stations("gpe16.csv")
m = folium.Map((48.85381285231611, 2.3449980166801163), zoom_start=13)
add_markers(m, stations)
m.save("gpe.html")
main()