Tile Selector
pick map tiles by clicking or shift-dragging, and round-trip them to Python.
---
.. llms_copy::Tile Selector
.. toc::
Overview
dl2.TileSelector is a map control that turns the map into a tile picker. While it is armed the cursor becomes a crosshair, a dashed outline tracks the tile under the pointer at the current zoom, and:
- click a tile to add or remove it from the selection
- shift-drag a box to capture every tile inside it
Place it as a child of dl2.Map, anywhere among the other layers.
The data boundary
Each selected tile round-trips to Python as a dict:
``python { "z": 11, "x": 470, "y": 843, "url": "https://tile.openstreetmap.org/11/470/843.png", "bounds": [south, west, north, east], } ``
tileUrl decides which template those url values are built from — it does not have to match the dl2.TileLayer you are displaying. The demo below renders CARTO light tiles but hands back OpenStreetMap PNG URLs, which is the usual shape when the map is a picker for some other tileset.
Selections are keyed by z/x/y, so panning and zooming never lose them.
Props
| Prop | Type | Default | What it does | |------|------|---------|--------------| | selectedTiles | list of dicts | [] | The selection. [MUTABLE] — an output (user clicks) *and* an input (write [] to clear). | | tileUrl | string | OSM {z}/{x}/{y} | Template the returned url values are built from. | | position | string | 'topleft' | topleft / topright / bottomleft / bottomright. | | hoverColor | string | '#fa5252' | Colour of the dashed outline under the cursor. | | selectedColor | string | '#228be6' | Stroke and fill of selected-tile rectangles and the box-drag preview. |
Because selectedTiles is [MUTABLE], a plain callback both reads the user's picks and pushes new state back — the Clear button in the demo is one line.
Live demo
.. exec::docs.tile-selector.example :code: false
Source
```python
File: docs/tile-selector/example.py
""" TileSelector — pick tiles off the map by clicking or shift-dragging.
dl2.TileSelector is a map control. While it is armed the cursor becomes a crosshair, a dashed outline tracks the tile under the pointer at the current zoom, and:
• click a tile to add / remove it from the selection • shift-drag a box to capture every tile inside it
Each selected tile round-trips to Python as {z, x, y, url, bounds}, where bounds is [south, west, north, east]. Selections are keyed by z/x/y, so they survive panning and zooming.
selectedTiles is [MUTABLE] — it is both an output (the user's clicks) and an input (the Clear button below writes [] straight back into it). """
import dash_leaflet2 as dl2 import dash_mantine_components as dmc from dash import Input, Output, callback, html, no_update from dash_iconify import DashIconify
from dl2_tiles import USGS_TOPO, register_theme_swap from dl2_locations import AUSTIN from dl2_shared import code_panel, info_panel
Basemap pair for this page — dl2_tiles owns the light/dark wiring.
TILES = USGS_TOPO
The template the PICKED tile URLs are built from. Deliberately not the
basemap: this page's point is that the two are independent.
OSM = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
CODE = """import dash_leaflet2 as dl2
dl2.Map( center=[30.2672, -97.7431], zoom=11, children=[ dl2.TileLayer(url=OSM), dl2.TileSelector( id="tile-picker", position="topright", tileUrl=OSM, # which tileset the picked URLs point at hoverColor="#fa5252", # dashed outline under the cursor selectedColor="#228be6", ), ], )
@callback(Output("out", "children"), Input("tile-picker", "selectedTiles")) def show(tiles): return f"{len(tiles or [])} tiles selected" """
def _tile_row(tile): """One selected tile as a table row: z/x/y, bounds, and its URL.""" s, w, n, e = tile.get("bounds") or [0, 0, 0, 0] return dmc.TableTr( [ dmc.TableTd( dmc.Code(f"{tile['z']}/{tile['x']}/{tile['y']}"), ), dmc.TableTd( dmc.Text(f"{s:.4f}, {w:.4f} → {n:.4f}, {e:.4f}", size="xs", c="dimmed", ff="monospace"), ), dmc.TableTd( dmc.Anchor( dmc.Group( [DashIconify(icon="tabler:photo", width=14), dmc.Text("PNG", size="xs")], gap=4, ), href=tile.get("url", "#"), target="_blank", ), style={"textAlign": "center"}, ), ] )
component is the name the .. exec:: directive looks for.
component = dmc.Stack( [ dmc.Alert( dmc.Stack( [ dmc.Text( "Click the crosshair button in the map's top-right corner to arm " "the selector, then click tiles — or hold Shift and drag a box.", size="sm", ), dmc.Text( "Zoom changes the tile grid; already-picked tiles stay picked " "because each one is keyed by z/x/y.", size="sm", c="dimmed", ), ], gap=4, ), title="How to use it", color="blue", variant="light", icon=DashIconify(icon="tabler:hand-click"), ), dmc.Paper( dl2.Map( id="ts-map", center=AUSTIN.center, zoom=11, style={"height": "58vh", "width": "100%"}, children=[ dl2.TileLayer(id="ts-tile", **TILES.kwargs("light")), dl2.TileSelector( id="ts-picker", position="topright", # The URLs handed back in selectedTiles point at THIS # template — it does not have to be the tileset you are # displaying. Here we show USGS Topo but hand back OSM PNGs. tileUrl=OSM, hoverColor="#fa5252", selectedColor="#228be6", ), ], ), shadow="sm", radius="md", withBorder=True, style={"overflow": "hidden"}, ), dmc.Group( [ dmc.Button( "Clear selection", id="ts-clear", variant="light", color="red", leftSection=DashIconify(icon="tabler:trash", width=16), ), html.Div(id="ts-count"), ], justify="space-between", align="center", ), info_panel("Selected tiles", html.Div(id="ts-table")), code_panel("Usage", CODE), ], gap="md", )
@callback( Output("ts-count", "children"), Output("ts-table", "children"), Input("ts-picker", "selectedTiles"), ) def show_selection(tiles): tiles = tiles or [] badge = dmc.Badge( f"{len(tiles)} tile{'' if len(tiles) == 1 else 's'} selected", color="blue" if tiles else "gray", variant="light", size="lg", ) if not tiles: return badge, dmc.Text( "Nothing selected yet — arm the control and click a tile.", c="dimmed", size="sm", )
table = dmc.Table( [ dmc.TableThead( dmc.TableTr( [ dmc.TableTh("z/x/y"), dmc.TableTh("bounds (S, W → N, E)"), dmc.TableTh("tile", style={"textAlign": "center"}), ] ) ), # Newest first, and capped — a shift-drag at low zoom can select a # lot of tiles and this is a doc page, not a data grid. dmc.TableTbody([_tile_row(t) for t in list(reversed(tiles))[:25]]), ], striped=True, highlightOnHover=True, ) footer = ( dmc.Text(f"Showing the 25 most recent of {len(tiles)}.", size="xs", c="dimmed", mt="xs") if len(tiles) > 25 else None ) return badge, dmc.Stack([table, footer], gap=0)
@callback( Output("ts-picker", "selectedTiles"), Input("ts-clear", "n_clicks"), prevent_initial_call=True, ) def clear_selection(n_clicks): """Python → map: writing the [MUTABLE] prop clears the component's state.""" return [] if n_clicks else no_update
Light/dark basemap, driven off the color-scheme store.
register_theme_swap("ts-tile", TILES) ```
:defaultExpanded: false :withExpandedButton: true
---
*Source: /tile-selector*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /tile-selector/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt