Skip to content

MapView

Usage

A MapView is a scrollable area that can show a map at varying levels of detail, from nation-level to street level. The map can be centered at a given coordinate, and zoomed to the required level of detail using an integer from 0 (for global detail) to 20 (for building level detail):

import toga

# Create a map centered in London, UK.
mapview = toga.MapView(location=(51.507222, -0.1275))

# Center the map in Perth, Australia
mapview.location = (-31.9559, 115.8606)

# Zoom to show the map to show street level detail
mapview.zoom = 15

A map can also display pins. A map pin must have a title, and can optionally have a subtitle. Pins can be added at time of map construction, or can be dynamically added, updated and removed at runtime:

import toga

mapview = toga.MapView(
    pins=[
        toga.MapPin((-31.95064, 115.85889), title="Yagan Square"),
    ]
)

# Create a new pin, and add it to the map
brutus = toga.MapPin((41.50375, -81.69475), title="Brutus was here")
mapview.pins.add(brutus)

# Update the pin label and position
brutus.location = (40.440831, -79.991162)
brutus.title = "Brutus will be here"

# Remove the Brutus pin
mapview.pins.remove(brutus)

# Remove all pins
mapview.pins.clear()

Pins can respond to being pressed. When a pin is pressed, the map generates an on_select event, which receives the pin as an argument.

System requirements

  • Using MapView on Windows 10 requires that your users have installed the Edge WebView2 Evergreen Runtime. This is installed by default on Windows 11.

  • Using MapView on Linux requires that the user has installed the system packages for WebKit2, plus the GObject Introspection bindings for WebKit2. The name of the system package required is distribution dependent:

    • Ubuntu 20.04; Debian 11: gir1.2-webkit2-4.0
    • Ubuntu 22.04+; Debian 12+: gir1.2-webkit2-4.1
    • Fedora: webkit2gtk4.1
    • Arch/Manjaro: webkit2gtk-4.1
    • OpenSUSE Tumbleweed: libwebkit2gtk3 typelib(WebKit2)
    • FreeBSD: webkit2-gtk3

    MapView is not fully supported on GTK4. If you want to contribute to the GTK4 MapView implementation, you will require v6.0 of the WebKit2 libraries. This is provided by gir1.2-webkit-6.0 on Ubuntu/Debian, and webkitgtk6.0 on Fedora; for other distributions, consult your distribution's platform documentation.

  • Using MapView on Android requires the OSMDroid package in your project's Gradle dependencies. Ensure your app declares a dependency on org\.osmdroid:osmdroid-android:6.1.20 or later.

Notes

  • The Android, GTK and Winforms implementations of MapView use OpenStreetMap as a source of map tiles. OpenStreetMap is an open data project with its own copyright, license terms, and acceptable use policies. If you make use of MapView in your application, it is your responsibility to ensure that your app complies with these terms. In addition, we strongly encourage you to financially support the OpenStreetMap Foundation, as their work is what allows Toga to provide map content on these platforms.
  • On macOS and iOS, MapView will not repeat map tiles if the viewable area at the given zoom level is bigger than the entire world. A zoom to a very low level will be clipped to the lowest level that allows displaying the map without repeating tiles.

Reference

Bases: Widget

Source code in core/src/toga/widgets/mapview.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
class MapView(Widget):
    def __init__(
        self,
        id: str | None = None,
        style: StyleT | None = None,
        location: toga.LatLng | tuple[float, float] | None = None,
        zoom: int = 11,
        pins: Iterable[MapPin] | None = None,
        on_select: toga.widgets.mapview.OnSelectHandler | None = None,
        **kwargs,
    ):
        """Create a new MapView widget.

        :param id: The ID for the widget.
        :param style: A style object. If no style is provided, a default style will be
            applied to the widget.
        :param location: The initial latitude/longitude where the map should be
            centered. If not provided, the initial location for the map is Perth,
            Australia.
        :param zoom: The initial zoom level for the map.
        :param pins: The initial pins to display on the map.
        :param on_select: A handler that will be invoked when the user selects a map
            pin.
        :param kwargs: Initial style properties.
        """
        super().__init__(id, style, **kwargs)

        self._pins = MapPinSet(self, pins)

        if location:
            self.location = location
        else:
            # Default location is Perth, Australia. Because why not?
            self.location = (-31.9559, 115.8606)

        self.zoom = zoom

        self.on_select = on_select

    def _create(self) -> Any:
        return self.factory.MapView(interface=self)

    @property
    def location(self) -> toga.LatLng:
        """The latitude/longitude where the map is centered.

        A tuple of `(latitude, longitude)` can be provided as input; this will be
        converted into a [`toga.LatLng`][] object.
        """
        return self._impl.get_location()

    @location.setter
    def location(self, coordinates: toga.LatLng | tuple[float, float]) -> None:
        self._impl.set_location(toga.LatLng(*coordinates))

    @property
    def zoom(self) -> int:
        """Set the zoom level for the map.

        The zoom level is an integer in the range 0-20 (inclusive). It can be used to
        set the number of degrees of longitude that will span a 256
        [CSS pixel][css-units]
        region in the horizontal axis of the map, following the
        relationship:

        ```
        longitude_per_256_pixels = 360 / (2**zoom)
        ```

        In practical terms, this means a 256px square will cover:

        * 0-2: Whole world
        * 3-6: Large countries
        * 7-8: Small countries, or a state in a large country
        * 9-11: The extent of a city
        * 12-14: Suburbs of a city, or small towns
        * 15-17: Roads at the level useful for navigation
        * 18-19: Individual buildings
        * 20: A single building

        These zoom levels use the same mathematical basis as the OpenStreetMap API. See
        [OpenStreetMap's documentation on zoom
        levels](https://wiki.openstreetmap.org/wiki/Zoom_levels)
        for more details.

        If the provided zoom value is outside the supported range, it will be clipped.

        At very low zoom levels, some backends may constrain the viewable range to avoid
        repeating map tiles in the visible area. This effectively sets a minimum bound
        on the zoom level that can be requested. The value of this minimum varies
        depending on the size and aspect ratio of the map view.
        """
        return round(self._impl.get_zoom())

    @zoom.setter
    def zoom(self, value: int) -> None:
        value = int(value)
        if value < 0:
            value = 0
        elif value > 20:
            value = 20

        self._impl.set_zoom(value)

    @property
    def pins(self) -> MapPinSet:
        """The set of pins currently being displayed on the map"""
        return self._pins

    @property
    def on_select(self) -> OnSelectHandler:
        """The handler to invoke when the user selects a pin on a map.

        **Note:** This is not currently supported on GTK or Windows.
        """
        return self._on_select

    @on_select.setter
    def on_select(self, handler: toga.widgets.mapview.OnSelectHandler | None) -> None:
        if handler and not getattr(self._impl, "SUPPORTS_ON_SELECT", True):
            self.factory.not_implemented("MapView.on_select")

        self._on_select = wrapped_handler(self, handler)

location property writable

The latitude/longitude where the map is centered.

A tuple of (latitude, longitude) can be provided as input; this will be converted into a toga.LatLng object.

on_select property writable

The handler to invoke when the user selects a pin on a map.

Note: This is not currently supported on GTK or Windows.

pins property

The set of pins currently being displayed on the map

zoom property writable

Set the zoom level for the map.

The zoom level is an integer in the range 0-20 (inclusive). It can be used to set the number of degrees of longitude that will span a 256 CSS pixel region in the horizontal axis of the map, following the relationship:

longitude_per_256_pixels = 360 / (2**zoom)

In practical terms, this means a 256px square will cover:

  • 0-2: Whole world
  • 3-6: Large countries
  • 7-8: Small countries, or a state in a large country
  • 9-11: The extent of a city
  • 12-14: Suburbs of a city, or small towns
  • 15-17: Roads at the level useful for navigation
  • 18-19: Individual buildings
  • 20: A single building

These zoom levels use the same mathematical basis as the OpenStreetMap API. See OpenStreetMap's documentation on zoom levels for more details.

If the provided zoom value is outside the supported range, it will be clipped.

At very low zoom levels, some backends may constrain the viewable range to avoid repeating map tiles in the visible area. This effectively sets a minimum bound on the zoom level that can be requested. The value of this minimum varies depending on the size and aspect ratio of the map view.

__init__(id=None, style=None, location=None, zoom=11, pins=None, on_select=None, **kwargs)

Create a new MapView widget.

:param id: The ID for the widget. :param style: A style object. If no style is provided, a default style will be applied to the widget. :param location: The initial latitude/longitude where the map should be centered. If not provided, the initial location for the map is Perth, Australia. :param zoom: The initial zoom level for the map. :param pins: The initial pins to display on the map. :param on_select: A handler that will be invoked when the user selects a map pin. :param kwargs: Initial style properties.

Source code in core/src/toga/widgets/mapview.py
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def __init__(
    self,
    id: str | None = None,
    style: StyleT | None = None,
    location: toga.LatLng | tuple[float, float] | None = None,
    zoom: int = 11,
    pins: Iterable[MapPin] | None = None,
    on_select: toga.widgets.mapview.OnSelectHandler | None = None,
    **kwargs,
):
    """Create a new MapView widget.

    :param id: The ID for the widget.
    :param style: A style object. If no style is provided, a default style will be
        applied to the widget.
    :param location: The initial latitude/longitude where the map should be
        centered. If not provided, the initial location for the map is Perth,
        Australia.
    :param zoom: The initial zoom level for the map.
    :param pins: The initial pins to display on the map.
    :param on_select: A handler that will be invoked when the user selects a map
        pin.
    :param kwargs: Initial style properties.
    """
    super().__init__(id, style, **kwargs)

    self._pins = MapPinSet(self, pins)

    if location:
        self.location = location
    else:
        # Default location is Perth, Australia. Because why not?
        self.location = (-31.9559, 115.8606)

    self.zoom = zoom

    self.on_select = on_select
Source code in core/src/toga/widgets/mapview.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class MapPin:
    def __init__(
        self,
        location: toga.LatLng | tuple[float, float],
        *,
        title: str,
        subtitle: str | None = None,
    ):
        """Create a new map pin.

        :param location: A tuple describing the (latitude, longitude) for the pin.
        :param title: The title to apply to the pin.
        :param subtitle: A subtitle label to apply to the pin.
        """
        self._location = toga.LatLng(*location)
        self._title = title
        self._subtitle = subtitle

        # A pin isn't tied to a map at time of creation.
        self.interface: MapView | None = None
        self._native = None

    def __repr__(self) -> str:
        if self.subtitle:
            label = f"; {self.title} - {self.subtitle}"
        else:
            label = f"; {self.title}"

        return f"<MapPin @ {self.location}{label}>"

    @property
    def location(self) -> toga.LatLng:
        """The (latitude, longitude) where the pin is located."""
        return self._location

    @location.setter
    def location(self, coord: toga.LatLng | tuple[float, float]) -> None:
        self._location = toga.LatLng(*coord)
        if self.interface:
            self.interface._impl.update_pin(self)

    @property
    def title(self) -> str:
        """The title of the pin."""
        return self._title

    @title.setter
    def title(self, title: str) -> None:
        self._title = str(title)
        if self.interface:
            self.interface._impl.update_pin(self)

    @property
    def subtitle(self) -> str | None:
        """The subtitle of the pin."""
        return self._subtitle

    @subtitle.setter
    def subtitle(self, subtitle: str | None) -> None:
        if subtitle is not None:
            subtitle = str(subtitle)
        self._subtitle = subtitle
        if self.interface:
            self.interface._impl.update_pin(self)

location property writable

The (latitude, longitude) where the pin is located.

subtitle property writable

The subtitle of the pin.

title property writable

The title of the pin.

__init__(location, *, title, subtitle=None)

Create a new map pin.

:param location: A tuple describing the (latitude, longitude) for the pin. :param title: The title to apply to the pin. :param subtitle: A subtitle label to apply to the pin.

Source code in core/src/toga/widgets/mapview.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def __init__(
    self,
    location: toga.LatLng | tuple[float, float],
    *,
    title: str,
    subtitle: str | None = None,
):
    """Create a new map pin.

    :param location: A tuple describing the (latitude, longitude) for the pin.
    :param title: The title to apply to the pin.
    :param subtitle: A subtitle label to apply to the pin.
    """
    self._location = toga.LatLng(*location)
    self._title = title
    self._subtitle = subtitle

    # A pin isn't tied to a map at time of creation.
    self.interface: MapView | None = None
    self._native = None
Source code in core/src/toga/widgets/mapview.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class MapPinSet:
    def __init__(self, interface: MapView, pins: Iterable[MapPin] | None):
        self.interface = interface
        self._pins: set[MapPin] = set()

        if pins is not None:
            for item in pins:
                self.add(item)

    def __repr__(self) -> str:
        return f"<MapPinSet ({len(self)} pins)>"

    def __iter__(self) -> Iterator[MapPin]:
        """Return an iterator over the pins on the map."""
        return iter(self._pins)

    def __len__(self) -> int:
        """Return the number of pins being displayed."""
        return len(self._pins)

    def add(self, pin: MapPin) -> None:
        """Add a new pin to the map.

        :param pin: The [`toga.MapPin`][] instance to add.
        """
        pin.interface = self.interface
        self._pins.add(pin)
        self.interface._impl.add_pin(pin)

    def remove(self, pin: MapPin) -> None:
        """Remove a pin from the map.

        :param pin: The  [`toga.MapPin`][] instance to remove.
        """
        self.interface._impl.remove_pin(pin)
        self._pins.remove(pin)
        pin.interface = None

    def clear(self) -> None:
        """Remove all pins from the map."""
        for pin in self._pins:
            self.interface._impl.remove_pin(pin)
        self._pins = set()

__iter__()

Return an iterator over the pins on the map.

Source code in core/src/toga/widgets/mapview.py
90
91
92
def __iter__(self) -> Iterator[MapPin]:
    """Return an iterator over the pins on the map."""
    return iter(self._pins)

__len__()

Return the number of pins being displayed.

Source code in core/src/toga/widgets/mapview.py
94
95
96
def __len__(self) -> int:
    """Return the number of pins being displayed."""
    return len(self._pins)

add(pin)

Add a new pin to the map.

:param pin: The toga.MapPin instance to add.

Source code in core/src/toga/widgets/mapview.py
 98
 99
100
101
102
103
104
105
def add(self, pin: MapPin) -> None:
    """Add a new pin to the map.

    :param pin: The [`toga.MapPin`][] instance to add.
    """
    pin.interface = self.interface
    self._pins.add(pin)
    self.interface._impl.add_pin(pin)

clear()

Remove all pins from the map.

Source code in core/src/toga/widgets/mapview.py
116
117
118
119
120
def clear(self) -> None:
    """Remove all pins from the map."""
    for pin in self._pins:
        self.interface._impl.remove_pin(pin)
    self._pins = set()

remove(pin)

Remove a pin from the map.

:param pin: The toga.MapPin instance to remove.

Source code in core/src/toga/widgets/mapview.py
107
108
109
110
111
112
113
114
def remove(self, pin: MapPin) -> None:
    """Remove a pin from the map.

    :param pin: The  [`toga.MapPin`][] instance to remove.
    """
    self.interface._impl.remove_pin(pin)
    self._pins.remove(pin)
    pin.interface = None

Bases: Protocol

Source code in core/src/toga/widgets/mapview.py
123
124
125
126
127
128
129
130
class OnSelectHandler(Protocol):
    def __call__(self, widget: MapView, *, pin: MapPin, **kwargs: Any) -> None:
        """A handler that will be invoked when the user selects a map pin.

        :param widget: The MapView that was selected.
        :param pin: The pin that was selected.
        :param kwargs: Ensures compatibility with arguments added in future versions.
        """

__call__(widget, *, pin, **kwargs)

A handler that will be invoked when the user selects a map pin.

:param widget: The MapView that was selected. :param pin: The pin that was selected. :param kwargs: Ensures compatibility with arguments added in future versions.

Source code in core/src/toga/widgets/mapview.py
124
125
126
127
128
129
130
def __call__(self, widget: MapView, *, pin: MapPin, **kwargs: Any) -> None:
    """A handler that will be invoked when the user selects a map pin.

    :param widget: The MapView that was selected.
    :param pin: The pin that was selected.
    :param kwargs: Ensures compatibility with arguments added in future versions.
    """