From 632201382ba1d642f4e183a249d90fa0a407e0d0 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:27:10 -0500 Subject: [PATCH 1/7] create example --- examples/scalar_data/ocean_bathymetry.py | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 examples/scalar_data/ocean_bathymetry.py diff --git a/examples/scalar_data/ocean_bathymetry.py b/examples/scalar_data/ocean_bathymetry.py new file mode 100644 index 000000000..d371b88d7 --- /dev/null +++ b/examples/scalar_data/ocean_bathymetry.py @@ -0,0 +1,88 @@ +""" +Ocean bathymetry +---------------- + +Produces a map of ocean seafloor depth. + +""" +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import cartopy as cart +import cartopy.crs as ccrs +from glob import glob + + +def load_bathymetry(zip_file_url): + """Read zip file from Natural Earth containing bathymetry shapefiles""" + # Download and extract shapefiles + import requests + import zipfile + import io + r = requests.get(zip_file_url) + z = zipfile.ZipFile(io.BytesIO(r.content)) + z.extractall("ne_10m_bathymetry_all/") + + # Read shapefiles, sorted by depth + shp_dict = {} + files = glob('ne_10m_bathymetry_all/*.shp') + assert len(files) > 0 + files.sort() + depths = [] + for f in files: + depth = '-' + f.split('_')[-1].split('.')[0] # depth from file name + depths.append(depth) + bbox = (90, -15, 160, 60) # (x0, y0, x1, y1) + nei = cart.io.shapereader.Reader(f, bbox=bbox) + shp_dict[depth] = nei + depths = np.array(depths)[::-1] # sort from surface to bottom + return depths, shp_dict + + +if __name__ == "__main__": + # Load data (14.8 MB file) + depths, shp_dict = load_bathymetry( + 'https://naturalearth.s3.amazonaws.com/' + + '10m_physical/ne_10m_bathymetry_all.zip') + + # Construct colormap: + # Depth levels in the colorbar are spaced as in the data + norm = matplotlib.colors.Normalize(vmin=-10000, vmax=0) # in meters + depth_fraction = depths.astype(float) / depths.astype(float).min() + tmp = matplotlib.colormaps['Blues'] # to quantize: use .resampled(10) + colors_depths = tmp(depth_fraction) # color values at fractional depths + + # Set up plot + subplot_kw = {'projection': ccrs.LambertCylindrical()} + fig, ax = plt.subplots(subplot_kw=subplot_kw, figsize=(9, 7)) + ax.set_extent([90, 160, -15, 60], crs=ccrs.PlateCarree()) # x0, x1, y0, y1 + + # Iterate and plot feature for each depth level + for i, depth in enumerate(depths): + ax.add_geometries(shp_dict[depth].geometries(), + crs=ccrs.PlateCarree(), + color=colors_depths[i]) + + # Add standard features + land_feature = cart.feature.NaturalEarthFeature(category='physical', + name='LAND', + scale='110m') + ax.add_feature(land_feature, color='grey') + ax.coastlines(lw=1, resolution='110m') + ax.gridlines(draw_labels=False) + ax.set_position([0.03, 0.05, 0.8, 0.9]) + + # Add custom colorbar + axi = fig.add_axes([0.85, 0.1, 0.025, 0.8]) + ax.add_feature(cart.feature.BORDERS, linestyle=':') + matplotlib.colorbar.ColorbarBase(ax=axi, + cmap=tmp.reversed(), + norm=norm, + boundaries=depths.astype(int)[::-1], + spacing='proportional', + ticks=depths.astype(int), + label='Depth (m)') + + # Convert vector bathymetries to raster (saves a lot of disk space) + # while leaving labels as vectors + ax.set_rasterized(True) From 8a07c56a7105f21bbea0451e5aea6b1a5e222321 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:55:10 -0500 Subject: [PATCH 2/7] add bbox=None to BasicReader --- lib/cartopy/io/shapereader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartopy/io/shapereader.py b/lib/cartopy/io/shapereader.py index 8be579377..d882d8ba4 100644 --- a/lib/cartopy/io/shapereader.py +++ b/lib/cartopy/io/shapereader.py @@ -130,7 +130,7 @@ class BasicReader: """ - def __init__(self, filename): + def __init__(self, filename, bbox=None): # Validate the filename/shapefile self._reader = reader = shapefile.Reader(filename) if reader.shp is None or reader.shx is None or reader.dbf is None: From 660f4d5f3bb7c33d4bd964dc12eadc404dde1e75 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:14:11 -0500 Subject: [PATCH 3/7] apply isort --- examples/scalar_data/ocean_bathymetry.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/scalar_data/ocean_bathymetry.py b/examples/scalar_data/ocean_bathymetry.py index d371b88d7..981bce0e5 100644 --- a/examples/scalar_data/ocean_bathymetry.py +++ b/examples/scalar_data/ocean_bathymetry.py @@ -5,20 +5,23 @@ Produces a map of ocean seafloor depth. """ -import numpy as np +from glob import glob + import matplotlib import matplotlib.pyplot as plt +import numpy as np + import cartopy as cart import cartopy.crs as ccrs -from glob import glob def load_bathymetry(zip_file_url): """Read zip file from Natural Earth containing bathymetry shapefiles""" # Download and extract shapefiles - import requests - import zipfile import io + import zipfile + + import requests r = requests.get(zip_file_url) z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall("ne_10m_bathymetry_all/") From bec38d8ec9535b26f951c83223c6add628206981 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Tue, 20 Jun 2023 21:57:33 -0500 Subject: [PATCH 4/7] use land feature shortcut --- examples/scalar_data/ocean_bathymetry.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/scalar_data/ocean_bathymetry.py b/examples/scalar_data/ocean_bathymetry.py index 981bce0e5..16dece1c8 100644 --- a/examples/scalar_data/ocean_bathymetry.py +++ b/examples/scalar_data/ocean_bathymetry.py @@ -11,8 +11,9 @@ import matplotlib.pyplot as plt import numpy as np -import cartopy as cart import cartopy.crs as ccrs +import cartopy.feature as cfeature +import cartopy.io.shapereader as shpreader def load_bathymetry(zip_file_url): @@ -36,7 +37,7 @@ def load_bathymetry(zip_file_url): depth = '-' + f.split('_')[-1].split('.')[0] # depth from file name depths.append(depth) bbox = (90, -15, 160, 60) # (x0, y0, x1, y1) - nei = cart.io.shapereader.Reader(f, bbox=bbox) + nei = shpreader.Reader(f, bbox=bbox) shp_dict[depth] = nei depths = np.array(depths)[::-1] # sort from surface to bottom return depths, shp_dict @@ -67,17 +68,14 @@ def load_bathymetry(zip_file_url): color=colors_depths[i]) # Add standard features - land_feature = cart.feature.NaturalEarthFeature(category='physical', - name='LAND', - scale='110m') - ax.add_feature(land_feature, color='grey') + ax.add_feature(cfeature.LAND, color='grey') ax.coastlines(lw=1, resolution='110m') ax.gridlines(draw_labels=False) ax.set_position([0.03, 0.05, 0.8, 0.9]) # Add custom colorbar axi = fig.add_axes([0.85, 0.1, 0.025, 0.8]) - ax.add_feature(cart.feature.BORDERS, linestyle=':') + ax.add_feature(cfeature.BORDERS, linestyle=':') matplotlib.colorbar.ColorbarBase(ax=axi, cmap=tmp.reversed(), norm=norm, From 16ce29605ae585ce7a7f997656827eeaf9e6a9ee Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:17:05 -0500 Subject: [PATCH 5/7] move file; expand docstring --- .../ocean_bathymetry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) rename examples/{scalar_data => lines_and_polygons}/ocean_bathymetry.py (84%) diff --git a/examples/scalar_data/ocean_bathymetry.py b/examples/lines_and_polygons/ocean_bathymetry.py similarity index 84% rename from examples/scalar_data/ocean_bathymetry.py rename to examples/lines_and_polygons/ocean_bathymetry.py index 16dece1c8..6007a130c 100644 --- a/examples/scalar_data/ocean_bathymetry.py +++ b/examples/lines_and_polygons/ocean_bathymetry.py @@ -2,7 +2,13 @@ Ocean bathymetry ---------------- -Produces a map of ocean seafloor depth. +Produces a map of ocean seafloor depth, demonstrating the +:class:`cartopy.io.shapereader.Reader` interface. The data is a series +of 10m resolution nested polygons obtained from Natural Earth, and derived +from the NASA SRTM Plus product. Since the dataset contains a zipfile with +multiple shapefiles representing different depths, the example demonstrates +manually downloading and reading them with the general shapereader interface, +instead of the specialized `cartopy.feature.NaturalEarthFeature` interface. """ from glob import glob @@ -81,6 +87,7 @@ def load_bathymetry(zip_file_url): norm=norm, boundaries=depths.astype(int)[::-1], spacing='proportional', + extend='min', ticks=depths.astype(int), label='Depth (m)') From cc6d0ffa4b30188cb9d0ce22758e25e69331ab43 Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Thu, 13 Jul 2023 22:55:56 -0500 Subject: [PATCH 6/7] use BoundaryNorm, fig.colorbar --- .../lines_and_polygons/ocean_bathymetry.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/lines_and_polygons/ocean_bathymetry.py b/examples/lines_and_polygons/ocean_bathymetry.py index 6007a130c..3e34ec4c5 100644 --- a/examples/lines_and_polygons/ocean_bathymetry.py +++ b/examples/lines_and_polygons/ocean_bathymetry.py @@ -55,12 +55,12 @@ def load_bathymetry(zip_file_url): 'https://naturalearth.s3.amazonaws.com/' + '10m_physical/ne_10m_bathymetry_all.zip') - # Construct colormap: - # Depth levels in the colorbar are spaced as in the data - norm = matplotlib.colors.Normalize(vmin=-10000, vmax=0) # in meters - depth_fraction = depths.astype(float) / depths.astype(float).min() - tmp = matplotlib.colormaps['Blues'] # to quantize: use .resampled(10) - colors_depths = tmp(depth_fraction) # color values at fractional depths + # Construct a discrete colormap with colors corresponding to each depth + bounds = sorted(depths.astype(float)) # low to high + N = len(bounds) + norm = matplotlib.colors.BoundaryNorm(bounds, N) + blues_cm = matplotlib.colormaps['Blues_r'].resampled(N) + colors_depths = blues_cm(norm(depths.astype(float))) # Set up plot subplot_kw = {'projection': ccrs.LambertCylindrical()} @@ -82,14 +82,14 @@ def load_bathymetry(zip_file_url): # Add custom colorbar axi = fig.add_axes([0.85, 0.1, 0.025, 0.8]) ax.add_feature(cfeature.BORDERS, linestyle=':') - matplotlib.colorbar.ColorbarBase(ax=axi, - cmap=tmp.reversed(), - norm=norm, - boundaries=depths.astype(int)[::-1], - spacing='proportional', - extend='min', - ticks=depths.astype(int), - label='Depth (m)') + sm = plt.cm.ScalarMappable(cmap=blues_cm, norm=norm) + fig.colorbar(mappable=sm, + cax=axi, + boundaries=depths.astype(int)[::-1], + spacing='proportional', + extend='min', + ticks=depths.astype(int), + label='Depth (m)') # Convert vector bathymetries to raster (saves a lot of disk space) # while leaving labels as vectors From 9041acae83a4e045b2bd2ba23be407de79aadfff Mon Sep 17 00:00:00 2001 From: lgolston <30876419+lgolston@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:53:43 -0500 Subject: [PATCH 7/7] fix 0 depth color; simplify --- .../lines_and_polygons/ocean_bathymetry.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/lines_and_polygons/ocean_bathymetry.py b/examples/lines_and_polygons/ocean_bathymetry.py index 3e34ec4c5..2210aeafe 100644 --- a/examples/lines_and_polygons/ocean_bathymetry.py +++ b/examples/lines_and_polygons/ocean_bathymetry.py @@ -4,7 +4,7 @@ Produces a map of ocean seafloor depth, demonstrating the :class:`cartopy.io.shapereader.Reader` interface. The data is a series -of 10m resolution nested polygons obtained from Natural Earth, and derived +of 10m resolution nested polygons obtained from Natural Earth, derived from the NASA SRTM Plus product. Since the dataset contains a zipfile with multiple shapefiles representing different depths, the example demonstrates manually downloading and reading them with the general shapereader interface, @@ -51,16 +51,18 @@ def load_bathymetry(zip_file_url): if __name__ == "__main__": # Load data (14.8 MB file) - depths, shp_dict = load_bathymetry( + depths_str, shp_dict = load_bathymetry( 'https://naturalearth.s3.amazonaws.com/' + '10m_physical/ne_10m_bathymetry_all.zip') # Construct a discrete colormap with colors corresponding to each depth - bounds = sorted(depths.astype(float)) # low to high - N = len(bounds) - norm = matplotlib.colors.BoundaryNorm(bounds, N) + depths = depths_str.astype(int) + N = len(depths) + nudge = 0.01 # shift bin edge slightly to include data + boundaries = [min(depths)] + sorted(depths+nudge) # low to high + norm = matplotlib.colors.BoundaryNorm(boundaries, N) blues_cm = matplotlib.colormaps['Blues_r'].resampled(N) - colors_depths = blues_cm(norm(depths.astype(float))) + colors_depths = blues_cm(norm(depths)) # Set up plot subplot_kw = {'projection': ccrs.LambertCylindrical()} @@ -68,8 +70,8 @@ def load_bathymetry(zip_file_url): ax.set_extent([90, 160, -15, 60], crs=ccrs.PlateCarree()) # x0, x1, y0, y1 # Iterate and plot feature for each depth level - for i, depth in enumerate(depths): - ax.add_geometries(shp_dict[depth].geometries(), + for i, depth_str in enumerate(depths_str): + ax.add_geometries(shp_dict[depth_str].geometries(), crs=ccrs.PlateCarree(), color=colors_depths[i]) @@ -85,10 +87,9 @@ def load_bathymetry(zip_file_url): sm = plt.cm.ScalarMappable(cmap=blues_cm, norm=norm) fig.colorbar(mappable=sm, cax=axi, - boundaries=depths.astype(int)[::-1], spacing='proportional', extend='min', - ticks=depths.astype(int), + ticks=depths, label='Depth (m)') # Convert vector bathymetries to raster (saves a lot of disk space)