Component Apps: Mapping with Popups and Overlays

Important

These recipes only apply to Component App development and will not work for Standard Apps.

Last Updated: December 2025

Marker Overlay

@App.page
def map_overlays(lib):
    static_position = lib.utils.transform_coordinate([48.208889, 16.3725], "EPSG:4326", "EPSG:3857")

    return lib.tethys.Display(
        lib.html.div(
            id_="marker",  # id_ is essential, as it's referenced in the associated "element" attribute below
            style=lib.Style(
                width="20px",
                height="20px",
                border="1px solid #088",
                border_radius="10px",
                background_color="#0FF",
                opacity="0.5",
            )
        ),
        lib.tethys.Map(
            lib.ol.Overlay(
                options=lib.Props(
                    stopEvent=False
                ),
                position=static_position,
                positioning="center-center",
                element="marker" # The id_ of the above component to be used for this Overlay
            ),
        )
    )

Combining All Previous Examples

@App.page
def map_overlays(lib):
    position, set_position = lib.hooks.use_state(None)
    static_position = lib.utils.transform_coordinate([48.208889, 16.3725], "EPSG:4326", "EPSG:3857")

    return lib.tethys.Display(
        lib.html.a(
            id_="vienna",  # id_ is essential, as it's referenced in the associated "element" attribute below
            className="overlay",
            target="_blank",
            href="https://en.wikipedia.org/wiki/Vienna",
            style=lib.Style(
                text_decoration=None,
                color="white",
                font_size="11pt",
                font_weight="bold",
                text_shadow="black 0.1em 0.1em 0.2em",
            )
        )("Vienna"),
        lib.html.div(
            id_="marker",  # id_ is essential, as it's referenced in the associated "element" attribute below
            style=lib.Style(
                width="20px",
                height="20px",
                border="1px solid #088",
                border_radius="10px",
                background_color="#0FF",
                opacity="0.5",
            )
        ),
        lib.html.div(
            id_="dynamic",  # id_ is essential, as it's referenced in the associated "element" attribute below
            style=lib.Style(
                position="relative",
            ),
            hidden=position is None
        )(
            lib.html.div(
                style=lib.Style(
                    position="absolute",
                    left="-6px",
                    width=0,
                    height=0,
                    border_left="6px solid transparent",   # Controls the width of the triangle
                    border_right="6px solid transparent",  # Controls the width of the triangle
                    border_bottom="6px solid #ff0000",
                )
            ),
            lib.html.div(
                style=lib.Style(
                    position="absolute",
                    left="-6px",
                    top="6px",
                    width="200px",
                    background_color="lightblue",
                    padding="1em",
                    border="1px black solid"
                )
            )(
                lib.icons.XCircle(
                    style=lib.Style(
                        position="absolute",
                        right="10px",
                        top="5px",
                        font_weight="bold"
                    ),
                    onClick=lambda _: set_position(None)
                ),
                lib.html.div(
                    f"You clicked at: {", ".join(map(str, position))}"
                ) if position else None
            )
        ),
        lib.tethys.Map(
            onClick=lambda e: set_position(e.coordinate)
        )(
            lib.ol.Overlay(
                options=lib.Props(
                    stopEvent=False
                ),
                position=static_position,
                element="vienna"  # The id_ of the above component to be used for this Overlay
            ),
            lib.ol.Overlay(
                options=lib.Props(
                    stopEvent=False
                ),
                position=static_position,
                positioning="center-center",
                element="marker" # The id_ of the above component to be used for this Overlay
            ),
            lib.ol.Overlay(
                position=position or [0,0],
                element="dynamic" # The id_ of the above component to be used for this Overlay
            )
        )
    )

Display Feature Props On Click

@App.page
def display_feature_props_on_click(lib):
    props, set_props = lib.hooks.use_state({})
    return lib.tethys.Display(
        lib.tethys.Panel(title="Properties", show=len(props) > 0, on_close=lambda e: set_props({}), extent="300px")(
            *[
                lib.html.div(
                    lib.html.span(
                        lib.html.b(f"{k}: ")
                    ),
                    lib.html.span(v)
                ) for k, v in props.items()
            ]
        ),
        lib.tethys.Map(
            lib.ol.layer.Vector(
                onClick=lambda e: set_props(e.features[0] if e.features else lib.Props(Message="No features found"))
            )(
                lib.ol.source.Vector(
                    options=lib.Props(
                        url="https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson",
                        format_="GeoJSON"
                    )
                )
            )
        )
    )