πŸ›Έ Callbacks

How to attach callbacks to a map

Callbacks are used to execute functions when you click on a map or press a key on the keyboard**.

There are many useful Pre-defined callbacks, but it is also possible to define Custom callbacks and attach them to the map.

  • To attach a pre-defined callback to a Maps object, use:

    m.cb.< EVENT CATEGORY >.attach.< CALLBACK NAME >( **kwargs )
    
  • To attach a custom callback to a Maps object, use:

    def my_callback(**kwargs):
        ...
    
    m.cb.< EVENT CATEGORY >.attach(my_callback, **kwargs )
    

The < EVENT CATEGORY > hereby specifies the event that will trigger the callback:

click

Callbacks that are executed if you click anywhere on the Map.

pick

Callbacks that identify the nearest datapoint(s) if you click on the map.

move

Callbacks that are executed if you press a key on the keyboard.

keypress

Callbacks that are executed if you move the mouse without holding down a button.

Callbacks are layer sensitive!

Callbacks are only executed if the layer of the associated Maps object is visible!

To define callbacks that are executed independent of the visible layer, you have the following possibilities:

  • Attach the callbacks to the "all" layer using something like:
    m.all.cb.click.attach.annotate()
  • Execute callbacks of an event category independent of the visible layer by setting:
    m.cb.< EVENT CATEGORY >.set_execute_on_all_layers(True)
from eomaps import Maps
import numpy as np
x, y = np.mgrid[-45:45, 20:60]

m = Maps(Maps.CRS.Orthographic())
m.all.add_feature.preset.coastline()
m.set_data(data=x+y**2, x=x, y=y, crs=4326)
m.plot_map()

m2 = m.new_layer(inherit_data=True, layer="second_layer")
m2.plot_map(cmap="tab10")

# get an annotation if you RIGHT-click anywhere on the map
m.cb.click.attach.annotate(xytext=(-60, -60),
                           bbox=dict(boxstyle="round", fc="r"))

# pick the nearest datapoint if you click on the MIDDLE mouse button
m.cb.pick.attach.annotate(button=2)
m.cb.pick.attach.mark(buffer=1, permanent=False, fc="none", ec="r", button=2)
m.cb.pick.attach.mark(buffer=4, permanent=False, fc="none", ec="r", button=2)

# peek at the second layer if you LEFT-click on the map
m.cb.click.attach.peek_layer("second_layer", how=.25, button=3)
_images/simple_callbacks.gif

In addition, each callback-container supports the following useful methods:

attach

Attach custom or pre-defined callbacks to the map.

remove

Remove previously attached callbacks from the map.

get

Accessor for objects generated/retrieved by callbacks.

share_events

Share callback-events between this Maps-object and other Maps-objects.

forward_events

Forward callback-events from this Maps-object to other Maps-objects.

add_temporary_artist

Make an artist temporary (remove it from the map at the next event).

set_sticky_modifiers

Define keys on the keyboard that should be treated as "sticky modifiers".

set_execute_during_toolbar_action

Set if callbacks should be executed during a toolbar action (e.g. pan/zoom).

set_execute_on_all_layers

If True, callbacks of this container are executed even if the associated layer is not visible.

Using callbacks with the companion-widget

Some of the most commonly used pre-defined callbacks are integrated in the 🧰 Companion Widget.

  • peek layer callbacks to interactively compare layers

  • basic click/pick callbacks to get information on the clicked point

_images/widget_callbacks.png

Pre-defined callbacks

Pre-defined click, pick and move callbacks

Callbacks that can be used with m.cb.click, m.cb.pick and m.cb.move:

peek_layer

Overlay a part of the map with a different layer if you click on the map.

annotate

Add a text-annotation to the plot at the position where the map was clicked.

mark

Draw markers at the location where the map was clicked.

print_to_console

Print details on the clicked pixel to the console.

Callbacks that can be used with m.cb.click and m.cb.pick:

get_values

Successively collect return-values in a dict.

clear_annotations

Remove all temporary and permanent annotations from the plot.

clear_markers

Remove all temporary and permanent annotations from the plot.

Callbacks that can be used only with m.cb.pick:

load

Load objects from a given database using the ID of the picked pixel.

highlight_geometry

Temporarily highlite the picked geometry of a GeoDataFrame.

Pre-defined keypress callbacks

Callbacks that can be used with m.cb.keypress

switch_layer

Set the currently visible layer of the map.

overlay_layer

Toggle displaying a layer on top of the currently visible layers.

fetch_layers

Fetch (and cache) layers of a map.

Custom callbacks

Custom callback functions can be attached to the map via:

m.cb.< EVENT >.attach(< CALLBACK FUNCTION >, **kwargs )

The < CALLBACK FUNCTION > must accept the following keyword-arguments:

  • ID: The ID of the picked data point

    • The index-value if a pandas.DataFrame is used as data

    • The (flattened) numerical index if a list or numpy.array is used as data

  • ind: The (flattened) numerical index (even if pandas.DataFrames are used)

  • pos: The coordinates of the picked data point in the crs of the plot

  • val: The value of the picked data point

  • val_color: The color of the picked data point

from eomaps import Maps
def some_callback(custom_kwarg, **kwargs):
    print("\n-------------------------------------------------------")
    print("The value of 'custom_kwarg' is", custom_kwarg)
    print("The position of the clicked pixel in plot-coordinates is", kwargs["pos"])
    print("The dataset-index of the nearest datapoint is", kwargs["ID"])
    print("The data-value of the nearest datapoint is", kwargs["val"])
    print("The color of the nearest datapoint is", kwargs["val_color"])
    print("The numerical index of the nearest datapoint is", kwargs["ind"])

# attaching custom callbacks works completely similar for "click", "pick", "move" and "keypress"!
m = Maps()
m.set_data([1, 2, 3], [10, 20, 30], [10, 20, 30])
m.plot_map()

m.cb.pick.attach.annotate()
m.cb.pick.attach(some_callback, button=1, custom_kwarg=123)

Note

  • ❗ for click callbacks, ID, ind, val and val_color are set to None!

  • ❗ for keypress callbacks, ID, ind, pos ,``val`` and val_color are set to None!

For better readability it is recommended that you β€œunpack” used arguments like this:

from eomaps import Maps

def cb(ID, val, **kwargs):
    print(f"the ID is {ID} and the value is {val}")

m = Maps()
m.set_data([1, 2, 3], [10, 20, 30], [10, 20, 30])
m.plot_map()
m.cb.pick.attach(cb)

Keypress modifiers

It is possible to trigger pick, click or move callbacks only if a specific key is pressed on the keyboard.

This is achieved by specifying a modifier when attaching a callback, e.g.:

from eomaps import Maps
m = Maps()
m.add_feature.preset.coastline()
# a callback that is executed if NO modifier is pressed
m.cb.move.attach.mark(radius=5)
# a callback that is executed if 1 is pressed while moving the mouse
m.cb.move.attach.mark(modifier="1", radius=10, fc="r", ec="g")
# a callback that is executed if 2 is pressed while moving the mouse
m.cb.move.attach.mark(modifier="2", radius=15, fc="none", ec="b")

To keep the last pressed modifier active until a new modifier is activated, you can make it β€œsticky” by using m.cb.move.set_sticky_modifiers().

  • β€œSticky modifiers” remain activated until

    • A new (sticky) modifier is activated

    • ctrl + <current (sticky) modifier> is pressed

    • escape is pressed

NOTE: sticky modifiers are defined for each callback method individually! (e.g. sticky modifiers are unique for click, pick and move callbacks)

from eomaps import Maps
m = Maps()
m.add_feature.preset.coastline()

# a callback that is executed if 1 is pressed while clicking on the map
m.cb.click.attach.annotate(modifier="1", text="modifier 1 active")
# a callback that is executed if 2 is pressed while clicking on the map
m.cb.click.attach.annotate(modifier="2", text="modifier 2 active")

# make the modifiers 1 and 2 sticky for click callbacks
m.cb.click.set_sticky_modifiers("1", "2")

# note that the modifier 1 is not sticky for move callbacks!
# m.cb.move.set_sticky_modifiers("1")  # (uncomment to make it sticky)
m.cb.move.attach.mark(radius=5)
m.cb.move.attach.mark(modifier="1", radius=5, fc="r")

Picking N nearest neighbours

[requires EOmaps >= 5.4]

By default pick-callbacks pick the nearest data point with respect to the click position.

To customize the picking-behavior, use m.cb.pick.set_props(). The following properties can be adjusted:

  • n: The (maximum) number of data points to pick within the search-circle.

  • search_radius: The radius of a circle (in units of the plot-crs) that is used to identify the nearest neighbours.

  • pick_relative_to_closest: Set the center of the search-circle.

    • If True, the nearest neighbours are searched relative to the closest identified data point.

    • If False, the nearest neighbours are searched relative to the click position.

  • consecutive_pick: Pick data points individually or altogether.

    • If True, callbacks are executed for each picked point individually

    • If False, callbacks are executed only once and get lists of all picked values as input-arguments.

set_props

Set the picker-properties (number of picked points, max.

from eomaps import Maps
import numpy as np

# create some random data
x, y = np.mgrid[-30:67, -12:50]
data = np.random.randint(0, 100, x.shape)

# a callback to indicate the search-radius
def indicate_search_radius(m, pos, *args, **kwargs):
    art = m.add_marker(
        xy=(np.atleast_1d(pos[0])[0],
            np.atleast_1d(pos[1])[0]),
        shape="ellipses", radius=m.tree.d, radius_crs="out",
        n=100, fc="none", ec="k", lw=2)
    m.cb.pick.add_temporary_artist(art)

# a callback to set the number of picked neighbours
def pick_n_neighbours(m, n, **kwargs):
    m.cb.pick.set_props(n=n)


m = Maps()
m.add_feature.preset.coastline()
m.set_data(data, x, y)
m.plot_map()
m.cb.pick.set_props(n=50, search_radius=10, pick_relative_to_closest=True)

m.cb.pick.attach.annotate()
m.cb.pick.attach.mark(fc="none", ec="r")
m.cb.pick.attach(indicate_search_radius, m=m)

for key, n in (("1", 1), ("2", 9), ("3", 50), ("4", 500)):
    m.cb.keypress.attach(pick_n_neighbours, key=key, m=m, n=n)
_images/pick_multi.gif

Picking a dataset without plotting it first

It is possible to attach pick callbacks to a Maps object without plotting the data first by using Maps.make_dataset_pickable().

from eomaps import Maps
m = Maps()
m.set_extent((0, 40, 30, 70))
m.add_feature.preset.coastline()
m.set_data([1, 2, 3], [10, 20, 30], [40, 50, 60])
m.make_dataset_pickable()
# now it's possible to attach pick-callbacks even though the data is still "invisible"
m.cb.pick.attach.annotate()

Note

Using make_dataset_pickable() is ONLY necessary if you want to use pick callbacks without actually plotting the data! Otherwise a call to Maps.plot_map() is sufficient!

Maps.make_dataset_pickable

Make the associated dataset pickable without plotting it first.