This article is about EOmaps: A python
package that helps you to create beautiful interactive maps with a few lines of code.
It provides many useful tools to create publication ready maps and allows you to use the maps for interactive geo-data analysis.
The main features are:
- A comprehensive python API to create highly customized maps
- A GUI-widget to interactively compare, analyze and edit maps
- Capabilities to visualize (possibly large) datasets
- Tools to use the maps for interactive data analysis
- An extensive documentation
Use it with your favorite IDE, in Jupyter Notebooks or as a command line tool!
The following sections provide a quick overview on the basic capabilities of EOmaps. We will not go into details, but just explore what's possible and create a few example maps!
Basic concepts - let's create a map!
EOmaps is all about Maps
objects.
To start a new map, we create a Maps
object named m
.
This will initialize a new matplotlib.Figure
and a cartopy.GeoAxes
for a map.
from eomaps import Maps
m = Maps()
m.add_feature.preset("coastline", "ocean")
The Maps
object m
can now be used to add features, plot datasets or to add callbacks to interact with the map.
Map Projections
By default, the map will be in PlateCarree
projection (e.g. lon/lat). To create a map in a different projection, you can use epsg-codes or any cartopy projection.
To add customized features like coastlines, ocean colouring etc. provided by NaturalEarth to the map, use
m.add_feature.<category>.<name>()
.
from eomaps import Maps
m = Maps(crs=Maps.CRS.Mollweide())
m.add_feature.physical.ocean(scale=110, fc="darkslategrey")
m.add_feature.physical.land(scale=110, fc="sienna")
m.add_feature.cultural.admin_0_countries(scale=110, fc="none", ec=(0.9, 0.8, 0.75), lw=0.5)
EOmaps provides a lot of additional features to customize the appearance of a map. Since a complete overview on the capabilities would go beyond the scope of this article, make sure to checkout the documentation to get a more detailed overview!
To give a quick example on the possibilities, here is how you can create the following map that includes gridlines, a scalebar, a compass, a logo, some text and imagery fetched from a WebMap service:
from eomaps import Maps
m = Maps(crs=3035, figsize=(7, 5))
# add some features, a compass, a scalebar and a logo
m.add_feature.physical.ocean(scale=50, fc=".5")
m.add_feature.physical.coastline(scale=50, fc="none", ec="k", lw=1)
m.add_compass(pos=(0.1, 0.1))
m.add_scalebar(pos=(-39, 36), scale=2e5, preset="bw", size_factor=1.8)
m.add_logo()
# add grid lines with bold labels
g = m.add_gridlines(45)
g.add_labels(fontsize=8, fontweight="bold")
# add a watercolor basemap provided by: maps.stamen.com
m.add_wms.OpenStreetMap.add_layer.stamen_watercolor()
# write some info-text below the axes
m.text(0.75, -0.02,
"data provided by: naturalearthdata.com, maps.stamen.com/watercolor",
fontsize=6, transform=m.ax.transAxes)
Data Visualization
Now that we've learned how we can create nice basemaps, its time to do some data visualization!
EOmaps can be used to visualize (possibly very large) datasets. It handles re-projection and classification of the data and provides the following methods to represent 1D or 2D data on a map:
- Draw projected polygons for each data point:
- ellipses/rectangles (radius defined in a given projection)
- geodesic circles (radius defined in meters)
- Draw a marker for each datapoint
- Draw a Voronoi Diagram or a Delaunay Triangulation of the data
- Draw (possibly large) 2D datasets as a rectangular raster
- Use data shading for 1D and 2D datasets to visualize very large datasets.
A detailed overview on how to visualize datasets is given in the Data Visualization section of the documentation.
For now, let's just have a quick look at the code to create a map with a dataset plotted on a globe and an inset-map that highlights a selected area:
# create some random data
import numpy as np
x, y = np.linspace(-10, 10, 100), np.linspace(-20, 70, 200)
x, y = np.meshgrid(x, y)
data = np.sqrt(x4 + y2 + np.random.randint(0, 2e3, x.shape))
from eomaps import Maps
# create a map
m = Maps(Maps.CRS.Orthographic(45, 45))
m.add_feature.preset("coastline", "ocean")
m.add_gridlines(20)
# plot the data
m.set_data(data, x, y, crs=4326) # assign the data
m.set_classify.NaturalBreaks(k=5) # classify the data
m.set_shape.raster() # set the shape
m.plot_map(cmap="RdYlBu", set_extent=False) # plot the data
# add a colorbar
cb = m.add_colorbar()
cb.set_labels("values: $\sqrt{x^4 + y^2 + random}$",
"hist. count", fontweight="bold")
# create a new inset-map to highlight a specific region
m_inset = m.new_inset_map(xy=(5, 45), radius=15)
m_inset.add_extent_indicator(lw=3)
m_inset.add_indicator_line()
m_inset.add_feature.preset("coastline", "ocean")
m_inset.add_gridlines(([0, 10], [50, 40]), lw=2, labels=True)
m_inset.add_gridlines(2.5, lw=0.3)
# plot the same data as before on the inset-map
m_inset.inherit_data(m) # inherit the data
m_inset.inherit_classification(m) # inherit the classification
m_inset.set_shape.ellipses() # use a different shape
m_inset.plot_map() # plot the data
# re-arrange the axes with respect to a given layout
>>> checkout the LayoutEditor!
m.apply_layout(
{'figsize': [6.4, 4.8],
'0_map': [0.025, 0.45, 0.4, 0.5],
'1_cb': [0.01, 0.05, 0.98, 0.25],
'1_cb_histogram_size': 0.8,
'2_inset_map': [0.5, 0.35, 0.42, 0.55]}
)
Layer management
With EOmaps, a figure can have as many layers as you like.
This is really useful to:
- Compare multiple datasets with each other
- Quickly check OpenStreetMap or some satellite imagery to get a broader picture of what's going on
The following image explains how you can create and manage multiple plot-layers. In short:
- Use
m_A = m.new_layer("layer A")
to create a new layer - Now you can use everything we've learned so far to add features to the new layer by using the associated
Maps
objectm_A
.
Companion Widget
Now that we know some basics on how to create (multi-layered) maps and visualize datasets, it is time to introduce the Companion Widget - A nice little graphical user interface that allows you to interact with the figure and explore the capabilities of EOmaps in many ways:
- Compare layers: Create / Switch / Overlay
- Add features: NaturalEarth, WebMaps, etc.
- Plot files: Drag-and-drop GeoTIFF, NetCDF, CSV and shape files
- Draw shapes: Draw and export geocoded polygons on the map
- Add/edit annotations: Add text indicators to the map
- Export images: Customize figure export (png, jpeg, svg...)
To activate the widget, press w
on the keyboard while the mouse is on top of an EOmaps map.
To get information on the buttons/sliders etc. click the info-button!
Data analysis
Finally, lets explore how we can use the maps as interactive data analysis widgets!
To interact with a map, you can attach callback functions that are triggered on one of the following events:
-
General events:
- click: You clicked with the mouse anywhere on a map.
- move: You moved the mouse over a map.
- keypress: A key was pressed on the keyboard.
-
Events associated with a dataset:
- pick: If you click on a map, EOmaps will identify the closest datapoint(s) and provide basic properties (ID, position, value, ...) as arguments to the callback.
There are many pre-defined callbacks, but you can of course also define custom callbacks and attach them to a map.
As a quick example lets create a map with the following click and pick callbacks:
- left-click: show a marker and a basic annotation
- right-click: identify the closest datapoint, indicate the point with a black boundary, show a customized annotation and execute a custom callback
from eomaps import Maps
m = Maps(3035)
m.add_title("Circles with 10° radius.")
m.add_feature.preset("coastline", "ocean")
m.add_logo(pad=(0, 0.2), size=0.2)
m.set_data(data=[1, 2, 3], x=[-47, -6, 63], y=[56, 46, 61], crs=4326)
m.set_shape.ellipses(radius=10, radius_crs=4326)
m.plot_map()
# Attach CLICK callbacks on RIGHT click (button 3)
m.cb.click.attach.annotate(button=3, fontsize=8, xytext=(-30,-70))
m.cb.click.attach.mark(button=3, radius=1, radius_crs=4326, fc="r")
# Attach PICK callbacks on LEFT click (the default)
m.cb.pick.attach.mark(ec="k", fc="none", lw=3, n=100)
def my_text(ID, **kwargs):
return f"Datapoint {ID}"
m.cb.pick.attach.annotate(
text=my_text,
xytext=(0.5, .9),
textcoords="axes fraction",
fontsize=20,
horizontalalignment="center",
bbox=dict(lw=3),
arrowprops=dict(
arrowstyle="fancy",
fc="k",
connectionstyle="Angle3"
)
)
def my_callback(ID, pos, val, **kwargs):
print(f"\nFound datapoint {ID}:\n"
f" xy (plot): {pos}\n"
f" xy (data): ({m.data_specs.x[ID]}, {m.data_specs.y[ID]})\n"
f" value: {val}\n")
m.cb.pick.attach(my_callback)
Conclusion
With this, we have completed our quick tour on the basic features of EOmaps... there is much more to explore!
Give it a try!
Note: EOmaps is a free and open source python module that requires a lot of time and effort to maintain.
-
Found EOmaps useful? Support the development!
- Spread the word!
- Add a citation to your publication
-
Interested in contributing to EOmaps?
- Awesome! Any contributions are welcome!
- Checkout the Contribution Guide on how to get started!
Top comments (0)