TkinterMapView: tile maps inside a Tkinter window
A python Tkinter widget to display tile based maps like OpenStreetMap or Google Satellite Images.
At a glance
- What is it?
- TkinterMapView is a Python widget that renders a slippy map inside a Tkinter application, defaulting to OpenStreetMap tiles with a second tile server reserved for overlays, and it also handles geocoding, markers, paths and polygons. It is a single-file-scale dependency with no API key, but it is a desktop widget with a hard dependency on a tkinter build and on pywin32 on Windows, and the network calls it makes are undocumented in terms of rate limits.
- Who is it for?
- Adopt TkinterMapView if you are building a desktop Python tool in Tkinter and need an interactive map without an API key, a browser component or a JavaScript runtime, since it ships OSM tiles by default, geocodes through Nomatim, and lets you drop markers, paths and polygons straight onto the canvas. Do not adopt it for a headless service or a web application, and do not plan around guaranteed tile availability, because the widget fetches from public tile servers at runtime.
- Can I use it commercially?
- Yes. CC0-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 117 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A slippy map rendered with Tkinter canvas primitives
TkinterMapView is described as a tile based interactive map renderer widget for the Python Tkinter library, and the repository description puts it more plainly: a Python Tkinter widget to display tile based maps like OpenStreetMap or Google Satellite Images.
The mechanism is a tile pyramid fetched over HTTP and drawn into a canvas, the same model any web map uses, with the widget handling the panning, zooming and the fetching of the right tiles for the current viewport. The default tile source is OpenStreetMap, and the documentation notes you can change the tile server to whatever you like, and that there is support for a second tile server used for overlays such as OpenSeaMap. That second server is a real design decision rather than a convenience, since it is what lets you put OpenSeaMap's harbour and depth data over the standard OSM base without compositing the images yourself.
What is missing from that description matters just as much. There is no API key anywhere in the setup, no account, and no attribution handling described beyond the OSM default. The widget is a view, and it expects the tile provider to be public and unmetered.
The package is published on PyPI as tkintermapview, and the licence is Creative Commons Zero v1.0 Universal, a public domain dedication rather than a conventional software licence.
Installing a widget whose real dependency is tkinter itself
The installation instruction is one line:
pip3 install tkintermapviewAnd the update path is given as well:
pip3 install tkintermapview --upgradeThe subtlety is that this command does not give you everything you need. tkinter is part of the Python standard library, but it is not always installed with it, and on Linux it is usually a separate system package. On macOS and Windows it comes with the Python installer, but on a slim Linux container or a minimal distribution you will find that the import fails because the Tcl/Tk libraries are missing, and pip cannot install them for you.
The declared dependencies also go beyond what a map widget strictly needs. setup.py lists geopy, certifi, geocoder, pillow, requests, pyperclip, pywin32 for Windows only, and customtkinter. The Windows-only marker is worth noting, since pywin32 is used for clipboard handling and does not exist on other platforms.
The requirements.txt file pins some of those more tightly than the setup.py does, with customtkinter at about 5.0.3, requests pinned to exactly 2.26.0, pillow at about 9.3.0, future at about 0.18.2 and pyperclip at about 1.8.2. A hard pin on an old requests release is the kind of thing that will conflict with the rest of your environment, and it is worth deciding deliberately whether to follow the pin or to let pip resolve.
The first run: a window, a widget and a mainloop
The simplest working example is short enough to read in full, and it is the one that produced the screenshot in the readme:
# create tkinter window
root_tk = tkinter.Tk()
root_tk.geometry(f"{800}x{600}")
root_tk.title("map_view_example.py")
# create map widget
map_widget = tkintermapview.TkinterMapView(root_tk, width=800, height=600, corner_radius=0)
map_widget.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)The widget takes its master as the first argument, and then width, height and corner_radius. The corner_radius argument is the giveaway about the lineage of the project, since it exists so the widget can match the rounded corners of the CustomTkinter library, and the documentation shows the widget embedded in a CustomTkinter application with rounded buttons and frames in a light and dark mode.
The widget is placed with the geometry manager rather than packed, using place with relx, rely and an anchor, which is how you centre it in the window. Calling root_tk.mainloop() at the end is all that is needed to have a running map.
The examples directory in the repository shows how the same widget is used in more involved ways, with separate files for the simple view, markers, marker icon images, polygons, offline tiles, and a CustomTkinter embedding.
Zoom levels, the default position and the geocoding path
The widget opens on Berlin by default, and both the position and the zoom are set with dedicated calls. Positions are given in decimal coordinates and the zoom ranges from 0 to 19, with 19 the highest level:
# set current widget position and zoom
map_widget.set_position(48.860381, 2.338594) # Paris, France
map_widget.set_zoom(15)There is a second way in, and it is the reason the package depends on geopy and geocoder. You can pass an address string and the widget will resolve it to coordinates:
# set current widget position by address
map_widget.set_address("colosseo, rome, italy")The documentation is explicit that the address is converted to a position by the OpenStreetMap geocode service Nomatim, which is a free public endpoint rather than a commercial one. That has a practical consequence: address lookup is a network call to a shared public service, it is slower than setting a coordinate, and it can fail or be rate limited without the widget telling you why. If your application geocodes on every keystroke or every search, that is a design problem the widget will not solve for you.
The two call styles also differ in what they return when a marker is requested, which is the next thing worth understanding.
Markers as objects you keep and mutate
Passing marker=True to set_position or set_address adds a marker at that location and returns a PositionMarker object, which is the part of the API worth designing around. The documentation is clear that you get the object back so you can modify or delete the marker later:
# set current widget position by address
marker_1 = map_widget.set_address("colosseo, rome, italy", marker=True)
print(marker_1.position, marker_1.text) # get position and text
marker_1.set_text("Colosseo in Rome") # set new text
# marker_1.set_position(48.860381, 2.338594) # change position
# marker_1.delete()You can also place a marker without focusing the map on it, and set_marker takes a text argument:
# set a position marker
marker_2 = map_widget.set_marker(52.516268, 13.377695, text="Brandenburger Tor")
marker_3 = map_widget.set_marker(52.55, 13.4, text="52.55, 13.4")
# methods
marker_3.set_position(...)
marker_3.set_text(...)
marker_3.change_icon(new_icon)
marker_3.hide_image(True) # or False
marker_3.delete()The customisation surface is wide. set_marker, set_address and set_position all accept text, font, icon, icon_anchor, image as a PhotoImage, image_zoom_visibility, marker_color_circle, marker_color_outside, text_color and command. The command callback fires on click and receives the clicked marker, so a click handler does not need to look the marker up.
The icon_anchor parameter accepts the usual compass values, and there is a catch with change_icon, which the documentation flags: it only works if the marker already had an icon image from the beginning. There is also a data attribute you can attach to a marker, and the same attribute exists on paths and polygons, which is a simple way to bind application state to a map object.
Paths, polygons and fitting a bounding box
Beyond markers, the widget draws lines and shapes over the map, and it can frame a region for you.
The bounding box call takes two decimal coordinate pairs, the first being the top-left corner and the second the bottom-right, and adjusts position and zoom so the whole box is visible:
map_widget.fit_bounding_box((<lat1>, <long1>), (<lat2>, <long2>))That single method is more useful than it looks. Zooming to a set of markers by hand means computing a bounding box yourself, then finding a zoom level that fits it, and both of those are easy to get subtly wrong. Handing the two corners over lets the widget do the arithmetic.
The examples directory confirms the shape support, with map_view_polygon_example.py alongside the marker examples. The documentation is truncated in the captured readme just where it starts describing the path and polygon creation methods, so the exact signatures are not available, but the tutorial table of contents lists Create path from position list and Create polygon from position list as separate sections, and the note about the data attribute on paths and polygons confirms that both are first class objects rather than a single combined call.
The limitation here is modest but real: this is a display layer. There is no hit testing, no editing of a drawn path, and no undo, because the widget hands you objects you can move yourself.
Offline tiles and the limits of the widget
The documentation lists Use other tile servers and Use offline tiles as tutorial sections, and the examples directory contains both load_offline_tiles.py and map_with_offline_tiles.py, so the offline path is a real feature rather than a wish.
That matters for the deployment cases the widget is least suited to. A tool that is expected to run on a machine with no internet, an avionics panel, a field instrument, or a locked-down corporate network, can point at a local tile directory instead of a public server. The cost is that you have to obtain the tiles yourself and keep them current.
The limits of the design are worth stating plainly. The widget is a desktop display inside a Tkinter window, so it needs a display server, a mainloop and a Python process with a GUI toolkit. It is the wrong tool for a headless service, for a web page, or for a server-side geocoding pipeline, and it is also the wrong tool if your application needs map interaction at the scale of thousands of features with clustering, heatmaps or vector layers.
There is also a dependency worth naming. The package requires customtkinter, which pulls in a modernised widget set that is not part of the standard library, and the setup.py classifiers list the operating system as independent even though the pywin32 conditional dependency and the requirement for a working tkinter build make the Windows and Linux stories different in practice.
Editorial conclusion
Adopt TkinterMapView if you are building a desktop Python tool in Tkinter and need an interactive map without an API key, a browser component or a JavaScript runtime, since it ships OSM tiles by default, geocodes through Nomatim, and lets you drop markers, paths and polygons straight onto the canvas. Do not adopt it for a headless service or a web application, and do not plan around guaranteed tile availability, because the widget fetches from public tile servers at runtime. Before you start, check that your Python environment actually has a working tkinter build, since pip cannot supply it on Linux, and note that the package pulls in pywin32 on Windows and a pinned set of dependencies in requirements.txt, including customtkinter, so the install footprint is larger than the widget itself suggests.
Frequently asked questions
How do I install TkinterMapView and show a map?
Run pip3 install tkintermapview, then import tkinter and tkintermapview, create a Tk() window, and place a TkinterMapView in it with the width, height and corner_radius arguments. Calling root_tk.mainloop() at the end makes it a working example, which is what the map_view_simple_example.py file does.
How do I set the position and zoom of a TkinterMapView?
Use set_position with decimal coordinates and set_zoom with a level between 0 and 19, where 19 is the highest. The widget opens on Berlin by default. You can also use set_address with an address string, which is resolved to coordinates through the OpenStreetMap geocode service Nomatim.
Can I add markers to a TkinterMapView and change them later?
Yes. set_marker, set_address and set_position accept a marker argument, and set_marker takes a text argument, and both return the marker object. The object supports set_position, set_text, change_icon, hide_image and delete, and you can attach your own data through the data attribute.
Does TkinterMapView need an API key for map tiles?
No key is required for the default OpenStreetMap tiles, and the documentation says you can change the tile server to whatever you like, and it supports a second tile server for overlays such as OpenSeaMap. Address lookups go through the free public Nomatim geocode service.
What licence is TkinterMapView released under?
Creative Commons Zero v1.0 Universal, a public domain dedication, declared in setup.py and recorded in the LICENSE.txt file at the root of the repository. The package version there is 1.30, and the repository has no GitHub releases.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tomschimansky-tkintermapview)Was this useful?