File size: 3,992 Bytes
82c899e
 
3a12729
 
82c899e
 
 
 
 
 
3a12729
 
 
 
 
 
 
 
 
 
 
 
82c899e
3a12729
 
 
94b76f8
 
3a12729
 
82c899e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a12729
 
 
 
 
 
82c899e
 
 
 
 
 
 
 
 
 
 
 
94b76f8
82c899e
 
 
 
 
 
94b76f8
82c899e
 
 
 
 
 
 
 
 
 
 
 
94b76f8
82c899e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import os
import hashlib
import tempfile
import contextily as ctx
from mpl_toolkits.basemap import Basemap
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from PIL import Image
import matplotlib.pyplot as plt

# Determine platform and fallback cache path
def get_cache_dir(app_name):
    if os.name == 'nt':
        return os.path.join(os.getenv('LOCALAPPDATA', tempfile.gettempdir()), f"{app_name}_cache")
    elif os.name == 'posix':
        home_dir = os.path.expanduser("~")
        if os.path.isdir(home_dir) and os.access(home_dir, os.W_OK):
            return os.path.join(home_dir, f".{app_name}_cache")
        else:
            return os.path.join(tempfile.gettempdir(), f"{app_name}_cache")
    else:
        return os.path.join(tempfile.gettempdir(), f"{app_name}_cache")

# Define cache directories
CTX_TILE_CACHE_DIR = get_cache_dir("contextily")
BASEMAP_TILE_CACHE_DIR = get_cache_dir("basemap")

os.environ["XDG_CACHE_HOME"] = CTX_TILE_CACHE_DIR
os.makedirs(CTX_TILE_CACHE_DIR, exist_ok=True)
os.makedirs(BASEMAP_TILE_CACHE_DIR, exist_ok=True)

def draw_etopo_basemap(ax, mode="basemap", zoom=11):
    """
    Draws a high-resolution basemap background on the provided Cartopy GeoAxes.

    Parameters
    ----------
    ax : matplotlib.axes._subplots.AxesSubplot
        The matplotlib Axes object (with Cartopy projection) to draw the map background on.

    mode : str, optional
        The basemap mode to use:
        - "stock": Default stock image from Cartopy.
        - "contextily": Web tile background (CartoDB Voyager), with caching.
        - "basemap": High-resolution shaded relief using Basemap, with caching.
        Default is "basemap".

    zoom : int, optional
        Tile zoom level (only for "contextily"). Higher = more detail. Default is 7.

    Notes
    -----
    - Uses high resolution for Basemap (resolution='h') and saves figure at 300 DPI.
    - Cached images are reused using extent-based hashing to avoid re-rendering.
    - Basemap is deprecated; Cartopy with web tiles is recommended for new projects.
    """
    try:
        if mode == "stock":
            ax.stock_img()

        elif mode == "contextily":
            extent = ax.get_extent(crs=ccrs.PlateCarree())
            ax.set_extent(extent, crs=ccrs.PlateCarree())
            ctx.add_basemap(
                ax,
                crs=ccrs.PlateCarree(),
                source=ctx.providers.CartoDB.Voyager,
                zoom=zoom
            )

        elif mode == "basemap":
            extent = ax.get_extent(crs=ccrs.PlateCarree())
            extent_str = f"{extent[0]:.4f}_{extent[1]:.4f}_{extent[2]:.4f}_{extent[3]:.4f}"
            cache_key = hashlib.md5(extent_str.encode()).hexdigest()
            cache_file = os.path.join(BASEMAP_TILE_CACHE_DIR, f"{cache_key}_highres.png")

            if os.path.exists(cache_file):
                img = Image.open(cache_file)
                ax.imshow(img, extent=extent, transform=ccrs.PlateCarree())
            else:
                temp_fig, temp_ax = plt.subplots(figsize=(12, 9),
                                                 subplot_kw={'projection': ccrs.PlateCarree()})
                temp_ax.set_extent(extent, crs=ccrs.PlateCarree())

                m = Basemap(projection='cyl',
                            llcrnrlon=extent[0], urcrnrlon=extent[1],
                            llcrnrlat=extent[2], urcrnrlat=extent[3],
                            resolution='f', ax=temp_ax)
                m.shadedrelief()

                temp_fig.savefig(cache_file, dpi=300, bbox_inches='tight', pad_inches=0)
                plt.close(temp_fig)

                img = Image.open(cache_file)
                ax.imshow(img, extent=extent, transform=ccrs.PlateCarree())

        else:
            raise ValueError(f"Unsupported basemap mode: {mode}")

    except Exception as e:
        print(f"[Relief Error - {mode} mode]:", e)
        ax.add_feature(cfeature.LAND)
        ax.add_feature(cfeature.OCEAN)