A python Tkinter widget to display tile based maps like OpenStreetMap or Google Satellite Images.
TkinterMapView - simple Tkinter map component

TkinterMapView is a tile based interactive map renderer widget for the python Tkinter library. By default, it displays the OpenStreetMap map, but you can change the tile server to whatever you like, and it also supports a second tile server for overlays like OpenSeaMap. You can set the current focus of the widget by a position or address, and place markers or a path on the map.
The above image program is produced by the code example mapviewsimple_example.py.
But you can also embed the widget into a program like the following image shows. For example by using the CustomTkinter library, which provides rounded buttons and frames in a light and dark mode:
https://user-images.githubusercontent.com/66446067/199613538-6be7bc5c-c88b-42d7-8cf5-a9ed2b011fa4.mp4
|examples/mapwithcustomtkinter.py on macOS
Installation
pip3 install tkintermapview
Update: `pip3 install tkintermapview --upgrade
https://pypi.org/project/tkintermapview/
Documentation / Tutorial
- Importing
- Create the widget
- Set coordinate position
- Set address position
- Set position with marker
- Set position and zoom to fit bounding box
- Create position markers
- Create path from position list
- Create polygon from position list
- Mouse events on the map
- Utility methods
- Use other tile servers
- Use offline tiles
Importing
Import tkinter as normal and from tkintermapview import the TkinterMapView widget. <pre><code class="lang-python">import tkinter import tkintermapview</code></pre>
Create the widget
Create the standard tkinter window and place a TkinterMapView in the middle of the window. The first argument must be the widgets master, then you specify the
width, height and corner_radius of the widget. <pre><code class="lang-python"># create tkinter window root_tk = tkinter.Tk() root_tk.geometry(f"{800}x{600}") roottk.title("mapview_example.py")
create map widget
mapwidget = tkintermapview.TkinterMapView(roottk, width=800, height=600, corner_radius=0)
map_widget.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)</code></pre>
If you also call root_tk.mainloop() at the end, this is already a fully working example to test the map widget.
Set coordinate position
The standard position on which the map is focused is Berlin, Germany, but you can change the position and zoom with the following commands. The position must be given in decimal coordinates and the zoom ranges from 0 to 19 where 19 is the highest zoom level. <pre><code class="lang-python"># set current widget position and zoom mapwidget.setposition(48.860381, 2.338594) # Paris, France mapwidget.setzoom(15)</code></pre>
Set address position
But you can not only set the position by decimal coordinates, but also by an address string like
"colosseo, rome, italy" or "3 St Margaret St, London, United Kingdom" . The address is converted to a position by the OpenStreetMap geocode service Nomatim. <pre><code class="lang-python"># set current widget position by address mapwidget.setaddress("colosseo, rome, italy")</code></pre>
Set position with marker
If you also want a red marker at the set position with a text of the current location, then you can pass the
marker=True argument to the setposition or setaddress funtions. You get back a PositionMarker object, so that you can modify or delete the marker later: <pre><code class="lang-python"># set current widget position by address marker1 = mapwidget.set_address("colosseo, rome, italy", marker=True)
print(marker1.position, marker1.text) # get position and text
marker1.settext("Colosseo in Rome") # set new text
marker1.setposition(48.860381, 2.338594) # change position
marker_1.delete()</code></pre>
Set position and zoom to fit bounding box
If you have two decimal coordinates (<lat1>, <long1>) and (<lat2>, <long2>), that define a box, so that the first coordinate is the top-left corner and the second coordinate is the bottom-right corner. Then you can use the fitboundingbox method of the map widget to fit this box into the map widget: <pre><code class="lang-python">mapwidget.fitbounding_box((<lat1>, <long1>), (<lat2>, <long2>))</code></pre>
Create position markers
You can also set a position marker without focusing the widget on it. You can pass a
text argument to the function and get back the marker object, so that you can store the marker and modify or delete it later. <pre><code class="lang-python"># set a position marker marker2 = mapwidget.set_marker(52.516268, 13.377695, text="Brandenburger Tor") marker3 = mapwidget.set_marker(52.55, 13.4, text="52.55, 13.4")
methods
marker3.setposition(...)
marker3.settext(...)
marker3.changeicon(new_icon)
marker3.hideimage(True) # or False
marker_3.delete()</code></pre>
A marker can be also customized by passing the following arguments to .set_marker(),
.setaddress() or .setposition(): text, font, icon, iconanchor, image (PhotoImage), imagezoom_visibility,
markercolorcircle, markercoloroutside, text_color, command.
The command function will be called when the marker is clicked and will pass the clicked marker as an argument to the functin which gets called.
The given image will be visible above the marker when the zoom level is in the range specified by
imagezoomvisibility, which is tuple like the following (minzoom, maxzoom). imagezoomvisibility=(0, float('inf')) means the image will be visible alle the time. The image can also be hidden by calling: marker.hideimage(True) or marker.hideimage(False). To check if the image is currently hidden you can access: marker.image_hidden which is True or False.
You can also store an object or some reference inside the marker with the
data attribute, which can be set when creating a marker, and accessed or modified with marker.data. This data attribute also exists for path and polygons.
With the
icon attribute you can pass a PIL.ImageTk.PhotoImage object to a marker, which will be displayed instead of the standard location icon. With icon_anchor you can specify the anchor for the icon image (center, n, nw, w, sw, s, ew, e, ne), corresponding to the position of the marker, standard is center, where the icon image is centered over the marker position. With the .changeicon(newicon) method you can change the icon image later, but only if the marker already has an icon image from the beginning. In examples/mapviewmarkericonimages.py you can find example code for the icon attributes.
<img src="documentationimages/markerwith_image.png" width="500"/>
With
mapwidget.deleteall_marker() all marker on the map will be deleted.
Create path from position list
You can also create a path which connects multiple markers or completely new positions. You pass a list with position tuples to the function
set_path and get back a path object. The path object can be modified by adding a new position or remove a specific position. The setpath method accepts the following arguments: positionlist, color, command (on click), name (string for identification), width, data (anything can be stored in the path object).
python
set a path
path1 = mapwidget.setpath([marker2.position, marker_3.position, (52.57, 13.4), (52.55, 13.35)])
methods
path1.setpositionlist(newposition_list)
path1.addposition(position)
path1.removeposition(position)
path_1.delete()</code></pre>
With mapwidget.deleteall_path() all path on the map will be deleted.
Create polygon from position list
To create a polygon on the map call the `mapwidget.setpolygon() function and pass a list of coordinate tuples from which the polygon will be created. You can edit the appearance with the following arguments: fillcolor, outlinecolor, border_width. You can also set a command function which will be called when the polygon gets clicked and which will get the polygon object as an argument. <pre><code class="lang-">python def polygon_click(polygon): print(f"polygon clicked - text: {polygon.name}") polygon1 = mapwidget.set_polygon([(46.0732306, 6.0095215), ... (46.3772542, 6.4160156)], # fill_color=None, # outline_color="red", # border_width=12, command=polygon_click, name="switzerland_polygon")
methods
polygon1.removeposition(46.3772542, 6.4160156)
polygon1.addposition(0, 0, index=5)
polygon_1.delete()
In
examples/mapviewpolygon_example.py you can find the full example program, which results in the following:

With
mapwidget.deleteall_polygon() all polygons on the map will be deleted.
Mouse events on the map
When you click on the map with the right mouse button, a menu pops up, where you can view the current decimal coordinates and copy them into the clipboard by clicking them. You can also add other options to this menu, with the
mapwidget.addrightclickmenu_command method: <pre><code class="lang-python">def addmarkerevent(coords): print("Add marker:", coords) newmarker = mapwidget.set_marker(coords[0], coords[1], text="new marker")
mapwidget.addrightclickmenu_command(label="Add Marker", command=addmarkerevent, pass_coords=True)</code></pre> With the
label argument you set the text inside the menu, and if pass_coords is True, the clicked coordinates will be passed to the command function as a tuple.
<img src="documentationimages/rightclickmenuexample.png" width="400"/>
You can also assign a callback function for a left click event on the map with: <pre><code class="lang-python">def leftclickevent(coordinates_tuple): print("Left click event with coordinates:", coordinates_tuple) mapwidget.addleftclickmapcommand(leftclick_event)</code></pre> The callback function will get the decimal coordinates of the clicked location as a tuple.
Utility methods
The following methods can be useful when working with coordinates and address strings, they all use the geocoder library with the OSM provider: https://geocoder.readthedocs.io/providers/OpenStreetMap.html:
Convert decimal coords to address object: <pre><code class="lang-python">adr = tkintermapview.convertcoordinatesto_address(51.5122057, -0.0994014) print(adr.street, adr.housenumber, adr.postal, adr.city, adr.state, adr.country, adr.latlng)
Output: Knightrider Street None EC4 City of London England United Kingdom [51.512284050000005, -0.09981746110011651]</code></pre>
Convert decimal coords to city name:
<pre><code class="lang-python">city = tkintermapview.convertcoordinatesto_city(51.5122057, -0.0994014)
city: "City of London"</code></pre>
Convert decimal coords to country name: <pre><code class="lang-python">country = tkintermapview.convertcoordinatesto_city(51.5122057, -0.0994014)
country: "United Kingdom"</code></pre>
Convert address string to decimal coords. If the address isn't found, the function returns None.
<pre><code class="lang-python">address = tkintermapview.convertaddressto_coordinates("London")
address: (51.5073219, -0.1276474)</code></pre>
Use other tile servers
TkinterMapView uses OpenStreetMap tiles by default, but you can also change the tile server to every url that includes
{x} {y} {z} coordinates. For example, you can use the standard Google Maps map style or Google Maps satellite images: <pre><code class="lang-">python example tile sever:
self.mapwidget.settile_server("https://a.tile.openstreetmap.org/{z}/{x}/{y}.png") # OpenStreetMap (default) self.mapwidget.settileserver("https://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}&s=Ga", maxzoom=22) # google normal self.mapwidget.settileserver("https://mt0.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}&s=Ga", maxzoom=22) # google satellite self.mapwidget.settile_server("http://c.tile.stamen.com/watercolor/{z}/{x}/{y}.png") # painting style self.mapwidget.settile_server("http://a.tile.stamen.com/toner/{z}/{x}/{y}.png") # black and white self.mapwidget.settile_server("https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png") # detailed hiking self.mapwidget.settile_server("https://tiles.wmflabs.org/osm-no-labels/{z}/{x}/{y}.png") # no labels self.mapwidget.settile_server("https://wmts.geo.admin.ch/1.0.0/ch.swisstopo.pixelkarte-farbe/default/current/3857/{z}/{x}/{y}.jpeg") # swisstopo mapexample overlay tile server
self.mapwidget.setoverlaytileserver("http://tiles.openseamap.org/seamark//{z}/{x}/{y}.png") # sea-map overlay self.mapwidget.setoverlaytileserver("http://a.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png") # railway infrastructure`Use offline tiles
You can load tiles into a database to use them offline when there is no connection to the tile server. Check out examples/loadofflinetiles.py for more information.
If you then create the TkinterMapView widget you pass the database path as an argument. An example of this can be found here: examples/mapwithoffline_tiles.py You can also pass a max_zoom argument to limit the possible zoom range if the database just holds the tiles until a specific zoom range which is not the limit of the used server.