Skip to main content

1. Your First Map

CustomTkinter window with search bar and TkinterMapView map

TkinterMapView​

TkinterMapView is a map widget for tkinter and CustomTkinter. It loads the map from a tile server, which is OpenStreetMap by default, and you can move and zoom it with the mouse. On top of the map you can show markers, paths and polygons, which are explained in the following tutorials.

TkinterMapView is installed with pip:

pip install tkintermapview

The tutorials need version 1.32 or newer. If you already have an older version, update it with pip install --upgrade tkintermapview. The map tiles are loaded from the internet, so the app needs an internet connection.

Map in a CTk window​

The map is created like any other widget, with the window or a frame as master. To fill the space it gets, the row and column need a weight and the map needs sticky="nsew":

import customtkinter
import tkintermapview

app = customtkinter.CTk()
app.geometry("800x560")
app.grid_columnconfigure(0, weight=1)
app.grid_rowconfigure(0, weight=1)

map_widget = tkintermapview.TkinterMapView(app, corner_radius=0)
map_widget.grid(row=0, column=0, sticky="nsew")

app.mainloop()

With every request to the tile server and to the address search, TkinterMapView sends the name of your script as User-Agent, like first_map (TkinterMapView/1.32), because OpenStreetMap blocks requests that don't identify the app. If your app is used by many people, you can set your own name and a contact with user_agent="MyApp/1.0 (contact@example.com)" when you create the map, or later with map_widget.set_user_agent(...).

With corner_radius the map can get rounded corners, which looks good if it doesn't fill the whole window. The arguments width and height set the size, if the map is not stretched by the grid. The map also works in a tkinter Tk window, the code is the same.

Position and zoom​

The position is set with latitude and longitude in degrees, the zoom with a number from 0 (whole world) to 19 (single houses):

map_widget.set_position(52.516268, 13.377695) # Brandenburg Gate, Berlin
map_widget.set_zoom(15)

get_position() returns the current position of the center of the map as a tuple (latitude, longitude).

Search for an address​

set_address looks up an address and moves the map to it. The address can be a city, a street with house number or the name of a place, like "Eiffel Tower". The lookup uses the Nominatim service of OpenStreetMap. If nothing is found, set_address returns False:

if not map_widget.set_address("Paris"):
print("nothing found")

The lookup needs a request to the internet and blocks the app for a short moment. With set_address("Paris", marker=True) a marker is placed at the address.

In the example we also accept coordinates like 48.86, 2.35 in the search field. The function parse_coordinates tries to read two numbers separated by a comma, and returns None if that doesn't work:

def parse_coordinates(text):
"""Returns (latitude, longitude) for text like "52.52, 13.40", otherwise None."""
try:
latitude, longitude = (float(part) for part in text.split(","))
except ValueError:
return None
return latitude, longitude

Full code​

first_map.py
import customtkinter
import tkintermapview


def parse_coordinates(text):
"""Returns (latitude, longitude) for text like "52.52, 13.40", otherwise None."""
try:
latitude, longitude = (float(part) for part in text.split(","))
except ValueError:
return None
return latitude, longitude


class App(customtkinter.CTk):
def __init__(self):
super().__init__()

self.title("Map")
self.geometry("800x560")
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)

self.search_frame = customtkinter.CTkFrame(self, corner_radius=0)
self.search_frame.grid(row=0, column=0, sticky="ew")
self.search_frame.grid_columnconfigure(0, weight=1)

self.search_entry = customtkinter.CTkEntry(self.search_frame,
placeholder_text="Address or coordinates, like Paris or 48.86, 2.35")
self.search_entry.grid(row=0, column=0, padx=(20, 10), pady=10, sticky="ew")
self.search_entry.bind("<Return>", self.search)
self.search_button = customtkinter.CTkButton(self.search_frame, text="Search", width=90, command=self.search)
self.search_button.grid(row=0, column=1, padx=(0, 10), pady=10)
self.status_label = customtkinter.CTkLabel(self.search_frame, text="", width=160, anchor="w")
self.status_label.grid(row=0, column=2, padx=(0, 20), pady=10)

self.map_widget = tkintermapview.TkinterMapView(self, corner_radius=0)
self.map_widget.grid(row=1, column=0, sticky="nsew")

self.map_widget.set_position(52.516268, 13.377695) # Brandenburg Gate, Berlin
self.map_widget.set_zoom(15)

def search(self, event=None):
text = self.search_entry.get().strip()
if not text:
return

coordinates = parse_coordinates(text)
if coordinates is not None:
self.map_widget.set_position(*coordinates)
self.status_label.configure(text="Moved to coordinates")
elif self.map_widget.set_address(text):
self.status_label.configure(text=f"Found {text}")
else:
self.status_label.configure(text="Nothing found")


app = App()
app.mainloop()

Show an area​

Instead of a position and zoom level, you can also pass the corners of an area with fit_bounding_box. The map then chooses the position and zoom so that the whole area is visible. The first argument is the top left corner, the second the bottom right corner:

map_widget.fit_bounding_box((52.55, 13.30), (52.48, 13.48)) # center of Berlin

Common errors​

The map shows "Access blocked"

OpenStreetMap rejected the requests because they don't identify the app. Update TkinterMapView to version 1.32 or newer with pip install --upgrade tkintermapview. Versions before 1.32 always send the same User-Agent TkinterMapView, which is blocked. If the tiles are still blocked, set your own user_agent with the name of your app.

The map stays gray

The tiles could not be loaded. Check the internet connection and whether a firewall or proxy blocks the requests. If the tile server rejects the requests, see Tile Servers.

The map doesn't fill the window

The row and column of the map need a weight and the map needs sticky="nsew", like in the example above.

set_address() returns False

The address was not found, or the Nominatim service could not be reached. Try a more precise address, or use coordinates with set_position.