GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
745 stars 216 forks source link

load_tile_map/tilemap: Raise an exception if tiles are not downloaded? #3154

Open seisman opened 5 months ago

seisman commented 5 months ago

See https://github.com/GenericMappingTools/pygmt/actions/runs/8516861579/job/23326547419?pr=3153 for an example run. Sometimes, the test test_tilemap_ogc_wgs84 fails with long error messages and the most useful error messages are:

----------------------------- Captured stderr call -----------------------------
grdimage (gmtapi_import_grid): Could not read from file [/tmp/pygmt-pe8klnis.tif]
[Session pygmt-session (2510)]: Error returned from GMT API: GMT_GRID_READ_ERROR (18)
[Session pygmt-session (2510)]: Error returned from GMT API: GMT_GRID_READ_ERROR (18)
[Session pygmt-session (2510)]: Error returned from GMT API: GMT_GRID_READ_ERROR (18)

As I understand it, when the contextily.bounds2img function fails to retrieve the tiles (likely due to temporary internet connection issues), it returns an empty numpy array (https://github.com/geopandas/contextily/blob/f8c34e0a25e14d9c36c15c04892754e9369ba9bb/contextily/tile.py#L665). The zero numpy array then is passed to the grdimage, which may cause the errors above.

So, maybe in load_tile_map/Figure.tilemap, when contextily.bounds2img returns an empty numpy array, we should raise an exception? The test still fails in this case, but at least the error messages will be much easier to understand.

seisman commented 2 months ago

Sometimes I can't build the documentation locally, because I can't access the tile servers. I'm wondering if we should catch the "requests.exceptions.ConnectionError" exception?

    ../examples/gallery/maps/tilemaps.py failed leaving traceback:

    Traceback (most recent call last):
      File "/home/seisman/OSS/gmt/pygmt/examples/gallery/maps/tilemaps.py", line 14, in <module>
        fig.tilemap(
      File "/home/seisman/OSS/gmt/pygmt/pygmt/helpers/decorators.py", line 609, in new_module
        return module_func(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/OSS/gmt/pygmt/pygmt/helpers/decorators.py", line 773, in new_module
        return module_func(*bound.args, **bound.kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/OSS/gmt/pygmt/pygmt/src/tilemap.py", line 131, in tilemap
        raster = load_tile_map(
                 ^^^^^^^^^^^^^^
      File "/home/seisman/OSS/gmt/pygmt/pygmt/datasets/tile_map.py", line 138, in load_tile_map
        image, extent = contextily.bounds2img(
                        ^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/contextily/tile.py", line 262, in bounds2img
        arrays = Parallel(n_jobs=n_connections, prefer=preferred_backend)(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/joblib/parallel.py", line 1918, in __call__
        return output if self.return_generator else list(output)
                                                    ^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/joblib/parallel.py", line 1847, in _get_sequential_output
        res = func(*args, **kwargs)
              ^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/joblib/memory.py", line 577, in __call__
        return self._cached_call(args, kwargs, shelving=False)[0]
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/joblib/memory.py", line 532, in _cached_call
        return self._call(call_id, args, kwargs, shelving)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/joblib/memory.py", line 771, in _call
        output = self.func(*args, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/contextily/tile.py", line 291, in _fetch_tile
        array = _retryer(tile_url, wait, max_retries)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/contextily/tile.py", line 430, in _retryer
        request = requests.get(tile_url, headers={"user-agent": USER_AGENT})
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/requests/api.py", line 73, in get
        return request("get", url, params=params, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/requests/api.py", line 59, in request
        return session.request(method=method, url=url, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/requests/sessions.py", line 589, in request
        resp = self.send(prep, **send_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/requests/sessions.py", line 703, in send
        r = adapter.send(request, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/seisman/opt/miniforge/envs/rtd/lib/python3.12/site-packages/requests/adapters.py", line 700, in send
        raise ConnectionError(e, request=request)
    requests.exceptions.ConnectionError: HTTPSConnectionPool(host='a.tile.openstreetmap.org', port=443): Max retries exceeded with url: /14/1008/7200.png (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7f7455b41ac0>: Failed to establish a new connection: [Errno 101] Network is unreachable'))