Adding Vector Map Underlays In Cartopy

metadata

Recently I decided that I wanted map backgrounds which worked quickly over a variety of scales; I did not want to have to load a 21,600 px × 10,800 px image just to get green and blue backgrounds for the south coast of England. To this end I wrote a Python function called pyguymer3.geo.add_map_underlay() which plots a variety of vector datasets from Natural Earth as well as the GeoJSON files from my previous blog post about vectorising the GLOBE dataset. It has the option too of plotting cultural datasets, such as railways and roads.

Below are three example images created by this function, showing a global extent, a local extent and an animation.

Download:
  1. 512 px × 265 px (0.1 Mpx; 172.4 KiB)
  2. 1,024 px × 530 px (0.5 Mpx; 573.3 KiB)
  3. 2,048 px × 1,061 px (2.2 Mpx; 1.8 MiB)
  4. 2,792 px × 1,446 px (4.0 Mpx; 2.1 MiB)
Download:
  1. 512 px × 394 px (0.2 Mpx; 303.2 KiB)
  2. 1,024 px × 787 px (0.8 Mpx; 940.1 KiB)
  3. 2,048 px × 1,574 px (3.2 Mpx; 2.5 MiB)
  4. 2,850 px × 2,191 px (6.2 Mpx; 2.4 MiB)
Download:
  1. 512 px × 512 px (0.3 Mpx; 39.1 MiB)
  2. 1,024 px × 1,024 px (1.0 Mpx; 107.9 MiB)
  3. 1,446 px × 1,446 px (2.1 Mpx; 100.6 MiB)

The three Pythonn scripts used to generate the above images are included below.

01: #!/usr/bin/env python3
02: 
03: # Use the proper idiom in the main module ...
04: # NOTE: See https://docs.python.org/3.9/library/multiprocessing.html#multiprocessing-programming
05: if __name__ == "__main__":
06:     # Import special modules ...
07:     try:
08:         import cartopy
09:     except:
10:         raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
11:     try:
12:         import matplotlib
13:         matplotlib.use("Agg")                                                   # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
14:         import matplotlib.pyplot
15:     except:
16:         raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
17: 
18:     # Import my modules ...
19:     try:
20:         import pyguymer3
21:         import pyguymer3.geo
22:         import pyguymer3.image
23:     except:
24:         raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None
25: 
26:     # **************************************************************************
27: 
28:     # Create figure and axis ...
29:     fg = matplotlib.pyplot.figure(figsize = (12, 6), dpi = 300)
30:     ax = fg.add_subplot(
31:         1,
32:         1,
33:         1,
34:         projection = cartopy.crs.Robinson(),
35:     )
36: 
37:     # Configure axis ...
38:     ax.set_global()
39:     pyguymer3.geo.add_map_underlay(ax, cultural = False, linewidth = 0.0, resolution = "10m")
40: 
41:     # Save, and close, figure ...
42:     fg.savefig("addMapUnderlay_global.png", bbox_inches = "tight", dpi = 300, pad_inches = 0.1)
43:     matplotlib.pyplot.close(fg)
44: 
45:     # Optimize PNG ...
46:     pyguymer3.image.optimize_image("addMapUnderlay_global.png", strip = True)
47: 
You may also download “addMapUnderlay_global.py” directly or view “addMapUnderlay_global.py” on GitHub Gist.
01: #!/usr/bin/env python3
02: 
03: # Use the proper idiom in the main module ...
04: # NOTE: See https://docs.python.org/3.9/library/multiprocessing.html#multiprocessing-programming
05: if __name__ == "__main__":
06:     # Import special modules ...
07:     try:
08:         import cartopy
09:     except:
10:         raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
11:     try:
12:         import matplotlib
13:         matplotlib.use("Agg")                                                   # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
14:         import matplotlib.pyplot
15:     except:
16:         raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
17: 
18:     # Import my modules ...
19:     try:
20:         import pyguymer3
21:         import pyguymer3.geo
22:         import pyguymer3.image
23:     except:
24:         raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None
25: 
26:     # **************************************************************************
27: 
28:     # Create figure and axis ...
29:     fg = matplotlib.pyplot.figure(figsize = (12, 12), dpi = 300)
30:     ax = fg.add_subplot(
31:         1,
32:         1,
33:         1,
34:         projection = cartopy.crs.Orthographic(
35:             central_longitude =  0.0,
36:              central_latitude = 40.0,
37:         ),
38:     )
39: 
40:     # Configure axis ...
41:     ax.set_extent(
42:         [
43:             -15.0, # left
44:              15.0, # right
45:              30.0, # bottom
46:              50.0, # top
47:         ]
48:     )
49:     pyguymer3.geo.add_map_underlay(ax, resolution = "10m")
50: 
51:     # Save, and close, figure ...
52:     fg.savefig("addMapUnderlay_local.png", bbox_inches = "tight", dpi = 300, pad_inches = 0.1)
53:     matplotlib.pyplot.close(fg)
54: 
55:     # Optimize PNG ...
56:     pyguymer3.image.optimize_image("addMapUnderlay_local.png", strip = True)
57: 
You may also download “addMapUnderlay_local.py” directly or view “addMapUnderlay_local.py” on GitHub Gist.
001: #!/usr/bin/env python3
002: 
003: # Use the proper idiom in the main module ...
004: # NOTE: See https://docs.python.org/3.9/library/multiprocessing.html#multiprocessing-programming
005: if __name__ == "__main__":
006:     # Import standard modules ...
007:     import os
008: 
009:     # Import special modules ...
010:     try:
011:         import cartopy
012:     except:
013:         raise Exception("\"cartopy\" is not installed; run \"pip install --user Cartopy\"") from None
014:     try:
015:         import matplotlib
016:         matplotlib.use("Agg")                                                   # NOTE: See https://matplotlib.org/stable/gallery/user_interfaces/canvasagg.html
017:         import matplotlib.pyplot
018:     except:
019:         raise Exception("\"matplotlib\" is not installed; run \"pip install --user matplotlib\"") from None
020:     try:
021:         import PIL
022:         import PIL.Image
023:     except:
024:         raise Exception("\"PIL\" is not installed; run \"pip install --user Pillow\"") from None
025: 
026:     # Import my modules ...
027:     try:
028:         import pyguymer3
029:         import pyguymer3.geo
030:         import pyguymer3.image
031:     except:
032:         raise Exception("\"pyguymer3\" is not installed; you need to have the Python module from https://github.com/Guymer/PyGuymer3 located somewhere in your $PYTHONPATH") from None
033: 
034:     # Configure PIL to open images up to 1 GiP ...
035:     PIL.Image.MAX_IMAGE_PIXELS = 1024 * 1024 * 1024                             # [px]
036: 
037:     # **************************************************************************
038: 
039:     # Initialize list ...
040:     fnames = []
041: 
042:     # Loop over longitudes ...
043:     for ilon in range(-180, 180):
044:         # Deduce longitude ...
045:         lon = float(ilon)                                                       # [°]
046: 
047:         # Deduce filename, append it to list and skip this longitude if it
048:         # already exists ...
049:         fname = f"addMapUnderlay_animation_lon={lon:+08.3f}.png"
050:         fnames.append(fname)
051:         if os.path.exists(fname):
052:             continue
053: 
054:         print(f"Making \"{fname}\" ...")
055: 
056:         # Create figure and axis ...
057:         fg = matplotlib.pyplot.figure(figsize = (6, 6), dpi = 300)
058:         ax = fg.add_subplot(
059:             1,
060:             1,
061:             1,
062:             projection = cartopy.crs.NearsidePerspective(
063:                 central_longitude = lon,
064:                  central_latitude = 51.5,
065:             ),
066:         )
067: 
068:         # Configure axis ...
069:         ax.set_global()
070:         pyguymer3.geo.add_map_underlay(ax, cultural = False, linewidth = 0.0, resolution = "50m")
071: 
072:         # Save, and close, figure ...
073:         fg.savefig(fname, bbox_inches = "tight", dpi = 300, pad_inches = 0.1)
074:         matplotlib.pyplot.close(fg)
075: 
076:         # Optimize PNG ...
077:         pyguymer3.image.optimize_image(fname, strip = True)
078: 
079:     # **************************************************************************
080: 
081:     print("Making \"addMapUnderlay_animation.webp\" ...")
082: 
083:     # Initialize list ...
084:     images = []
085: 
086:     # Loop over frames ...
087:     for fname in fnames:
088:         # Open image as RGB (even if it is paletted) ...
089:         image = PIL.Image.open(fname).convert("RGB")
090: 
091:         # Append it to the list ...
092:         images.append(image)
093: 
094:     # Save 25fps WEBP ...
095:     images[0].save("addMapUnderlay_animation.webp", lossless = True, quality = 100, method = 6, save_all = True, append_images = images[1:], duration = 40, loop = 0, minimize_size = True)
096: 
097:     # Clean up ...
098:     for image in images:
099:         image.close()
100:     del images
101: 
102:     # **************************************************************************
103: 
104:     # Set heights ...
105:     # NOTE: By inspection, the PNG frames are 1446px tall.
106:     heights = [512, 1024]                                                       # [px]
107: 
108:     # Loop over heights ...
109:     for height in heights:
110:         print(f"Making \"addMapUnderlay_animation{height:04d}px.webp\" ...")
111: 
112:         # Initialize list ...
113:         images = []
114: 
115:         # Loop over frames ...
116:         for fname in fnames:
117:             # Open image as RGB (even if it is paletted) ...
118:             image = PIL.Image.open(fname).convert("RGB")
119: 
120:             # Calculate width ...
121:             ratio = float(image.width) / float(image.height)                    # [px/px]
122:             width = round(ratio * float(height))                                # [px]
123: 
124:             # Downscale the image and append it to the list ...
125:             images.append(image.resize((width, height), resample = PIL.Image.LANCZOS))
126: 
127:         # Save 25fps WEBP ...
128:         images[0].save(f"addMapUnderlay_animation{height:04d}px.webp", lossless = True, quality = 100, method = 6, save_all = True, append_images = images[1:], duration = 40, loop = 0, minimize_size = True)
129: 
130:         # Clean up ...
131:         for image in images:
132:             image.close()
133:         del images
134: 
You may also download “addMapUnderlay_animation.py” directly or view “addMapUnderlay_animation.py” on GitHub Gist.

Enjoy!