DEV Community

Cover image for Geographic data visualization and analysis with EOmaps: Interactive maps in python!
Raphael Quast
Raphael Quast

Posted on

Geographic data visualization and analysis with EOmaps: Interactive maps in python!

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")


Enter fullscreen mode Exit fullscreen mode

A very basic EOmaps map.

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)


Enter fullscreen mode Exit fullscreen mode

A map in Mollweide-projection


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:

A map showcasing some of the features available with EOmaps.



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)

Enter fullscreen mode Exit fullscreen mode




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:

A map showing some data and an [inset-map](https://eomaps.readthedocs.io/en/latest/api_inset_maps.html) to highlight a specific area on the map



# 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))

Enter fullscreen mode Exit fullscreen mode


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]}
)

Enter fullscreen mode Exit fullscreen mode




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 object m_A.

EOmaps layer management

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

EOmaps

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!
?

Using the companion widget.

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

Using Callbacks



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)

Enter fullscreen mode Exit fullscreen mode




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)