Using folium to visualize the traveling salesman path

folium_logo.png

The purpose of this article is to use folium to visualize in a map, the itinerary of the salesman that I obtained in a previous article.

Folium builds on the data wrangling strengths of the Python ecosystem and the mapping strengths of the Leaflet.js library. Manipulate your data in Python, then visualize it in a Leaflet map via Folium.

I heard about folium while I was going through one of my certifications. I was amazed by the capabilities of the library. If you check the pypi package page you'll find that it is a very recent project (released on 16th June, 2025… half a year ago!), still in its version 0.20.0; but it is very usable and somehow mature package.

We started with a dictionary containing the names of the cities as keys and the geographic location as values:

city_coords = {
    "São Paulo":       (-23.5505, -46.6333),
    "Rio de Janeiro":  (-22.9068, -43.1729),
    "Brasília":        (-15.8267, -47.9218),
    "Salvador":        (-12.9777, -38.5016),
    "Fortaleza":       (-3.7319,  -38.5267),
    "Belo Horizonte":  (-19.9167, -43.9345),
    "Manaus":          (-3.1190,  -60.0217),
    "Curitiba":        (-25.4284, -49.2733),
    "Recife":          (-8.0476,  -34.8770),
    "Porto Alegre":    (-30.0346, -51.2177),
    "Goiânia":         (-16.6786, -49.2550),
    "Belém":           (-1.4558,  -48.5044),
    "Guarulhos":       (-23.4548, -46.5333),
    "Campinas":        (-22.9056, -47.0608),
    "São Luís":        (-2.5307,  -44.3068),
    "São Gonçalo":     (-22.8222, -42.9625),
    "Maceió":          (-9.6658,  -35.7350),
    "Duque de Caxias": (-22.7856, -43.3115),
    "Nova Iguaçu":     (-22.7595, -43.4528),
    "Natal":           (-5.7950,  -35.2094)
}

We can now use folium to visualize one of this cities in a map

from PIL import Image

m = folium.Map(
    location=city_coords["Manaus"],
    zoom_start=12
    )

The map cannot be saved directly as a PNG file, but we can build a function to help us with this task (I'll use the solution posted here to save a PNG version of the map)

import io
from PIL import Image

def save_map(map, filename):
    img_data = map._to_png(5)
    img = Image.open(io.BytesIO(img_data))
    img.save(filename)
    print(f"Map saved as PNG with name: {filename}")
save_map(m, "../images/folium-manaus.png")
Map saved as PNG with name: ../images/folium-manaus.png

folium-manaus.png

Returning to our problem. By inspection, we note that the middle value of the city coordinates are approximately [-15,-42]. So, let's relocate the map to visualize the whole set of cities.

m = folium.Map(location=[-15,-42], zoom_start=5)
m
save_map(m, "folium-brazil.png")

folium-brazil.png

Let me recycle some of the code from the traveling salesman problem article, but I'll modify the function defining_path to return both the itinerary and visited_cities

from geopy.distance import geodesic
from itertools import combinations
from random import sample, choice
import numpy as np

distances = list()
cities = list(city_coords.keys())

for city_a, city_b in combinations(cities, 2):
    coord_a, coord_b = city_coords[city_a], city_coords[city_b]
    km = int(round(geodesic(coord_a, coord_b).kilometers))
    distances.append((city_a, city_b, km))

dt = np.dtype([("city_start", "U15"), ("city_end", "U15"), ("distance", int)])
data_set = np.array(distances, dtype=dt)

def random_start_city(cities):
    """Returns the random city to start the travel"""
    return choice(cities)

def closest_city(routes, ind=0):
    """Sort the list by distance and return shortest distance route"""
    route = sorted(routes, key=lambda dist: dist[2], reverse=True).pop(-1-ind)
    return route

def defining_path(city=None):
    itinerary = list()
    starting_city = random_start_city(cities) if city==None else city
    print("Starting City:", starting_city)
    visited_cities = [starting_city]
    for i in range(len(cities)-1):
        ind = 0
        search = True
        while search:
            path = closest_city(data_set[[starting_city in data for data in data_set]], ind)
            city_a = path[0] if starting_city == path[0] else path[1]
            city_b = path[0] if starting_city != path[0] else path[1]
            if city_b in visited_cities:
                ind += 1
                continue
            else:
                itinerary.append(path)
                visited_cities.append(city_b)
                starting_city = city_b
                search = not search
    return itinerary, visited_cities

Let's try it!

paths = defining_path()
for num, path in enumerate(paths):
    print(num+1, path)
Starting City: Manaus
1 ('Manaus', 'Belém', 1294)
2 ('Belém', 'São Luís', 482)
3 ('Fortaleza', 'São Luís', 656)
4 ('Fortaleza', 'Natal', 433)
5 ('Recife', 'Natal', 252)
6 ('Recife', 'Maceió', 202)
7 ('Salvador', 'Maceió', 475)
8 ('Salvador', 'Belo Horizonte', 962)
9 ('Belo Horizonte', 'Nova Iguaçu', 319)
10 ('Duque de Caxias', 'Nova Iguaçu', 15)
11 ('Rio de Janeiro', 'Duque de Caxias', 20)
12 ('Rio de Janeiro', 'São Gonçalo', 24)
13 ('Guarulhos', 'São Gonçalo', 372)
14 ('São Paulo', 'Guarulhos', 15)
15 ('São Paulo', 'Campinas', 84)
16 ('Curitiba', 'Campinas', 359)
17 ('Curitiba', 'Porto Alegre', 545)
18 ('Porto Alegre', 'Goiânia', 1493)
19 ('Brasília', 'Goiânia', 171)

While the list of visited cities is

for city in visited:
    print(city)
Manaus
Belém
São Luís
Fortaleza
Natal
Recife
Maceió
Salvador
Belo Horizonte
Nova Iguaçu
Duque de Caxias
Rio de Janeiro
São Gonçalo
Guarulhos
São Paulo
Campinas
Curitiba
Porto Alegre
Goiânia
Brasília

Next, we build a list contain the coordinates of the cities in our visited_cities list:

path_coord = [city_coords[city] for city in visited]
print(path_coord)
[(-3.119, -60.0217), (-1.4558, -48.5044), (-2.5307, -44.3068), (-3.7319, -38.5267), (-5.795, -35.2094), (-8.0476, -34.877), (-9.6658, -35.735), (-12.9777, -38.5016), (-19.9167, -43.9345), (-22.7595, -43.4528), (-22.7856, -43.3115), (-22.9068, -43.1729), (-22.8222, -42.9625), (-23.4548, -46.5333), (-23.5505, -46.6333), (-22.9056, -47.0608), (-25.4284, -49.2733), (-30.0346, -51.2177), (-16.6786, -49.255), (-15.8267, -47.9218)]

And use it to visualize the "path" the salesman should follow

m = folium.Map(location=[-15,-42], zoom_start=5)
folium.PolyLine(path_coord, tooltip="Coast").add_to(m)
m
save_map(m, "folium-itinerary.png")

folium-itinerary.png

We could use our function definingpath to find the itinerary path starting from a given city, for example São Paulo:

m = folium.Map(location=[-15,-42], zoom_start=5)
paths, visited = defining_path("São Paulo")
path_coord = [city_coords[city] for city in visited]
folium.PolyLine(path_coord, tooltip="Coast").add_to(m)
m

folium-itinerary-sp.png

Author: Oscar Castillo-Felisola

Created: 2026-04-02 Thu 14:59