WMTS failing on KeyError: 'urn:ogc:def:crs:EPSG::4326'
Description
Hi. I'm trying to use a wmts map layer as the background for a plot, but when trying to plot it I fail with the trace given below. I'm unsure if it is me using cartopy wrongly or an issue.
Code to reproduce
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.epsg(32632))
ax.add_wmts('https://opencache.statkart.no/gatekeeper/gk/gk.open_wmts?request=GetCapabilities&service=wmts',
layer_name='norgeskart_bakgrunn2')
plt.show()
Traceback
Traceback (most recent call last):
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3.py", line 307, in idle_draw
self.draw()
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3agg.py", line 76, in draw
self._render_figure(allocation.width, allocation.height)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/backends/backend_gtk3agg.py", line 20, in _render_figure
backend_agg.FigureCanvasAgg.draw(self)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/backends/backend_agg.py", line 388, in draw
self.figure.draw(self.renderer)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/figure.py", line 1709, in draw
renderer, self, artists, self.suppressComposite)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/image.py", line 135, in _draw_list_compositing_images
a.draw(renderer)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/foo/.local/lib/python3.6/site-packages/Cartopy-0.17.1.dev219_-py3.6-linux-x86_64.egg/cartopy/mpl/geoaxes.py", line 434, in draw
inframe=inframe)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/axes/_base.py", line 2647, in draw
mimage._draw_list_compositing_images(renderer, self, artists)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/image.py", line 135, in _draw_list_compositing_images
a.draw(renderer)
File "/home/foo/.local/lib/python3.6/site-packages/matplotlib/artist.py", line 38, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "/home/foo/.local/lib/python3.6/site-packages/Cartopy-0.17.1.dev219_-py3.6-linux-x86_64.egg/cartopy/mpl/slippy_image_artist.py", line 69, in draw
target_resolution=(window_extent.width, window_extent.height))
File "/home/foo/.local/lib/python3.6/site-packages/Cartopy-0.17.1.dev219_-py3.6-linux-x86_64.egg/cartopy/io/ogc_clients.py", line 484, in fetch_raster
max_pixel_span=max_pixel_span)
File "/home/foo/.local/lib/python3.6/site-packages/Cartopy-0.17.1.dev219_-py3.6-linux-x86_64.egg/cartopy/io/ogc_clients.py", line 578, in _wmts_images
meters_per_unit = METERS_PER_UNIT[tile_matrix_set.crs]
KeyError: 'urn:ogc:def:crs:EPSG::4326'
Full environment definition
Operating system
Linus Mint
Cartopy version
'0.17.1.dev219+' a9192f8dde5a2ec36c398dfbea2da0e1dc7f012c
Looks like that value is just missing from the internal dictionary:
https://github.com/SciTools/cartopy/blob/bd4b90c4bfec99afd97e8acd29bc05bde29070d8/lib/cartopy/io/ogc_clients.py#L77-L84
I'm not sure if that just hadn't been encountered before or what. Also not entirely sure what the correct value is--I guess matching the existing WGS84?
I would guess it should match WGS84
I am having the same issue and am unable to find a solution. Would appreciate any help / pointers if this has been solved somewhere.
Code to reproduce:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.PlateCarree())
ax.add_wmts('https://tiles.emodnet-bathymetry.eu/wmts/1.0.0/WMTSCapabilities.xml',
layer_name='baselayer')
plt.show()
Traceback
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj)
339 pass
340 else:
--> 341 return printer(obj)
342 # Finally look for special method names
343 method = get_real_method(obj, self.print_method)
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/IPython/core/pylabtools.py in <lambda>(fig)
246
247 if 'png' in formats:
--> 248 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
249 if 'retina' in formats or 'png2x' in formats:
250 png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/IPython/core/pylabtools.py in print_figure(fig, fmt, bbox_inches, **kwargs)
130 FigureCanvasBase(fig)
131
--> 132 fig.canvas.print_figure(bytes_io, **kw)
133 data = bytes_io.getvalue()
134 if fmt == 'svg':
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2228 else suppress())
2229 with ctx:
-> 2230 self.figure.draw(renderer)
2231
2232 if bbox_inches:
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
72 @wraps(draw)
73 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74 result = draw(artist, renderer, *args, **kwargs)
75 if renderer._rasterizing:
76 renderer.stop_rasterizing()
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/figure.py in draw(self, renderer)
2735
2736 self.patch.draw(renderer)
-> 2737 mimage._draw_list_compositing_images(
2738 renderer, self, artists, self.suppressComposite)
2739
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
--> 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py in draw(self, renderer, **kwargs)
556 self._done_img_factory = True
557
--> 558 return matplotlib.axes.Axes.draw(self, renderer=renderer, **kwargs)
559
560 def _update_title_position(self, renderer):
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/_api/deprecation.py in wrapper(*inner_args, **inner_kwargs)
429 else deprecation_addendum,
430 **kwargs)
--> 431 return func(*inner_args, **inner_kwargs)
432
433 return wrapper
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/axes/_base.py in draw(self, renderer, inframe)
2923 renderer.stop_rasterizing()
2924
-> 2925 mimage._draw_list_compositing_images(renderer, self, artists)
2926
2927 renderer.close_group('axes')
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
130 if not_composite or not has_images:
131 for a in artists:
--> 132 a.draw(renderer)
133 else:
134 # Composite any adjacent images together
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
49 renderer.start_filter()
50
---> 51 return draw(artist, renderer, *args, **kwargs)
52 finally:
53 if artist.get_agg_filter() is not None:
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/cartopy/mpl/slippy_image_artist.py in draw(self, renderer, *args, **kwargs)
56 [x1, y1], [x2, y2] = ax.viewLim.get_points()
57 if not self.user_is_interacting:
---> 58 located_images = self.raster_source.fetch_raster(
59 ax.projection, extent=[x1, x2, y1, y2],
60 target_resolution=(window_extent.width, window_extent.height))
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/cartopy/io/ogc_clients.py in fetch_raster(self, projection, extent, target_resolution)
455
456 # Fetch a suitable image and its actual extent.
--> 457 wmts_image, wmts_actual_extent = self._wmts_images(
458 self.wmts, self.layer, matrix_set_name,
459 extent=wmts_desired_extent,
~/.pyenv/versions/scoot_venv_v2/lib/python3.8/site-packages/cartopy/io/ogc_clients.py in _wmts_images(self, wmts, layer, matrix_set_name, extent, max_pixel_span)
552 tile_matrix_set = wmts.tilematrixsets[matrix_set_name]
553 tile_matrices = tile_matrix_set.tilematrix.values()
--> 554 meters_per_unit = METERS_PER_UNIT[tile_matrix_set.crs]
555 tile_matrix = self._choose_matrix(tile_matrices, meters_per_unit,
556 max_pixel_span)
KeyError: 'urn:ogc:def:crs:EPSG::4326'
Operating system Mac OS 12.2 cartopy-0.20.2
If you know the scaling, something like this will at least set the key for you locally.
from cartopy.io import ogc_clients
ogc_clients.METERS_PER_UNIT['urn:ogc:def:crs:EPSG::4326'] = 1
# or perhaps it needs to equal: METERS_PER_UNIT["urn:ogc:def:crs:OGC:1.3:CRS84"]
PRs to update these values are welcome! It looks like that code hasn't been touched in ~8 years now.