โš™ Usage๏ƒ

๐Ÿš€ Basics๏ƒ

_images/intro.png

๐ŸŒ Initialization of Maps objects๏ƒ

EOmaps is all about Maps objects.
The first Maps object that is created will initialize a matplotlib.Figure

and a cartopy.GeoAxes for a map.

from eomaps import Maps
m = Maps(crs=4326,               # create a Maps-object with a map in lon/lat (epsg=4326) projection
    layer="first layer",         # assign the layer "first_layer"
    figsize=(7, 5))              # set the figure-size to 7x5
m.set_extent((-25, 35, 25, 70))  # set the extent of the map
m.add_feature.preset.coastline() # add coastlines to the map
  • crs represents the projection used for plotting

  • layer represents the name of the layer associated with the Maps-object (see โ–ค Layers).

  • all additional keyword arguments are forwarded to the creation of the matplotlib-figure (e.g.: figsize, frameon, edgecolor etc).

Possible ways for specifying the crs for plotting are:

  • If you provide an integer, it is identified as an epsg-code (e.g. 4326, 3035, etc.)

    • 4326 defaults to PlateCarree projection

  • All other CRS usable for plotting are accessible via Maps.CRS, e.g.: crs=Maps.CRS.Orthographic(), crs=Maps.CRS.Equi7_EUโ€ฆ

    • Maps.CRS is just an accessor for cartopy.crs

    • For a full list of available projections see: Cartopy projections

Maps

The base-class for generating plots with EOmaps.

Maps.CRS

The crs module defines Coordinate Reference Systems and the transformations between them.

Maps.set_extent

Set the extent (x0, x1, y0, y1) of the map in the given coordinate system.

โ–ค Layers๏ƒ

A Maps object represents a collection of features, callbacks,.. on the assigned layer.

Once you have created a map, you can create additional Maps objects for the same map by using Maps.new_layer().

๐ŸŒฑ If no explicit layer-name is provided, the returned Maps object will use the same layer as the parent Maps object.

  • This is especially useful if you want to plot multiple datasets on the same map and layer.

๐ŸŒฑ To create a NEW layer named "my_layer", use m2 = m.new_layer("my_layer")

  • Features, Colorbars etc. added to a Maps object are only visible if the associated layer is visible.

  • Callbacks are only executed if the associated layer is visible.

  • See ๐Ÿ—— Combine & compare multiple layers on how to select the currently visible layer(s).

m = Maps()                           # same as `m = Maps(crs=4326, layer="base")`
m.add_feature.preset.coastline()     # add coastlines to the "base" layer

m_ocean = m.new_layer(layer="ocean") # create a new layer named "ocean"
m_ocean.add_feature.preset.ocean()   # features on this layer will only be visible if the "ocean" layer is visible!

m_ocean2 = m_ocean.new_layer()       # "m_ocean2" is just another Maps-object on the same layer as "m_ocean"!
m_ocean2.set_data(                   # assign a dataset to this Maps-object
    data=[.14,.25,.38],
    x=[1,2,3], y=[3,5,7],
    crs=4326)
m_ocean2.set_shape.ellipses()        # set the shape that is used to represent the datapoints
m_ocean2.plot_map()                  # plot the data

m.show_layer("ocean")                # show the "ocean" layer
m.util.layer_selector()              # get a utility widget to quickly switch between existing layers

The โ€œallโ€ layer

There is one layer-name that has a special meaningโ€ฆ the "all" layer.
Any callbacks and features added to this layer will be executed on ALL other layers as well!

You can add features and callbacks to the all layer via:

  • using the shortcut m.all. ...

  • creating a dedicated Maps object via m_all = Maps(layer="all") or m_all = m.new_layer("all")

  • using the โ€œlayerโ€ kwarg of functions e.g. m.plot_map(layer="all")

m = Maps()
m.all.add_feature.preset.coastline() # add coastlines to ALL layers of the map

m_ocean = m.new_layer(layer="ocean") # create a new layer named "ocean"
m_ocean.add_feature.preset.ocean()   # add ocean-coloring to the "ocean" layer
m.show_layer("ocean")                # show the "ocean" layer (note that it has coastlines as well!)

Artists added with methods outside of EOmaps

If you use methods that are NOT provided by EOmaps, the corresponding artists will always appear on the "base" layer by default! (e.g. cartopy or matplotlib methods accessible via m.ax. or m.f. like m.ax.plot(...))

In most cases this behavior is sufficientโ€ฆ for more complicated use-cases, artists must be explicitly added to the Blit Manager (m.BM) so that EOmaps can handle drawing accordingly.

To put the artists on dedicated layers, use one of the the following options:

  • For artists that are dynamically updated on each event, use m.BM.add_artist(artist, layer=...)

  • For โ€œbackgroundโ€ artists that only require updates on pan/zoom/resize, use m.BM.add_bg_artist(artist, layer=...)

m = Maps()
m.all.add_feature.preset.coastline() # add coastlines to ALL layers of the map

# draw a red X over the whole axis and put the lines
# as background-artists on the layer "mylayer"
(l1, ) = m.ax.plot([0, 1], [0, 1], lw=5, c="r", transform=m.ax.transAxes)
(l2, ) = m.ax.plot([0, 1], [1, 0], lw=5, c="r", transform=m.ax.transAxes)

m.BM.add_bg_artist(l1, layer="mylayer")
m.BM.add_bg_artist(l2, layer="mylayer")
m.show_layer("mylayer")

๐Ÿ—— Combine & compare multiple layers๏ƒ

Using the ๐Ÿงฐ Companion Widget

Usually it is most convenient to combine and compare layers via the ๐Ÿงฐ Companion Widget.

  • Use the dropdown-list at the top-right to select a single layer or overlay multiple layers.

    • Click on a single layer to make it the visible layer.

    • Hold down control or shift to overlay multiple layers.

_images/select_layers_dropdown.gif

  • Select one or more layers to dynamically adjust the stacking-order via the layer-tabs of the Compare and Edit views.

    • Hold down control while clicking on a tab to make it the visible layer.

    • Hold down shift while clicking on a tab to overlay multiple layers.

    • Re-arrange the tabs to change the stacking-order of the layers.

_images/rearrange_layers.gif

To programmatically switch between layers or view a layer that represents a combination of multiple existing layers, use Maps.show_layer().

๐ŸŒฑ If you provide a single layer-name, the map will show the corresponding layer, e.g. m.show_layer("my_layer")

๐ŸŒฑ To (transparently) overlay multiple existing layers, use one of the following options:

  • Provide multiple layer names or tuples of the form (< layer-name >, < transparency [0-1] >)

    • m.show_layer("A", "B") will overlay all features of the layer B on top of the layer A.

    • m.show_layer("A", ("B", 0.5)) will overlay the layer B with 50% transparency on top of the layer A.

  • Provide a combined layer name by separating the individual layer names you want to show with a "|" character.

    • m.show_layer("A|B") will overlay all features of the layer B on top of the layer A.

    • To transparently overlay a layer, add the transparency to the layer-name in curly-brackets, e.g.: "<layer-name>{<transparency>}".

      • m.show_layer("A|B{0.5}") will overlay the layer B with 50% transparency on top of the layer A.

m = Maps(layer="first")
m.add_feature.physical.land(fc="k")

m2 = m.new_layer("second")                # create a new layer and plot some data
m2.add_feature.preset.ocean(zorder=2)
m2.set_data(data=[.14,.25,.38],
            x=[10,20,30], y=[30,50,70],
            crs=4326)
m2.plot_map(zorder=1)                     # plot the data "below" the ocean

m.show_layer("first", ("second", .75))   # overlay the second layer with 25% transparency

๐ŸŒฑ If you want to overlay a part of the screen with a different layer, have a look at peek_layer() callbacks**!

peek_layer

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

m = Maps()
m.all.add_feature.preset.coastline()
m.add_feature.preset.urban_areas()

m.add_feature.preset.ocean(layer="ocean")
m.add_feature.physical.land(layer="land", fc="g")
m.cb.click.attach.peek_layer(layer=["ocean", ("land", 0.5)], shape="round", how=0.4)

The โ€œstacking orderโ€ of features and layers

The stacking order of features at the same layer is controlled by the zorder argument.

  • e.g. m.plot_map(zorder=1) or m.add_feature.cultural.urban_areas(zorder=10)

If you stack multiple layers on top of each other, the stacking is determined by the order of the layer-names (from right to left)

  • e.g. m.show_layer("A", "B") will show the layer "B" on top of the layer "A"

  • you can stack as many layers as you like! m.show_layer("A", "B", ("C", 0.5), "D", ...)

Maps.new_layer

Create a new Maps-object that shares the same plot-axes.

Maps.all

Get a Maps-object on the "all" layer.

Maps.show

Make the layer of this Maps-object visible.

Maps.show_layer

Show a single layer or (transparently) overlay multiple selected layers.

Maps.fetch_layers

Fetch (and cache) the layers of a map.

๐Ÿ—บ Export the map as jpeg/png, etc.๏ƒ

Once the map is ready, an image of the map can be saved at any time by using Maps.savefig()

m = Maps()
...
m.savefig("snapshot1.png", dpi=100, transparent=False, ...)

To adjust the margins of the subplots, use m.subplots_adjust(), m.f.tight_layout() or have a look at the ๐Ÿ—๏ธ Layout Editor!

from eomaps import Maps
m = Maps()
m.subplots_adjust(left=0.1, right=0.9, bottom=0.05, top=0.95)

Maps.subplots_adjust

Update the subplot parameters of the grid.

Notes on exporting high-dpi figures

EOmaps tries its best to follow the WYSIWYG concept (e.g. โ€œWhat You See Is What You Getโ€). However, if you export the map with a dpi-value other than 100, there are certain circumstances where the final image might look different. To summarize:

  • Changing the dpi of the figure requires a re-draw of all plotted datasets.

    • if you use shade shapes to represent the data, using a higher dpi-value can result in a very different appearance of the data!

  • WebMap services usually come as image-tiles with 96 dpi

    • by default, images are not re-fetched when saving the map to keep the original appearance

    • If you want to re-fetch the WebMap based on the export-dpi, use m.savefig(refetch_wms=True).

      • Note: increasing the dpi will result in an increase in the number of tiles that have to be fetched. If the number of required tiles is too large, the server might reject the request and the map might have gaps or no tiles at all.

๐Ÿฑ Multiple Maps (and/or plots) in one figure๏ƒ

It is possible to combine multiple EOmaps maps and/or ordinary matpltolib plots in one figure.

The figure used by a Maps object is set via the f argument, e.g.: m = Maps(f=...). If no figure is provided, a new figure is created whenever you initialize a Maps object. The figure-instance of an existing Maps object is accessible via m.f

  • To add a map to an existing figure, use m2 = m.new_map() (requires EOmaps >= v6.1) or pass the figure-instance on initialization of a new Maps object.

  • To add a ordinary matplotlib plot to a figure containing an eomaps-map, use m.f.add_subplot() or m.f.add_axes().

The initial position of the axes used by a Maps object is set via the ax argument, e.g.: m = Maps(ax=...) or m2 = m.new_map(ax=...)

  • The syntax for positioning axes is similar to matplotlibs f.add_subplot() or f.add_axes()

  • The axis-instance of an existing Maps object is accessible via m.ax

  • โ€ฆfor more information, checkout the matplotlib tutorial: Customizing Figure Layouts

Note

Make sure to have a look at the ๐Ÿ—๏ธ Layout Editor on how to re-position and re-scale axes to arbitrary positions!

Maps

The base-class for generating plots with EOmaps.

Maps.new_map

Create a new map that shares the figure with this Maps-object.

In the following, the most commonly used cases are introduced:

Grid positioning๏ƒ

To position the map in a (virtual) grid, one of the following options are possible:

  • Three integers (nrows, ncols, index) (or 2 integers and a tuple).

    • The map will take the index position on a grid with nrows rows and ncols columns.

    • index starts at 1 in the upper left corner and increases to the right.

    • index can also be a two-tuple specifying the (first, last) indices (1-based, and including last) of the map, e.g., Maps(ax=(3, 1, (1, 2))) makes a map that spans the upper 2/3 of the figure.

from eomaps import Maps
# ----- initialize a figure with an EOmaps map
# position = item 1 of a 2x1 grid
m = Maps(ax=(2, 1, 1))
# ----- add a normal matplotlib axes
# position = item 2 of a 2x1 grid
ax = m.f.add_subplot(2, 1, 2)
_images/grid1.png
from eomaps import Maps
# ----- initialize a figure with an EOmaps map
# position = item 1 of a 2x2 grid
m = Maps(ax=(2, 2, 1))
# ----- add another Map to the same figure
# position = item 3 of a 2x2 grid
m2 = m.new_map(ax=(2, 2, 3))
# ----- add a normal matplotlib axes
# position = second item of a 1x2 grid
ax = m.f.add_subplot(1, 2, 2)
_images/grid2.png
from eomaps import Maps
# ----- initialize a figure with an EOmaps map
# position = span 2 rows of a 3x1 grid
m = Maps(ax=(3, 1, (1, 2)))
# ----- add a normal matplotlib axes
# position = item 3 of a 3x1 grid
ax = m.f.add_subplot(3, 1, 3)
_images/grid3.png
  • A 3-digit integer.

    • The digits are interpreted as if given separately as three single-digit integers, i.e. Maps(ax=235) is the same as Maps(ax=(2, 3, 5)).

    • Note that this can only be used if there are no more than 9 subplots.

from eomaps import Maps
# ----- initialize a figure with an EOmaps map
m = Maps(ax=211)
# ----- add a normal matplotlib axes
ax = m.f.add_subplot(212)
_images/grid4.png
from eomaps import Maps
# ----- initialize a figure with an EOmaps map
m = Maps(ax=221)
# ----- add 2 more Maps to the same figure
m2 = m.new_map(ax=222)
m3 = m.new_map(ax=223)
# ----- add a normal matplotlib axes
ax = m.f.add_subplot(224)
_images/grid5.png
from matplotlib.gridspec import GridSpec
from eomaps import Maps

gs = GridSpec(2, 2)
m = Maps(ax=gs[0,0])
m2 = m.new_map(ax=gs[0,1])
ax = m.f.add_subplot(gs[1,:])
_images/grid6.png

Absolute positioning๏ƒ

To set the absolute position of the map, provide a list of 4 floats representing (left, bottom, width, height).

  • The absolute position of the map expressed in relative figure coordinates (e.g. ranging from 0 to 1)

Note

Since the effective size of the Map is dependent on the current zoom-region, the position always represents the maximal area that can be occupied by the map!

Also, using m.f.tight_layout() will not work with axes added in this way.

from eomaps import Maps
# ----- initialize a figure with an EOmaps map
m = Maps(ax=(.07, 0.53, .6, .3))
# ----- add a normal matplotlib axes
ax = m.f.add_axes((.35, .15, .6, .2))
_images/grid7.png

Using already existing figures / axes๏ƒ

It is also possible to insert an EOmaps map into an existing figure or re-use an existing axes.

  • To put a map on an existing figure, provide the figure-instance via m = Maps(f= <the figure instance>)

  • To use an existing axes, provide the axes-instance via m = Maps(ax= <the axes instance>)

    • NOTE: The axes MUST be a cartopy-GeoAxes!

import matplotlib.pyplot as plt
import cartopy
from eomaps import Maps

f = plt.figure(figsize=(10, 7))
ax = f.add_subplot(projection=cartopy.crs.Mollweide())
m = Maps(f=f, ax=ax)

Dynamic updates of plots in the same figure๏ƒ

As soon as a Maps-object is attached to a figure, EOmaps will handle re-drawing of the figure! Therefore dynamically updated artists must be added to the โ€œblit-managerโ€ (m.BM) to ensure that they are correctly updated.

  • use m.BM.add_artist(artist, layer=...) if the artist should be re-drawn on any event in the figure

  • use m.BM.add_bg_artist(artist, layer=...) if the artist should only be re-drawn if the extent of the map changes

Note

In most cases it is sufficient to simply add the whole axes-object as artist via m.BM.add_artist(...).

This ensures that all artists of the axes are updated as well!

Hereโ€™s an example to show how it works:

from eomaps import Maps

# Initialize a new figure with an EOmaps map
m = Maps(ax=223)
m.ax.set_title("click me!")
m.add_feature.preset.coastline()
m.cb.click.attach.mark(radius=20, fc="none", ec="r", lw=2)

# Add another map to the figure
m2 = m.new_map(ax=224, crs=Maps.CRS.Mollweide())
m2.add_feature.preset.coastline()
m2.add_feature.preset.ocean()
m2.cb.click.attach.mark(radius=20, fc="none", ec="r", lw=2, n=200)

# Add a "normal" matplotlib plot to the figure
ax = m.f.add_subplot(211)
# Since we want to dynamically update the data on the axis, it must be
# added to the BlitManager to ensure that the artists are properly updated.
# (EOmaps handles interactive re-drawing of the figure)
m.BM.add_artist(ax, layer=m.layer)

# plot some static data on the axis
ax.plot([10, 20, 30, 40, 50], [10, 20, 30, 40, 50])

# define a callback that plots markers on the axis if you click on the map
def cb(pos, **kwargs):
    ax.plot(*pos, marker="o")

m.cb.click.attach(cb)        # attach the callback to the first map
m.cb.click.share_events(m2)  # share click events between the 2 maps
_images/dynamic_axes_updates.gif

๐„œ MapsGrid objects๏ƒ

MapsGrid objects can be used to create (and manage) multiple maps in one figure.

Note

While MapsGrid objects provide some convenience, starting with EOmaps v6.x, the preferred way of combining multiple maps and/or matplotlib axes in a figure is by using one of the options presented in the previous sections!

A MapsGrid creates a grid of Maps objects (and/or ordinary matpltolib axes), and provides convenience-functions to perform actions on all maps of the figure.

from eomaps import MapsGrid
mg = MapsGrid(r=2, c=2, crs=..., layer=..., ... )
# you can then access the individual Maps-objects via:
mg.m_0_0
mg.m_0_1
mg.m_1_0
mg.m_1_1

m2 = mg.m_0_0.new_layer("newlayer")
...

# there are many convenience-functions to perform actions on all Maps-objects:
mg.add_feature.preset.coastline()
mg.add_compass()
...

# to perform more complex actions on all Maps-objects, simply loop over the MapsGrid object
for m in mg:
    ...

# set the margins of the plot-grid
mg.subplots_adjust(left=0.1, right=0.9, bottom=0.05, top=0.95, hspace=0.1, wspace=0.05)

Make sure to checkout the ๐Ÿ—๏ธ Layout Editor which greatly simplifies the arrangement of multiple axes within a figure!

Custom grids and mixed axes๏ƒ

Fully customized grid-definitions can be specified by providing m_inits and/or ax_inits dictionaries of the following structure:

  • The keys of the dictionary are used to identify the objects

  • The values of the dictionary are used to identify the position of the associated axes

  • The position can be either an integer N, a tuple of integers or slices (row, col)

  • Axes that span over multiple rows or columns, can be specified via slice(start, stop)

dict(
    name1 = N  # position the axis at the Nth grid cell (counting firs)
    name2 = (row, col), # position the axis at the (row, col) grid-cell
    name3 = (row, slice(col_start, col_end)) # span the axis over multiple columns
    name4 = (slice(row_start, row_end), col) # span the axis over multiple rows
    )
  • m_inits is used to initialize Maps objects

  • ax_inits is used to initialize ordinary matplotlib axes

The individual Maps-objects and matpltolib-Axes are then accessible via:

mg = MapsGrid(2, 3,
              m_inits=dict(left=(0, 0), right=(0, 2)),
              ax_inits=dict(someplot=(1, slice(0, 3)))
              )
mg.m_left   # the Maps object with the name "left"
mg.m_right   # the Maps object with the name "right"

mg.ax_someplot   # the ordinary matplotlib-axis with the name "someplot"

โ— NOTE: if m_inits and/or ax_inits are provided, ONLY the explicitly defined objects are initialized!

  • The initialization of the axes is based on matplotlibโ€™s GridSpec functionality. All additional keyword-arguments (width_ratios, height_ratios, etc.) are passed to the initialization of the GridSpec object.

  • To specify unique crs for each Maps object, provide a dictionary of crs specifications.

from eomaps import MapsGrid

# initialize a grid with 2 Maps objects and 1 ordinary matplotlib axes
mgrid = MapsGrid(2, 2,
                 m_inits=dict(top_row=(0, slice(0, 2)),
                              bottom_left=(1, 0)),
                 crs=dict(top_row=4326,
                          bottom_left=3857),
                 ax_inits=dict(bottom_right=(1, 1)),
                 width_ratios=(1, 2),
                 height_ratios=(2, 1))

mgrid.m_top_row # a map extending over the entire top-row of the grid (in epsg=4326)
mgrid.m_bottom_left # a map in the bottom left corner of the grid (in epsg=3857)

mgrid.ax_bottom_right # an ordinary matplotlib axes in the bottom right corner of the grid

MapsGrid

Initialize a grid of Maps objects

MapsGrid.join_limits

Join axis limits between all Maps objects of the grid (only possible if all maps share the same crs!)

MapsGrid.share_click_events

Share click events between all Maps objects of the grid

MapsGrid.share_pick_events

Share pick events between all Maps objects of the grid

MapsGrid.set_data_specs

This will execute the corresponding action on ALL Maps objects of the MapsGrid!

MapsGrid.set_classify_specs

This will execute the corresponding action on ALL Maps objects of the MapsGrid!

MapsGrid.add_wms

A collection of open-access WebMap services that can be added to the maps.

MapsGrid.add_feature

Interface to the feature-layers provided by NaturalEarth.

MapsGrid.add_annotation

This will execute the corresponding action on ALL Maps objects of the MapsGrid!

MapsGrid.add_marker

This will execute the corresponding action on ALL Maps objects of the MapsGrid!

MapsGrid.add_gdf

This will execute the corresponding action on ALL Maps objects of the MapsGrid!

๐Ÿงฑ Naming conventions and autocompletion๏ƒ

The goal of EOmaps is to provide a comprehensive, yet easy-to-use interface.

To avoid having to remember a lot of names, a concise naming-convention is applied so that autocompletion can quickly narrow-down the search to relevant functions and properties.

Once a few basics keywords have been remembered, finding the right functions and properties should be quick and easy.

Note

EOmaps works best in conjunction with โ€œdynamic autocompletionโ€, e.g. by using an interactive console where you instantiate a Maps object first and then access dynamically updated properties and docstrings on the object.

To clarify:

  • First, execute m = Maps() in an interactive console

  • then (inside the console, not inside the editor!) use autocompletion on m. to get autocompletion for dynamically updated attributes.

For example the following accessors only work properly after the Maps object has been initialized: - m.add_wms: browse available WebMap services - m.set_classify: browse available classification schemes

The following list provides an overview of the naming-conventions used within EOmaps:

Add features to a map - โ€œm.add_โ€๏ƒ

All functions that add features to a map start with add_, e.g.: - m.add_feature, m.add_wms, m.add_annotation, m.add_marker, m.add_gdf, โ€ฆ

WebMap services (e.g. m.add_wms) are fetched dynamically from the respective APIs. Therefore the structure can vary from one WMS to another. The used convention is the following: - You can navigate into the structure of the API by using โ€œdot-accessโ€ continuously - once you reach a level that provides layers that can be added to the map, the .add_layer. directive will be visible - any <LAYER> returned by .add_layer.<LAYER> can be added to the map by simply calling it, e.g.:

  • m.add_wms.OpenStreetMap.add_layer.default()

  • m.add_wms.OpenStreetMap.OSM_mundialis.add_layer.OSM_WMS()

Set data specifications - โ€œm.set_โ€๏ƒ

All functions that set properties of the associated dataset start with set_, e.g.: - m.set_data, m.set_classify, m.set_shape, โ€ฆ

Create new Maps-objects - โ€œm.new_โ€๏ƒ

Actions that result in a new Maps objects start with new_. - m.new_layer, m.new_inset_map, โ€ฆ

Callbacks - โ€œm.cb.โ€๏ƒ

Everything related to callbacks is grouped under the cb accessor.

  • use m.cb.<METHOD>.attach.<CALLBACK>() to attach pre-defined callbacks

    • <METHOD> hereby can be one of click, pick or keypress (but thereโ€™s no need to remember since autocompletion will do the job!).

  • use m.cb.<METHOD>.attach(custom_cb) to attach a custom callback

๐Ÿ”ด Data Visualization๏ƒ

To visualize a dataset, first assign the dataset to the Maps object, then select how you want to visualize the data and finally call Maps.plot_map().

  1. Assign the data to a Maps object via Maps.set_data()

  2. (optional) set the shape used to represent the data via Maps.set_shape

  3. (optional) assign a classification scheme for the data via Maps.set_classify

  4. Plot the data by calling Maps.plot_map()

๐Ÿ—ƒ Assign the data๏ƒ

To assign a dataset to a Maps object, use Maps.set_data().

Maps.set_data

Set the properties of the dataset you want to plot.

A dataset is fully specified by setting the following properties:

  • data : The data-values

  • x, y: The coordinates of the provided data

  • crs: The coordinate-reference-system of the provided coordinates

  • parameter (optional): The parameter name

  • encoding (optional): The encoding of the data

  • cpos, cpos_radius (optional): the pixel offset

Note

Make sure to use a individual Maps object (e.g. with m2 = m.new_layer() for each dataset! Calling Maps.plot_map() multiple times on the same :py:class:`Maps`object will remove and override the previously plotted dataset!

A note on data-reprojectionโ€ฆ

EOmaps handles the reprojection of the data from the input-crs to the plot-crs.

  • Plotting data in its native crs will omit the reprojection step and is therefore a lot faster!

  • If your dataset is 2D (e.g. a raster), it is best (for speed and memory) to provide the coordinates as 1D vectors!

    • Note that reprojecting 1D coordinate vectors to a different crs will result in (possibly very large) 2D coordinate arrays!

The following data-types are accepted as input:

pandas DataFrames

  • data: pandas.DataFrame

  • x, y: The column-names to use as coordinates (string)

  • parameter: The column-name to use as data-values (string)

from eomaps import Maps
import pandas as pd

df = pd.DataFrame(dict(lon=[1,2,3], lat=[2,5,4], data=[12, 43, 2]))
m = Maps()
m.set_data(df, x="lon", y="lat", crs=4326, parameter="data")
m.plot_map()

pandas Series

  • data, x, y: pandas.Series

  • parameter: (optional) parameter name (string)

from eomaps import Maps
import pandas as pd

x, y, data = pd.Series([1,2,3]), pd.Series([2, 5, 4]), pd.Series([12, 43, 2])
m = Maps()
m.set_data(data, x=x, y=y, crs=4326, parameter="param_name")
m.plot_map()

1D or 2D data and coordinates

  • data, x, y: equal-size numpy.array (or list)

  • parameter: (optional) parameter name (string)

from eomaps import Maps
import numpy as np

x, y = np.mgrid[-20:20, -40:40]
data = x + y
m = Maps()
m.set_data(data=data, x=x, y=y, crs=4326, parameter="param_name")
m.plot_map()

1D coordinates and 2D data

  • data: numpy.array (or list) with shape (n, m)

  • x: numpy.array (or list) with shape (n,)

  • y: numpy.array (or list) with shape (m,)

  • parameter: (optional) parameter name (string)

from eomaps import Maps
import numpy as np

x = np.linspace(10, 50, 100)
y = np.linspace(10, 50, 50)
data = np.random.normal(size=(100, 50))

m = Maps()
m.set_data(data=data, x=x, y=y, crs=4326, parameter="param_name")
m.plot_map()

๐Ÿ’  Specify how to visualize the data๏ƒ

To specify how a dataset is visualized on the map, you have to set the โ€œplot-shapeโ€ via Maps.set_shape().

Maps.set_shape

Set the plot-shape to represent the data-points.

A note on speed and performance

Some ways to visualize the data require more computational effort than others! Make sure to select an appropriate shape based on the size of the dataset you want to plot!

EOmaps dynamically pre-selects the data with respect to the current plot-extent before the actual plot is created! If you do not need to see the whole extent of the data, make sure to set the desired plot-extent via Maps.set_extent() or Maps.set_shape_to_extent() BEFORE calling Maps.plot_map() to get a (possibly huge) speedup!

The number of datapoints mentioned in the following always refer to the number of datapoints that are visible in the desired plot-extent.

Possible shapes that work nicely for datasets with up to ~500 000 data-points:

geod_circles

Draw geodesic circles with a radius defined in meters.

ellipses

Draw projected ellipses with dimensions defined in units of a given crs.

rectangles

Draw projected rectangles with fixed dimensions (and possibly curved edges).

voronoi_diagram

Draw a Voronoi-Diagram of the data.

delaunay_triangulation

Draw a Delaunay-Triangulation of the data.

Possible shapes that work nicely for up to a few million data-points:

raster

Draw the data as a rectangular raster.

While raster can still be used for datasets with a few million datapoints, for extremely large datasets (> 10 million datapoints) it is recommended to use โ€œshadingโ€ to greatly speed-up plotting. If shading is used, a dynamic averaging of the data based on the screen-resolution and the currently visible plot-extent is performed (resampling based on the mean-value is used by default).

Possible shapes that can be used to quickly generate a plot for extremely large datasets are:

shade_points

Shade the data as infinitesimal points (>> usable for very large datasets!).

shade_raster

Shade the data as a rectangular raster (>> usable for very large datasets!).

from eomaps import Maps
data, x, y = [.3,.64,.2,.5,1], [1,2,3,4,5], [2,5,3,7,5]

m = Maps()                                # create a Maps-object
m.set_data(data, x, y)                    # assign some data to the Maps-object
m.set_shape.rectangles(radius=1,          # represent the datapoints as 1x1 degree rectangles
                        radius_crs=4326)  # (in epsg=4326 projection)
m.plot_map(cmap="viridis", zorder=1)      # plot the data

m2 = m.new_layer()                        # create a new Maps-object on the same layer
m2.set_data(data, x, y)                   # assign another dataset to the new Maps object
m2.set_shape.geod_circles(radius=50000,   # draw geodetic circles with 50km radius
                        n=100)          # use 100 intermediate points to represent the shape
m2.plot_map(ec="k", cmap="Reds",          # plot the data
            zorder=2, set_extent=False)   # (and avoid resetting the plot-extent)

Note

The โ€œshadeโ€-shapes require the additional datashader dependency! You can install it via:

mamba install -c conda-forge datashader

Whatโ€™s used by default?

By default, the plot-shape is assigned based on the associated dataset.

  • For datasets with less than 500 000 pixels, m.set_shape.ellipses() is used.

  • For larger 2D datasets m.set_shape.shade_raster() is used
    โ€ฆ and m.set_shape.shade_points() is used for the rest.

To get an overview of the existing shapes and their main use-cases, hereโ€™s a simple decision-tree: (โ€ฆ and donโ€™t forget to set the plot-extent if you only want to see a subset of the data!)

_images/shapes_decision_tree.png _images/plot_shapes.gif

๐Ÿ“Š Classify the data๏ƒ

EOmaps provides an interface for mapclassify to classify datasets prior to plotting.

To assign a classification scheme to a Maps object, use m.set_classify.< SCHEME >(...).

  • Available classifier names are accessible via Maps.CLASSIFIERS.

Maps.set_classify

Interface to the classifiers provided by the 'mapclassify' module.

m = Maps()
m.set_data(...)
m.set_shape.ellipses(...)
m.set_classify.Quantiles(k=5)
m.plot_map()

Currently available classification-schemes are (see mapclassify for details):

๐Ÿ–จ Plot the data๏ƒ

If you want to plot a map based on a dataset, first set the data and then call Maps.plot_map().

Any additional keyword-arguments passed to Maps.plot_map() are forwarded to the actual plot-command for the selected shape.

Useful arguments that are supported by all shapes are:

  • โ€œcmapโ€ : the colormap to use

  • โ€œvminโ€, โ€œvmaxโ€ : the range of values used when assigning the colors

  • โ€œalphaโ€ : the color transparency

  • โ€œzorderโ€ : the โ€œstacking-orderโ€ of the feature

Arguments that are supported by all shapes except shade shapes are:
  • โ€œfcโ€ or โ€œfacecolorโ€ : set the face color for the whole dataset

  • โ€œecโ€ or โ€œedgecolorโ€ : set the edge color for the whole dataset

  • โ€œlwโ€ or โ€œlinewidthโ€ : the line width of the shapes

By default, the plot-extent of the axis is adjusted to the extent of the data if the extent has not been set explicitly before. To always keep the extent as-is, use m.plot_map(set_extent=False).

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

m.set_data([1,2,3,4,5], [10,20,40,60,70], [10,20,50,70,30], crs=4326)
m.set_shape.geod_circles(radius=7e5)
m.plot_map(cmap="viridis", ec="b", lw=1.5, alpha=0.85, set_extent=False)

You can then continue to add a ๐ŸŒˆ Colorbars (with a histogram) or create ๐Ÿ”Ž Zoomed in views on datasets.

Maps.plot_map

Plot the dataset assigned to this Maps-object.

Maps.savefig

Save the current figure.

๐ŸŽจ Customize the plot๏ƒ

All arguments to customize the appearance of a dataset are passed to Maps.plot_map().

In general, the colors assigned to the shapes are specified by

  • selecting a colormap (cmap)

    • either a name of a pre-defined matplotlib colormap (e.g. "viridis", "RdYlBu" etc.)

    • or a general matplotlib colormap object (see matplotlib-docs for more details)

  • (optionally) setting appropriate data-limits via vmin and vmax.

    • vmin and vmax set the range of data-values that are mapped to the colorbar-colors

    • Any values outside this range will get the colormaps over and under colors assigned.

m = Maps()
m.set_data(...)
m.plot_map(cmap="viridis", vmin=0, vmax=1)

Colors can also be set manually by providing one of the following arguments to Maps.plot_map():

  • to set both facecolor AND edgecolor use color=...

  • to set the facecolor use fc=... or facecolor=...

  • to set the edgecolor use ec=... or edgecolor=...

Note

  • Manual color specifications do not work with the shade_raster and shade_points shapes!

  • Providing manual colors will override the colors assigned by the cmap!

  • The colorbar does not represent manually defined colors!

Uniform colors๏ƒ

To apply a uniform color to all datapoints, you can use matpltolibโ€™s named colors or pass an RGB or RGBA tuple.

  • m.plot_map(fc="orange")

  • m.plot_map(fc=(0.4, 0.3, 0.2))

  • m.plot_map(fc=(1, 0, 0.2, 0.5))

from eomaps import Maps

m = Maps()
m.set_data(data=None, x=[10,20,30], y=[10,20,30])

# Use any of matplotlibs "named colors"
m1 = m.new_layer(copy_data_specs=True)
m1.set_shape.ellipses(radius=10)
m1.plot_map(fc="r", zorder=0)

m2 = m.new_layer(copy_data_specs=True)
m2.set_shape.ellipses(radius=8)
m2.plot_map(fc="orange", zorder=1)

# Use RGB or RGBA tuples
m3 = m.new_layer(copy_data_specs=True)
m3.set_shape.ellipses(radius=6)
m3.plot_map(fc=(1, 0, 0.5), zorder=2)

m4 = m.new_layer(copy_data_specs=True)
m4.set_shape.ellipses(radius=4)
m4.plot_map(fc=(1, 1, 1, .75), zorder=3)

# For grayscale use a string of a number between 0 and 1
m5 = m.new_layer(copy_data_specs=True)
m5.set_shape.ellipses(radius=2)
m5.plot_map(fc="0.3", zorder=4)

Explicit colors๏ƒ

To explicitly color each datapoint with a pre-defined color, simply provide a list or array of the aforementioned types.

from eomaps import Maps

m = Maps()
m.set_data(data=None, x=[10, 20, 30], y=[10, 20, 30])

# Use any of matplotlibs "named colors"
# (https://matplotlib.org/stable/gallery/color/named_colors.html)
m1 = m.new_layer(copy_data_specs=True)
m1.set_shape.ellipses(radius=10)
m1.plot_map(fc=["indigo", "g", "orange"], zorder=1)

# Use RGB tuples
m2 = m.new_layer(copy_data_specs=True)
m2.set_shape.ellipses(radius=6)
m2.plot_map(fc=[(1, 0, 0.5),
                (0.3, 0.4, 0.5),
                (1, 1, 0)], zorder=2)

# Use RGBA tuples
m3 = m.new_layer(copy_data_specs=True)
m3.set_shape.ellipses(radius=8)
m3.plot_map(fc=[(1, 0, 0.5, 0.25),
                (1, 0, 0.5, 0.75),
                (0.1, 0.2, 0.5, 0.5)], zorder=3)

# For grayscale use a string of a number between 0 and 1
m4 = m.new_layer(copy_data_specs=True)
m4.set_shape.ellipses(radius=4)
m4.plot_map(fc=[".1", ".2", "0.3"], zorder=4)

RGB/RGBA composites๏ƒ

To create an RGB or RGBA composite from 3 (or 4) datasets, pass the datasets as tuple:

  • the datasets must have the same size as the coordinate arrays!

  • the datasets must be scaled between 0 and 1

If you pass a tuple of 3 or 4 arrays, they will be used to set the RGB (or RGBA) colors of the shapes, e.g.:

  • m.plot_map(fc=(<R-array>, <G-array>, <B-array>))

  • m.plot_map(fc=(<R-array>, <G-array>, <B-array>, <A-array>))

You can fix individual color channels by passing a list with 1 element, e.g.:

  • m.plot_map(fc=(<R-array>, [0.12345], <B-array>, <A-array>))

from eomaps import Maps
import numpy as np

x, y = np.meshgrid(np.linspace(-20, 40, 100),
                np.linspace(50, 70, 50))

# values must be between 0 and 1
r = np.random.randint(0, 100, x.shape) / 100
g = np.random.randint(0, 100, x.shape) / 100
b = [0.4]
a = np.random.randint(0, 100, x.shape) / 100

m = Maps()
m.add_feature.preset.ocean()
m.set_data(data=None, x=x, y=y)
m.plot_map(fc=(r, g, b, a))

๐ŸŒˆ Colorbars (with a histogram)๏ƒ

Before adding a colorbar, you must plot the data using m.plot_map(vmin=..., vmax=...).

  • vmin and vmax hereby specify the value-range used for assigning colors (e.g. the limits of the colorbar).

  • If no explicit limits are provided, the min/max values of the data are used.

  • For more details, see ๐Ÿ”ด Data Visualization.

Once a dataset has been plotted, a colorbar with a colored histogram on top can be added to the map by calling Maps.add_colorbar().

Note

The colorbar always represents the dataset that was used in the last call to Maps.plot_map().
If you need multiple colorbars, use an individual Maps object for each dataset! (e.g. via m2  = m.new_layer())

Note

Colorbars are only visible if the layer at which the data was plotted is visible!

m = Maps(layer=0)
...
m.plot_map()
m.add_colorbar()   # this colorbar is only visible on the layer 0

m2 = m.new_layer("data")
...
m2.plot_map()
m2.add_colorbar()  # this colorbar is only visible on the "data" layer

Maps.add_colorbar

Add a colorbar to the map.

from eomaps import Maps
import numpy as np
x, y = np.mgrid[-45:45, 20:60]

m = Maps()
m.add_feature.preset.coastline()
m.set_data(data=x+y, x=x, y=y, crs=4326)
m.set_classify_specs(scheme=Maps.CLASSIFIERS.EqualInterval, k=5)
m.plot_map()
m.add_colorbar(label="what a nice colorbar", hist_bins="bins")
_images/colorbar.png

Once the colorbar has been created, the colorbar-object can be accessed via m.colorbar. It has the following useful methods defined:

ColorBar.set_position

Set the position of the colorbar (and all colorbars that share the same location)

ColorBar.set_labels

Set the labels (and the styling) for the colorbar (and the histogram).

ColorBar.set_hist_size

Set the size of the histogram (relative to the total colorbar size)

ColorBar.tick_params

Set the appearance of the colorbar (or histogram) ticks.

ColorBar.set_visible

Set the visibility of the colorbar.

ColorBar.remove

Remove the colorbar from the map.

๐Ÿ“Ž Set colorbar tick labels based on bins๏ƒ

To label the colorbar with custom names for a given set of bins, use ColorBar.set_bin_labels():

import numpy as np
from eomaps import Maps
# specify some random data
lon, lat = np.mgrid[-45:45, -45:45]
data = np.random.normal(0, 50, lon.shape)

# use a custom set of bins to classify the data
bins = np.array([-50, -30, -20, 20, 30, 40, 50])
names = np.array(["below -50", "A", "B", "C", "D", "E", "F", "above 50"])

m = Maps()
m.add_feature.preset.coastline()
m.set_data(data, lon, lat)
m.set_classify.UserDefined(bins=bins)
m.plot_map(cmap="tab10")
m.add_colorbar()

# set custom colorbar-ticks based on the bins
m.colorbar.set_bin_labels(bins, names)
_images/colorbar_ticks.png

ColorBar.set_bin_labels

Set the tick-labels of the colorbar to custom names with respect to a given set of bins.

๐ŸŒ  Using the colorbar as a โ€œdynamic shade indicatorโ€๏ƒ

Note

This will only work if you use m.set_shape.shade_raster() or m.set_shape.shade_points() as plot-shape!

For shade shapes, the colorbar can be used to indicate the distribution of the shaded pixels within the current field of view by setting dynamic_shade_indicator=True.

from eomaps import Maps
import numpy as np
x, y = np.mgrid[-45:45, 20:60]

m = Maps()
m.add_feature.preset.coastline()
m.set_data(data=x+y, x=x, y=y, crs=4326)
m.set_shape.shade_raster()
m.plot_map()
m.add_colorbar(dynamic_shade_indicator=True, hist_bins=20)
_images/dynamic_colorbar.gif

๐Ÿงฐ Companion Widget๏ƒ

Starting with v5.0, EOmaps comes with an awesome companion widget that greatly simplifies using interactive capabilities.

  • To activate the widget, press w on the keyboard while the mouse is on top of the map you want to edit.

    • If multiple maps are present in the figure, a green border indicates the map that is currently targeted by the widget.

    • Once the widget is initialized, pressing w will show/hide the widget.

What are all those buttons and sliders for??

To get information on how the individual controls work, simply click on the ? symbol in the top left corner of the widget!

  • This will activate help tooltips that explain the individual controls.

_images/companion_widget.gif

Note

The companion-widget is written in PyQt5 and therefore only works when using the matplotlib qt5agg backend (matplotlibs default if QT5 is installed)!

To manually set the backend, execute the following lines at the start of your script:

import matplotlib
matplotlib.use("qt5agg")

For more details, have a look at ๐Ÿ’ป Configuring the editor (IDE).

The main purpose of the widget is to provide easy-access to features that usually donโ€™t need to go into a python-script, such as:

  • Compare layers (e.g. overlay multiple layers)

  • Switch between existing layers (or combine existing layers)

  • Add simple click or pick callbacks

  • Quickly create new WebMap layers (or add WebMap services to existing layers)

  • Draw shapes, add Annotations and NaturalEarth features to the map

  • Quick-edit existing map-artists (show/hide, remove or set basic properties color, linewidth, zorder)

  • Save the current state of the map to a file (at the desired dpi setting)

  • A basic interface to plot data from files (with drag-and-drop support) (csv, NetCDF, GeoTIFF, shapefile)

๐Ÿ›ธ Callbacks - make the map interactive!๏ƒ

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

They can be attached to a Maps object via:

m = Maps()
...
m.cb.< EVENT >.attach.< CALLBACK >( **kwargs )

< EVENT > specifies the event that will trigger the callback:

click

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

pick

Callbacks that select 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.

< CALLBACK > specifies the action you want to assign to the event.

There are many ๐Ÿฌ Pre-defined callbacks, but it is also possible to define ๐Ÿ‘ฝ Custom callbacks and attach them to the map.

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(copy_data_specs=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

Note

Callbacks are only executed if the layer of the associated Maps object is actually visible! (This assures that pick-callbacks always refer to the visible dataset.)

To define callbacks that are executed independent of the visible layer, attach it to the "all" layer using something like m.all.cb.click.attach.annotate().

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.

set_sticky_modifiers

Define keys on the keyboard that should be treated as โ€œsticky modifiersโ€.

share_events

Share callback-events between this Maps-object and all 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).

๐Ÿฌ 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

Change the default layer of the map.

fetch_layers

Fetch (and cache) layers of a map.

๐Ÿ‘ฝ Custom callbacks๏ƒ

Custom callback functions can be attached to the map via:

m = Maps()
...
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

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

# attaching custom callbacks works completely similar for "click", "pick" and "keypress"!
m = Maps()
...
m.cb.pick.attach(some_callback, double_click=False, button=1, custom_kwarg=1)
m.cb.click.attach(some_callback, double_click=False, button=2, custom_kwarg=2)
m.cb.keypress.attach(some_callback, key="x", custom_kwarg=3)

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:

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

๐Ÿ‘พ Using modifiers for pick- click- and move callbacks๏ƒ

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.:

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)

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().

m = Maps()
m.add_feature.preset.coastline()
m.set_data(... the dataset ...)
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.

๐Ÿ›ฐ WebMap layers๏ƒ

WebMap services (TS/WMS/WMTS) can be attached to the map via Maps.add_wms()

m.add_wms.attach.< SERVICE > ... .add_layer.< LAYER >(...)

< SERVICE > hereby specifies the pre-defined WebMap service you want to add, and < LAYER > indicates the actual layer-name.

m = Maps(Maps.CRS.GOOGLE_MERCATOR) # (the native crs of the service)
m.add_wms.OpenStreetMap.add_layer.default()

Maps.add_wms

A collection of open-access WebMap services that can be added to the maps.

Note

It is highly recommended (and sometimes even required) to use the native crs of the WebMap service in order to avoid re-projecting the images (which degrades image quality and sometimes takes quite a lot of time to finishโ€ฆ)

  • most services come either in epsg=4326 or in Maps.CRS.GOOGLE_MERCATOR projection

from eomaps import Maps, MapsGrid
mg = MapsGrid(crs=Maps.CRS.GOOGLE_MERCATOR)
mg.join_limits()

mg.m_0_0.add_wms.OpenStreetMap.add_layer.default()
mg.m_0_1.add_wms.OpenStreetMap.add_layer.stamen_toner()

mg.m_1_1.add_wms.S1GBM.add_layer.vv()

# ... for more advanced
layer = mg.m_1_0.add_wms.ISRIC_SoilGrids.nitrogen.add_layer.nitrogen_0_5cm_mean
layer.set_extent_to_bbox() # set the extent according to the boundingBox
layer.info                 # the "info" property provides useful information on the layer
layer()                    # call the layer to add it to the map
layer.add_legend()         # if a legend is provided, you can add it to the map!
_images/add_wms.png

Pre-defined WebMap services:๏ƒ

Global:

OpenStreetMap

OpenStreetMap WebMap layers https://wiki.openstreetmap.org/wiki/WMS

ESA_WorldCover

ESA Worldwide land cover mapping https://esa-worldcover.org/en

NASA_GIBS

NASA Global Imagery Browse Services (GIBS) https://wiki.earthdata.nasa.gov/display/GIBS/

ISRIC_SoilGrids

Interface to the ISRIC SoilGrids database https://www.isric.org

EEA_DiscoMap

European Environment Agency Discomap services https://discomap.eea.europa.eu/Index/

ESRI_ArcGIS

Interface to the ERSI ArcGIS REST Services Directory http://services.arcgisonline.com/arcgis/rest/services

S1GBM

Sentinel-1 Global Backscatter Model https://researchdata.tuwien.ac.at/records/n2d1v-gqb91

S2_cloudless

Global cloudless Sentinel-2 maps, crafted by EOX https://s2maps.eu/

GEBCO

Global ocean & land terrain models https://www.gebco.net/

GMRT

Global Multi-Resolution Topography (GMRT) Synthesis https://gmrt.org/

GLAD

Datasets from University of Maryland, Global Land Analysis and Discovery Team https://glad.umd.edu/

CAMS

Copernicus Atmosphere Monitoring Service (Global and European) https://atmosphere.copernicus.eu/

DLR_basemaps

Basemaps hosted by DLR's EOC Geoservice https://geoservice.dlr.de

OpenPlanetary

Planetary layers (Moon & Mars) provided by OpenPlanetary https://www.openplanetary.org

Services specific for Austria (Europe)

Austria.AT_basemap

Basemap for Austria https://basemap.at/

Austria.Wien_basemap

Basemaps for the city of Vienna (Austria) https://www.wien.gv.at

Note

Services might be nested directory structures! The actual layer is always added via the add_layer directive.

m.add_wms.<...>. ... .<...>.add_layer.<LAYER NAME>()

Some of the services dynamically fetch the structure via HTML-requests. Therefore it can take a short moment before autocompletion is capable of showing you the available options! A list of available layers from a sub-folder can be fetched via:

m.add_wms.<...>. ... .<LAYER NAME>.layers

Using custom WebMap services๏ƒ

It is also possible to use custom WMS/WMTS/XYZ services. (see docstring of get_service() for more details and examples)

get_service

Get a object that can be used to add WMS, WMTS or XYZ services based on a GetCapabilities-link or a link to a ArcGIS REST API

m = Maps()
# define the service
service = m.add_wms.get_service(<... link to GetCapabilities.xml ...>,
                                service_type="wms",
                                res_API=False,
                                maxzoom=19)
# once the service is defined, you can use it just like the pre-defined ones
service.layers   # >> get a list of all layers provided by the service

# select one of the layers
layer = service.add_layer. ... .< LAYER >
layer.info                  # >> get some additional infos for the selected layer
layer.set_extent_to_bbox()  # >> set the map-extent to the bbox of the layer

# call the layer to add it to the map
# (optionally providing additional kwargs for fetching map-tiles)
layer(...)

Setting date, style and other WebMap properties๏ƒ

Some WebMap services allow passing additional arguments to set properties such as the date or the style of the map. To pass additional arguments to a WebMap service, simply provide them when when calling the layer, e.g.:

m = Maps()
m.add_wms.< SERVICE >. ... .add_layer.< LAYER >(time=..., styles=[...], ...)

To show an example, hereโ€™s how to fetch multiple timestamps for the UV-index of the Copernicus Airquality service. (provided by https://atmosphere.copernicus.eu/)

from eomaps import Maps
import pandas as pd

m = Maps(layer="all", figsize=(8, 4))
m.subplots_adjust(left=0.05, right=0.95)
m.all.add_feature.preset.coastline()
m.add_logo()

layer = m.add_wms.CAMS.add_layer.composition_uvindex_clearsky
timepos = layer.wms_layer.timepositions   # available time-positions
all_styles = list(layer.wms_layer.styles) # available styles

# create a list of timestamps to fetch
start, stop, freq = timepos[1].split(r"/")
times = pd.date_range(start, stop, freq=freq.replace("PT", ""))
times = times.strftime("%Y-%m-%dT%H:%M:%SZ")

style = all_styles[0]     # use the first available style
for time in times[:6]:
    # call the layer to add it to the map
    layer(time=time,
          styles=[style],   # provide a list with 1 entry here
          layer=time        # put each WebMap on an individual layer
          )

layer.add_legend()  # add a legend for the WebMap service

# add a "slider" and a "selector" widget
m.util.layer_selector(ncol=3, loc="upper center", fontsize=6, labelspacing=1.3)
m.util.layer_slider()

# attach a callback to fetch all layers if you press l on the keyboard
cid = m.all.cb.keypress.attach.fetch_layers(key="l")
# fetch all layers so that they are cached and switching layers is fast
m.fetch_layers()
m.show_layer(times[0])      # make the first timestamp visible
_images/advanced_wms.gif

๐ŸŒต NaturalEarth features๏ƒ

Feature-layers provided by NaturalEarth can be directly added to the map via Maps.add_feature().

Maps.add_feature

Interface to the feature-layers provided by NaturalEarth.

The call-signature is: m.add_feature.< CATEGORY >.< FEATURE >(...):

< CATEGORY > specifies the general category of the feature, e.g.:

  • cultural: cultural features (e.g. countries, states etc.)

  • physical: physical features (e.g. coastlines, land, ocean etc.)

  • preset: a set of pre-defined layers for convenience (see below)

< FEATURE > is the name of the NaturalEarth feature, e.g. "coastlines", "admin_0_countries" etc..

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

m.add_feature.physical.lakes(scale=110, ec="b")
m.add_feature.cultural.admin_0_pacific_groupings(fc="none", ec="m")

# (only if geopandas is installed)
places = m.add_feature.cultural.populated_places.get_gdf(scale=110)
m.add_gdf(places, markersize=places.NATSCALE/10, fc="r")
_images/add_feature.gif

NaturalEarth provides features in 3 different scales: 1:10m, 1:50m, 1:110m. By default EOmaps uses features at 1:50m scale. To set the scale manually, simply use the scale argument when calling the feature.

  • It is also possible to automatically update the scale based on the map-extent by using scale="auto". (Note that if you zoom into a new scale the data might need to be downloaded and reprojected so the map might be irresponsive for a couple of seconds until everything is properly cached.)

If you want to get a geopandas.GeoDataFrame containing all shapes and metadata of a feature, use: (Have a look at ๐Ÿ’  Vector Data (or GeoDataFrames) on how to add the obtained GeoDataFrame to the map)

from eomaps import Maps
m = Maps()
gdf = m.add_feature.physical.coastline.get_gdf(scale=10)

The most commonly used features are accessible with pre-defined colors via the preset category:

coastline

Add a coastline to the map.

ocean

Add ocean-coloring to the map.

land

Add a land-coloring to the map.

countries

Add country-boundaries to the map.

urban_areas

Add urban-areas to the map.

lakes

Add lakes to the map.

rivers_lake_centerlines

Add rivers_lake_centerlines to the map.

๐Ÿ’  Vector Data (or GeoDataFrames)๏ƒ

For vector data visualization, EOmaps utilizes the plotting capabilities of geopandas .

A geopandas.GeoDataFrame can be added to the map via Maps.add_gdf(). This is basically just a wrapper for the plotting capabilities of geopandas (e.g. GeoDataFrame.plot(โ€ฆ) ) supercharged with EOmaps features.

  • If you provide a string or pathlib.Path object to Maps.add_gdf(), the contents of the file will be read into a GeoDataFrame via geopandas.read_file().

    • Many file-types such as shapefile, GeoPackage, geojson โ€ฆ are supported!

Maps.add_gdf

Plot a geopandas.GeoDataFrame on the map.

from eomaps import Maps
import geopandas as gpd

gdf = gpd.GeoDataFrame(geometries=[...], crs=...)<>

m = Maps()
m.add_gdf(gdf, fc="r", ec="g", lw=2)

It is possible to make the shapes of a GeoDataFrame pickable (e.g. usable with m.cb.pick callbacks) by providing a picker_name (and specifying a pick_method).

  • use pick_method="contains" if your GeoDataFrame consists of polygon-geometries (the default)

    • pick a geometry if geometry.contains(mouse-click-position) == True

  • use pick_method="centroids" if your GeoDataFrame consists of point-geometries

    • pick the geometry with the closest centroid

Once the picker_name is specified, pick-callbacks can be attached via:

  • m.cb.pick[<PICKER NAME>].attach.< CALLBACK >()

For example, to highlight the clicked country, you could use:
from eomaps import Maps
m = Maps()
# get the GeoDataFrame for a given NaturalEarth feature
gdf = m.add_feature.cultural.admin_0_countries.get_gdf(scale=110)

# pick the shapes of the GeoDataFrame based on a "contains" query
m.add_gdf(gdf, picker_name="countries", pick_method="contains")

# temporarily highlight the picked geometry
m.cb.pick["countries"].attach.highlight_geometry(fc="r", ec="g", lw=2)
_images/add_gdf_pick.gif

๐Ÿ• Annotations, Markers, Lines, Logos, etc.๏ƒ

๐Ÿ”ด Markers๏ƒ

Static markers can be added to the map via Maps.add_marker().

  • If a dataset has been plotted, you can mark any datapoint via its ID, e.g. ID=...

  • To add a marker at an arbitrary position, use xy=(...)

    • By default, the coordinates are assumed to be provided in the plot-crs

    • You can specify arbitrary coordinates via xy_crs=...

  • The radius is defined via radius=...

    • By default, the radius is assumed to be provided in the plot-crs

    • You can specify the radius in an arbitrary crs via radius_crs=...

  • The marker-shape is set via shape=...

    • Possible arguments are "ellipses", "rectangles", "geod_circles"

  • Additional keyword-arguments are passed to the matplotlib collections used to draw the shapes (e.g. โ€œfacecolorโ€, โ€œedgecolorโ€, โ€œlinewidthโ€, โ€œalphaโ€, etc.)

  • Multiple markers can be added in one go by using lists for xy, radius, etc.

๐Ÿ›ธ For dynamic markers checkout m.cb.click.attach.mark() or m.cb.pick.attach.mark()

Maps.add_marker

Add a marker to the plot.

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

# ----- SINGLE MARKERS
# by default, MARKER DIMENSIONS are defined in units of the plot-crs!
m.add_marker(xy=(0, 0), radius=20, shape="rectangles",
             fc="y", ec="r", ls=":", lw=2)
m.add_marker(xy=(0, 0), radius=10, shape="ellipses",
             fc="darkorange", ec="r", ls=":", lw=2)

# MARKER DIMENSIONS can be specified in any CRS!
m.add_marker(xy=(12000000, 0), xy_crs=3857,
             radius=5000000, radius_crs=3857,
             fc=(.5, .5, 0, .4), ec="r", lw=3, n=100)

# GEODETIC CIRCLES with radius defined in meters
m.add_marker(xy=(-135, 35), radius=3000000, shape="geod_circles",
             fc="none", ec="r", hatch="///", lw=2, n=100)

# ----- MULTIPLE MARKERS
x = [-80, -40, 40, 80]    # x-coordinates of the markers
fc = ["r", "g", "b", "c"] # the colors of the markers

# N markers with the same radius
m.add_marker(xy=(x, [-60]*4), radius=10, fc=fc)

# N markers with different radius and properties
m.add_marker(xy=(x, [0]*4),  radius=[15, 10, 5, 2],
             fc=fc, ec=["none", "r", "g", "b"], alpha=[1, .5, 1, .5])

# N markers with different widths and heights
radius = ([15, 10, 5, 15], [5, 15, 15, 2])
m.add_marker(xy=(x, [60]*4), radius=radius, fc=fc)
_images/markers.png

๐Ÿ“‘ Annotations๏ƒ

Static annotations can be added to the map via Maps.add_annotation().

  • The location is defined completely similar to m.add_marker() above.

    • You can annotate a datapoint via its ID, or arbitrary coordinates in any crs.

  • Additional arguments are passed to matplotlib.pyplot.annotate and matplotlib.pyplot.text

    • This gives a lot of flexibility to style the annotations!

๐Ÿ›ธ For dynamic annotations checkout m.cb.click.attach.annotate() or m.cb.pick.attach.annotate()

Maps.add_annotation

Add an annotation to the plot.

from eomaps import Maps
import numpy as np
x, y = np.mgrid[-45:45, 20:60]

m = Maps(crs=4326)
m.set_data(x+y, x, y)
m.add_feature.preset.coastline(ec="k", lw=.75)
m.plot_map()

# annotate any point in the dataset via the data-index
m.add_annotation(ID=345)
# annotate an arbitrary position (in the plot-crs)
m.add_annotation(
    xy=(20,25), text="A formula:\n $z=\sqrt{x^2+y^2}$",
    fontweight="bold", bbox=dict(fc=".6", ec="none", pad=0.2))
# annotate coordinates defined in arbitrary crs
m.add_annotation(
    xy=(2873921, 6527868), xy_crs=3857, xytext=(5,5),
    text="A location defined \nin epsg 3857", fontsize=8,
    rotation=-45, bbox=dict(fc="skyblue", ec="k", ls="--", pad=0.2))

# functions can be used for more complex text
def text(m, ID, val, pos, ind):
    return f"lon={pos[0]}\nlat={pos[1]}"

props = dict(xy=(-1.5, 38.45), text=text,
             arrowprops=dict(arrowstyle="-|>", fc="fuchsia",
                             mutation_scale=15))

m.add_annotation(**props, xytext=(20, 20), color="darkred")
m.add_annotation(**props, xytext=(-60, 20), color="purple")
m.add_annotation(**props, xytext=(-60, -40), color="dodgerblue")
m.add_annotation(**props, xytext=(20, -40), color="olive")

# multiple annotations can be added in one go (xy=([...], [...]) also works!)
m.add_annotation(ID=[2500, 2700, 2900], text=lambda ID, **kwargs: str(ID),
                 color="w", fontweight="bold", rotation=90,
                 arrowprops=dict(width=5, fc="b", ec="orange", lw=2),
                 bbox=dict(boxstyle="round, rounding_size=.8, pad=.5",
                           fc="b", ec="orange", lw=2))

m.add_annotation(ID=803, xytext=(-80,60),
                 bbox=dict(ec="r", fc="gold", lw=3),
                 arrowprops=dict(
                     arrowstyle="fancy", relpos=(.48,-.2),
                     mutation_scale=40, fc="r",
                     connectionstyle="angle3, angleA=90, angleB=-25"))
_images/annotations.png

๐Ÿšฒ Lines๏ƒ

Lines can be added to a map with Maps.add_line().

  • A line is defined by a list of anchor-points and a connection-method

  • The coordinates of the anchor-points can be provided in any crs

  • Possible connection-methods are:

    • connect="geod": connect points via geodesic lines (the default)

      • use n=10 to calculate 10 intermediate points between each anchor-point

      • or use del_s=1000 to calculate intermediate points (approximately) every 1000 meters

        • check the return-values of Maps.add_line() to get the actual distances used in each line-segment

    • connect="straight": connect points via straight lines

    • connect="straight_crs": connect points with reprojected lines that are straight in a given projection

      • use n=10 to calculate 10 (equally-spaced) intermediate points between each anchor-point

  • Additional keyword-arguments are passed to matplotlib.pyplot.plot

    • This gives a lot of flexibility to style the lines!

Maps.add_line

Draw a line by connecting a set of anchor-points.

from eomaps import Maps
import matplotlib.patheffects as path_effects

m = Maps(Maps.CRS.Sinusoidal(), figsize=(8, 4))
m.add_feature.preset.ocean()

p0 = [(-100,10), (34, -56), (125, 57)]
p1 = [(-120,50), (-42, 63), (45, 57)]
p2 = [(-20,-45), (-20, 45), (45, 45), (45, -20), (-20,-45)]


m.add_line(p0, connect="geod", del_s=100000,
           lw=0.5, c="k", mark_points="rs",
           marker=".", markevery=10)

m.add_line(p1, connect="straight", c="b", ls="--",
           mark_points=dict(fc="y", ec="k", lw=.5))

m.add_line(p2, connect="straight_crs", c="r",
           n=5, lw=0.25, ms=5,
           path_effects=[
               path_effects.withStroke(linewidth=3,
                                       foreground="gold"),
               path_effects.TickedStroke(angle=90,
                                         linewidth=1,
                                         length=0.5)])
_images/lines.png

โ–ญ Rectangular areas๏ƒ

To indicate rectangular areas in any given crs, simply use Maps.indicate_extent():

Maps.indicate_extent

Indicate a rectangular extent in a given crs on the map.

from eomaps import Maps
m = Maps(crs=3035)
m.add_feature.preset.coastline(ec="k")

# indicate a lon/lat rectangle
m.indicate_extent(-20, 35, 40, 50, hatch="//", fc="none", ec="r")

# indicate some rectangles in epsg:3035
hatches = ["*", "xxxx", "...."]
colors = ["yellow", "r", "darkblue"]
for i, h, c in zip(range(3), hatches, colors):
    pos0 = (2e6 + i*2e6, 7e6, 3.5e6 + i*2e6, 9e6)
    pos1 = (2e6 + i*2e6, 7e6 + 3e6, 3.5e6 + i*2e6, 9e6 + 3e6)

    m.indicate_extent(*pos0, crs=3857, hatch=h, lw=0.25, ec=c)
    m.indicate_extent(*pos1, crs=3857, hatch=h, lw=0.25, ec=c)

# indicate a rectangle in European Equi7Grid  projection
m.indicate_extent(1000000, 1000000, 4800000, 4800000,
                  crs=Maps.CRS.Equi7_EU,
                  fc="g", alpha=0.5, ec="k")
_images/indicate_extent.png

๐Ÿฅฆ Logos๏ƒ

To add a logo (or basically any image file .png, .jpeg etc.) to the map, you can use Maps.add_logo().

Logos can be re-positioned and re-sized with the ๐Ÿ—๏ธ Layout Editor!

  • To fix the relative position of the logo with respect to the map-axis, use fix_position=True

from eomaps import Maps
m = Maps()
m.add_feature.preset.coastline()
m.add_logo(position="ul", size=.15)
m.add_logo(position="ur", size=.15)
# notice that the bottom logos maintain their relative position on resize/zoom events!
# (and also that they can NOT be moved with the layout-editor)
m.add_logo(position="lr", size=.3, pad=(0.1,0.05), fix_position=True)
m.add_logo(position="ll", size=.4, fix_position=True)
_images/logos.png

Maps.add_logo

Add a small image (png, jpeg etc.) to the map.

๐Ÿ“ Scalebars๏ƒ

A scalebar can be added to a map via Maps.add_scalebar().

  • By default, the scalebar will dynamically estimate an appropriate scale and position based on the currently visible map extent.

    • To change the number of segments for the scalebar, use s = m.add_scalebar(n=5) or s.set_n(5)

    • To set the length of the segments to a fixed distance, use s = m.add_scalebar(scale=1000) or s.set_scale(1000)

    • To fix the position of the scalebar, use s = m.add_scalebar(pos=(20, 40)) or s.set_position(20, 40)

In addition, many style properties of the scalebar can be adjusted to get the look you want.

  • check the associated setter-functions ScaleBar.set_< label / scale / lines / labels >_props below!

Maps.add_scalebar

Add a scalebar to the map.

from eomaps import Maps
m = Maps(Maps.CRS.Sinusoidal())
m.add_feature.preset.ocean()
s = m.add_scalebar()
_images/scalebar.gif

Interacting with the scalebar

The scalebar is a pickable object!

Click on it with the LEFT mouse button to drag it around, and use the RIGHT mouse button (or press escape) to make it fixed again.

If the scalebar is picked (indicated by a red border), you can use the following functionalities to adjust some of the ScaleBar properties:

  • use the scroll-wheel to adjust the auto-scale of the scalebar (hold down shift for larger steps)

  • use control + scroll-wheel to adjust the size of the labels

  • press delte to remove the scalebar from the plot

  • press + or - to rotate the scalebar

  • press up/down/left/right to increase the size of the frame

  • press alt + up/down/left/right: decrease the size of the frame

  • press control + left/right: to increase / decrease the spacing between labels and scale

  • press control + up/down: to rotate the labels

Note: Once you have created a nice scalebar, you can always use s.print_code() to get an executable code that will reproduce the current appearance of the scalebar.

The returned ScaleBar object provides the following useful methods:

ScaleBar.print_code

Print the command that will reproduce the scalebar in its current state.

ScaleBar.apply_preset

Apply a style-preset to the Scalebar.

ScaleBar.remove

Remove the scalebar from the map.

ScaleBar.set_scale

Set the length of a segment of the scalebar in meters.

ScaleBar.set_n

Set number of segments to use for the scalebar.

ScaleBar.set_position

Set the position of the colorbar.

ScaleBar.set_label_props

Set the style properties of the labels.

ScaleBar.set_scale_props

Set the style properties of the scale.

ScaleBar.set_line_props

Set the style properties of the lines connecting the scale and the labels.

ScaleBar.set_patch_props

Set the style properties of the background patch.

ScaleBar.set_auto_scale

Automatically evaluate an appropriate scale for the scalebar.

ScaleBar.set_pickable

Set if the scalebar is interactive (True) or not (False).

ScaleBar.set_size_factor

Set the size_factor that is used to adjust the size of the labels.

ScaleBar.get_position

Return the current position (and orientation) of the scalebar.

ScaleBar.get_scale

Get the currently used scale of the scalebar.

ScaleBar.get_size_factor

Get the current size-factor of the scalebar.

๐Ÿงญ Compass (or North Arrow)๏ƒ

A compass can be added to the map via Maps.add_compass():

  • To add a North-Arrow, use m.add_compass(style="north arrow")

Interacting with the compass

The compass is a pickable object!

Click on it with the LEFT mouse button to drag it around!

While a compass is picked (and the LEFT mouse button is pressed), the following additional interactions are available:

  • press delte on the keyboard: remove the compass from the plot

  • rotate the mouse wheel: scale the size of the compass

Maps.add_compass

Add a "compass" or "north-arrow" to the map.

from eomaps import Maps
m = Maps(Maps.CRS.Stereographic())
m.add_feature.preset.ocean()

m.add_compass()
_images/compass.gif

The compass object is dynamically updated if you pan/zoom the map, and it can be dragged around on the map with the mouse.

The returned compass object has the following useful methods assigned:

Compass.remove

Remove the compass from the map.

Compass.set_patch

Set the style of the background patch.

Compass.set_scale

Set the size scale-factor of the compass.

Compass.set_pickable

Set if the compass can be picked with the mouse or not.

Compass.set_ignore_invalid_angles

Set how to deal with invalid rotation-angles.

Compass.get_position

Return the current position of the compass.

Compass.get_scale

Return the current size scale-factor of the compass.

โ–ฆ Gridlines๏ƒ

Gridlines can be added to the map via Maps.add_gridlines().

If d is provided, the gridlines will be fixed

  • If you provide a number, it is used as grid-spcing (in degrees)

  • If you provide a list or numpy.array, it is used to draw lines only at the specific coordinates

  • To use different settings for latitude and longitude lines, provide a 2-tuple of the aforementioned types.

If no explicit grid-spacing is provided (e.g. d=None), the grid is dynamically updated based on the visible extent.

  • Use auto_n=... to adjust the density of the auto-gridlines.

Maps.add_gridlines

Add gridlines to the map.

from eomaps import Maps
m = Maps(Maps.CRS.Mollweide(), frameon=False)
m.add_feature.preset.ocean()

# add gridlines with a fixed grid-spacing
mg = m.new_layer("grid")
g0 = mg.add_gridlines(d=40, ec="orange", lw=3, zorder=2)
g1 = mg.add_gridlines(d=(10, 20), ec="orange", lw=.5, zorder=1)

# add fine-grained gridlines in a specific area
g2 = mg.add_gridlines(d=2, ec="darkred", lw=0.5, zorder=0,
                      bounds=(-20, 20, -10, 30))
g3 = mg.add_gridlines(d=2, ec="b", lw=0.5, zorder=0,
                      bounds=(60, 100, 30, 70))

# add dedicated gridlines at specific coordinates
g4 = mg.add_gridlines(([-123, -112, -75], [35, 65]),
                      ec="k", lw=2, ls="--", zorder=20,
                      bounds=(-140, 20, -50, 70)
                      )

m.show_layer(m.layer, "grid")
_images/grid_01.png

In addition, the returned GridLines instance supports the following useful methods:

GridLines.set_d

Set a fixed gridline distance (in degrees).

GridLines.set_auto_n

Set the number of (auto) gridlines to draw in the currently visible extent.

GridLines.set_n

Set the number of intermediate points to calculate for each gridline.

GridLines.set_bounds

Set the extent of the area in which gridlines are drawn.

GridLines.update_line_props

Set/update the properties of the drawn lines (e.g.

GridLines.remove

Remove the grid from the map.

GridLines.add_labels

Add labels to the gridlines.

โœ Add Labels to the Grid๏ƒ

Labels can be added to a grid via the GridLines.add_labels() directive.

In general, labels are added at points where the lines of the grid intersects with the axis-boundary. (Note that this provides a lot of flexibility since a map can have as many grids as you like and each grid can have its own labels!)

The where parameter can be used to control where grid labels are added:

  • Use an arbitrary combination of the letters "tblr" to draw labels at the top, bottom, left or right boundaries.

    • If this option is used, longitude-lines are only labeled top/bottom and latitude-lines are only labeled left/right.

  • Use "all" to label all intersection points.

  • Use an integer to draw labels only at the nth found intersection-points.

In addition, the exclude parameter can be used to exclude specific labels based on their lon/lat values and the every parameter can be used to add a label only to every nth grid line.

To change the appearance of the labels, any kwarg supported by matplotlib.pyplot.text can be used (e.g. color, fontsize, fontweight, โ€ฆ).

from eomaps import Maps
m = Maps(Maps.CRS.Stereographic(), figsize=(5, 6))
m.set_extent((-83, -20, -59, 13))
m.add_feature.preset.coastline()
m.add_feature.preset.ocean()

# draw a regular grid with 10 degree grid-spacing
# and add labels to all lines except some selected lines
g = m.add_gridlines(10, lw=0.25, alpha=0.5)
g.add_labels(fontsize=6, exclude=([-40, -30], [-30]))

# draw some specific gridlines and add bold green labels
g = m.add_gridlines(([-40, -30], [-30]), c="g", lw=1.5)
gl0 = g.add_labels(where="tlr", c="g", offset=15, fontweight="bold")

# draw a bounded grid and add labels
g = m.add_gridlines(10, bounds=[-50, -20, -40, -20], c="b", lw=2)
g = m.add_gridlines(5,  bounds=[-50, -20, -40, -20], c="b")
gl = g.add_labels(where=0, fontsize=8, every=(1, -1, 2), c="b")
_images/grid_labels_01.png

๐Ÿฆœ Utility widgets๏ƒ

Some helpful utility widgets can be added to a map via Maps.util.

Maps.util

A collection of utility tools that can be added to EOmaps plots.

Layer switching๏ƒ

To simplify switching between layers, there are currently 2 widgets available:

  • m.util.layer_selector() : Add a set of clickable LayerSelector buttons to the map that activates the corresponding layers.

  • m.util.layer_slider() : Add a LayerSlider to the map that iterates through the available layers.

By default, the widgets will show all available layers (except the โ€œallโ€ layer) and the widget will be automatically updated whenever a new layer is created on the map.

  • To show only a subset of layers, provide an explicit list via: layers=[...layer names...].

  • To exclude certain layers from the widget, use exclude_layers=[...layer-names to exclude...]

  • To remove a previously created widget s from the map, simply use s.remove()

layer_selector

A button-widget that can be used to select the displayed plot-layer.

layer_slider

Get a slider-widget that can be used to switch between layers.

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

m2 = m.new_layer(layer="ocean")
m2.add_feature.preset.ocean()

s = m.util.layer_selector()
_images/layer_selector.gif

๐Ÿ”ฌ Inset-maps - zoom-in on interesting areas๏ƒ

Inset maps that show zoomed-in regions can be created with Maps.new_inset_map().

m = Maps()                                      # the "parent" Maps-object (e.g. the "big" map)
m.add_feature.preset.coastline()
m_i = m.new_inset_map(xy=(125, 40), radius=10)  # a new Maps-object that represents the inset-map
m_i.add_feature.preset.ocean()                  # it can be used just like any other Maps-objects!
  • An inset-map is defined by itโ€™s center-position and a radius

  • The used boundary-shape can be one of:

    • โ€œellipsesโ€ (e.g. projected ellipses with a radius defined in a given crs)

    • โ€œrectanglesโ€ (e.g. projected rectangles with a radius defined in a given crs)

    • โ€œgeod_circlesโ€ (e.g. geodesic circles with a radius defined in meters)

For convenience, inset-map objects have the following special methods defined:

InsetMaps.set_inset_position

Set the (center) position and size of the inset-map.

InsetMaps.indicate_inset_extent

Add a polygon to a map that indicates the extent of the inset-map.

InsetMaps.add_indicator_line

Add a line that connects the inset-map to the inset location on a given map.

Checkout the associated example on how to use inset-maps: ๐Ÿ”ฌ Inset-maps - get a zoomed-in view on selected areas

To quickly re-position (and re-size) inset-maps, have a look at the ๐Ÿ—๏ธ Layout Editor!

from eomaps import Maps
m = Maps(Maps.CRS.PlateCarree(central_longitude=-60))
m.add_feature.preset.ocean()

m_i = m.new_inset_map(xy=(5, 45), radius=10,
                      plot_position=(.3, .5), plot_size=.7,
                      boundary=dict(ec="r", lw=4),
                      indicate_extent=dict(fc=(1,0,0,.5),
                                           ec="r", lw=1)
                      )
m_i.add_indicator_line(m, c="r")

m_i.add_feature.preset.coastline()
m_i.add_feature.preset.countries()
m_i.add_feature.preset.ocean()
m_i.add_feature.cultural.urban_areas(fc="r", scale=10)
m_i.add_feature.physical.rivers_europe(ec="b", lw=0.25,
                                       fc="none", scale=10)
m_i.add_feature.physical.lakes_europe(fc="b", scale=10)
_images/inset_maps.png

new_inset_map

Create a new (empty) inset-map that shows a zoomed-in view on a given extent.

๐Ÿ”Ž Zoomed in views on datasets๏ƒ

To simplify the creation of โ€œzoomed-inโ€ views on datasets, both the data and the classification of the data must be the same.

For this purpose, EOmaps provides 2 convenience-functions:

from eomaps import Maps
import numpy as np

x, y = np.meshgrid(np.linspace(-20, 20, 50), np.linspace(-50, 60, 100))
data = x + y

m = Maps(ax=131)
m.set_data(data, x, y)
m.set_shape.raster()
m.set_classify.Quantiles(k=10)
m.plot_map(cmap="tab10", vmin=-10, vmax=40)

# Create a new inset-map that shows a zoomed-in view on a given dataset
m_inset = m.new_inset_map(xy=(5, 20), radius=8, plot_position=(0.75, .5))

# inherit both the data and the classification specs from "m"
m_inset.inherit_data(m)
m_inset.inherit_classification(m)

m_inset.set_shape.rectangles()
m_inset.plot_map(ec="k", lw=0.25)

โœ๏ธ Draw Shapes on the map๏ƒ

Starting with EOmaps v5.0 it is possible to draw simple shapes on the map using Maps.draw.

  • The shapes can be saved to disk as geo-coded shapefiles using m.draw.save_shapes(filepath).
    (Saving shapes requires the geopandas module!)
  • To remove the most recently drawn shape use m.draw.remove_last_shape().

m = Maps()
m.add_feature.preset.coastline()
m.draw.polygon()
_images/draw_shapes.gif

Note

Drawing capabilities are fully integrated in the ๐Ÿงฐ Companion Widget. In most cases it is much more convenient to draw shapes with the widget instead of executing the commands in a console!

In case you still stick to using the commands for drawing shape, it is important to know that the calls for drawing shapes are non-blocking and starting a new draw will silently cancel active draws!

ShapeDrawer.new_drawer

Initialize a new ShapeDrawer.

ShapeDrawer.rectangle

Draw a rectangle.

ShapeDrawer.circle

Draw a circle.

ShapeDrawer.polygon

Draw arbitarary polygons

ShapeDrawer.save_shapes

Save the drawn shapes to a file.

ShapeDrawer.remove_last_shape

Remove the most recently plotted polygon from the map.

๐Ÿ—๏ธ Layout Editor๏ƒ

EOmaps provides a Layout Editor that can be used to quickly re-arrange the positions of all axes of a figure. You can use it to simply drag the axes the mouse to the desired locations and change their size with the scroll-wheel.

Keyboard shortcuts are assigned as follows:

  • Press ALT + L: enter the Layout Editor mode

  • Press ALT + L again or escape to exit the Layout Editor

Pick and re-arrange the axes as you like with the mouse.

  • To pick multiple axes, hold down shift!

  • Resize picked axes with the scroll-wheel
    (or by pressing the + and - keys)
  • Hold down h or v to change horizontal/vertical size

  • Hold down control to change ratio between colorbar and histogram

Press keys 1-9 to set the grid-spacing for the โ€œsnap-to-gridโ€ functionality

  • Press 0 to deactivate โ€œsnap-to-gridโ€

Press control + z to undo the last step Press control + y to redo the last undone step

_images/layout_editor.gif

Save and restore layouts๏ƒ

Once a layout (e.g. the desired position of the axes within a figure) has been arranged, the layout can be saved and re-applied with:

  • ๐ŸŒŸ Maps.get_layout(): get the current layout (or dump the layout as a json-file)

  • ๐ŸŒŸ Maps.apply_layout(): apply a given layout (or load and apply the layout from a json-file)

It is also possible to enter the Layout Editor and save the layout automatically on exit with:

  • ๐ŸŒŸ m.edit_layout(filepath=...): enter LayoutEditor and save layout as a json-file on exit

Note

A layout can only be restored if the number (and order) of the axes remains the same! In other words:

  • you always need to save a new layout-file after adding additional axes (or colorbars!) to a map

get_layout

Get the positions of all axes within the current plot.

apply_layout

Set the positions of all axes within the current plot based on a previously defined layout.

edit_layout

Activate the "layout-editor" to quickly re-arrange the positions of subplots.

๐Ÿ“ฆ Reading data (NetCDF, GeoTIFF, CSVโ€ฆ)๏ƒ

EOmaps provides some basic capabilities to read and plot directly from commonly used file-types.

By default, Maps.from_file and Maps.new_layer_from_file will attempt to plot the data with shade_raster (if it fails it will fallback to shade_points and finally to ellipses).

Note

At the moment, the readers are intended as a โ€œshortcutโ€ to read well-structured datasets! If they fail, read the data manually and then set the data as usual via m.set_data(...).

Under the hood, EOmaps uses the following libraries to read data:

  • GeoTIFF (rioxarray + xarray.open_dataset())

  • NetCDF (xarray.open_dataset())

  • CSV (pandas.read_csv())

Maps.from_file

A collection of methods to initialize a new Maps-object from a file.

Maps.new_layer_from_file

A collection of methods to add a new layer to an existing Maps-object from a file.

Read relevant data from a file๏ƒ

m.read_file.<filetype>(...) can be used to read all relevant data (e.g. values, coordinates & crs) from a file.

m = Maps()
data = m.read_file.NetCDF(
    "the filepath",
    parameter="adsf",
    coords=("longitude", "latitude"),
    data_crs=4326,
    isel=dict(time=123)
    )
m.set_data(**data)
...
m.plot_map()

read_file.GeoTIFF

Read all relevant information necessary to add a GeoTIFF to the map.

read_file.NetCDF

Read all relevant information necessary to add a NetCDF to the map.

read_file.CSV

Read all relevant information necessary to add a CSV-file to the map.

Initialize Maps-objects from a file๏ƒ

Maps.from_file.<filetype>(...) can be used to directly initialize a Maps object from a file. (This is particularly useful if you have a well-defined file-structure that you need to access regularly)

m = Maps.from_file.GeoTIFF(
    "the filepath",
    classify_specs=dict(Maps.CLASSFIERS.Quantiles, k=10),
    cmap="RdBu"
    )
m.add_colorbar()
m.cb.pick.attach.annotate()

from_file.GeoTIFF

Convenience function to initialize a new Maps-object from a GeoTIFF file.

from_file.NetCDF

Convenience function to initialize a new Maps-object from a NetCDF file.

from_file.CSV

Convenience function to initialize a new Maps-object from a CSV file.

Add new layers to existing Maps-objects from a file๏ƒ

Similar to Maps.from_file, a new layer based on a file can be added to an existing Maps object via Maps.new_layer_from_file.<filetype>(...).

m = Maps()
m.add_feature.preset.coastline()

m2 = m.new_layer_from_file(
    "the filepath",
    parameter="adsf",
    coords=("longitude", "latitude"),
    data_crs=4326,
    isel=dict(time=123),
    classify_specs=dict(Maps.CLASSFIERS.Quantiles, k=10),
    cmap="RdBu"
    )

new_layer_from_file.GeoTIFF

Convenience function to initialize a new Maps-object from a GeoTIFF file.

new_layer_from_file.NetCDF

Convenience function to initialize a new Maps-object from a NetCDF file.

new_layer_from_file.CSV

Convenience function to initialize a new Maps-object from a CSV file.

๐Ÿ”ธ Miscellaneous๏ƒ

some additional functions and properties that might come in handy:

Maps.on_layer_activation

Attach a callback that is executed if the associated layer is activated.

Maps.set_extent_to_location

Set the map-extent based on a given location query.

Maps.get_crs

Get the pyproj CRS instance of a given crs specification.

Maps.BM

The Blit-Manager used to dynamically update the plots.

Maps.join_limits

Join the x- and y- limits of the maps (crs must be equal!).

Maps.snapshot

Print a static image of the figure to the active IPython display.

Maps.refetch_wms_on_size_change

Set the behavior of WebMap services with respect to size changes.

Maps.fetch_companion_wms_layers

Fetch (and cache) WebMap layer names for the companion-widget.

Maps.inherit_classification

Use the classification of another Maps-object when plotting the data.

Maps.inherit_data

Use the data of another Maps-object (without copying).