File size: 3,992 Bytes
730ca0c
 
928c17e
291f0ae
 
 
 
730ca0c
 
 
d3fc047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928c17e
 
d3fc047
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2818072
 
 
 
 
 
 
730ca0c
 
 
 
928c17e
730ca0c
2818072
 
 
730ca0c
2818072
d3fc047
2818072
d3fc047
2818072
 
 
730ca0c
 
2818072
d3fc047
2818072
 
 
928c17e
2818072
db84eaa
d3fc047
730ca0c
2818072
d3fc047
 
2818072
 
 
 
 
730ca0c
2818072
 
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)