Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0ce8f78
update example image
raphaelquast Jun 3, 2025
9a2a7fa
fix m.get_layout() reports figure dimensions as np.float64
Nov 18, 2025
4104cd5
minor
Nov 18, 2025
0442828
fix issues with "always_on_top"
Nov 18, 2025
e0c8cbe
add private method to identify maps-object based on figure coordinates
Nov 18, 2025
2fb8b5e
stop searching for maps object on first hit
Nov 18, 2025
d36ef2b
make pre-commit happy
Nov 18, 2025
ff9154c
Merge branch 'master' into dev
raphaelquast Nov 18, 2025
23c44e2
address deprecation warning for `gdf.unary_union` -> `gdf.union_all()`
Nov 18, 2025
b2659bb
stop testing python 3.8 and add 3.13
Nov 19, 2025
6003ae7
update min. supported python version and increase package version to …
Nov 19, 2025
28ff9ee
allow multiple artists for add_temporary_artist
raphaelquast Nov 20, 2025
f39ce30
allow multiple artists for BM.add_artist and BM.add_bg_artist
raphaelquast Nov 20, 2025
7763215
add convenience-method "m.add_subplot" to add a new subplot to a map
raphaelquast Nov 21, 2025
e35f2b2
use more descriptive debug message for layer-change actions
Nov 21, 2025
a16b37f
fix wms "invalid values for BBox" issues with numpy v2.x
Nov 21, 2025
8ebf515
minor - change callback labels in widget
Nov 21, 2025
c2fcbc8
minor - change labels in widget
Nov 21, 2025
f009cb5
update GEBCO wms urls
Nov 21, 2025
142b99f
make pre-commit happy
Nov 21, 2025
e9d5a3a
minor
Nov 22, 2025
4470acc
add `make_artists_temporary` contextmanager for callback containers
Nov 22, 2025
f861b26
add webmap layer abstract text to info if avaialble
Nov 23, 2025
84b6443
Add new WebMap service provided by the city of Vienna (data.wien.gv.at)
Nov 23, 2025
847b921
fix typo
Nov 23, 2025
3c90522
minor code formatting
Nov 23, 2025
0f607bc
minor
Nov 23, 2025
acfa8cb
add pip install option [docs] to install all doc-build dependencies
Nov 23, 2025
a782750
fix dynamic grids should not be redrawn before fetching a background
Nov 24, 2025
ed12793
properly handle temporary figure artists in "make_artists_temporary"
Nov 24, 2025
2e71149
add methods "transform_lonlat_to_plot" and "transform_plot_to_lonlat"
Nov 24, 2025
d8773e7
minor
Nov 24, 2025
f845c5a
add unittests for all python-files used in the examples-directory
Nov 24, 2025
a6e4785
put example codes in dedicated python-files
Nov 24, 2025
330a2b2
add new "location_indicator" example
Nov 24, 2025
ead2d1c
update examples
Nov 24, 2025
8ed97c5
minor
Nov 24, 2025
1b21722
fix typo
Nov 24, 2025
9350307
update examples
Nov 24, 2025
0d83625
fix layout-editor does not properly handle artists on all layer
raphaelquast Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/testMaps.yml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
# set operating systems to test
os: [ubuntu-latest]
# set python versions to test
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

name: test_Maps ${{ matrix.os }} ${{ matrix.python-version }}
steps:
Expand Down
Binary file modified docs/source/_static/example_images/example_inset_maps.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ In addition, you can use the following dependency-groups to activate only select
- ``pip install eomaps[io]`` Add support for ``pandas``, ``xarray``, ``geopandas`` and ``rioxarray``
- ``pip install eomaps[shade]`` Add capabilities to visualize extremely large datasets (via ``datashader``)
- ``pip install eomaps[classify]`` Add support for ``mapclassify`` to classify datasets
- ``pip install eomaps[docs]`` Install all required dependencies to build the docs

It is also possible to combine dependency-groups, e.g.: ``pip install eomaps[wms, gui]``.

Expand Down
81 changes: 41 additions & 40 deletions eomaps/_blit_manager.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ def cb(*args, **kwargs):
if _log.getEffectiveLevel() <= 10:
logmsg = (
f"Adding {'persistent' if persistent else 'single-shot'} "
f"layer change action for: '{layer}'"
f"layer change action for: '{layer if layer else 'all layers'}': {getattr(func, '__qualname__', func)}"
)
_log.debug(logmsg)

Expand Down Expand Up @@ -903,14 +903,14 @@ def _on_draw_cb(self, event):
if loglevel <= 5:
_log.log(5, "There was an error during draw!", exc_info=True)

def add_artist(self, art, layer=None):
def add_artist(self, *artists, layer=None):
"""
Add a dynamic-artist to be managed.
(Dynamic artists are re-drawn on every update!)

Parameters
----------
art : Artist
artists : Artist

The artist to be added. Will be set to 'animated' (just
to be safe). *art* must be in the figure associated with
Expand All @@ -922,38 +922,38 @@ def add_artist(self, art, layer=None):

The default is None in which case the layer of the base-Maps object is used.
"""

if art.figure != self.figure:
raise RuntimeError(
"EOmaps: The artist does not belong to the figure"
"of this Maps-object!"
)

if layer is None:
layer = self._m.layer

# make sure all layers are converted to string
layer = str(layer)

self._artists.setdefault(layer, list())
for art in artists:
if art.figure != self.figure:
raise RuntimeError(
"EOmaps: The artist does not belong to the figure"
"of this Maps-object!"
)

if art in self._artists[layer]:
return
else:
art.set_animated(True)
self._artists[layer].append(art)
self._artists.setdefault(layer, list())

if isinstance(art, plt.Axes):
self._managed_axes.add(art)
if art in self._artists[layer]:
continue
else:
art.set_animated(True)
self._artists[layer].append(art)

def add_bg_artist(self, art, layer=None, draw=True):
if isinstance(art, plt.Axes):
self._managed_axes.add(art)

def add_bg_artist(self, *artists, layer=None, draw=True):
"""
Add a background-artist to be managed.
(Background artists are only updated on zoom-events... they are NOT animated!)

Parameters
----------
art : Artist
artists : Artist
The artist to be added. Will be set to 'animated' (just
to be safe). *art* must be in the figure associated with
the canvas this class is managing.
Expand All @@ -974,31 +974,32 @@ def add_bg_artist(self, art, layer=None, draw=True):
# make sure all layer names are converted to string
layer = str(layer)

if art.figure != self.figure:
raise RuntimeError
for art in artists:
if art.figure != self.figure:
raise RuntimeError

# put all artist of inset-maps on dedicated layers
if (
getattr(art, "axes", None) is not None
and art.axes.get_label() == "inset_map"
and not layer.startswith("__inset_")
):
layer = "__inset_" + str(layer)
# put all artist of inset-maps on dedicated layers
if (
getattr(art, "axes", None) is not None
and art.axes.get_label() == "inset_map"
and not layer.startswith("__inset_")
):
layer = "__inset_" + str(layer)

if layer in self._bg_artists and art in self._bg_artists[layer]:
_log.info(
f"EOmaps: Background-artist '{art}' already added on layer '{layer}'"
)
return
if layer in self._bg_artists and art in self._bg_artists[layer]:
_log.info(
f"EOmaps: Background-artist '{art}' already added on layer '{layer}'"
)
continue

art.set_animated(True)
self._bg_artists.setdefault(layer, []).append(art)
art.set_animated(True)
self._bg_artists.setdefault(layer, []).append(art)

if isinstance(art, plt.Axes):
self._managed_axes.add(art)
if isinstance(art, plt.Axes):
self._managed_axes.add(art)

# tag all relevant layers for refetch
self._refetch_layer(layer)
# tag all relevant layers for refetch
self._refetch_layer(layer)

for f in self._on_add_bg_artist:
f()
Expand Down
4 changes: 2 additions & 2 deletions eomaps/_data_manager.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -916,9 +916,9 @@ def on_fetch_bg(self, layer=None, bbox=None, check_redraw=True):
self.m.ax.add_collection(coll, autolim=False)

if self.m._coll_dynamic:
self.m.BM.add_artist(coll, self.layer)
self.m.BM.add_artist(coll, layer=self.layer)
else:
self.m.BM.add_bg_artist(coll, self.layer)
self.m.BM.add_bg_artist(coll, layer=self.layer)

self.m._coll = coll

Expand Down
32 changes: 32 additions & 0 deletions eomaps/_maps_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1220,6 +1220,38 @@ def _transf_lonlat_to_plot(self):
self.crs_plot,
)

def transform_plot_to_lonlat(self, x, y):
"""
Transform plot-coordinates to longitude and latitude values.

Parameters
----------
x, y : float or array-like
The coordinates values in the coordinate-system of the plot.

Returns
-------
lon, lat : The coordinates transformed to longitude and latitude values.

"""
return self._transf_plot_to_lonlat.transform(x, y)

def transform_lonlat_to_plot(self, lon, lat):
"""
Transform longitude and latitude values to plot coordinates.

Parameters
----------
lon, lat : float or array-like
The longitude and latitude values to transform.

Returns
-------
x, y : The coordinates transformed to the plot-coordinate system.

"""
return self._transf_lonlat_to_plot.transform(lon, lat)

def on_layer_activation(self, func, layer=None, persistent=False, **kwargs):
"""
Attach a callback that is executed if the associated layer is activated.
Expand Down
22 changes: 16 additions & 6 deletions eomaps/_webmap.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from urllib3.exceptions import InsecureRequestWarning
from io import BytesIO
from pprint import PrettyPrinter
from textwrap import dedent

from packaging import version

Expand Down Expand Up @@ -171,7 +172,7 @@ def add_legend(self, style=None, img=None):
if not self._m.BM._layer_visible(self._layer):
legax.set_visible(False)

self._m.BM.add_artist(legax, self._layer)
self._m.BM.add_artist(legax, layer=self._layer)

def cb_move(event):
if not self._legend_picked:
Expand Down Expand Up @@ -336,6 +337,17 @@ def _set_style(self, style=None):

return style

def _get_layer_info_text(self):
"""Get layer-specific info text of a given layer"""
t = ""
abstract = getattr(self.wms_layer, "abstract", None)
if abstract:
t += f"**Abstract**: <i>{abstract}</i>\n \n---- \n "

t += f"{dedent(getattr(self, '_EOmaps_info', ''))}"

return t


class _WMTSLayer(_WebMapLayer):
def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -440,7 +452,7 @@ def _do_add_layer(self, m, layer, **kwargs):

# attach the info to the artist so it can be identified by the companion widget
if hasattr(self, "_EOmaps_info"):
art._EOmaps_info = self._EOmaps_info
art._EOmaps_info = self._get_layer_info_text()
if hasattr(self, "_EOmaps_source_code"):
art._EOmaps_source_code = self._EOmaps_source_code

Expand Down Expand Up @@ -581,12 +593,10 @@ def _do_add_layer(self, m, layer, **kwargs):
art = self._add_wms(
m.ax, self._wms, self.name, interpolation="spline36", **kwargs
)

art.set_label(f"WebMap service: {self.name}")

# attach the info to the artist so it can be identified by the companion widget
if hasattr(self, "_EOmaps_info"):
art._EOmaps_info = self._EOmaps_info
art._EOmaps_info = self._get_layer_info_text()
if hasattr(self, "_EOmaps_source_code"):
art._EOmaps_source_code = self._EOmaps_source_code

Expand Down Expand Up @@ -1396,7 +1406,7 @@ def draw(self, renderer, *args, **kwargs):
# only re-fetch tiles if the extent has changed
located_images = self.raster_source.fetch_raster(
ax.projection,
extent=[x1, x2, y1, y2],
extent=list(map(float, [x1, x2, y1, y2])),
target_resolution=(window_extent.width, window_extent.height),
)
self.cache = located_images
Expand Down
8 changes: 3 additions & 5 deletions eomaps/annotation_editor.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ def show_info_text(self):
fontfamily="monospace",
)

self.m.BM.add_artist(self._info_artist, "all")
self.m.BM.add_artist(self._info_artist, layer="all")

self._info_cids.add(
self.m.f.canvas.mpl_connect("button_press_event", self._on_press)
Expand Down Expand Up @@ -655,11 +655,9 @@ def print_code(

xy_crs = 4326
if ann.transf is not None:
xy = self.m._transf_plot_to_lonlat.transform(
*ann.transf.transform(*a.xy)
)
xy = self.m.transform_plot_to_lonlat(*ann.transf.transform(*a.xy))
else:
xy = self.m._transf_plot_to_lonlat.transform(*a.xy)
xy = self.m.transform_plot_to_lonlat(*a.xy)

if not all(i in replace for i in ("xy", "xy_crs")):
s = "# NOTE: Anchor coordinates reprojected to epsg=4326!\n" + s
Expand Down
8 changes: 4 additions & 4 deletions eomaps/callbacks.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def _get_annotation_text(
else:
if not crs_is_lonlat:
xlabel, ylabel = "x", "y"
lon, lat = self.m._transf_plot_to_lonlat.transform(*pos)
lon, lat = self.m.transform_plot_to_lonlat(*pos)
lon, lat = [
np.format_float_positional(i, trim="-", precision=pos_precision)
for i in (lon, lat)
Expand Down Expand Up @@ -654,11 +654,11 @@ def mark(
if permanent is False:
# make the annotation temporary
self._temporary_artists.append(marker)
self.m.BM.add_artist(marker, layer)
self.m.BM.add_artist(marker, layer=layer)
elif permanent is None:
self.m.BM.add_bg_artist(marker, layer)
self.m.BM.add_bg_artist(marker, layer=layer)
elif permanent is True:
self.m.BM.add_artist(marker, layer)
self.m.BM.add_artist(marker, layer=layer)

if not hasattr(self, "permanent_markers"):
self.permanent_markers = [marker]
Expand Down
Loading
Loading