Map#
Display interactive maps in your Flet apps with markers, overlays, and rich attributions provided by the flet-map extension. The control is built on top of flutter_map and supports multiple tile providers and layers.
Platform Support#
| Platform | Windows | macOS | Linux | iOS | Android | Web |
|---|---|---|---|---|---|---|
| Supported | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Usage#
Add the flet-map package to your project dependencies:
Important
Different tile providers have their own usage policies. Make sure you fully comply with their requirements (ex: attribution, rate limits) when using them in your app, to avoid being blocked or facing legal issues. More details here.
Examples#
Basic#
import flet as ft
import flet_map as ftm
def main(page: ft.Page):
page.add(
ft.SafeArea(
content=ftm.Map(
expand=True,
layers=[
ftm.TileLayer(
url_template="https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png",
on_image_error=lambda e: print(f"TileLayer Error: {e.data}"),
),
],
),
)
)
ft.run(main)
Camera Controls#
import flet as ft
import flet_map as ftm
def main(page: ft.Page):
page.padding = 16
async def update_camera_status(trigger: str):
camera = await my_map.get_camera()
camera_status.value = (
f"Camera [{trigger}]: "
f"center=({camera.center.latitude:.5f}, {camera.center.longitude:.5f}), "
f"zoom={camera.zoom:.2f}, rotation={camera.rotation:.1f}"
)
page.update()
async def zoom_in(e: ft.Event[ft.Button]):
await my_map.zoom_in()
await update_camera_status("zoom_in")
async def zoom_out(e: ft.Event[ft.Button]):
await my_map.zoom_out()
await update_camera_status("zoom_out")
async def rotate_plus_15(e: ft.Event[ft.Button]):
await my_map.rotate_from(15)
await update_camera_status("rotate_from(+15)")
async def reset_rotation(e: ft.Event[ft.Button]):
await my_map.reset_rotation()
await update_camera_status("reset_rotation")
async def center_berlin(e: ft.Event[ft.Button]):
await my_map.center_on(point=ftm.MapLatitudeLongitude(52.52, 13.405), zoom=12)
await update_camera_status("center_on(berlin)")
async def move_tokyo(e: ft.Event[ft.Button]):
await my_map.move_to(
destination=ftm.MapLatitudeLongitude(35.6762, 139.6503), zoom=11
)
await update_camera_status("move_to(tokyo)")
async def set_world_zoom(e: ft.Event[ft.Button]):
await my_map.zoom_to(3)
await update_camera_status("zoom_to(3)")
page.appbar = ft.AppBar(title="Camera controls")
page.add(
ft.Column(
expand=True,
controls=[
ft.Text(
"Use buttons to control map camera programmatically.",
size=12,
),
ft.Row(
wrap=True,
spacing=8,
run_spacing=8,
controls=[
ft.Button("Zoom in", on_click=zoom_in),
ft.Button("Zoom out", on_click=zoom_out),
ft.Button("Rotate +15°", on_click=rotate_plus_15),
ft.Button("Reset rotation", on_click=reset_rotation),
ft.Button("Center Berlin", on_click=center_berlin),
ft.Button("Move to Tokyo", on_click=move_tokyo),
ft.Button("World zoom (3)", on_click=set_world_zoom),
],
),
camera_status := ft.Text(selectable=True, font_family="monospace"),
my_map := ftm.Map(
expand=True,
initial_center=ftm.MapLatitudeLongitude(52.52, 13.405),
initial_zoom=5,
layers=[
ftm.TileLayer(
url_template="https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png"
),
ftm.SimpleAttribution(
text="OpenStreetMap contributors",
on_click=lambda e: e.page.launch_url(
"https://www.openstreetmap.org/copyright"
),
),
ftm.MarkerLayer(
markers=[
ftm.Marker(
coordinates=ftm.MapLatitudeLongitude(52.52, 13.405),
content=ft.Icon(ft.Icons.LOCATION_ON),
),
ftm.Marker(
coordinates=ftm.MapLatitudeLongitude(
35.6762, 139.6503
),
content=ft.Icon(ft.Icons.LOCATION_ON),
),
]
),
],
),
],
)
)
ft.run(main)
Idle Camera#
import flet as ft
import flet_map as ftm
IDLE_EVENT_TYPES = {
ftm.MapEventType.MOVE_END,
ftm.MapEventType.FLING_ANIMATION_END,
ftm.MapEventType.FLING_ANIMATION_NOT_STARTED,
ftm.MapEventType.DOUBLE_TAP_ZOOM_END,
ftm.MapEventType.ROTATE_END,
}
def main(page: ft.Page):
page.padding = 16
async def handle_map_event(e: ftm.MapEvent):
last_event.value = (
"Last event: "
f"type={e.event_type}, source={e.source.value}, zoom={e.camera.zoom:.2f}"
)
if e.event_type in IDLE_EVENT_TYPES: # here the camera is settled/idle
camera = await e.control.get_camera()
settled_camera.value = (
"Settled camera: "
f"center=({camera.center.latitude:.3f}, "
f"{camera.center.longitude:.3f}), "
f"zoom={camera.zoom:.2f}, rotation={camera.rotation:.1f}, "
f"trigger={e.event_type}"
)
page.update()
page.add(
ft.Column(
expand=True,
controls=[
ft.Text("Camera idle pattern", size=20, weight=ft.FontWeight.BOLD),
ft.Text(
"Drag, fling, rotate, zoom and watch when the camera is settled.",
size=12,
),
last_event := ft.Text(
"Last event: -", selectable=True, font_family="monospace"
),
settled_camera := ft.Text(
"Settled camera: -", selectable=True, font_family="monospace"
),
ftm.Map(
expand=True,
initial_center=ftm.MapLatitudeLongitude(
latitude=52.52, longitude=13.405
),
initial_zoom=11,
on_event=handle_map_event,
interaction_configuration=ftm.InteractionConfiguration(
flags=ftm.InteractionFlag.ALL
),
layers=[
ftm.TileLayer(
url_template="https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png"
),
ftm.SimpleAttribution(
text="OpenStreetMap contributors",
on_click=lambda e: e.page.launch_url(
"https://www.openstreetmap.org/copyright"
),
),
],
),
],
)
)
ft.run(main)
Interaction Flags#
import flet as ft
import flet_map as ftm
FLAG_OPTIONS: list[tuple[str, ftm.InteractionFlag]] = [
("Drag", ftm.InteractionFlag.DRAG),
("Fling animation", ftm.InteractionFlag.FLING_ANIMATION),
("Pinch move", ftm.InteractionFlag.PINCH_MOVE),
("Pinch zoom", ftm.InteractionFlag.PINCH_ZOOM),
("Double tap zoom", ftm.InteractionFlag.DOUBLE_TAP_ZOOM),
("Double tap drag zoom", ftm.InteractionFlag.DOUBLE_TAP_DRAG_ZOOM),
("Scroll wheel zoom", ftm.InteractionFlag.SCROLL_WHEEL_ZOOM),
("Rotate", ftm.InteractionFlag.ROTATE),
]
def main(page: ft.Page):
page.padding = 16
def get_selected_flags() -> ftm.InteractionFlag:
flags = ftm.InteractionFlag.NONE
for checkbox in checkboxes:
if checkbox.value:
flags |= checkbox.data
return flags
def update_interaction_flags(e: ft.Event[ft.Checkbox] = None):
flags = get_selected_flags()
my_map.interaction_configuration = ftm.InteractionConfiguration(flags=flags)
page.update()
def handle_map_event(e: ftm.MapEvent):
event_type = e.event_type.value if e.event_type else "-"
last_event.value = (
"Last event: "
f"type={event_type}, source={e.source.value}, zoom={e.camera.zoom:.2f}"
)
page.update()
checkboxes = [
ft.Checkbox(
label=label,
value=True,
data=flag,
on_change=update_interaction_flags,
)
for label, flag in FLAG_OPTIONS
]
my_map = ftm.Map(
expand=True,
initial_center=ftm.MapLatitudeLongitude(latitude=52.52, longitude=13.405),
initial_zoom=11,
on_event=handle_map_event,
interaction_configuration=ftm.InteractionConfiguration(
flags=ftm.InteractionFlag.ALL
),
layers=[
ftm.TileLayer(
url_template="https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png",
on_image_error=lambda e: print(f"TileLayer Error: {e.data}"),
),
ftm.RichAttribution(
attributions=[
ftm.TextSourceAttribution(
text="OpenStreetMap contributors",
on_click=lambda e: e.page.launch_url(
"https://www.openstreetmap.org/copyright"
),
)
]
),
],
)
page.appbar = ft.AppBar(title="Interaction flags")
page.add(
ft.Column(
expand=True,
controls=[
ft.Text(
"Toggle flags and try dragging, zooming, rotating, and scrolling.",
size=12,
),
ft.ResponsiveRow(
controls=[
ft.Container(col={"sm": 6, "md": 4}, content=c)
for c in checkboxes
]
),
last_event := ft.Text(
"Last event: -", selectable=True, font_family="monospace"
),
ft.Container(expand=True, content=my_map),
],
)
)
update_interaction_flags()
ft.run(main)
Multiple Layers#
import random
import flet as ft
import flet_map as ftm
def main(page: ft.Page):
def handle_tap(e: ftm.MapTapEvent):
if e.name == "tap":
marker_layer.markers.append(
ftm.Marker(
content=ft.Icon(
ft.Icons.LOCATION_ON, color=ft.CupertinoColors.DESTRUCTIVE_RED
),
coordinates=e.coordinates,
)
)
elif e.name == "secondary_tap":
circle_layer.circles.append(
ftm.CircleMarker(
radius=random.randint(5, 10),
coordinates=e.coordinates,
color=ft.Colors.random(),
border_color=ft.Colors.random(),
border_stroke_width=4,
)
)
page.update()
page.appbar = ft.AppBar(title="Multiple Layers")
page.add(
ft.Text("Click anywhere to add a Marker, right-click to add a CircleMarker."),
ftm.Map(
expand=True,
initial_center=ftm.MapLatitudeLongitude(15, 10),
initial_zoom=4.2,
interaction_configuration=ftm.InteractionConfiguration(
flags=ftm.InteractionFlag.ALL
),
on_tap=handle_tap,
on_secondary_tap=handle_tap,
on_event=print,
layers=[
ftm.TileLayer(
url_template="https://tile.openstreetmap.org/{z}/{x}/{y}.png",
on_image_error=lambda e: print("TileLayer Error"),
),
ftm.RichAttribution(
attributions=[
ftm.TextSourceAttribution(
text="OpenStreetMap Contributors",
on_click=lambda e: e.page.launch_url(
"https://www.openstreetmap.org/copyright"
),
),
ftm.TextSourceAttribution(
text="Flet",
on_click=lambda e: e.page.launch_url("https://flet.dev"),
),
]
),
ftm.SimpleAttribution(
text="Flet",
alignment=ft.Alignment.TOP_RIGHT,
on_click=lambda e: print("Clicked SimpleAttribution"),
),
marker_layer := ftm.MarkerLayer(
markers=[
ftm.Marker(
content=ft.Icon(ft.Icons.LOCATION_ON),
coordinates=ftm.MapLatitudeLongitude(30, 15),
),
ftm.Marker(
content=ft.Icon(ft.Icons.LOCATION_ON),
coordinates=ftm.MapLatitudeLongitude(10, 10),
),
ftm.Marker(
content=ft.Icon(ft.Icons.LOCATION_ON),
coordinates=ftm.MapLatitudeLongitude(25, 45),
),
],
),
circle_layer := ftm.CircleLayer(
circles=[
ftm.CircleMarker(
radius=10,
coordinates=ftm.MapLatitudeLongitude(16, 24),
color=ft.Colors.RED,
border_color=ft.Colors.BLUE,
border_stroke_width=4,
),
],
),
ftm.PolygonLayer(
polygons=[
ftm.PolygonMarker(
label="Popular Touristic Area",
label_text_style=ft.TextStyle(
color=ft.Colors.BLACK,
size=15,
weight=ft.FontWeight.BOLD,
),
color=ft.Colors.with_opacity(0.3, ft.Colors.BLUE),
coordinates=[
ftm.MapLatitudeLongitude(10, 10),
ftm.MapLatitudeLongitude(30, 15),
ftm.MapLatitudeLongitude(25, 45),
],
),
],
),
ftm.PolylineLayer(
polylines=[
ftm.PolylineMarker(
border_stroke_width=3,
border_color=ft.Colors.RED,
gradient_colors=[ft.Colors.BLACK, ft.Colors.BLACK],
color=ft.Colors.with_opacity(0.6, ft.Colors.GREEN),
coordinates=[
ftm.MapLatitudeLongitude(10, 10),
ftm.MapLatitudeLongitude(30, 15),
ftm.MapLatitudeLongitude(25, 45),
],
),
],
),
],
),
)
ft.run(main)
Reference#
Map- Layers:
TileLayer,MarkerLayer,CircleLayer,PolygonLayer,PolylineLayer - Markers and overlays:
Marker,CircleMarker,PolygonMarker,PolylineMarker - Attributions:
SimpleAttribution,RichAttribution,SourceAttribution
See the types section for additional configuration helpers.