diff --git a/.github/workflows/generate_notebooks.yml b/.github/workflows/generate_notebooks.yml index 03a1bd43..30660b6d 100644 --- a/.github/workflows/generate_notebooks.yml +++ b/.github/workflows/generate_notebooks.yml @@ -7,7 +7,6 @@ on: branches-ignore: - master paths: - - Notebooks/14-RuleBasedGPMLProcessingPipeline.py - Notebooks/Examples/hello_world.py - Notebooks/Examples/icosahedron_mesh.py - Notebooks/Examples/introducing_plate_model_manager.py @@ -44,7 +43,6 @@ jobs: - name: Generate notebooks run: | notebooks=( - Notebooks/14-RuleBasedGPMLProcessingPipeline.ipynb Notebooks/Examples/hello_world.ipynb Notebooks/Examples/icosahedron_mesh.ipynb Notebooks/Examples/introducing_plate_model_manager.ipynb @@ -69,7 +67,6 @@ jobs: - name: Commit updated notebooks run: | notebooks=( - Notebooks/14-RuleBasedGPMLProcessingPipeline.ipynb Notebooks/Examples/hello_world.ipynb Notebooks/Examples/icosahedron_mesh.ipynb Notebooks/Examples/introducing_plate_model_manager.ipynb diff --git a/.github/workflows/protect_generated_notebooks.yml b/.github/workflows/protect_generated_notebooks.yml index 265b8990..bc8c4ab5 100644 --- a/.github/workflows/protect_generated_notebooks.yml +++ b/.github/workflows/protect_generated_notebooks.yml @@ -39,7 +39,6 @@ jobs: set -euo pipefail protected_notebooks=( - Notebooks/14-RuleBasedGPMLProcessingPipeline.ipynb Notebooks/Examples/hello_world.ipynb Notebooks/Examples/icosahedron_mesh.ipynb Notebooks/Examples/introducing_plate_model_manager.ipynb diff --git a/Notebooks/01-GettingStarted.ipynb b/Notebooks/01-GettingStarted.ipynb index 5dc8b475..a345baf4 100644 --- a/Notebooks/01-GettingStarted.ipynb +++ b/Notebooks/01-GettingStarted.ipynb @@ -6,15 +6,15 @@ "source": [ "# 1 - Getting Started\n", "\n", - "_Welcome to GPlately!_\n", + "_Welcome to GPlately._\n", "\n", - "GPlately uses object-oriented programming to make life simple. In this notebook we will explore some of the main objects you will use:\n", + "GPlately provides an object-oriented interface for plate reconstruction workflows. In this notebook, we introduce several core objects you will use frequently:\n", "\n", - "- `PlateReconstruction` - reconstruct features, tessellate mid ocean ridges, subduction zones\n", - "- `Points` - partition points onto plates, rotate back through time\n", - "- `Raster` - read in NetCDF grids, interpolation, resampling.\n", - "- `PlotTopologies` - plotting topologies e.g. ridges, trenches, subduction teeth on maps\n", - "- `PlateModelManager` - downloading plate models, features, and rasters e.g. .rot, .gpml, .shp and .nc files" + "- `PlateReconstruction` - reconstruct features and tessellate mid-ocean ridges and subduction zones\n", + "- `Points` - partition points onto plates and rotate them back through time\n", + "- `Raster` - read NetCDF grids, interpolate, and resample data\n", + "- `PlotTopologies` - plot topologies (for example, ridges, trenches, and subduction teeth) on maps\n", + "- `PlateModelManager` - download plate models and data files (for example, `.rot`, `.gpml`, `.shp`, and `.nc`)" ] }, { @@ -23,20 +23,32 @@ "metadata": {}, "outputs": [], "source": [ + "import os, warnings\n", "import cartopy.crs as ccrs\n", "import gplately\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from plate_model_manager import PlateModelManager" + "from plate_model_manager import PlateModelManager\n", + "from gplately.plot.gmt_cpt import get_cmap_from_gmt_cpt\n", + "\n", + "# Download the age-grid CPT file if it is not present. This CPT is used for map plotting below.\n", + "cpt_file = \"agegrid.cpt\"\n", + "if not os.path.isfile(cpt_file):\n", + " import urllib.request\n", + "\n", + " urllib.request.urlretrieve(\n", + " \"https://raw.githubusercontent.com/GPlates/gplately/refs/heads/master/tests-dir/unittest/create-age-grids-video/agegrid.cpt\",\n", + " cpt_file,\n", + " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Tectonic plate reconstructions\n", + "## Tectonic Plate Reconstructions\n", "\n", - "We simply supply a rotation model, plate topologies, and static polygons to initialise a plate reconstruction model. You can download these files into your machine's cache using GPlately's `PlateModelManager` object." + "To initialize a plate reconstruction model, provide a rotation model, plate topologies, and static polygons. You can download these files into your local folder using GPlately's `PlateModelManager`." ] }, { @@ -45,14 +57,15 @@ "metadata": {}, "outputs": [], "source": [ - "# Call GPlately's PlateModelManager object and request data from the Müller et al. 2019 study\n", + "# Use PlateModelManager to request data from the Zahirovic2022 plate model\n", "pm_manager = PlateModelManager()\n", - "muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "static_polygons = muller2019_model.get_static_polygons()\n", + "z22_model = pm_manager.get_model(\"Zahirovic2022\", data_dir=\"plate-model-repo\")\n", + "assert z22_model is not None\n", + "rotation_model = z22_model.get_rotation_model()\n", + "topology_features = z22_model.get_topologies()\n", + "static_polygons = z22_model.get_static_polygons()\n", "\n", - "# Tessellate the subduction zones to 0.5 degrees.\n", + "# Tessellate subduction zones at 0.05 degrees.\n", "tessellation_threshold_radians = np.radians(0.05)\n", "\n", "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)" @@ -62,7 +75,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's find the subduction zones and mid-ocean ridges at 10 Ma." + "Now let's retrieve subduction zones and mid-ocean ridges at 10 Ma." ] }, { @@ -73,9 +86,13 @@ "source": [ "time = 10\n", "\n", - "# These bundle a lot of information - check the docs for more info.\n", - "subduction_data = model.tessellate_subduction_zones(time)\n", - "ridge_data = model.tessellate_mid_ocean_ridges(time)" + "# These methods return rich topology data; see the documentation for details.\n", + "subduction_data = model.tessellate_subduction_zones(\n", + " time, tessellation_threshold_radians=tessellation_threshold_radians\n", + ")\n", + "ridge_data = model.tessellate_mid_ocean_ridges(\n", + " time, tessellation_threshold_radians=tessellation_threshold_radians\n", + ")" ] }, { @@ -84,19 +101,21 @@ "source": [ "## Mapping\n", "\n", - "The `PlotTopologies` function injests the plate model we have defined as well as the coastlines, continents, and COB. It computes all of the plate topologies for a given reconstruction time.\n", + "The `PlotTopologies` object uses the plate model we defined, along with coastlines, continents, and COBs, to compute topologies at a given reconstruction time.\n", "\n", - "This object has been designed to work specifically with `cartopy`. Define your figure and supply your axes to these plotting routines. Some common favourites include:\n", + "`PlotTopologies` supports both `cartopy` and `PyGMT` as plotting engines.\n", + "\n", + "In this example, we use `cartopy`. Define a figure and pass axes to the plotting routines. Common layers include:\n", "\n", "- coastlines\n", "- continents\n", "- ridges and transforms\n", "- trenches\n", - "- subduction teeth (!!)\n", - "- netCDF grids\n", + "- subduction teeth\n", + "- NetCDF grids\n", "- plate motion vectors\n", "\n", - "You can still supply optional keywords as you normally would." + "You can also pass optional keyword arguments as usual." ] }, { @@ -105,25 +124,28 @@ "metadata": {}, "outputs": [], "source": [ - "# Obtain features for the PlotTopologies object with PlateModelManager\n", - "coastlines = muller2019_model.get_layer('Coastlines')\n", - "continents = muller2019_model.get_layer('ContinentalPolygons')\n", - "COBs = muller2019_model.get_layer('COBs')\n", - "\n", - "# Call the PlotTopologies object\n", - "gplot = gplately.plot.PlotTopologies(model, coastlines=coastlines, continents=continents, COBs=COBs)\n", - "\n", - "# Download all Muller et al. 2019 netCDF age grids with PlateModelManager. This is returned as a Raster object.\n", - "agegrid = gplately.Raster(data=muller2019_model.get_raster(\"AgeGrids\",time))" + "# Retrieve feature layers for the PlotTopologies object\n", + "assert z22_model is not None\n", + "coastlines = z22_model.get_layer(\"Coastlines\")\n", + "continents = z22_model.get_layer(\"ContinentalPolygons\")\n", + "COBs = z22_model.get_layer(\"COBs\")\n", + "\n", + "# Create the PlotTopologies object\n", + "gplot = gplately.plot.PlotTopologies(\n", + " model, coastlines=coastlines, continents=continents, COBs=COBs\n", + ")\n", + "\n", + "# Download an age-grid NetCDF raster from the Zahirovic2022 model and create a Raster object.\n", + "agegrid = gplately.Raster(data=z22_model.get_raster(\"AgeGrids\", time))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Set the `time` attribute to reconstruct all topologies to the specified time.\n", + "Set the `time` attribute to reconstruct all topologies at a specified reconstruction time.\n", "\n", - "> __IMPORTANT:__ You must set `gplot.time` or provide a `time` at initialisation before plotting anything." + "> **Important:** Before plotting, set `gplot.time` or provide `time` during initialization." ] }, { @@ -139,7 +161,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Create a map with some useful geological information" + "Create a map with key geological information." ] }, { @@ -148,20 +170,35 @@ "metadata": {}, "outputs": [], "source": [ - "fig = plt.figure(figsize=(16,12))\n", - "\n", - "ax1 = fig.add_subplot(111, projection=ccrs.Mollweide(190))\n", - "\n", - "gplot.plot_continents(ax1, facecolor='0.8')\n", - "gplot.plot_coastlines(ax1, color='0.5')\n", - "gplot.plot_ridges(ax1, color='red')\n", - "gplot.plot_transforms(ax1, color='red')\n", - "gplot.plot_trenches(ax1, color='k')\n", - "gplot.plot_subduction_teeth(ax1, color='k')\n", - "im = gplot.plot_grid(ax1, agegrid.data, cmap='YlGnBu', vmin=0, vmax=200)\n", - "gplot.plot_plate_motion_vectors(ax1, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5)\n", - "\n", - "fig.colorbar(im, orientation='horizontal', shrink=0.4, pad=0.05, label='Age (Ma)')" + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + " fig = plt.figure(figsize=(8, 6))\n", + "\n", + " ax = fig.add_subplot(111, projection=ccrs.Mollweide(190))\n", + "\n", + " gplot.plot_continents(ax, facecolor=\"0.8\")\n", + " gplot.plot_coastlines(ax, color=\"0.5\", linewidth=0.5)\n", + " gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"black\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", + " im = gplot.plot_grid(\n", + " ax, agegrid.data, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200\n", + " )\n", + " gplot.plot_plate_motion_vectors(\n", + " ax, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5\n", + " )\n", + " if im:\n", + " fig.colorbar(\n", + " im, orientation=\"horizontal\", shrink=0.4, pad=0.05, label=\"Age (Ma)\"\n", + " )\n", + " assert gplot.time is not None\n", + " ax.set_title(f\"{int(gplot.time)} Ma\")\n", + " plt.show()" ] }, { @@ -170,32 +207,40 @@ "metadata": {}, "outputs": [], "source": [ - "# update the time to regenerate topologies\n", - "time = 100\n", - "gplot.time = time\n", - "agegrid = gplately.Raster(data=muller2019_model.get_raster(\"AgeGrids\",time))\n", - "\n", - "fig = plt.figure(figsize=(16,12))\n", - "\n", - "ax1 = fig.add_subplot(111, projection=ccrs.Mollweide(190))\n", - "\n", - "gplot.plot_continents(ax1, facecolor='0.8')\n", - "gplot.plot_coastlines(ax1, color='0.5')\n", - "gplot.plot_ridges(ax1, color='red')\n", - "gplot.plot_transforms(ax1, color='red')\n", - "gplot.plot_trenches(ax1, color='k')\n", - "gplot.plot_subduction_teeth(ax1, color='k')\n", - "im = gplot.plot_grid(ax1, agegrid.data, cmap='YlGnBu', vmin=0, vmax=200)\n", - "gplot.plot_plate_motion_vectors(ax1, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5)\n", - "\n", - "fig.colorbar(im, orientation='horizontal', shrink=0.4, pad=0.05, label='Age (Ma)')" + "with warnings.catch_warnings():\n", + " warnings.filterwarnings('ignore', category=UserWarning)\n", + " # Update time and recompute topologies.\n", + " time = 100\n", + " gplot.time = time\n", + " agegrid = gplately.Raster(data=z22_model.get_raster(\"AgeGrids\",time))\n", + " \n", + " fig = plt.figure(figsize=(8,6))\n", + " \n", + " ax = fig.add_subplot(111, projection=ccrs.Mollweide(190))\n", + " \n", + " gplot.plot_continents(ax, facecolor='0.8')\n", + " gplot.plot_coastlines(ax, color='0.5')\n", + " gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"black\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", + " im = gplot.plot_grid(ax, agegrid.data, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200)\n", + " gplot.plot_plate_motion_vectors(ax, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5)\n", + " \n", + " fig.colorbar(im, orientation='horizontal', shrink=0.4, pad=0.05, label='Age (Ma)')\n", + " ax.set_title(f\"{int(gplot.time)} Ma\")\n", + " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Working with points\n", + "## Working with Points\n", "\n", "Now that we have defined our reconstruction object, we can reconstruct point data." ] @@ -206,8 +251,8 @@ "metadata": {}, "outputs": [], "source": [ - "pt_lons = np.array([140., 150., 160.])\n", - "pt_lats = np.array([-30., -40., -50.])\n", + "pt_lons = np.array([140.0, 150.0, 160.0])\n", + "pt_lats = np.array([-30.0, -40.0, -50.0])\n", "\n", "gpts = gplately.Points(model, pt_lons, pt_lats)\n", "\n", @@ -222,7 +267,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Plot their position from `time=0` to `time=20`" + "Plot point positions from `time=0` to `time=20`." ] }, { @@ -234,7 +279,6 @@ "rlons = np.empty((21, pt_lons.size))\n", "rlats = np.empty((21, pt_lons.size))\n", "\n", - "\n", "for time in range(0, 21):\n", " rlons[time], rlats[time] = gpts.reconstruct(time, return_array=True)" ] @@ -245,16 +289,17 @@ "metadata": {}, "outputs": [], "source": [ - "gplot.time = 0 # present day\n", + "gplot.time = 0 # present day\n", "\n", - "fig = plt.figure(figsize=(16,12))\n", - "ax1 = fig.add_subplot(111, projection=ccrs.Mercator(190)) \n", - "ax1.set_extent([130,180,-60,-10])\n", + "fig = plt.figure(figsize=(6, 6))\n", + "ax = fig.add_subplot(111, projection=ccrs.Mercator(190))\n", + "ax.set_extent([130, 180, -60, -10])\n", "\n", - "gplot.plot_coastlines(ax1, color='0.8')\n", + "gplot.plot_coastlines(ax, color=\"0.8\")\n", "\n", "for i in range(0, len(pt_lons)):\n", - " ax1.plot(rlons[:,i], rlats[:,i], 'o', transform=ccrs.PlateCarree())" + " ax.plot(rlons[:, i], rlats[:, i], \"o\", transform=ccrs.PlateCarree())\n", + "plt.show()" ] }, { @@ -263,9 +308,9 @@ "source": [ "## Rasters\n", "\n", - "You can initialise a `Raster` object by providing a raster (either a NetCDF file path or a regular numpy array), and optionally passing a `PlateReconstruction` model. The `data` attribute stores the raster data as a 2D numpy array.\n", + "You can initialize a `Raster` object by providing raster data (either a NetCDF file path or a NumPy array), and optionally a `PlateReconstruction` model. The `data` attribute stores raster data as a 2D NumPy array.\n", "\n", - "In this example, we will pass a NetCDF file path to the `Raster` object." + "In this example, we pass a NetCDF file path to the `Raster` object." ] }, { @@ -276,19 +321,20 @@ "source": [ "time = 100\n", "\n", - "# download a netcdf age grid Raster and assign a plate reconstruction `model'\n", - "graster = gplately.Raster(data=muller2019_model.get_raster(\"AgeGrids\",time))\n", + "# Download a NetCDF age-grid raster and assign a plate reconstruction `model`.\n", + "graster = gplately.Raster(data=z22_model.get_raster(\"AgeGrids\", time))\n", "graster.plate_reconstruction = model\n", "\n", - "# the underlying numpy (masked) array can be accessed using the data attribute\n", + "# Access the underlying NumPy masked array using the data attribute.\n", "print(type(graster.data))\n", "\n", - "# alternatively initialise a Raster from a numpy array\n", - "graster = gplately.Raster(data=graster.data, # 2D numpy array\n", - " plate_reconstruction=model, # PlateReconstruction object\n", - " extent='global', # equivalent to [-180, 180, -90, 90]\n", - " time=100, # time in Ma\n", - " )\n" + "# Alternatively, initialize a Raster from a NumPy array.\n", + "graster = gplately.Raster(\n", + " data=graster.data, # 2D NumPy array\n", + " plate_reconstruction=model, # PlateReconstruction object\n", + " extent=\"global\", # equivalent to [-180, 180, -90, 90]\n", + " time=100, # time in Ma\n", + ")" ] }, { @@ -299,40 +345,42 @@ "source": [ "gplot.time = time\n", "\n", - "fig = plt.figure(figsize=(16,12))\n", + "fig = plt.figure(figsize=(8, 6))\n", + "\n", + "ax = fig.add_subplot(111, projection=ccrs.Robinson())\n", + "\n", + "use_raster_plot = (\n", + " False # Set this flag to True to use Raster.plot() for raster plotting.\n", + ")\n", "\n", - "ax1 = fig.add_subplot(111, projection=ccrs.Robinson())\n", - "gplot.plot_grid(ax1, graster, cmap='YlGnBu', vmin=0, vmax=100)\n", - "# Alternatively:\n", - "# graster.imshow(ax=ax1, cmap=\"YlGnBu\", vmin=0, vmax=100)\n", - "gplot.plot_coastlines(ax1, edgecolor='k', facecolor='none')" + "if not use_raster_plot:\n", + " gplot.plot_grid(ax, graster, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200)\n", + "else:\n", + " graster.plot(ax=ax, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200)\n", + "\n", + "gplot.plot_coastlines(ax, edgecolor=\"0.5\", facecolor=\"none\", linewidth=0.5)\n", + "ax.set_title(f\"{int(gplot.time)} Ma\")\n", + "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "There are a bunch of routines such as,\n", + "Additional routines include:\n", "\n", - "- filling masked (NaN) regions\n", + "- filling masked (`NaN`) regions\n", "- interpolation\n", "- resampling\n", - "- reconstructing rasters\n", + "- raster reconstruction\n", "\n", - "In-place operations can be achieved using `inplace=True` which will update the internal data structures." + "Use `inplace=True` for in-place operations that update internal data structures." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -346,12 +394,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" - }, - "vscode": { - "interpreter": { - "hash": "a10fed8c503fa0e7abcec38684bcaa5ab84af52f4a155e8c08912d91252721a5" - } + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/02-PlateReconstructions.ipynb b/Notebooks/02-PlateReconstructions.ipynb index be00eb91..3eb87ebb 100644 --- a/Notebooks/02-PlateReconstructions.ipynb +++ b/Notebooks/02-PlateReconstructions.ipynb @@ -17,7 +17,7 @@ ")\n", "```\n", "\n", - "The `PlateReconstruction` object contains methods to reconstruct topology features to a specific geological time. All you need to do to use this object is provide a rotation model, topology features (or feature collection) and a set of static polygons. " + "The `PlateReconstruction` object provides methods for reconstructing topology features to a specific geological time. To use this object, you simply need to supply a rotation model, a set of topology features (or feature collection), and a set of static polygons." ] }, { @@ -28,18 +28,18 @@ "outputs": [], "source": [ "import gplately\n", - "import pygplates\n", "import numpy as np\n", - "import glob, os\n", + "import glob, os, warnings\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", - "import cartopy.mpl.gridliner as grd\n", "\n", - "from matplotlib.patches import Patch\n", - "from matplotlib.patches import FancyArrowPatch\n", "from matplotlib.lines import Line2D\n", "\n", - "from plate_model_manager import PlateModelManager" + "from plate_model_manager import PlateModelManager\n", + "from gplately.auxiliary import get_gplot\n", + "\n", + "model_repo_dir = \"plate-model-repo\"\n", + "model_name = \"zahirovic2022\"" ] }, { @@ -47,20 +47,12 @@ "id": "96f36e3b", "metadata": {}, "source": [ - "We use gplately to generate a plate reconstruction model using data from \"A Global Plate Model Including Lithospheric Deformation Along Major Rifts and Orogens Since the Triassic\" by Müller et al. (2019). (Source: https://www.earthbyte.org/muller-et-al-2019-deforming-plate-reconstruction-and-seafloor-age-grids-tectonics/). \n", - "\n", - "To generate this model, we will need three types of files:\n", - "1. A set of rotation files - files that end in \".rot\"\n", - "2. A set of topology feature files - typically of type \".gpml\" or \".gpmlz\"\n", - "2. A set of static polygons\n", - "\n", - "... and these need to be turned to certain pygplates objects using [pyGPlates](https://www.gplates.org/docs/pygplates/index.html):\n", - "\n", - "- Rotation files must be passed to a `` object,\n", - "- Topology features must be passed to a `` object\n", - "- Static polygons must be passed to a `` object\n", + "To create a `PlateReconstruction` object, we need three file sets:\n", + "1. A set of rotation files — files with the \".rot\" extension\n", + "2. A set of topology feature files — typically of type \".gpml\" or \".gpmlz\"\n", + "3. A set of static polygons\n", "\n", - "We demonstrate **two ways** to load plate model files:" + "We will demonstrate **two ways** to load plate model files below." ] }, { @@ -70,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "# set True to use Method 2\n", + "# Set to True to use Method 2 (load files from the local hard drive)\n", "use_local_files = False" ] }, @@ -79,16 +71,16 @@ "id": "0981cc8d", "metadata": {}, "source": [ - "### Method 1: Loading files with gplately's `PlateModelManager`\n", + "### Method 1: Loading Files with `PlateModelManager`\n", "\n", - "You can also use gplately's `PlateModelManager` object to download necessary plate reconstruction files from supported plate models. You can use command `pmm ls` to list all supported plate models. `PlateModelManager` stores these files in your local folder. \n", + "You can use `PlateModelManager` to download the files required for a supported plate reconstruction model. Run the command `pmm ls` to list all supported plate models.\n", "\n", - "To select a supported plate model, pass an ID string to the PlateModelManager's get_model() function, e.g. `\"Muller2019\"` for the Müller et al. (2019) model.\n", + "Use `PlateModelManager`'s `get_model()` method to obtain a `PlateModel` object.\n", "\n", - "Now let's get the following files from a PlateModel object, such as muller2019_model:\n", - "- Rotation files \n", + "Now let's retrieve the following files from the `PlateModel` object:\n", + "- Rotation files\n", "- Topology files\n", - "- Static polygons files" + "- Static polygon files" ] }, { @@ -98,14 +90,13 @@ "metadata": {}, "outputs": [], "source": [ + "# Use the model name to create a PlateModel object\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=model_repo_dir)\n", "if not use_local_files:\n", - " # Obtain all rotation files, topology features and static polygons from Muller et al. 2019\n", - " pm_manager = PlateModelManager()\n", - " muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - " rotation_model = muller2019_model.get_rotation_model()\n", - " topology_features = muller2019_model.get_topologies()\n", - " static_polygons = muller2019_model.get_static_polygons()\n", - " " + " # Obtain all rotation files, topology features, and static polygons\n", + " rotation_model = pmm_model.get_rotation_model()\n", + " topology_features = pmm_model.get_topologies()\n", + " static_polygons = pmm_model.get_static_polygons()" ] }, { @@ -113,8 +104,8 @@ "id": "d807986a", "metadata": {}, "source": [ - "### Method 2: Loading local files\n", - "The cell below shows how the `glob` and `os` libraries locate rotation files, topology feature files, and static polygon files from a directory(s) on your computer. We then use these file path strings to generate the necessary ``, `` and `` objects." + "### Method 2: Loading Local Files\n", + "The cell below shows how the `glob` and `os` libraries can be used to locate rotation files, topology feature files, and static polygon files from one or more directories on your computer." ] }, { @@ -125,26 +116,20 @@ "outputs": [], "source": [ "if use_local_files:\n", - " # Directory to plate model files\n", - " input_directory = \"./NotebookFiles/Muller_etal_2019_PlateMotionModel_v2.0_Tectonics/\"\n", - "\n", - " # Locate rotation files and set up the RotationModel object\n", - " rotation_filenames = glob.glob(os.path.join(input_directory, '*.rot'))\n", - " rotation_model = pygplates.RotationModel(rotation_filenames)\n", - "\n", - " # Locate topology feature files and set up a FeatureCollection object \n", - " topology_filenames = glob.glob(os.path.join(input_directory, '*.gpml'))\n", - " topology_features = pygplates.FeatureCollection()\n", - " for topology_filename in topology_filenames:\n", - " # (omit files with the string \"inactive\" in the filepath)\n", + " # For demonstration purposes only, we download the plate model files once.\n", + " # This call will not re-download the files unless they have been updated on the server.\n", + " PlateModelManager().get_model(model_name, data_dir=model_repo_dir).download_all_layers()\n", + "\n", + " model_path = os.path.join(model_repo_dir, model_name)\n", + " rotation_model = glob.glob(os.path.join(model_path, \"Rotations\", \"*.rot\"))\n", + " topology_features = []\n", + " for topology_filename in glob.glob(\n", + " os.path.join(model_path, \"Topologies\", \"*.gpml\")\n", + " ):\n", + " # Skip files whose name contains \"Inactive\"\n", " if \"Inactive\" not in topology_filename:\n", - " topology_features.add( pygplates.FeatureCollection(topology_filename) )\n", - " else:\n", - " topology_filenames.remove(topology_filename)\n", - "\n", - " # Locate static polygons and set up another FeatureCollection object\n", - " static_polygon_file = input_directory+\"StaticGeometries/StaticPolygons/Global_EarthByte_GPlates_PresentDay_StaticPlatePolygons_2019_v1.shp\"\n", - " static_polygons = pygplates.FeatureCollection(static_polygon_file)\n" + " topology_features.append(topology_filename)\n", + " static_polygons = glob.glob(os.path.join(model_path, \"StaticPolygons\", \"*.shp\"))" ] }, { @@ -152,9 +137,9 @@ "id": "c9f6f1bd", "metadata": {}, "source": [ - "### Constructing a plate reconstruction model using the `PlateReconstruction` object\n", + "### Constructing a Plate Reconstruction Model Using the `PlateReconstruction` Object\n", "\n", - "Once we have our rotation model, topology features and static polygons, we can supply them to the `PlateReconstruction` object to construct the plate motion model." + "Once we have our rotation model, topology features, and static polygons, we can supply them to the `PlateReconstruction` object to construct the plate motion model." ] }, { @@ -172,9 +157,15 @@ "id": "2f4d23c1", "metadata": {}, "source": [ - "### Reconstructing feature geometries\n", + "### Reconstructing Feature Geometries\n", + "\n", + "The plate motion model we created can be used to generate plate reconstructions through geological time. Let's reconstruct subduction zones and mid-ocean ridges to 50 Ma.\n", + "\n", + "[`tessellate_subduction_zones()`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.PlateReconstruction.html#gplately.PlateReconstruction.tessellate_subduction_zones) samples points along subduction zone trenches and returns subduction data at a given geological time as a 10-column, vertically-stacked tuple.\n", + "\n", + "[`tessellate_mid_ocean_ridges()`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.PlateReconstruction.html#gplately.PlateReconstruction.tessellate_mid_ocean_ridges) samples points along resolved spreading features (e.g. mid-ocean ridges) and returns spreading rates and ridge segment lengths at a given geological time as a 4-column, vertically-stacked tuple.\n", "\n", - "The plate motion model we created can be used to generate plate reconstructions through geological time. Let's reconstruct subduction zones and mid-ocean ridges to 50 Ma." + "You may notice that the tessellated subduction zones and MORs in the plot below are missing some sections compared to the maps plotted by `PlotTopologies`. This is because the tessellation algorithm applies stricter rules and excludes some ineligible sections. See the online documentation for details." ] }, { @@ -184,54 +175,54 @@ "metadata": {}, "outputs": [], "source": [ - "time = 50 #Ma\n", + "time = 50 # Ma\n", "subduction_data = model.tessellate_subduction_zones(time)\n", - "ridge_data = model.tessellate_mid_ocean_ridges(time)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "da8a308b", - "metadata": {}, - "outputs": [], - "source": [ - "# plot ridges and trenches at 50 Ma\n", - "\n", - "fig = plt.figure(figsize=(16,8))\n", - "ax1 = fig.add_subplot(111)\n", - "ax1.scatter(subduction_data[:,0], # longitude\n", - " subduction_data[:,1], # latitude\n", - " color='blue')\n", - "ax1.scatter(ridge_data[:,0], # longitude\n", - " ridge_data[:,1], # latitude\n", - " color='red')\n", - "\n", + "ridge_data = model.tessellate_mid_ocean_ridges(time)\n", + "\n", + "print(subduction_data.shape)\n", + "print(ridge_data.shape)\n", + "\n", + "fig = plt.figure(figsize=(8, 6), dpi=72)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", + "ax.set_global()\n", + "ax.scatter(\n", + " subduction_data[:, 0], # longitude\n", + " subduction_data[:, 1], # latitude\n", + " color=\"blue\",\n", + " s=1,\n", + " transform=ccrs.PlateCarree(),\n", + ")\n", + "ax.scatter(\n", + " ridge_data[:, 0], # longitude\n", + " ridge_data[:, 1], # latitude\n", + " color=\"red\",\n", + " s=1,\n", + " transform=ccrs.PlateCarree(),\n", + ")\n", + "plt.title(f\"Tessellated Subduction Zones and MORs at {time} Ma\")\n", "plt.show()" ] }, - { - "cell_type": "markdown", - "id": "bb52adb8", - "metadata": {}, - "source": [ - "This doesn't look terrific. Let's add some more topologies..." - ] - }, { "cell_type": "markdown", "id": "614f2e96", "metadata": {}, "source": [ - "### Plotting plate reconstructions with the `PlotTopologies` object. \n", + "### Plotting Plate Reconstructions with the `PlotTopologies` Object\n", "\n", - "Let's visualise this reconstruction on a GeoAxis plot using gplately's `PlotTopologies` object. To call the object, we need to supply:\n", + "Let's visualise this reconstruction on a GeoAxes plot using GPlately's `PlotTopologies` object. To create the object, we need to supply:\n", "\n", - "- the `PlateReconstruction` plate motion model we just created\n", + "- the `PlateReconstruction` plate motion model we just created,\n", "- a coastline filename or `` object,\n", "- a continent filename or `` object,\n", - "- and a continent-ocean boundary (COBs) filename or `` object,\n", - "- a specific reconstruction time (Ma),\n", + "- a continent-ocean boundary (COBs) filename or `` object, and\n", + "- a specific reconstruction time (Ma).\n", "\n", "```python\n", "gplot = gplately.PlotTopologies(\n", @@ -244,7 +235,7 @@ ")\n", "```\n", "\n", - "We demonstrate the same methods used above to locate coastline, continent and COB files." + "We demonstrate the same methods used above to locate the coastline, continent, and COB files." ] }, { @@ -252,9 +243,9 @@ "id": "b268d07a", "metadata": {}, "source": [ - "### Method 1: Loading files with `PlateModelManager` \n", + "### Method 1: Loading Files with `PlateModelManager`\n", "\n", - "We already defined a `PlateModel` object above(muller2019_model) to get a rotation model, topology features and static polygons. Let's re-use this object to locate coastlines, continents and COBs downloaded to a local folder." + "We already defined a `PlateModel` object above (`pmm_model`) to obtain a rotation model, topology features, and static polygons. Let's reuse this object to locate the coastlines, continents, and COBs downloaded to the local folder." ] }, { @@ -265,10 +256,9 @@ "outputs": [], "source": [ "if not use_local_files:\n", - " # Obtain geometry shapefiles with gdownload\n", - " coastlines = muller2019_model.get_coastlines()\n", - " continents = muller2019_model.get_continental_polygons()\n", - " COBs = muller2019_model.get_COBs()" + " coastlines = pmm_model.get_coastlines()\n", + " continents = pmm_model.get_continental_polygons()\n", + " COBs = pmm_model.get_COBs()" ] }, { @@ -276,8 +266,8 @@ "id": "e1fecbde", "metadata": {}, "source": [ - "### Method 2: Loading local files\n", - "We re-use the same `input_directory` defined to fetch local plate reconstruction files to now load coastlines, continents and COBs:" + "### Method 2: Loading Local Files\n", + "Load the local coastlines, continents, and COBs files." ] }, { @@ -288,9 +278,9 @@ "outputs": [], "source": [ "if use_local_files:\n", - " coastlines = input_directory+\"StaticGeometries/Coastlines/Global_coastlines_2019_v1_low_res.shp\"\n", - " continents = input_directory+\"StaticGeometries/ContinentalPolygons/Global_EarthByte_GPlates_PresentDay_ContinentalPolygons_2019_v1.shp\"\n", - " COBs = input_directory+\"StaticGeometries/AgeGridInput/Global_EarthByte_GeeK07_IsoCOB_2019_v2.gpml\"" + " coastlines = glob.glob(os.path.join(model_path, \"Coastlines\", \"*.shp\"))\n", + " continents = glob.glob(os.path.join(model_path, \"ContinentalPolygons\", \"*.shp\"))\n", + " COBs = glob.glob(os.path.join(model_path, \"COBs\", \"*.shp\"))" ] }, { @@ -298,8 +288,8 @@ "id": "16d876a4", "metadata": {}, "source": [ - "### Define the `PlotTopologies` object\n", - "Let's call the `PlotTopologies` object 'gplot' and set it up to visualise geologic features at 50 Ma:" + "### Define the `PlotTopologies` Object\n", + "Let's create a `PlotTopologies` object named `gplot` and set it up to visualise geologic features at 50 Ma:" ] }, { @@ -309,8 +299,8 @@ "metadata": {}, "outputs": [], "source": [ - "# Call the PlotTopologies object\n", - "time = 50 #Ma\n", + "# Create the PlotTopologies object\n", + "time = 50 # Ma\n", "gplot = gplately.PlotTopologies(model, coastlines=coastlines, continents=continents, COBs=COBs, time=time)" ] }, @@ -319,7 +309,7 @@ "id": "4e22b335", "metadata": {}, "source": [ - "To plot using GPlately's `PlotTopologies` object, first create a GeoAxis plot (here we call it `ax`) and [select a projection using Cartopy](https://scitools.org.uk/cartopy/docs/v0.15/crs/projections.html). This is the plot we supply to our gplot object." + "To plot using GPlately's `PlotTopologies` object, first create a GeoAxes plot (here we call it `ax`) and [select a projection using Cartopy](https://scitools.org.uk/cartopy/docs/v0.15/crs/projections.html). This is the plot we then supply to our `gplot` object." ] }, { @@ -329,22 +319,33 @@ "metadata": {}, "outputs": [], "source": [ - "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(16,12), dpi=100)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "plt.title('Subduction zones and mid-ocean ridges reconstructed to %i Ma' % (time))\n", - "\n", - "# Plot shapefile features, subduction zones and MOR boundaries at 50 Ma\n", - "gplot.time = time # Ma\n", - "gplot.plot_continent_ocean_boundaries(ax, color='b', alpha=0.05)\n", - "gplot.plot_continents(ax, facecolor='palegoldenrod', alpha=0.2)\n", - "gplot.plot_coastlines(ax, color='DarkKhaki')\n", - "gplot.plot_ridges(ax, color='red')\n", - "gplot.plot_transforms(ax, color='red')\n", - "gplot.plot_trenches(ax, color='k')\n", - "gplot.plot_subduction_teeth(ax, color='k')\n", - "ax.set_global()" + "with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + " # Set up a GeoAxes plot\n", + " fig = plt.figure(figsize=(8, 6), dpi=100)\n", + " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + " ax.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + " )\n", + " plt.title(f\"Subduction Zones and MORs at {time} Ma\")\n", + "\n", + " # Plot shapefile features, subduction zones, and MOR boundaries at the given time\n", + " gplot.time = time # Ma\n", + " gplot.plot_continent_ocean_boundaries(ax, color=\"b\", alpha=0.05)\n", + " gplot.plot_continents(ax, facecolor=\"palegoldenrod\", alpha=0.2)\n", + " gplot.plot_coastlines(ax, color=\"DarkKhaki\")\n", + " gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", + " ax.set_global()" ] }, { @@ -352,7 +353,7 @@ "id": "813c05d2", "metadata": {}, "source": [ - "If you have moviepy available, you can create a gif that illustrates plate motions through geological time. Let's reconstruct plate movements up to 100 Ma in intervals of 10 Ma!" + "If you have `moviepy` installed, you can create a GIF that illustrates plate motions through geological time. Let's reconstruct plate movements up to 100 Ma in intervals of 10 Ma!" ] }, { @@ -362,27 +363,33 @@ "metadata": {}, "outputs": [], "source": [ - "def generate_frame(output_filename, time):\n", - " \n", - " # Set up a GeoAxis plot\n", - " fig = plt.figure(figsize=(18,10), dpi=100)\n", - " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - " ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - " plt.title('Subduction zones and mid-ocean ridges reconstructed to %i Ma' % (time))\n", - "\n", - " # Update the reconstruction time to allocate to PlotTopologies\n", - " gplot.time = time\n", - " \n", - " # Plot shapefile features, subduction zones and MOR boundaries at 50 Ma\n", - " gplot.plot_continent_ocean_boundaries(ax, color='b', alpha=0.05)\n", - " gplot.plot_continents(ax, facecolor='palegoldenrod', alpha=0.2)\n", - " gplot.plot_coastlines(ax, color='DarkKhaki')\n", - " gplot.plot_ridges(ax, color='red')\n", - " gplot.plot_transforms(ax, color='red')\n", - " gplot.plot_trenches(ax, color='k')\n", - " gplot.plot_subduction_teeth(ax, color='k')\n", + "def generate_frame(output_filename, gplot_m):\n", + " time = gplot_m.time\n", + " # Set up a GeoAxes plot\n", + " fig = plt.figure(figsize=(8, 6), dpi=72)\n", + " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + " ax.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + " )\n", + " plt.title(f\"Subduction Zones and MORs at {int(time)} Ma\")\n", + "\n", + " # Plot shapefile features, subduction zones, and MOR boundaries at the given time\n", + " gplot_m.plot_continent_ocean_boundaries(ax, color=\"b\", alpha=0.05)\n", + " gplot_m.plot_continents(ax, facecolor=\"palegoldenrod\", alpha=0.2)\n", + " gplot_m.plot_coastlines(ax, color=\"DarkKhaki\", alpha=0.4)\n", + " gplot_m.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", " ax.set_global()\n", - " plt.savefig(output_filename)\n", + " plt.savefig(output_filename, bbox_inches=\"tight\")\n", " plt.close()" ] }, @@ -395,34 +402,38 @@ "source": [ "import tempfile\n", "from IPython.display import Image\n", + "\n", "try:\n", " from moviepy.editor import ImageSequenceClip # moviepy 1.x\n", "except ImportError:\n", " from moviepy import ImageSequenceClip # moviepy 2.x\n", "\n", "# Time variables\n", - "oldest_seed_time = 100 # Ma\n", - "time_step = 10 # Ma\n", + "oldest_seed_time = 100 # Ma\n", + "time_step = 10 # Ma\n", + "\n", + "with tempfile.TemporaryDirectory() as tmpdir, warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=UserWarning)\n", "\n", - "with tempfile.TemporaryDirectory() as tmpdir:\n", - " \n", " frame_list = []\n", "\n", - " # Create a plot for each 10 Ma interval\n", - " for time in np.arange(oldest_seed_time, 0., -time_step):\n", - " print('Generating %d Ma frame...' % time)\n", + " # Create a plot for each time step\n", + " gplot_m = get_gplot(\"Zahirovic2022\", time=oldest_seed_time)\n", + " for time in np.arange(oldest_seed_time, 0.0, -time_step):\n", + " gplot_m.time = time\n", + " print(\"Generating %d Ma frame...\" % time)\n", " frame_filename = os.path.join(tmpdir, \"frame_%d_Ma.png\" % time)\n", - " generate_frame(frame_filename, time)\n", + " generate_frame(frame_filename, gplot_m)\n", " frame_list.append(frame_filename)\n", "\n", - " video_filename = os.path.join(tmpdir, \"subd_mor_boundary_features.gif\")\n", - " \n", + " video_filename = \"subd_mor_boundary_features.gif\"\n", + "\n", " clip = ImageSequenceClip(frame_list, fps=5)\n", " clip.write_gif(video_filename)\n", "\n", - " print('The movie will show up in a few seconds...')\n", - " with open(video_filename,'rb') as f:\n", - " display(Image(data=f.read(), format='png', width = 2000, height = 500))" + " print(\"Displaying the animation below...\")\n", + " with open(video_filename, \"rb\") as f:\n", + " display(Image(data=f.read(), format=\"png\", width=500, height=250))" ] }, { @@ -430,42 +441,8 @@ "id": "eb988b27", "metadata": {}, "source": [ - "## Comparing two different plate models\n", - "Let's create another `PlateReconstruction` object with another set of `rotation_model`, `topology_features`, and `static_polygons` files from _\"Ocean basin evolution and global-scale plate reorganization events since Pangea breakup\"_ by Muller et al. (2016). This time, let's pass the string `\"Muller2016\"` into `PlateModelManager` to get these plate model files." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "643861e7", - "metadata": {}, - "outputs": [], - "source": [ - "# Obtain rotation files, topology features and static polygons from Müller et al. 2016\n", - "pm_manager = PlateModelManager()\n", - "muller2016_model = pm_manager.get_model(\"Muller2016\", data_dir=\"plate-model-repo\")\n", - "rotation_model2 = muller2016_model.get_rotation_model()\n", - "topology_features2 = muller2016_model.get_topologies()\n", - "static_polygons2 = muller2016_model.get_static_polygons()\n", - "\n", - "model2 = gplately.PlateReconstruction(rotation_model2, topology_features2)\n", - "\n", - "# Obtain features for the PlotTopologies object\n", - "coastlines2 = muller2016_model.get_coastlines()\n", - "continents2 = None\n", - "COBs2 = muller2016_model.get_COBs()\n", - "\n", - "# Call the PlotTopologies object\n", - "time = 0 #Ma\n", - "gplot2= gplately.plot.PlotTopologies(model2, coastlines=coastlines2, continents=continents2, COBs=COBs2, time=time)" - ] - }, - { - "cell_type": "markdown", - "id": "df9c4070", - "metadata": {}, - "source": [ - "Let's plot these plate topologies along with those from Müller et al. (2019) which uses near-neighbor interpolation, ultiamtely removing topologies that have had no deformation." + "## Comparing Two Different Plate Models\n", + "Let's create two `PlotTopologies` objects using two different plate models and plot the resulting maps side by side for comparison." ] }, { @@ -475,87 +452,116 @@ "metadata": {}, "outputs": [], "source": [ - "# set reconstruction time\n", - "time = 100\n", - "gplot.time = time\n", - "gplot2.time = time\n", - "\n", - "# Get the Müller et al. (2016) and Müller et al. (2019) age grids at corresponding time.\n", - "# Create gplately.Raster objects.\n", - "muller2016_nc = gplately.Raster(data=muller2016_model.get_raster(\"AgeGrids\",time))\n", - "muller2019_nc = gplately.Raster(data=muller2019_model.get_raster(\"AgeGrids\",time))\n", - "\n", - "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(18,10), dpi=300)\n", - "\n", - "# ----------------------------------------------- FIRST SUBPLOT -----------------------------------------------------\n", - "ax1 = fig.add_subplot(121, projection=ccrs.Mollweide(central_longitude = 0))\n", - "plt.title(\"Müller et al. (2019) at {} Myr\".format(time), fontsize=15)\n", - "ax1.set_title(\"a\", loc='left', fontsize=\"16\", weight=\"demi\")\n", - "ax1.set_global()\n", + "reconstruction_time = 100 # Ma\n", + "\n", + "# You can change the model names below to compare different plate models.\n", + "# Make sure both models contain the required data files.\n", + "model_name_1 = \"Zahirovic2022\"\n", + "model_name_2 = \"Muller2025\"\n", "\n", - "# Plot seafloor age grids, coastlines, subduction zones, MOR and transform boundaries at present day\n", + "gplot_1 = get_gplot(model_name_1, time=reconstruction_time)\n", + "gplot_2 = get_gplot(model_name_2, time=reconstruction_time)\n", "\n", - "gplot.plot_coastlines(ax1, color='0.5')\n", - "gplot.plot_ridges(ax1, color='r')\n", - "gplot.plot_transforms(ax1, color='r')\n", - "im = gplot.plot_grid(ax1, muller2019_nc.data, cmap='YlGnBu', vmin=0, vmax=200, alpha=0.4)\n", - "gplot.plot_trenches(ax1, color='k')\n", - "gplot.plot_subduction_teeth(ax1, color='k', zorder=4)\n", - "gplot.plot_plate_motion_vectors(ax1, spacingX=10, spacingY=10, normalise=False, zorder=4, alpha=0.4)\n", + "pmm_1 = gplot_1.plate_reconstruction.plate_model\n", + "pmm_2 = gplot_2.plate_reconstruction.plate_model\n", "\n", + "agegrid_1 = gplately.Raster(data=pmm_1.get_raster(\"AgeGrids\", reconstruction_time))\n", + "agegrid_2 = gplately.Raster(data=pmm_2.get_raster(\"AgeGrids\", reconstruction_time))\n", "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", "\n", - "# ----------------------------------------------- SECOND SUBPLOT ----------------------------------------------------\n", - "ax2 = fig.add_subplot(122, projection=ccrs.Mollweide(central_longitude = 0))\n", - "plt.title(\"Müller et al. (2016) at {} Myr\".format(time), fontsize=15)\n", - "ax2.set_title(\"b\", loc='left', fontsize=\"16\", weight=\"demi\")\n", + "# Set up a GeoAxes plot\n", + "fig = plt.figure(figsize=(20, 10), dpi=72)\n", + "\n", + "# --- First subplot ---\n", + "ax1 = fig.add_subplot(121, projection=ccrs.Mollweide(central_longitude=0))\n", + "plt.title(f\"{model_name_1} {reconstruction_time} Ma\", fontsize=15)\n", + "ax1.set_title(\"a\", loc=\"left\", fontsize=\"16\", weight=\"demi\")\n", + "ax1.set_global()\n", + "\n", + "# Plot seafloor age grid, coastlines, subduction zones, and MOR/transform boundaries at the reconstruction time\n", + "gplot_1.plot_coastlines(ax1, color=\"0.5\")\n", + "im = gplot_1.plot_grid(ax1, agegrid_1.data, cmap=\"YlGnBu\", vmin=0, vmax=250, alpha=0.4)\n", + "gplot_1.plot_all_topological_sections(\n", + " ax1,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", + "gplot_1.plot_plate_motion_vectors(\n", + " ax1, spacingX=10, spacingY=10, normalise=False, zorder=4, alpha=0.4\n", + ")\n", + "\n", + "# --- Second subplot ---\n", + "ax2 = fig.add_subplot(122, projection=ccrs.Mollweide(central_longitude=0))\n", + "plt.title(f\"{model_name_2} {reconstruction_time} Ma\", fontsize=15)\n", + "ax2.set_title(\"b\", loc=\"left\", fontsize=\"16\", weight=\"demi\")\n", + "ax2.set_global()\n", + "\n", + "# Plot seafloor age grid, coastlines, subduction zones, and MOR/transform boundaries at the reconstruction time\n", + "gplot_2.plot_coastlines(ax2, color=\"0.5\")\n", "\n", - "# Plot seafloor age grids, coastlines, subduction zones, MOR and transform boundaries at present day\n", - "gplot2.plot_coastlines(ax2, color='0.5')\n", - "gplot2.plot_ridges(ax2, color='r',)\n", - "gplot2.plot_transforms(ax2, color='r',)\n", "# Use the age grid for the current time step\n", - "im = gplot.plot_grid(ax2, muller2016_nc.data, cmap='YlGnBu', vmin=0, vmax=200, alpha=0.4)\n", - "gplot2.plot_trenches(ax2, color='k')\n", - "gplot2.plot_subduction_teeth(ax2, color='k', label=\"Subduction polarity teeth\")\n", - "gplot2.plot_plate_motion_vectors(ax2, spacingX=10, spacingY=10, normalise=False, zorder=10, alpha=0.4)\n", + "im = gplot_2.plot_grid(ax2, agegrid_2.data, cmap=\"YlGnBu\", vmin=0, vmax=250, alpha=0.4)\n", + "gplot_2.plot_all_topological_sections(\n", + " ax2,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", + "gplot_2.plot_plate_motion_vectors(\n", + " ax2, spacingX=10, spacingY=10, normalise=False, zorder=10, alpha=0.4\n", + ")\n", "\n", "\n", - "# ----------------------------------------------- PLOT PROPERTIES -----------------------------------------------------\n", - "plt.subplots_adjust(wspace=0.075) # spacing between subplots\n", + "# --- Plot properties ---\n", + "plt.subplots_adjust(wspace=0.025) # spacing between subplots\n", "\n", "# Colorbar settings\n", "cb_ax = fig.add_axes([0.17, 0.25, 0.15, 0.02])\n", - "cb = fig.colorbar(im, cax=cb_ax, orientation='horizontal', shrink=0.4, pad=0.05)\n", - "cb.set_label(label='Age (Ma)', fontsize=15)\n", - "ticks = np.arange(0,201,50)\n", + "cb = fig.colorbar(im, cax=cb_ax, orientation=\"horizontal\", shrink=0.4, pad=0.05)\n", + "cb.set_label(label=\"Age (Ma)\", fontsize=15)\n", + "ticks = np.arange(0, 251, 50)\n", "cb.set_ticks(ticks, labels=ticks, fontsize=15)\n", "\n", "# Legend settings\n", - "legend_elements = [Line2D([0], [0], linestyle='-', color='r',\n", - " label=\"Mid-ocean ridges and transform boundaries\"),\n", - " Line2D([0], [0], marker='^', linestyle='-', color='k',\n", - " label='Subduction zones with polarity teeth',\n", - " markerfacecolor='k', markersize=5),\n", - " # FancyArrowPatch(0,0,0,0, color='k', alpha=0.4, label=\"Plate velocity vectors\"),\n", - " ]\n", - "\n", - "lg = fig.legend(handles=legend_elements, bbox_to_anchor=(0.65,0.3), ncol=1, fontsize = 15, frameon=False)" + "legend_elements = [\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " linestyle=\"-\",\n", + " color=\"r\",\n", + " label=\"Mid-ocean Ridges and Transform Boundaries\",\n", + " ),\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " marker=\"^\",\n", + " linestyle=\"-\",\n", + " color=\"blue\",\n", + " label=\"Subduction Zones with Polarity Teeth\",\n", + " markerfacecolor=\"blue\",\n", + " markersize=5,\n", + " ),\n", + "]\n", + "\n", + "lg = fig.legend(\n", + " handles=legend_elements,\n", + " bbox_to_anchor=(0.65, 0.3),\n", + " ncol=1,\n", + " fontsize=15,\n", + " frameon=False,\n", + ")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "87599828", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -569,7 +575,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/03-WorkingWithPoints.ipynb b/Notebooks/03-WorkingWithPoints.ipynb index b7f235c5..7b34033e 100644 --- a/Notebooks/03-WorkingWithPoints.ipynb +++ b/Notebooks/03-WorkingWithPoints.ipynb @@ -7,7 +7,7 @@ "source": [ "# 3 - Working with points\n", "\n", - "In this notebooks we use gplately to manipulate and reconstruct point data.\n", + "In this notebook, we use GPlately to manipulate and reconstruct point data.\n", "\n", "```python\n", "gpts = gplately.Points(\n", @@ -15,12 +15,12 @@ " lons, # list or numpy array of longitudinal coordinates\n", " lats, # list or numpy array of latitudinal coordinates\n", " time=0, # time is set to the present day by default\n", - " plate_id=None # optionally pass an array (or single integer) of pre-determined plate IDs\n", - " age=numpy.inf # optionally pass an array (or single float) of pre-determined appearance ages (defaults to: appearing for all time)\n", + " plate_id=None, # optionally pass an array (or single integer) of pre-determined plate IDs\n", + " age=numpy.inf, # optionally pass an array (or single float) of pre-determined appearance ages (defaults to: appearing for all time)\n", ")\n", "```\n", "\n", - "In this example, we will reconstruct data from the [Paleobiology Database (PBDB)](https://paleobiodb.org/#/). This data is in csv format." + "In this example, we will reconstruct data from the [Paleobiology Database (PBDB)](https://paleobiodb.org/#/). This data is in CSV format." ] }, { @@ -30,6 +30,7 @@ "metadata": {}, "outputs": [], "source": [ + "import os, warnings\n", "import pandas as pd\n", "import numpy as np\n", "import gplately\n", @@ -43,7 +44,7 @@ "id": "bbdff197", "metadata": {}, "source": [ - "We first download Müller et al. (2019) plate reconstruction model files to use in this Notebook, and set up the `PlateReconstruction` and `PlotTopologies` objects (call them `model` and `gplot`) from these model files." + "We first download the **Zahirovic2022** plate reconstruction model files to use in this notebook, and set up the `PlateReconstruction` and `PlotTopologies` objects (calling them `model` and `gplot`) from these model files." ] }, { @@ -53,19 +54,23 @@ "metadata": {}, "outputs": [], "source": [ - "pm_manager = PlateModelManager()\n", - "muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", + "model_name = \"Zahirovic2022\" # You can change this to another model name available in PlateModelManager\n", + "# print(PlateModelManager().get_available_model_names()) # uncomment this line to see all available model names\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=\"plate-model-repo\")\n", + "assert pmm_model is not None, f\"Model {model_name} not found.\"\n", "\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "static_polygons = muller2019_model.get_static_polygons()\n", + "rotation_model = pmm_model.get_rotation_model()\n", + "topology_features = pmm_model.get_topologies()\n", + "static_polygons = pmm_model.get_static_polygons()\n", "\n", - "coastlines = muller2019_model.get_layer('Coastlines')\n", - "continents = muller2019_model.get_layer('ContinentalPolygons')\n", - "COBs = muller2019_model.get_layer('COBs')\n", + "coastlines = pmm_model.get_layer(\"Coastlines\")\n", + "continents = pmm_model.get_layer(\"ContinentalPolygons\")\n", + "COBs = pmm_model.get_layer(\"COBs\")\n", "\n", "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", - "gplot = gplately.PlotTopologies(model, coastlines=coastlines, continents=continents, COBs=COBs)" + "gplot = gplately.PlotTopologies(\n", + " model, coastlines=coastlines, continents=continents, COBs=COBs\n", + ")" ] }, { @@ -75,10 +80,10 @@ "source": [ "## Download and import PBDB data\n", "\n", - "We can import data from the PBDB using the data url straight into [`pandas`](https://pandas.pydata.org/docs/reference/index.html#api). Alternatively, we can download the csv file from their [website](https://paleobiodb.org/classic/displayDownloadGenerator) and import that.\n", + "We can import data from the PBDB directly into [`pandas`](https://pandas.pydata.org/docs/reference/index.html#api) using the data URL. Alternatively, we can download the CSV file from their [website](https://paleobiodb.org/classic/displayDownloadGenerator) and import that.\n", "\n", - "For importing csv files: it is often easier if the first row is the column name, although `pandas` does allow you to skip these header rows if needed.\n", - "Conveniently, the PBDB provides an option when downloading data to exclude the metadata at the beginning of the file. " + "When importing CSV files, it is often easier if the first row is the column names, although `pandas` does allow you to skip header rows if needed.\n", + "Conveniently, the PBDB provides an option when downloading data to exclude the metadata at the beginning of the file." ] }, { @@ -88,12 +93,18 @@ "metadata": {}, "outputs": [], "source": [ - "# download data for the Late Cretaceous, and inclue the paleoenvironment column.\n", - "# You can use the download page to play with the options and get the download link and/or CSV.\n", - "pbdb_data_url = 'https://paleobiodb.org/data1.2/occs/list.csv?datainfo&rowcount&base_name=Foraminifera&interval=Jurassic&show=coords,env'\n", + "# First, check if the data file already exists locally. If it does, we can read it directly from the file. If not, we will download it from the PBDB website.\n", + "data_file_path = \"NotebookFiles/pbdb-data.csv\"\n", "\n", - "## import from the URL\n", - "pbdb_data = pd.read_csv(pbdb_data_url, sep=',', skiprows=18) " + "if os.path.exists(data_file_path):\n", + " pbdb_data = pd.read_csv(data_file_path)\n", + " print(f\"Data loaded from {data_file_path}.\")\n", + "else:\n", + " # Download data for the Jurassic period, and include the paleoenvironment column.\n", + " # You can use the download page to play with the options and get the download link and/or CSV.\n", + " pbdb_data_url = \"https://paleobiodb.org/data1.2/occs/list.csv?datainfo&rowcount&base_name=Foraminifera&interval=Jurassic&show=coords,env\"\n", + " pbdb_data = pd.read_csv(pbdb_data_url, sep=\",\", skiprows=18)\n", + " print(f\"Data downloaded from {pbdb_data_url}.\")" ] }, { @@ -103,7 +114,8 @@ "metadata": {}, "outputs": [], "source": [ - "pbdb_data.columns" + "print(pbdb_data.columns)\n", + "print(pbdb_data.shape)" ] }, { @@ -114,24 +126,41 @@ "outputs": [], "source": [ "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(16,12), dpi=100)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax.set_global()\n", - "ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "ax.set_title(\"Present day distribution of Jurassic Foraminifera\")\n", + "fig = plt.figure(figsize=(16, 8), dpi=300)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax.set_global() # type: ignore\n", + "ax.gridlines( # type: ignore\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", + "ax.set_title(\"Present Day Distribution of Jurassic Foraminifera\")\n", "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", "# Plot shapefile features, subduction zones and MOR boundaries at 0 Ma\n", - "gplot.time = 0 # Ma\n", - "gplot.plot_continent_ocean_boundaries(ax, color='b', alpha=0.05)\n", - "gplot.plot_continents(ax, facecolor='palegoldenrod', alpha=0.2)\n", - "gplot.plot_coastlines(ax, color='DarkGrey')\n", - "gplot.plot_ridges(ax, color='red')\n", - "gplot.plot_trenches(ax, color='k')\n", - "gplot.plot_subduction_teeth(ax, color='k')\n", + "gplot.time = 0 # Ma\n", + "gplot.plot_continent_ocean_boundaries(ax, color=\"b\", alpha=0.05)\n", + "gplot.plot_continents(ax, facecolor=\"palegoldenrod\", alpha=0.2)\n", + "gplot.plot_coastlines(ax, color=\"DarkGrey\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", "\n", - "sc = ax.scatter(pbdb_data['lng'], pbdb_data['lat'], color='orange', \n", - " transform=ccrs.PlateCarree(), label='Jurassic Foraminifera')\n", - "ax.legend(frameon=False)" + "sc = ax.scatter(\n", + " pbdb_data[\"lng\"],\n", + " pbdb_data[\"lat\"],\n", + " color=\"orange\",\n", + " transform=ccrs.PlateCarree(),\n", + " label=\"Jurassic Foraminifera\",\n", + ")\n", + "ax.legend(frameon=False)\n", + "plt.show()" ] }, { @@ -142,11 +171,9 @@ "\n", "## Reconstruct PBDB data with GPlately\n", "\n", - "Use the lon, lat coordinates and mean age of the PBDB data. \n", + "We use the lon/lat coordinates of the PBDB data, keeping only occurrences whose age range (`min_ma` to `max_ma`) contains a chosen `reconstruction_time`.\n", "\n", - "The `Points` object needs the `PlateReconstruction` object as a parameter.\n", - "\n", - "We can create the `PlateReconstruction` object with a `rotation_model`, `topology_features` and some `static_polygons`, which we can get using GPlately's `DataServer` object. Let's get these files from Müller et al. 2019 and call the DataServer object `gdownload`. " + "The `Points` object needs a `PlateReconstruction` object as a parameter — we already created one above (`model`), built from a `rotation_model`, `topology_features`, and `static_polygons`." ] }, { @@ -156,10 +183,15 @@ "metadata": {}, "outputs": [], "source": [ - "gpts = gplately.Points(model, pbdb_data['lng'], pbdb_data['lat'])\n", - "\n", - "reconstruction_time = np.mean(0.5*(pbdb_data['min_ma'] + pbdb_data['max_ma']))\n", - "rlons, rlats = gpts.reconstruct(reconstruction_time, return_array=True)" + "reconstruction_time = 185 # Ma\n", + "# Filter the data to only include occurrences whose age range (min_ma to max_ma) contains reconstruction_time\n", + "filtered_data = pbdb_data[\n", + " (pbdb_data[\"min_ma\"] <= reconstruction_time) & (reconstruction_time <= pbdb_data[\"max_ma\"])\n", + "]\n", + "print(filtered_data[\"min_ma\"].max(), filtered_data[\"max_ma\"].min())\n", + "print(reconstruction_time,pbdb_data.shape, filtered_data.shape)\n", + "gpts = gplately.Points(model, filtered_data[\"lng\"], filtered_data[\"lat\"])\n", + "rlons, rlats = gpts.reconstruct(reconstruction_time, return_array=True) # type: ignore" ] }, { @@ -170,24 +202,40 @@ "outputs": [], "source": [ "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(16,12), dpi=100)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax.set_global()\n", - "ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "ax.set_title(\"Reconstructed locations of Jurassic Foraminifera\")\n", + "fig = plt.figure(figsize=(16, 8), dpi=300)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax.set_global() # type: ignore\n", + "ax.gridlines( # type: ignore\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", "\n", "# Plot shapefile features, subduction zones and MOR boundaries at 0 Ma\n", - "gplot.time = reconstruction_time # Ma\n", - "gplot.plot_continent_ocean_boundaries(ax, color='b', alpha=0.05)\n", - "gplot.plot_continents(ax, facecolor='palegoldenrod', alpha=0.2)\n", - "gplot.plot_coastlines(ax, color='DarkGrey')\n", - "gplot.plot_ridges(ax, color='red')\n", - "gplot.plot_trenches(ax, color='k')\n", - "gplot.plot_subduction_teeth(ax, color='k')\n", + "gplot.time = reconstruction_time # Ma\n", + "gplot.plot_continent_ocean_boundaries(ax, color=\"b\", alpha=0.05)\n", + "gplot.plot_continents(ax, facecolor=\"palegoldenrod\", alpha=0.2)\n", + "gplot.plot_coastlines(ax, color=\"DarkGrey\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", "\n", - "sc = ax.scatter(rlons, rlats, color='orange', \n", - " transform=ccrs.PlateCarree(), label='Jurassic Foraminifera')\n", - "ax.legend(frameon=False)" + "sc = ax.scatter(\n", + " rlons,\n", + " rlats,\n", + " color=\"orange\",\n", + " transform=ccrs.PlateCarree(),\n", + " label=\"Jurassic Foraminifera\",\n", + ")\n", + "ax.legend(frameon=False)\n", + "ax.set_title(f\"Jurassic Foraminifera Locations at {reconstruction_time} Ma\")\n", + "plt.show()" ] }, { @@ -195,7 +243,7 @@ "id": "b1a10362", "metadata": {}, "source": [ - "We can make this map look a little bit nicer by condensing data that are close to each other. One way is to bin data by longitude/latitudinal grid cell. Alternatively we can use `stripy` to create an icosohedral mesh which has relatively uniform point spacing." + "We can make this map look a little nicer by condensing data points that are close to each other. One way is to bin the data on a longitude/latitude grid. Alternatively, we can use an icosahedral mesh, which has relatively uniform point spacing." ] }, { @@ -205,9 +253,13 @@ "metadata": {}, "outputs": [], "source": [ - "import stripy\n", + "from gplately.lib.icosahedron import get_mesh, xyz2lonlat\n", + "\n", + "mesh_vertices, mesh_faces = get_mesh(level=5)\n", "\n", - "mesh = stripy.spherical_meshes.icosahedral_mesh(refinement_levels=5, tree=True)" + "mesh_lons, mesh_lats = xyz2lonlat(\n", + " mesh_vertices[:, 0], mesh_vertices[:, 1], mesh_vertices[:, 2]\n", + ")" ] }, { @@ -217,12 +269,23 @@ "metadata": {}, "outputs": [], "source": [ - "distance, indices = mesh.nearest_vertices(np.deg2rad(rlons), np.deg2rad(rlats))\n", + "# Convert reconstructed lon/lat points to unit Cartesian coordinates.\n", + "rlon_rad = np.deg2rad(rlons) # type: ignore\n", + "rlat_rad = np.deg2rad(rlats) # type: ignore\n", + "rx = np.cos(rlat_rad) * np.cos(rlon_rad)\n", + "ry = np.cos(rlat_rad) * np.sin(rlon_rad)\n", + "rz = np.sin(rlat_rad)\n", + "reconstructed_xyz = np.column_stack((rx, ry, rz))\n", + "\n", + "# On the unit sphere, nearest vertex is the one with the largest dot product.\n", + "dot_products = reconstructed_xyz @ mesh_vertices.T\n", + "indices = np.argmax(dot_products, axis=1)\n", + "distance = np.arccos(np.clip(np.max(dot_products, axis=1), -1.0, 1.0))\n", "\n", "uindices, ucount = np.unique(indices, return_counts=True)\n", "\n", - "ulons = np.rad2deg(mesh.lons[uindices])\n", - "ulats = np.rad2deg(mesh.lats[uindices])" + "ulons = mesh_lons[uindices]\n", + "ulats = mesh_lats[uindices]" ] }, { @@ -233,32 +296,62 @@ "outputs": [], "source": [ "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(10,12), dpi=300)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax.set_global()\n", - "ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "ax.set_title(\"Reconstructed locations of Jurassic Foraminifera\")\n", + "fig = plt.figure(figsize=(16, 20), dpi=300)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax.set_global() # type: ignore\n", + "ax.gridlines( # type: ignore\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", + "ax.set_title(f\"Jurassic Foraminifera Locations at {reconstruction_time} Ma\")\n", "\n", "# Plot shapefile features, subduction zones and MOR boundaries at 0 Ma\n", - "gplot.time = reconstruction_time # Ma\n", + "gplot.time = reconstruction_time # Ma\n", "# gplot.plot_continent_ocean_boundaries(ax, color='b', alpha=0.05)\n", "# gplot.plot_continents(ax, facecolor='palegoldenrod', alpha=0.2)\n", - "gplot.plot_coastlines(ax, color='DarkGrey')\n", - "gplot.plot_ridges(ax, color='red')\n", - "gplot.plot_transforms(ax, color='red')\n", - "gplot.plot_trenches(ax, color='k')\n", - "gplot.plot_subduction_teeth(ax, color='k')\n", + "gplot.plot_coastlines(ax, color=\"DarkGrey\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", "\n", "mask_interval = np.ones_like(ucount, dtype=bool)\n", "\n", - "sc = ax.scatter(ulons, ulats, s=50+ucount, color='DarkOrange', edgecolor='k', alpha=0.5,\n", - " transform=ccrs.PlateCarree(), label='Jurassic Foraminifera', zorder=10)\n", + "sc = ax.scatter(\n", + " ulons,\n", + " ulats,\n", + " s=50 + ucount, # type: ignore\n", + " color=\"DarkOrange\",\n", + " edgecolor=\"k\",\n", + " alpha=0.5,\n", + " transform=ccrs.PlateCarree(),\n", + " label=\"Jurassic Foraminifera\",\n", + " zorder=10,\n", + ")\n", "\n", - "handles, labels = sc.legend_elements(prop=\"sizes\", num=5, color='DarkOrange', markeredgecolor='k')\n", - "ax.legend(handles, labels, loc=\"upper right\", title=\"Number of\\nForaminifera\", labelspacing=3, handletextpad=2,\n", - " bbox_to_anchor=(1.2,1.05), frameon=False)\n", + "handles, labels = sc.legend_elements(\n", + " prop=\"sizes\", num=7, color=\"DarkOrange\", markeredgecolor=\"k\"\n", + ")\n", + "ax.legend(\n", + " handles,\n", + " labels,\n", + " markerscale=2.4,\n", + " loc=\"upper right\",\n", + " title=\"Number of\\nForaminifera\",\n", + " labelspacing=4.2,\n", + " handletextpad=2,\n", + " bbox_to_anchor=(1.1, 1.0),\n", + " frameon=False,\n", + ")\n", "\n", - "fig.savefig(\"Reconstruct_Jurassic_Foraminifera.pdf\", bbox_inches='tight')" + "fig.savefig(\"Reconstructed_Jurassic_Foraminifera.pdf\", bbox_inches=\"tight\")\n", + "plt.show()" ] }, { @@ -268,13 +361,13 @@ "source": [ "## Add feature attributes\n", "\n", - "Adding attributes to each point can be done seamlessly using the `add_attributes` method by supplying keyword-value pairs. Some key attributes that can easily be read by GPlates include:\n", + "Attributes can be added to each point using the `add_attributes` method by supplying keyword-value pairs. Some key attributes that can easily be read by GPlates include:\n", "\n", - "- __FROMAGE__: the 'from' age specifies the oldest limit the data was active\n", - "- __TOAGE__: the 'to' age specifies the youngest limit the data was active\n", + "- __FROMAGE__: the 'from' age specifies the oldest limit at which the data was active\n", + "- __TOAGE__: the 'to' age specifies the youngest limit at which the data was active\n", "- __PLATEID__: the plate ID\n", "\n", - "Below, we add FROMAGE and TOAGE attributes to the `Points` object and save to a GPML file which can be directly read by GPlates." + "Below, we add the FROMAGE and TOAGE attributes to the `Points` object and save it to a GPML file, which can be read directly by GPlates." ] }, { @@ -284,26 +377,17 @@ "metadata": {}, "outputs": [], "source": [ - "gpts.add_attributes(FROMAGE=pbdb_data['max_ma'],\n", - " TOAGE=pbdb_data['min_ma'])\n", - "\n", - "# save to file\n", - "gpts.save(\"pbdb_data.csv\")\n", - "gpts.save(\"pbdb_data.gpml\")" + "filtered_data = filtered_data.reset_index(drop=True)\n", + "gpts.add_attributes(FROMAGE=filtered_data[\"max_ma\"], TOAGE=filtered_data[\"min_ma\"])\n", + "# save to files\n", + "gpts.save(\"output_pbdb_data.csv\")\n", + "gpts.save(\"output_pbdb_data.gpml\")" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c46dd3f4", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -317,7 +401,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/04-VelocityBasics.ipynb b/Notebooks/04-VelocityBasics.ipynb index cf198701..e43b071a 100644 --- a/Notebooks/04-VelocityBasics.ipynb +++ b/Notebooks/04-VelocityBasics.ipynb @@ -19,16 +19,22 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "import tempfile\n", - "\n", + "import os, warnings\n", + "from pathlib import Path\n", "import cartopy.crs as ccrs\n", "import gplately\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from matplotlib.colors import Normalize\n", "from plate_model_manager import PlateModelManager\n", - "import pygplates" + "import pygplates\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", + "data_dir = Path(\"4-Velocity-Basics-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)" ] }, { @@ -36,7 +42,7 @@ "id": "f1e12c96", "metadata": {}, "source": [ - "Let's first create a plate motion model using GPlately's `PlateReconstruction` object. To create the object, we need to pass a `rotation_model`, a set of pygplates `topology_features` and a path to a `static_polygons` file, which we will obtain from the Muller et al. (2019) plate model using `PlateModelManager`." + "Let's first create a plate motion model using GPlately's `PlateReconstruction` object. To create the object, we need to pass a `rotation_model`, a set of pygplates `topology_features` and a path to a `static_polygons` file." ] }, { @@ -46,15 +52,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Download Muller et al. 2019 files\n", - "pm_manager = PlateModelManager()\n", - "muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", + "model_name = \"Zahirovic2022\"\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=\"plate-model-repo\")\n", "\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "static_polygons = muller2019_model.get_static_polygons()\n", + "rotation_model = pmm_model.get_rotation_model()\n", + "topology_features = pmm_model.get_topologies()\n", + "static_polygons = pmm_model.get_static_polygons()\n", "\n", - "# Use the PlateReconstruction object to create a plate motion model\n", "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)" ] }, @@ -63,7 +67,7 @@ "id": "810550ab", "metadata": {}, "source": [ - "We will need this plate model to call GPlately's `PlotTopologies` object. Let's get Muller et al. (2019) `coastlines`, `continents` and `COBs` from `PlateModelManager`." + "We will need this plate model to call GPlately's `PlotTopologies` object. Let's get `coastlines`, `continents` and `COBs` from `PlateModelManager`." ] }, { @@ -73,12 +77,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Obtain geometries \n", - "coastlines = muller2019_model.get_layer('Coastlines')\n", - "continents = muller2019_model.get_layer('ContinentalPolygons')\n", - "COBs = muller2019_model.get_layer('COBs')\n", + "coastlines = pmm_model.get_layer(\"Coastlines\")\n", + "continents = pmm_model.get_layer(\"ContinentalPolygons\")\n", + "COBs = pmm_model.get_layer(\"COBs\")\n", "\n", - "# Call the PlotTopologies object\n", "gplot = gplately.PlotTopologies(model, coastlines, continents, COBs)" ] }, @@ -96,7 +98,7 @@ " \n", " This is done by calling the `get_point_velocities` method of a `PlateReconstruction` object.\n", "\n", - " It is implemented to find which topology (topological rigid plate or deforming network) each point is contained within (at the requested reconstruction time) and calculate the plate (or network) velocity at that point. This can be thought of as *dynamically* assigning a plate ID to each point (at the reconstruction time). In other words, each point is essentially assigned a plate ID every time velocities are calculated (in contrast to static polygons below). This concept applies well to topological plates, but for deforming networks it's a little more complicated since a network is essentially a deforming triangulation of different plate IDs, however it's the same principle.\n", + " It is implemented to find which topology (topological rigid plate or deforming network) each point is contained within (at the requested reconstruction time) and calculate the plate (or network) velocity at that point. This can be thought of as *dynamically* assigning a plate ID to each point (at the reconstruction time). In other words, each point is essentially assigned a plate ID every time velocities are calculated (in contrast to static polygons below). This concept applies well to topological plates. For deforming networks it's a little more complicated, since a network is essentially a deforming triangulation of different plate IDs — however, the same principle applies.\n", "\n", "- Using static polygons:\n", "\n", @@ -119,23 +121,6 @@ "This is useful when you have a grid of *static* points that you want to calculate velocities at. The points do not reconstruct, so they remain unmoved regardless of the reconstruction time." ] }, - { - "cell_type": "markdown", - "id": "ec4ab4e6", - "metadata": {}, - "source": [ - "### Calculating velocity data using the `PlateReconstruction` object\n", - "\n", - "Let's calculate plate velocity data for the `Muller2019` model using `get_point_velocities`, a method in the `PlateReconstruction` object. It returns the east and north components of velocities for each point that we give it at a specific reconstruction time.\n", - "\n", - "We need the following parameters:\n", - "- the `PlateReconstruction` model\n", - "- 2 1d flattened meshnode arrays representing the longitudinal and latitudinal extent of the velocity domain; \n", - "- the reconstruction time (Ma);\n", - "\n", - "It returns a list of lists containing the east and north components of velocity for each point in the velocity domain at a given time. " - ] - }, { "cell_type": "code", "execution_count": null, @@ -144,11 +129,11 @@ "outputs": [], "source": [ "# The distribution of points in the velocity domain: set global extent with 5 degree intervals\n", - "Xnodes = np.arange(-180,180,5)\n", - "Ynodes = np.arange(-90,90,5)\n", + "Xnodes = np.arange(-180, 180, 5)\n", + "Ynodes = np.arange(-90, 90, 5)\n", "\n", "# Create a lat-lon mesh and convert to 1d lat-lon arrays\n", - "x, y = np.meshgrid(Xnodes,Ynodes)\n", + "x, y = np.meshgrid(Xnodes, Ynodes)\n", "x = x.flatten()\n", "y = y.flatten()\n", "\n", @@ -159,11 +144,34 @@ "# So we explicitly specify cms/yr.\n", "#\n", "# Also, we return two separate velocity arrays (the east and north components) instead of a single 2D array of (north, east).\n", - "vel_x, vel_y = model.get_point_velocities(x, y, time, velocity_units=pygplates.VelocityUnits.cms_per_yr, return_east_north_arrays=True)\n", + "vel_x, vel_y = model.get_point_velocities(\n", + " x,\n", + " y,\n", + " time,\n", + " velocity_units=pygplates.VelocityUnits.cms_per_yr,\n", + " return_east_north_arrays=True,\n", + ")\n", "vel_mag = np.hypot(vel_x, vel_y)\n", "\n", - "print('Number of points in our velocity domain = ', len(vel_x))\n", - "print('Average velocity at {} Ma = {:.2f} cm/yr'.format(time, vel_mag.mean()))" + "print(f\"Number of points in our velocity domain = {len(vel_x)}\")\n", + "print(f\"Average velocity at {time} Ma = {vel_mag.mean():.2f} cm/yr\")" + ] + }, + { + "cell_type": "markdown", + "id": "ec4ab4e6", + "metadata": {}, + "source": [ + "### Calculating velocity data using the `PlateReconstruction` object\n", + "\n", + "Let's calculate plate velocity data using `get_point_velocities`, a method of the `PlateReconstruction` object. It returns the east and north velocity components for each point we give it, at a specific reconstruction time.\n", + "\n", + "We need the following parameters:\n", + "- the `PlateReconstruction` model\n", + "- 2 1D flattened arrays of mesh-node longitudes and latitudes representing the velocity domain\n", + "- the reconstruction time (Ma)\n", + "\n", + "By default it returns a single array of (north, east) pairs for each point; passing `return_east_north_arrays=True` (as we did above) instead returns two separate arrays for the east and north components." ] }, { @@ -189,16 +197,22 @@ "vel_std = np.zeros(time_range.size)\n", "\n", "for t, time in enumerate(time_range):\n", - " vel_x, vel_y = model.get_point_velocities(x, y, time, velocity_units=pygplates.VelocityUnits.cms_per_yr, return_east_north_arrays=True)\n", + " vel_x, vel_y = model.get_point_velocities(\n", + " x,\n", + " y,\n", + " time,\n", + " velocity_units=pygplates.VelocityUnits.cms_per_yr,\n", + " return_east_north_arrays=True,\n", + " )\n", " vel_mag = np.hypot(vel_x, vel_y)\n", - " \n", + "\n", " # an optional setting: if there are points in the velocity domain with a large plate velocity,\n", " # we can ignore these outliers. This should not be used when debugging plate models.\n", " ignore_outliers = True\n", - " \n", + "\n", " # Set the outlier velocity to be 50 cm/yr\n", - " outlier_velocity = 50.\n", - " \n", + " outlier_velocity = 50.0\n", + "\n", " if ignore_outliers is True:\n", " vel_mag_new = [v for v in vel_mag if v < outlier_velocity]\n", " vel_av[t] = np.mean(vel_mag_new)\n", @@ -217,23 +231,19 @@ "source": [ "# save to a CSV file\n", "\n", - "output_data = np.column_stack([\n", - " time_range,\n", - " vel_av,\n", - " vel_std\n", - "])\n", + "output_data = np.column_stack([time_range, vel_av, vel_std])\n", "\n", - "header = 'Time (Ma),Mean plate velocities (cm/yr),Standard deviation (cm/yr)'\n", + "header = \"Time (Ma),Mean plate velocities (cm/yr),Standard deviation (cm/yr)\"\n", "\n", "np.savetxt(\n", " os.path.join(\n", - " \"NotebookFiles\",\n", + " output_dir,\n", " \"GlobalAveragePlateVelocities.csv\",\n", " ),\n", " output_data,\n", - " delimiter=',',\n", + " delimiter=\",\",\n", " header=header,\n", - " comments='',\n", + " comments=\"\",\n", ")" ] }, @@ -244,21 +254,32 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "fig = plt.figure(figsize=(8, 4), dpi=100)\n", - "ax1 = fig.add_subplot(111, xlim=(250,0), ylim=(0,10), xlabel='Age (Ma)', ylabel=\"Velocity (cm/yr)\",\n", - " title='Global average plate velocity (cm/yr)')\n", - "\n", - "ax1.fill_between(time_range, vel_av-vel_std, vel_av+vel_std, color='0.8', label=\"Standard deviation (cm/yr)\")\n", - "ax1.plot(time_range, vel_av, c='k', label=\"Mean plate velocity (cm/yr)\")\n", + "ax1 = fig.add_subplot(\n", + " 111,\n", + " xlim=(250, 0),\n", + " ylim=(0, 10),\n", + " xlabel=\"Age (Ma)\",\n", + " ylabel=\"Velocity (cm/yr)\",\n", + " title=\"Global average plate velocity (cm/yr)\",\n", + ")\n", + "\n", + "ax1.fill_between(\n", + " time_range,\n", + " vel_av - vel_std,\n", + " vel_av + vel_std,\n", + " color=\"0.8\",\n", + " label=\"Standard deviation (cm/yr)\",\n", + ")\n", + "ax1.plot(time_range, vel_av, c=\"k\", label=\"Mean plate velocity (cm/yr)\")\n", "\n", "ax1.legend(loc=\"upper right\", frameon=False)\n", "fig.savefig(\n", " os.path.join(\n", - " \"NotebookFiles\",\n", + " output_dir,\n", " \"average_plate_velocity.pdf\",\n", " ),\n", - " bbox_inches='tight',\n", + " bbox_inches=\"tight\",\n", ")" ] }, @@ -268,7 +289,7 @@ "metadata": {}, "source": [ "### Visualising `PlateReconstruction` velocity data\n", - "As a first example, let's reconstruct all topological plates and boundaries to 50Ma and illustrate the velocity of each moving plate! One way to do this is by plotting a velocity vector field using the `plot_plate_motion_vectors` method on the `PlotTopologies` object (which internally uses `PlateReconstruction.get_point_velocities` to calculate velocities on a regular longitude-latitude grid of points).\n", + "As a first example, let's reconstruct all topological plates and boundaries to 50 Ma and illustrate the velocity of each moving plate! One way to do this is by plotting a velocity vector field using the `plot_plate_motion_vectors` method on the `PlotTopologies` object (which internally uses `PlateReconstruction.get_point_velocities` to calculate velocities on a regular longitude-latitude grid of points).\n", "\n", "Since `plot_plate_motion_vectors` uses Cartopy's `quiver` function, it accepts `quiver` keyword arguments like `regrid_shape`. This is useful if you'd like your vectors interpolated onto a regular grid in projection space." ] @@ -283,25 +304,37 @@ "time = 50\n", "\n", "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(16,12))\n", - "ax1 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax1.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "plt.title('Global plate motion velocity field at %i Ma' % (time))\n", + "fig = plt.figure(figsize=(16, 12))\n", + "ax1 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax1.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", + "plt.title(\"Global plate motion velocity field at %i Ma\" % (time))\n", "\n", "# Plot all topologies\n", - "gplot.time=time\n", - "gplot.plot_continents(ax1, facecolor='navajowhite')\n", - "gplot.plot_coastlines(ax1, color='orange')\n", - "gplot.plot_ridges(ax1, color='r')\n", - "gplot.plot_transforms(ax1, color='r')\n", - "gplot.plot_trenches(ax1, color='k')\n", - "gplot.plot_subduction_teeth(ax1, color='k')\n", + "gplot.time = time\n", + "gplot.plot_continents(ax1, facecolor=\"navajowhite\")\n", + "gplot.plot_coastlines(ax1, color=\"orange\")\n", + "gplot.plot_all_topological_sections(\n", + " ax1,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", "ax1.set_global()\n", "\n", - "# Plot a veloctiy vector field on both maps\n", + "# Plot a velocity vector field\n", "#\n", "# Use a 10 degree longitude-latitude spacing between points.\n", - "gplot.plot_plate_motion_vectors(ax1, spacingX=10, spacingY=10, regrid_shape=20, alpha=0.5, color='green', zorder=2)" + "gplot.plot_plate_motion_vectors(\n", + " ax1, spacingX=10, spacingY=10, regrid_shape=20, alpha=0.5, color=\"green\", zorder=2\n", + ")\n", + "plt.show()" ] }, { @@ -322,26 +355,45 @@ "outputs": [], "source": [ "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(16,12))\n", - "ax2 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "ax2.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - "plt.title('Global plate motion velocity streamplot at %i Ma' % (time))\n", + "fig = plt.figure(figsize=(16, 12))\n", + "ax2 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax2.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", + "plt.title(\"Global plate motion velocity streamplot at %i Ma\" % (time))\n", "\n", "# Plot all topologies\n", - "gplot.time = time # Ma\n", - "gplot.plot_continents(ax2, facecolor='0.95')\n", - "gplot.plot_coastlines(ax2, color='0.9')\n", - "gplot.plot_ridges(ax2, color='r')\n", - "gplot.plot_transforms(ax2, color='r')\n", - "gplot.plot_trenches(ax2, color='k')\n", - "gplot.plot_subduction_teeth(ax2, color='k')\n", + "gplot.time = time # Ma\n", + "gplot.plot_continents(ax2, facecolor=\"0.95\")\n", + "gplot.plot_coastlines(ax2, color=\"0.9\")\n", + "gplot.plot_all_topological_sections(\n", + " ax2,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", "ax2.set_global()\n", "\n", "vel_x, vel_y = model.get_point_velocities(x, y, time, return_east_north_arrays=True)\n", "vel_mag = np.hypot(vel_x, vel_y)\n", "\n", - "ax2.streamplot(x, y, vel_x, vel_y, color=vel_mag, transform=ccrs.PlateCarree(), \n", - " linewidth=0.02*vel_mag, cmap=plt.cm.turbo, density=2)" + "ax2.streamplot(\n", + " x,\n", + " y,\n", + " vel_x,\n", + " vel_y,\n", + " color=vel_mag,\n", + " transform=ccrs.PlateCarree(),\n", + " linewidth=0.02 * vel_mag,\n", + " cmap=plt.cm.turbo,\n", + " density=2,\n", + ")\n", + "plt.show()" ] }, { @@ -352,44 +404,70 @@ "outputs": [], "source": [ "# Set up a GeoAxis plot\n", - "fig = plt.figure(figsize=(12,4))\n", + "fig = plt.figure(figsize=(12, 4))\n", "norm = Normalize(0, 10)\n", "\n", "for i, time in enumerate([80, 60, 40, 20]):\n", - " ax2 = fig.add_subplot(1,4,i+1, projection=ccrs.Orthographic(70, 0), title='{:.0f} Ma'.format(time))\n", + " ax2 = fig.add_subplot(\n", + " 1, 4, i + 1, projection=ccrs.Orthographic(70, 0), title=\"{:.0f} Ma\".format(time)\n", + " )\n", " ax2.set_global()\n", - " ax2.gridlines(color='0.7',linestyle=':', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", + " ax2.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\":\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + " )\n", " # plt.title('Global plate motion velocity streamplot at %i Ma' % (time))\n", "\n", - "# gplot.plot_grid(ax2, rgb)\n", + " # gplot.plot_grid(ax2, rgb)\n", "\n", " # Plot topologies\n", - " gplot.time = time # Ma\n", - " gplot.plot_continents(ax2, facecolor='0.9')\n", - " gplot.plot_coastlines(ax2, color='0.7')\n", - " gplot.plot_ridges(ax2, color='r')\n", - " gplot.plot_transforms(ax2, color='b')\n", - " gplot.plot_misc_boundaries(ax2, color='r')\n", - " gplot.plot_trenches(ax2, color='k')\n", - " gplot.plot_subduction_teeth(ax2, color='k')\n", + " gplot.time = time # Ma\n", + " gplot.plot_continents(ax2, facecolor=\"0.9\")\n", + " gplot.plot_coastlines(ax2, color=\"0.7\")\n", + " gplot.plot_all_topological_sections(\n", + " ax2,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", "\n", " vel_x, vel_y = model.get_point_velocities(x, y, time, return_east_north_arrays=True)\n", " vel_mag = np.hypot(vel_x, vel_y)\n", "\n", - " sp = ax2.streamplot(x, y, vel_x, vel_y, color=vel_mag*0.1, transform=ccrs.PlateCarree(), \n", - " norm=norm, linewidth=0.01*vel_mag, cmap='plasma', density=1)\n", - " \n", + " sp = ax2.streamplot(\n", + " x,\n", + " y,\n", + " vel_x,\n", + " vel_y,\n", + " color=vel_mag * 0.1,\n", + " transform=ccrs.PlateCarree(),\n", + " norm=norm,\n", + " linewidth=0.01 * vel_mag,\n", + " cmap=\"plasma\",\n", + " density=1,\n", + " )\n", + "\n", "fig.subplots_adjust(wspace=0.05)\n", "\n", - "cax = plt.axes([0.36,0.1, 0.3, 0.04])\n", - "fig.colorbar(sp.lines, cax=cax, orientation='horizontal', label='Plate velocity (cm/yr)', extend='max')\n", + "cax = plt.axes([0.36, 0.1, 0.3, 0.04])\n", + "fig.colorbar(\n", + " sp.lines,\n", + " cax=cax,\n", + " orientation=\"horizontal\",\n", + " label=\"Plate velocity (cm/yr)\",\n", + " extend=\"max\",\n", + ")\n", "\n", "fig.savefig(\n", " os.path.join(\n", - " \"NotebookFiles\",\n", + " output_dir,\n", " \"India_collision.pdf\",\n", " ),\n", - " bbox_inches='tight',\n", + " bbox_inches=\"tight\",\n", ")" ] }, @@ -415,24 +493,42 @@ " vel_mag = np.hypot(vel_x, vel_y)\n", "\n", " # Set up a GeoAxis plot\n", - " fig = plt.figure(figsize=(16,12))\n", - " ax2 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - " ax2.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", - " plt.title('Global plate motion velocity streamplot at %i Ma' % (time))\n", + " fig = plt.figure(figsize=(16, 12))\n", + " ax2 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + " ax2.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + " )\n", + " plt.title(\"Global plate motion velocity streamplot at %i Ma\" % (time))\n", "\n", " # Reconstruct topological plates and boundaries with PlotTopologies\n", - " gplot.time = time # Ma\n", - " gplot.plot_continents(ax2, facecolor='0.95')\n", - " gplot.plot_coastlines(ax2, color='0.9')\n", - " gplot.plot_ridges(ax2, color='r')\n", - " gplot.plot_transforms(ax2, color='r')\n", - " gplot.plot_trenches(ax2, color='k')\n", - " gplot.plot_subduction_teeth(ax2, color='k')\n", + " gplot.time = time # Ma\n", + " gplot.plot_continents(ax2, facecolor=\"0.95\")\n", + " gplot.plot_coastlines(ax2, color=\"0.9\")\n", + " gplot.plot_all_topological_sections(\n", + " ax2,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", " ax2.set_global()\n", "\n", " # Create the streamplot, using speed as a colormap.\n", - " ax2.streamplot(x, y, vel_x, vel_y, color=vel_mag, transform=ccrs.PlateCarree(), \n", - " linewidth=0.02*vel_mag, cmap=plt.cm.turbo, density=2)\n", + " ax2.streamplot(\n", + " x,\n", + " y,\n", + " vel_x,\n", + " vel_y,\n", + " color=vel_mag,\n", + " transform=ccrs.PlateCarree(),\n", + " linewidth=0.02 * vel_mag,\n", + " cmap=plt.cm.turbo,\n", + " density=2,\n", + " )\n", "\n", " fig.savefig(output_filename, bbox_inches=\"tight\")\n", " plt.close(fig)" @@ -447,33 +543,34 @@ "source": [ "import tempfile\n", "from IPython.display import Image\n", + "\n", "try:\n", " from moviepy.editor import ImageSequenceClip # moviepy 1.x\n", "except ImportError:\n", " from moviepy import ImageSequenceClip # moviepy 2.x\n", "\n", "# Time variables\n", - "oldest_seed_time = 100 # Ma\n", - "time_step = 10 # Ma\n", - "\n", - "with tempfile.TemporaryDirectory() as tmpdir:\n", - " frame_list = []\n", - "\n", - " # Create a plot for each 10 Ma interval\n", - " for time in np.arange(oldest_seed_time, 0., -time_step):\n", - " print('Generating %d Ma frame...' % time)\n", - " frame_filename = os.path.join(tmpdir, \"frame_%d_Ma.png\" % time)\n", - " generate_frame(frame_filename, time)\n", - " frame_list.append(frame_filename)\n", - "\n", - " video_filename = os.path.join(tmpdir, \"plate_velocity_stream_plot.gif\")\n", - "\n", - " clip = ImageSequenceClip(frame_list, fps=5)\n", - " clip.write_gif(video_filename)\n", - "\n", - " print('The movie will show up in a few seconds...')\n", - " with open(video_filename, \"rb\") as f:\n", - " display(Image(data=f.read(), format='gif', width = 3000, height = 1000))" + "oldest_seed_time = 100 # Ma\n", + "time_step = 10 # Ma\n", + "\n", + "frame_list = []\n", + "# Create a plot for each 10 Ma interval\n", + "for time in np.arange(oldest_seed_time, 0.0, -time_step):\n", + " print(f\"Generating {time} Ma frame...\")\n", + " frame_filename = os.path.join(\n", + " output_dir, f\"plate_velocity_stream_plot_frame_{time}_Ma.png\"\n", + " )\n", + " generate_frame(frame_filename, time)\n", + " frame_list.append(frame_filename)\n", + "\n", + "video_filename = os.path.join(output_dir, \"plate_velocity_stream_plot.gif\")\n", + "\n", + "clip = ImageSequenceClip(frame_list, fps=5)\n", + "clip.write_gif(video_filename)\n", + "\n", + "print(\"The movie will show up in a few seconds...\")\n", + "with open(video_filename, \"rb\") as f:\n", + " display(Image(data=f.read(), format=\"png\", width=3000, height=1000))" ] }, { @@ -499,7 +596,7 @@ "\n", "Another way to calculate velocities at point locations is to specify points at an *initial* time in a `Points` object. Then we can reconstruct them to any reconstruction time and calculate velocities at the reconstructed locations. Hence these points are not *static*. So, instead of using *topological* features to calculate velocities (with `PlateReconstruction.get_point_velocities`), a `Points` object optionally assigns a plate ID to each point (using a model's *static polygons* dataset) and uses that (along with the model's rotation model) to both reconstruct each point and calculate each point's velocity at any reconstruction time (using `Points.plate_velocity`).\n", "\n", - "A `Points` object requires a `PlateReconstruction` object for its rotation model and static polygons. We'll continue to use the `Muller2019` model that we loaded into `model`. A `Points` object also requires initial point locations and an initial time. By default, the initial time is present day (0 Ma), in which case the initial locations represent the present-day locations of the points. However, if the initial time is *not* present day then the initial locations represent locations at the initial time.\n", + "A `Points` object requires a `PlateReconstruction` object for its rotation model and static polygons. A `Points` object also requires initial point locations and an initial time. By default, the initial time is present day (0 Ma), in which case the initial locations represent the present-day locations of the points. However, if the initial time is *not* present day then the initial locations represent locations at the initial time.\n", "\n", "Let's calculate plate velocity data for a global grid of longitude-latitude points (__x__ and __y__). We'll use the `plate_velocity` method in the `Points` object. It returns the east and north components of velocities for each point, at the specified reconstruction time." ] @@ -512,11 +609,11 @@ "outputs": [], "source": [ "# The distribution of points in the velocity domain: set global extent with 5 degree intervals\n", - "Xnodes = np.arange(-180,180,5)\n", - "Ynodes = np.arange(-90,90,5)\n", + "Xnodes = np.arange(-180, 180, 5)\n", + "Ynodes = np.arange(-90, 90, 5)\n", "\n", "# Create a lat-lon mesh and convert to 1d lat-lon arrays\n", - "x, y = np.meshgrid(Xnodes,Ynodes)\n", + "x, y = np.meshgrid(Xnodes, Ynodes)\n", "x = x.flatten()\n", "y = y.flatten()\n", "\n", @@ -538,8 +635,8 @@ "vel_x, vel_y = gpts.plate_velocity(time=0)\n", "vel_mag = np.hypot(vel_x, vel_y)\n", "\n", - "print('Number of points in our velocity domain = ', len(vel_x))\n", - "print('Average velocity at 0 Ma = {:.2f} cm/yr'.format(vel_mag.mean()))" + "print(f\"Number of points in our velocity domain = {len(vel_x)}\")\n", + "print(f\"Average velocity at 0 Ma = {vel_mag.mean():.2f} cm/yr\")" ] }, { @@ -563,41 +660,70 @@ "metadata": {}, "outputs": [], "source": [ - "def plot_point_velocities(vel_time, vel_x, vel_y, rlons, rlats, draw_arrows=False, plot_time=None):\n", + "def plot_point_velocities(\n", + " vel_time, vel_x, vel_y, rlons, rlats, draw_arrows=False, plot_time=None\n", + "):\n", " # It's possible to calculate velocities at one time but plot them at another.\n", " if plot_time is None:\n", " plot_time = vel_time\n", - " \n", + "\n", " # Velocity magnitudes.\n", " vel_mag = np.hypot(vel_x, vel_y)\n", "\n", " # Set up a GeoAxis plot\n", - " fig = plt.figure(figsize=(18,14))\n", - " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - " \n", + " fig = plt.figure(figsize=(18, 14))\n", + " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "\n", " # Plot continents, coastlines and topologies.\n", - " gplot.time = plot_time # Ma\n", - " gplot.plot_continents(ax, facecolor='0.95')\n", - " gplot.plot_coastlines(ax, color='0.9')\n", - " gplot.plot_ridges(ax, color='r', zorder=3)\n", - " gplot.plot_transforms(ax, color='r', zorder=3)\n", - " gplot.plot_trenches(ax, color='k', zorder=3)\n", - " gplot.plot_subduction_teeth(ax, color='k', zorder=3)\n", - "\n", - " title = 'Global point velocities at %i Ma' % (vel_time)\n", + " gplot.time = plot_time # Ma\n", + " gplot.plot_continents(ax, facecolor=\"0.95\")\n", + " gplot.plot_coastlines(ax, color=\"0.9\")\n", + " gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", + "\n", + " title = f\"Global point velocities at {vel_time} Ma\"\n", " if plot_time != vel_time:\n", - " title += ' (plotted at %i Ma)' % (plot_time)\n", + " title += f\" (plotted at {plot_time} Ma)\"\n", " plt.title(title)\n", - " \n", + "\n", " # Plot the velocity domain points with their velocity magnitudes as a colour scale.\n", - " im = ax.scatter(rlons, rlats, transform=ccrs.PlateCarree(), c=vel_mag, s=10, cmap=plt.cm.afmhot_r, vmin=0, vmax=10, zorder=2)\n", + " im = ax.scatter(\n", + " rlons,\n", + " rlats,\n", + " transform=ccrs.PlateCarree(),\n", + " c=vel_mag,\n", + " s=10,\n", + " cmap=plt.cm.afmhot_r,\n", + " vmin=0,\n", + " vmax=10,\n", + " zorder=2,\n", + " )\n", "\n", " if draw_arrows:\n", " # Plot the velocity arrows (with their velocity magnitudes as a colour scale).\n", - " im = ax.quiver(rlons, rlats, vel_x, vel_y, vel_mag, transform=ccrs.PlateCarree(), cmap=plt.cm.afmhot_r, clim=(0,10), regrid_shape=None, zorder=2)\n", - " \n", + " im = ax.quiver(\n", + " rlons,\n", + " rlats,\n", + " vel_x,\n", + " vel_y,\n", + " vel_mag,\n", + " transform=ccrs.PlateCarree(),\n", + " cmap=plt.cm.afmhot_r,\n", + " clim=(0, 10),\n", + " regrid_shape=None,\n", + " zorder=2,\n", + " )\n", + "\n", " # Add colorbar and set global extent.\n", - " fig.colorbar(im, ax=ax, shrink=0.5).set_label('Velocity magntitude (cm/yr)', fontsize=12)\n", + " fig.colorbar(im, ax=ax, shrink=0.5).set_label(\n", + " \"Velocity magnitude (cm/yr)\", fontsize=12\n", + " )\n", " ax.set_global()\n", "\n", " plt.show()" @@ -630,13 +756,11 @@ "# Present day (0 Ma).\n", "time = 0\n", "\n", - "# Calculate the velocities at present day (0 Ma).\n", - "vel_x, vel_y = gpts.plate_velocity(time)\n", + "# Calculate the velocities at present day (0 Ma), returning locations aligned with velocity arrays.\n", + "vel_x, vel_y, rlons, rlats = gpts.plate_velocity(time, return_reconstructed_points=True)\n", "\n", "# Plot the velocities at present day (0 Ma).\n", - "#\n", - "# At present day (0 Ma) we can just use the present day locations of the points ('gpts.lons' and 'gpts.lats').\n", - "plot_point_velocities(time, vel_x, vel_y, gpts.lons, gpts.lats, draw_arrows=True)" + "plot_point_velocities(time, vel_x, vel_y, rlons, rlats, draw_arrows=True)" ] }, { @@ -680,7 +804,7 @@ "\n", "By default, _all_ points exist for _all_ time. However this is not true for oceanic points. And this is why the above plot is drawing points that it should not.\n", "\n", - "To fix this we can specify `age=None` to `gpts.plate_velocity()` (insteading of relying on the default `age=numpy.inf`). This will use the static polygons of the model to assign a time of appearance to each point. This way, if we calculate velocities at a time prior to the age of appearance of some oceanic points, then those points will now be missing from the output (as desired)." + "To fix this we can specify `age=None` to `gpts.plate_velocity()` (instead of relying on the default `age=numpy.inf`). This will use the static polygons of the model to assign a time of appearance to each point. This way, if we calculate velocities at a time prior to the age of appearance of some oceanic points, then those points will now be missing from the output (as desired)." ] }, { @@ -738,7 +862,9 @@ "# Plot the velocities calculated at 'time'.\n", "# But at the locations of the points at the initial time 'gpts.time' (instead of the reconstructed point locations at 'time').\n", "# In our case 'gpts.time' is zero (ie, present day).\n", - "plot_point_velocities(time, vel_x, vel_y, initial_lons, initial_lats, plot_time=gpts.time)" + "plot_point_velocities(\n", + " time, vel_x, vel_y, initial_lons, initial_lats, plot_time=gpts.time\n", + ")" ] }, { @@ -765,13 +891,15 @@ "\n", "# Create points with initial positions at 100 Ma (ie, not present day).\n", "#\n", - "# Note that we're now specifing the 'time' argument.\n", + "# Note that we're now specifying the 'time' argument.\n", "# Previously we did not specify it, and so it would default to 0 Ma.\n", "#\n", "# With 'age=None': the age of each point (its time of appearance) is determined from the static polygons of 'model'.\n", "#\n", "# With 'remove_unreconstructable_points=True': we're removing any points that were not assigned plate IDs and ages.\n", - "gpts = gplately.Points(model, x, y, time, age=None, remove_unreconstructable_points=True)" + "gpts = gplately.Points(\n", + " model, x, y, time, age=None, remove_unreconstructable_points=True\n", + ")" ] }, { @@ -791,8 +919,12 @@ "metadata": {}, "outputs": [], "source": [ - "print('Number of points passed into \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, len(x)))\n", - "print('Number of points actually in \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, gpts.size))" + "print(\n", + " f'Number of points passed into \"gpts\" at initial time ({gpts.time} Ma) = {len(x)}'\n", + ")\n", + "print(\n", + " f'Number of points actually in \"gpts\" at initial time ({gpts.time} Ma) = {gpts.size}'\n", + ")" ] }, { @@ -833,8 +965,12 @@ "metadata": {}, "outputs": [], "source": [ - "print('Number of points passed into \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, len(x)))\n", - "print('Number of points actually in \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, gpts.size))" + "print(\n", + " f'Number of points passed into \"gpts\" at initial time ({gpts.time} Ma) = {len(x)}'\n", + ")\n", + "print(\n", + " f'Number of points actually in \"gpts\" at initial time ({gpts.time} Ma) = {gpts.size}'\n", + ")" ] }, { @@ -878,8 +1014,8 @@ "metadata": {}, "outputs": [], "source": [ - "print('Number of points in \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, gpts.size))\n", - "print('Number of reconstructed points at initial time ({} Ma) = {}'.format(time, len(rlons)))" + "print(f'Number of points in \"gpts\" at initial time ({gpts.time} Ma) = {gpts.size}')\n", + "print(f\"Number of reconstructed points at initial time ({time} Ma) = {len(rlons)}\")" ] }, { @@ -926,8 +1062,8 @@ "metadata": {}, "outputs": [], "source": [ - "print('Number of points in \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, gpts.size))\n", - "print('Number of points in \"gpts\" reconstructed to present day (0 Ma) = {}'.format(len(rlons)))" + "print(f'Number of points in \"gpts\" at initial time ({gpts.time} Ma) = {gpts.size}')\n", + "print(f'Number of points in \"gpts\" reconstructed to present day (0 Ma) = {len(rlons)}')" ] }, { @@ -974,8 +1110,8 @@ "metadata": {}, "outputs": [], "source": [ - "print('Number of points in \"gpts\" at initial time ({} Ma) = {}'.format(gpts.time, gpts.size))\n", - "print('Number of points in \"gpts\" reconstructed to {} Ma = {}'.format(time, len(rlons)))" + "print(f'Number of points in \"gpts\" at initial time ({gpts.time} Ma) = {gpts.size}')\n", + "print(f'Number of points in \"gpts\" reconstructed to {time} Ma = {len(rlons)}')" ] }, { @@ -985,7 +1121,7 @@ "source": [ "#### Create a velocity animation\n", "\n", - "If you have [moviepy](https://moviepy.readthedocs.io/en/latest/install.html) installed, you can animate the motion of topological plates through geological time with a scatterplot of domain point velocities (in cm/yr) overlying the plates. Let's reconstruct plate movements from 0-100Ma in intervals of 10 Ma. With each iteration of the time loop we re-calculate velocity data." + "If you have [moviepy](https://moviepy.readthedocs.io/en/latest/install.html) installed, you can animate the motion of topological plates through geological time with a scatterplot of domain point velocities (in cm/yr) overlying the plates. Let's reconstruct plate movements from 0-100 Ma in intervals of 10 Ma. With each iteration of the time loop we re-calculate velocity data." ] }, { @@ -997,31 +1133,48 @@ "source": [ "def generate_frame(output_filename, time):\n", " # Get all point velocities and their magnitudes\n", - " vel_x, vel_y, rlons, rlats = gpts.plate_velocity(time, return_reconstructed_points=True)\n", + " vel_x, vel_y, rlons, rlats = gpts.plate_velocity(\n", + " time, return_reconstructed_points=True\n", + " )\n", " vel_mag = np.hypot(vel_x, vel_y)\n", - " \n", + "\n", " # Set up a GeoAxis plot\n", - " fig = plt.figure(figsize=(18,14))\n", - " ax3 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - " plt.title('Global point velocity scatterplot at %i Ma' % (time))\n", + " fig = plt.figure(figsize=(18, 14))\n", + " ax3 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + " plt.title(f\"Global point velocity scatterplot at {time} Ma\")\n", "\n", " # Plot all topologies reconstructed to the current Ma\n", - " gplot.time = time # Ma\n", - " gplot.plot_continents(ax3, facecolor='0.95')\n", - " gplot.plot_coastlines(ax3, color='0.9')\n", - " gplot.plot_ridges(ax3, color='r', zorder=3)\n", - " gplot.plot_transforms(ax3, color='r', zorder=3)\n", - " gplot.plot_trenches(ax3, color='k', zorder=3)\n", - " gplot.plot_subduction_teeth(ax3, color='k', zorder=3)\n", + " gplot.time = time # Ma\n", + " gplot.plot_continents(ax3, facecolor=\"0.95\")\n", + " gplot.plot_coastlines(ax3, color=\"0.9\")\n", + " gplot.plot_all_topological_sections(\n", + " ax3,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", "\n", " # Plot the velocity domain points with their velocity magnitudes as a colour scale.\n", - " im = ax3.scatter(rlons, rlats, transform=ccrs.PlateCarree(),c=vel_mag,s=30,cmap=plt.cm.afmhot_r,vmin=0,vmax=10,\n", - " zorder=2)\n", + " im = ax3.scatter(\n", + " rlons,\n", + " rlats,\n", + " transform=ccrs.PlateCarree(),\n", + " c=vel_mag,\n", + " s=30,\n", + " cmap=plt.cm.afmhot_r,\n", + " vmin=0,\n", + " vmax=10,\n", + " zorder=2,\n", + " )\n", "\n", " # Add colorbar, set global extent and show plot\n", - " fig.colorbar(im, ax=ax3,shrink=0.5).set_label('Velocity magntitude (cm/yr)',fontsize=12)\n", + " cbar = fig.colorbar(im, ax=ax3, shrink=0.5)\n", + " cbar.set_label(\"Velocity magnitude (cm/yr)\", fontsize=12)\n", + "\n", " ax3.set_global()\n", - " fig.savefig(output_filename, bbox_inches='tight')\n", + " fig.savefig(output_filename, dpi=300, bbox_inches=\"tight\")\n", " plt.close(fig)" ] }, @@ -1032,49 +1185,71 @@ "metadata": {}, "outputs": [], "source": [ - "import tempfile\n", "from IPython.display import Image\n", + "\n", "try:\n", " from moviepy.editor import ImageSequenceClip # moviepy 1.x\n", "except ImportError:\n", " from moviepy import ImageSequenceClip # moviepy 2.x\n", "\n", "# Time variables\n", - "oldest_seed_time = 100 # Ma\n", - "time_step = 10 # Ma\n", - "\n", - "with tempfile.TemporaryDirectory() as tmpdir:\n", - " frame_list = []\n", - "\n", - " # Create a plot for each 10 Ma interval\n", - " for time in np.arange(oldest_seed_time, 0., -time_step):\n", - " print('Generating %d Ma frame...' % time)\n", - " frame_filename = os.path.join(tmpdir, \"frame_%d_Ma.png\" % time)\n", - " generate_frame(frame_filename, time)\n", - " frame_list.append(frame_filename)\n", - "\n", - " video_filename = os.path.join(tmpdir, \"plate_velocity_scatter_plot.gif\")\n", - "\n", - " clip = ImageSequenceClip(frame_list, fps=5)\n", - " clip.write_gif(video_filename)\n", - "\n", - " print('The movie will show up in a few seconds...')\n", - " with open(video_filename, 'rb') as f:\n", - " display(Image(data=f.read(), format='gif', width = 1000, height = 500))" + "oldest_seed_time = 100 # Ma\n", + "time_step = 10 # Ma\n", + "\n", + "frame_list = []\n", + "# Create a plot for each 10 Ma interval\n", + "for time in np.arange(oldest_seed_time, 0.0, -time_step):\n", + " print(f\"Generating {time} Ma frame...\")\n", + " frame_filename = os.path.join(\n", + " output_dir, f\"plate_velocity_scatter_plot_frame_{time}_Ma.png\"\n", + " )\n", + " generate_frame(frame_filename, time)\n", + " frame_list.append(frame_filename)\n", + "\n", + "video_filename = os.path.join(output_dir, \"plate_velocity_scatter_plot.gif\")\n", + "\n", + "clip = ImageSequenceClip(frame_list, fps=5)\n", + "clip.write_gif(video_filename)\n", + "\n", + "print(\"The movie will show up in a few seconds...\")\n", + "with open(video_filename, \"rb\") as f:\n", + " display(Image(data=f.read(), format=\"png\", width=1000, height=500))" + ] + }, + { + "cell_type": "markdown", + "id": "0857eee5-ac30-42dc-87e5-555f8c6ff042", + "metadata": {}, + "source": [ + "You may have noticed the banding problem of the color bar in the .gif above. GIF's 256-color limit means smooth gradients get reduced to visible steps. To avoid this banding problem, we can create a .mp4 file instead." ] }, { "cell_type": "code", "execution_count": null, - "id": "16e9f185-c8ad-434c-8ff7-9012bed0034e", + "id": "df344dcb-74ff-4c91-9903-5b28e81ee2ec", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "mp4_filename = os.path.join(output_dir, \"plate_velocity_scatter_plot.mp4\")\n", + "clip = ImageSequenceClip(frame_list, fps=5)\n", + "clip.write_videofile(\n", + " mp4_filename,\n", + " fps=5,\n", + " codec=\"libx264\",\n", + " audio=False,\n", + " ffmpeg_params=[\"-pix_fmt\", \"yuv420p\"],\n", + ")\n", + "\n", + "from IPython.display import Video\n", + "\n", + "display(Video(mp4_filename, embed=True, mimetype=\"video/mp4\", width=600))" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1088,12 +1263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" - }, - "vscode": { - "interpreter": { - "hash": "a10fed8c503fa0e7abcec38684bcaa5ab84af52f4a155e8c08912d91252721a5" - } + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/05-WorkingWithFeatureGeometries.ipynb b/Notebooks/05-WorkingWithFeatureGeometries.ipynb index 84c45f55..ae16d41d 100644 --- a/Notebooks/05-WorkingWithFeatureGeometries.ipynb +++ b/Notebooks/05-WorkingWithFeatureGeometries.ipynb @@ -5,7 +5,7 @@ "id": "61b5c30a", "metadata": {}, "source": [ - "# 5 - Working With Feature Geometries" + "# 5 - Working with feature geometries" ] }, { @@ -21,9 +21,7 @@ " - Polygons\n", " - Points\n", "\n", - "_(These GPML files are downloaded with `PlateModelManager`.)_\n", - "\n", - "Let's set up all our packages:" + "These GPML files are downloaded with `PlateModelManager`." ] }, { @@ -33,14 +31,20 @@ "metadata": {}, "outputs": [], "source": [ + "from pathlib import Path\n", "import gplately\n", - "\n", "import numpy as np\n", - "import pygplates\n", - "import glob, os\n", + "import os, warnings\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", - "from plate_model_manager import PlateModelManager" + "from plate_model_manager import PlateModelManager\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", + "data_dir = Path(\"5-Working-With-Feature-Geometries-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)" ] }, { @@ -50,13 +54,13 @@ "source": [ "GPlately's `PlotTopologies` object uses the `PlateReconstruction` object to reconstruct geological features. It then turns the reconstructed features into Shapely MultiPolygon, MultiPoint and/or MultiLine geometries and plots them onto GeoAxis maps. To call `PlotTopologies`, we need to pass:\n", "\n", - "- the `PlateReconstruction` plate motion model we just created\n", + "- the `PlateReconstruction` plate motion model we just created,\n", "- a specific reconstruction time (Ma),\n", "- a coastline filename or `` object,\n", "- a continent filename or `` object,\n", - "- and a continent-ocean boundary (COBs) filename or `` object,\n", + "- and a continent-ocean boundary (COBs) filename or `` object.\n", "\n", - "We'll first construct the `PlateReconstruction` object, which needs a `rotation model`, a `topology_feature` collection and `static_polygons`. Let's use GPlately's `PlateModelManager` to download these files from Müller et al. (2019) (https://www.earthbyte.org/muller-et-al-2019-deforming-plate-reconstruction-and-seafloor-age-grids-tectonics/)." + "We'll first construct the `PlateReconstruction` object, which needs a `rotation model`, a `topology_feature` collection and `static_polygons`. Let's use GPlately's `PlateModelManager` to download these files." ] }, { @@ -66,40 +70,24 @@ "metadata": {}, "outputs": [], "source": [ - "# Download Muller et al. 2019 model data\n", - "pm_manager = PlateModelManager()\n", - "muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "static_polygons = muller2019_model.get_static_polygons()\n", - "\n", - "# Create the plate motion model!\n", - "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)" - ] - }, - { - "cell_type": "markdown", - "id": "b2fb6209", - "metadata": {}, - "source": [ - "We can also download the `coastlines`, `continents` and `COBs` needed for our `PlotTopologies` object using `PlateModelManager`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5efaeece", - "metadata": {}, - "outputs": [], - "source": [ - "# Obtain Muller et al. 2019 geometries \n", - "coastlines = muller2019_model.get_layer('Coastlines')\n", - "continents = muller2019_model.get_layer('ContinentalPolygons')\n", - "COBs = muller2019_model.get_layer('COBs')\n", - "\n", - "# Call the PlotTopologies object \n", - "gplot = gplately.PlotTopologies(model, coastlines=coastlines, continents=continents, COBs=COBs)" + "model_name = \"Muller2019\" # We must use the Muller et al. 2019 plate model for this notebook, as it contains some feature data which is not available in other models.\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=\"plate-model-repo\")\n", + "assert pmm_model is not None, f\"Failed to load the {model_name} plate model.\"\n", + "rotation_model = pmm_model.get_rotation_model()\n", + "topology_features = pmm_model.get_topologies()\n", + "static_polygons = pmm_model.get_static_polygons()\n", + "\n", + "# Create the plate motion model\n", + "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", + "\n", + "coastlines = pmm_model.get_layer(\"Coastlines\")\n", + "continents = pmm_model.get_layer(\"ContinentalPolygons\")\n", + "COBs = pmm_model.get_layer(\"COBs\")\n", + "\n", + "# Create a PlotTopologies object to visualize the plate model\n", + "gplot = gplately.PlotTopologies(\n", + " model, coastlines=coastlines, continents=continents, COBs=COBs\n", + ")" ] }, { @@ -119,11 +107,11 @@ "source": [ "### Plotting Shapely Polylines\n", "\n", - "Let's visualise a set of polylines from Matthews et al. 2011** that define the tectonic fabric of global seafloors at present day digitised from vertical gravity gradient (VGG) maps. These polylines represent global fracture zones (FZs), v-shaped structures (VANOMs), discordant zones (DZs), fracture zones (hand-traced with less certainty) (FZLCs), unclassified V-anomalies (UNCVs) and extinct ridges. \n", + "Let's visualise a set of polylines from Matthews et al. (2011) that define the tectonic fabric of global seafloors at present day, digitised from vertical gravity gradient (VGG) maps. These polylines represent global fracture zones (FZs), v-shaped structures (VANOMs), discordant zones (DZs), fracture zones (hand-traced with less certainty) (FZLCs), unclassified V-anomalies (UNCVs), and extinct ridges.\n", "\n", "Let's load these features in with `PlateModelManager`.\n", "\n", - "**_(Matthews, K. J., Müller, R. D., Wessel, P., Whittaker, J. M. 2011. The tectonic fabric of the ocean basins, The Journal of Geophysical Research. Doi: 10.1029/2011JB008413.)_ " + "*(Matthews, K. J., Müller, R. D., Wessel, P., Whittaker, J. M., 2011. The tectonic fabric of the ocean basins, The Journal of Geophysical Research. DOI: 10.1029/2011JB008413.)*" ] }, { @@ -134,8 +122,8 @@ "outputs": [], "source": [ "# Set present day\n", - "time = 0 # Ma\n", - "seafloor_fabric = muller2019_model.get_layer(\"SeafloorFabric\")" + "time = 0 # Ma\n", + "seafloor_fabric = pmm_model.get_layer(\"SeafloorFabric\")" ] }, { @@ -159,26 +147,33 @@ "outputs": [], "source": [ "# Set up GeoAxis and plot shapefile topologies\n", - "ax2 = plt.figure(figsize=(16,12)).add_subplot(111, projection=ccrs.Robinson(central_longitude=10))\n", + "ax = plt.figure(figsize=(10, 8)).add_subplot(\n", + " 111, projection=ccrs.Robinson(central_longitude=10)\n", + ")\n", "gplot.time = time\n", - "gplot.plot_continents(ax2, facecolor='0.8')\n", - "gplot.plot_continent_ocean_boundaries(ax2, color='0.98')\n", - "gplot.plot_coastlines(ax2, color='0.9')\n", - "gplot.plot_ridges(ax2, color='r')\n", - "gplot.plot_transforms(ax2, color='r')\n", - "gplot.plot_trenches(ax2, color='navy')\n", - "gplot.plot_subduction_teeth(ax2, color='navy')\n", - "plt.title('Global seafloor fabric at %i Ma' % (time))\n", + "gplot.plot_continents(ax, facecolor=\"0.8\")\n", + "gplot.plot_continent_ocean_boundaries(ax, color=\"0.98\")\n", + "gplot.plot_coastlines(ax, color=\"0.9\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", + "plt.title(f\"Global seafloor fabric at {time} Ma\")\n", "\n", "# Seafloor fabric topology identification variables\n", - "colours = ['powderblue', 'k', 'm', 'g', 'b', 'y']\n", + "colours = [\"powderblue\", \"k\", \"m\", \"g\", \"b\", \"y\"]\n", "\n", - "# Loop through all seafloor fabric filenames, reconstruct each topology and plot onto ax2 using GPlately\n", + "# Loop through all seafloor fabric filenames, reconstruct each topology and plot onto ax using GPlately\n", "for i, fabric in enumerate(seafloor_fabric):\n", " reconstructed_seafloor_topology = model.reconstruct(fabric, time)\n", " polylines = gplately.plot.shapelify_feature_lines(reconstructed_seafloor_topology)\n", - " ax2.add_geometries(polylines, crs=ccrs.PlateCarree(), facecolor=colours[i], edgecolor=colours[i])\n", - " " + " ax.add_geometries(\n", + " polylines, crs=ccrs.PlateCarree(), facecolor=colours[i], edgecolor=colours[i]\n", + " )" ] }, { @@ -197,30 +192,50 @@ "outputs": [], "source": [ "# Seafloor fabric topology identification variables\n", - "colours = ['powderblue', 'k', 'm', 'g', 'b', 'y']\n", - "feat = ['Fracture Zones', 'V-Shaped Structures', 'Discordant Zones', 'Fracture Zones (less certainty)', \n", - " 'Unclassified V-Anomalies', 'Extinct Ridges']\n", - " \n", + "colours = [\"powderblue\", \"k\", \"m\", \"g\", \"b\", \"y\"]\n", + "feat = [\n", + " \"Fracture Zones\",\n", + " \"V-Shaped Structures\",\n", + " \"Discordant Zones\",\n", + " \"Fracture Zones (less certainty)\",\n", + " \"Unclassified V-Anomalies\",\n", + " \"Extinct Ridges\",\n", + "]\n", + "\n", + "\n", "def generate_frame(output_filename, time):\n", " # Set up GeoAxis and plot shapefile topologies\n", - " ax5 = plt.figure(figsize=(16,12), dpi=200).add_subplot(111, projection=ccrs.Robinson(central_longitude=10))\n", + " ax = plt.figure(figsize=(10, 8), dpi=200).add_subplot(\n", + " 111, projection=ccrs.Robinson(central_longitude=10)\n", + " )\n", " gplot.time = time\n", - " gplot.plot_continents(ax5, facecolor='0.8')\n", - " gplot.plot_continent_ocean_boundaries(ax5, color='0.98')\n", - " gplot.plot_coastlines(ax5, color='0.9')\n", - " gplot.plot_ridges(ax5, color='r')\n", - " gplot.plot_transforms(ax5, color='r')\n", - " gplot.plot_trenches(ax5, color='navy')\n", - " gplot.plot_subduction_teeth(ax5, color='navy')\n", - " plt.title('Global seafloor fabric at %i Ma' % (time))\n", - " \n", - " # Loop through all seafloor fabric filenames, reconstruct each topology and plot onto ax2 using GPlately\n", + " gplot.plot_continents(ax, facecolor=\"0.8\")\n", + " gplot.plot_continent_ocean_boundaries(ax, color=\"0.98\")\n", + " gplot.plot_coastlines(ax, color=\"0.9\")\n", + " gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", + " plt.title(f\"Global seafloor fabric at {time} Ma\")\n", + "\n", + " # Loop through all seafloor fabric filenames, reconstruct each topology and plot onto ax using GPlately\n", " for i, fabric in enumerate(seafloor_fabric):\n", " reconstructed_seafloor_topology = model.reconstruct(fabric, time)\n", - " polylines = gplately.plot.shapelify_feature_lines(reconstructed_seafloor_topology)\n", - " ax5.add_geometries(polylines, crs=ccrs.PlateCarree(), facecolor=colours[i], edgecolor=colours[i])\n", - " ax5.set_global()\n", - " \n", + " polylines = gplately.plot.shapelify_feature_lines(\n", + " reconstructed_seafloor_topology\n", + " )\n", + " ax.add_geometries(\n", + " polylines,\n", + " crs=ccrs.PlateCarree(),\n", + " facecolor=colours[i],\n", + " edgecolor=colours[i],\n", + " )\n", + " ax.set_global()\n", + "\n", " plt.savefig(output_filename, bbox_inches=\"tight\")\n", " plt.close()" ] @@ -232,35 +247,33 @@ "metadata": {}, "outputs": [], "source": [ - "import tempfile\n", "from IPython.display import Image\n", + "\n", "try:\n", " from moviepy.editor import ImageSequenceClip # moviepy 1.x\n", "except ImportError:\n", " from moviepy import ImageSequenceClip # moviepy 2.x\n", "\n", "# Time variables\n", - "oldest_seed_time = 150 # Ma\n", - "time_step = 10 # Ma\n", - "\n", - "with tempfile.TemporaryDirectory() as tmpdir:\n", - " frame_list = []\n", + "oldest_seed_time = 150 # Ma\n", + "time_step = 10 # Ma\n", "\n", - " # Create a plot for each 10 Ma interval\n", - " for time in np.arange(oldest_seed_time, 0., -time_step):\n", - " print('Generating %d Ma frame...' % time)\n", - " frame_filename = os.path.join(tmpdir, \"frame_%d_Ma.png\" % time)\n", - " generate_frame(frame_filename, time)\n", - " frame_list.append(frame_filename)\n", + "frame_list = []\n", + "# Create a plot for each 10 Ma interval\n", + "for time in np.arange(oldest_seed_time, 0.0, -time_step):\n", + " print(f\"Generating {time} Ma frame...\")\n", + " frame_filename = os.path.join(output_dir, f\"seafloor_fabric_frame_{time}_Ma.png\")\n", + " generate_frame(frame_filename, time)\n", + " frame_list.append(frame_filename)\n", "\n", - " video_filename = os.path.join(tmpdir, \"seafloor_fabric_movie.gif\")\n", + "video_filename = os.path.join(output_dir, \"seafloor_fabric_movie.gif\")\n", "\n", - " clip = ImageSequenceClip(frame_list, fps=5)\n", - " clip.write_gif(video_filename)\n", + "clip = ImageSequenceClip(frame_list, fps=5)\n", + "clip.write_gif(video_filename)\n", "\n", - " print('The movie will show up in a few seconds...')\n", - " with open(video_filename, 'rb') as f:\n", - " display(Image(data=f.read(), format='gif', width = 1000, height = 500))" + "print(\"The movie will show up in a few seconds...\")\n", + "with open(video_filename, \"rb\") as f:\n", + " display(Image(data=f.read(), format=\"png\", width=1000, height=500))" ] }, { @@ -272,9 +285,9 @@ "\n", "Let's visualise two polygon-feature data sets:\n", "\n", - "1) Global __volcanic provinces__ at present day from \"The interplay between the eruption and weathering of Large Igneous Provinces and the deep-time carbon cycle\" by Johansson et al. (2018)\n", + "1) Global __volcanic provinces__ at present day from \"The interplay between the eruption and weathering of Large Igneous Provinces and the deep-time carbon cycle\" by Johansson et al. (2018).\n", "\n", - "2) Global __large igneous provinces (LIPs)__ from the \"Long-term interaction between mid-ocean ridges and mantle plumes\" by Whittaker et al. (2015).\n", + "2) Global __large igneous provinces (LIPs)__ from \"Long-term interaction between mid-ocean ridges and mantle plumes\" by Whittaker et al. (2015).\n", "\n", "We'll obtain these files using GPlately's `PlateModelManager` object:" ] @@ -287,8 +300,10 @@ "outputs": [], "source": [ "# Locate the Johansson et al. (2018) and Whittaker et al. (2015) gpmlz files containing volcanic provinces & LIPs\n", - "lip_volcanic_provinces = [muller2019_model.get_layer(\"Johansson2018LIPs\"),\n", - " muller2019_model.get_layer(\"Whittaker2015LIPs\")]" + "lip_volcanic_provinces = [\n", + " pmm_model.get_layer(\"Johansson2018LIPs\"),\n", + " pmm_model.get_layer(\"Whittaker2015LIPs\"),\n", + "]" ] }, { @@ -298,7 +313,7 @@ "source": [ "We now have a list containing the LIP and Volcanic Province topology files. Let's use a `for` loop to loop through each `` object in the list and:\n", "\n", - "- Reconstruct topologies to a specific geological time,\n", + "- Reconstruct topologies to a specific geological time\n", "- Turn topologies into shapely polygons\n", "- Add shapely geometries onto a GeoAxis map with formatting keyword arguments" ] @@ -314,25 +329,37 @@ "time = 0\n", "\n", "# Set up GeoAxis and plot shapefile topologies to present day\n", - "ax3 = plt.figure(figsize=(16,12)).add_subplot(111, projection=ccrs.Robinson(central_longitude=10))\n", + "ax = plt.figure(figsize=(10, 8)).add_subplot(\n", + " 111, projection=ccrs.Robinson(central_longitude=10)\n", + ")\n", "gplot.time = time\n", - "gplot.plot_continents(ax3, facecolor='0.8')\n", - "gplot.plot_continent_ocean_boundaries(ax3, color='0.98')\n", - "gplot.plot_coastlines(ax3, color='0.9')\n", - "gplot.plot_ridges(ax3, color='r')\n", - "gplot.plot_transforms(ax3, color='r')\n", - "gplot.plot_trenches(ax3, color='navy')\n", - "gplot.plot_subduction_teeth(ax3, color='navy')\n", - "plt.title('Global volcanic & large igneous provinces at %i Ma' % (time))\n", - "\n", - "# Loop through all seafloor fabric filenames, reconstruct each topology and plot onto ax2 using GPlately\n", + "gplot.plot_continents(ax, facecolor=\"0.8\")\n", + "gplot.plot_continent_ocean_boundaries(ax, color=\"0.98\")\n", + "gplot.plot_coastlines(ax, color=\"0.9\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", + "plt.title(f\"Global volcanic & large igneous provinces at {time} Ma\")\n", + "\n", + "# Loop through all LIP/volcanic province files, reconstruct each topology and plot onto ax using GPlately\n", "feat = [\"Johansson et al. 2018\", \"Whittaker et al. 2015\"]\n", - "colours = ['cyan', 'maroon']\n", + "colours = [\"cyan\", \"maroon\"]\n", "for i, topology in enumerate(lip_volcanic_provinces):\n", " reconstructed_topology = model.reconstruct(topology, time)\n", " polygons = gplately.plot.shapelify_feature_polygons(reconstructed_topology)\n", - " ax3.add_geometries(polygons, crs=ccrs.PlateCarree(), facecolor=colours[i], edgecolor=colours[i], label=feat[i])\n", - " ax3.set_global()" + " ax.add_geometries(\n", + " polygons,\n", + " crs=ccrs.PlateCarree(),\n", + " facecolor=colours[i],\n", + " edgecolor=colours[i],\n", + " label=feat[i],\n", + " )\n", + " ax.set_global()" ] }, { @@ -342,7 +369,7 @@ "source": [ "### Plotting Shapely Points\n", "\n", - "Let's visualise present day __surface hotspot/plume locations__ from the \"Long-term interaction between mid-ocean ridges and mantle plumes\" by Whittaker et al. (2015). These locations are point data split into Pacific and Indo/Atlantic domains. They were compiled from studies by Montelli et al. (2004), Courtillot et al. 2003, Anderson and Schramm (2005) and Steinberger et al. (2000). Any plume points are separated by 500 km or less have been combined into an average point location.\n", + "Let's visualise present day __surface hotspot/plume locations__ from \"Long-term interaction between mid-ocean ridges and mantle plumes\" by Whittaker et al. (2015). These locations are point data split into Pacific and Indo/Atlantic domains. They were compiled from studies by Montelli et al. (2004), Courtillot et al. (2003), Anderson and Schramm (2005), and Steinberger et al. (2000). Any plume points separated by 500 km or less have been combined into an average point location.\n", "\n", "Let's obtain these topology files with GPlately's `PlateModelManager` object and reconstruct these point features to present-day." ] @@ -355,9 +382,9 @@ "outputs": [], "source": [ "# Set reconstruction time to present day\n", - "time = 0 # Ma\n", - "hotspot_plumes = muller2019_model.get_layer(\"Hotspots\")\n", - " \n", + "time = 0 # Ma\n", + "hotspot_plumes = pmm_model.get_layer(\"Hotspots\")\n", + "\n", "# Reconstruct hotspot and plume point locations to present day.\n", "reconstructed_hotspot_plumes = model.reconstruct(hotspot_plumes, time)" ] @@ -383,20 +410,35 @@ "for i, feature in enumerate(reconstructed_hotspot_plumes):\n", " geometry = feature.get_reconstructed_geometry()\n", " plat[i], plon[i] = geometry.to_lat_lon()\n", - " \n", + "\n", "# Set up GeoAxis, plot shapefile topologies and hotspot/plume point features to present day\n", - "ax4 = plt.figure(figsize=(18,10)).add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "ax = plt.figure(figsize=(10, 8)).add_subplot(\n", + " 111, projection=ccrs.Mollweide(central_longitude=0)\n", + ")\n", "gplot.time = time\n", - "gplot.plot_continents(ax4, facecolor='0.8')\n", - "gplot.plot_continent_ocean_boundaries(ax4, color='0.98')\n", - "gplot.plot_coastlines(ax4, color='0.9')\n", - "gplot.plot_ridges(ax4, color='r')\n", - "gplot.plot_transforms(ax4, color='r')\n", - "gplot.plot_trenches(ax4, color='navy')\n", - "gplot.plot_subduction_teeth(ax4, color='navy')\n", - "plt.title('Global surface hotspot & plume locations at %i Ma' % (time))\n", - "ax4.scatter(plon, plat, transform=ccrs.PlateCarree(), marker='o', color='greenyellow', edgecolor='k', s=30, zorder=2)\n", - "ax4.set_global()" + "gplot.plot_continents(ax, facecolor=\"0.8\")\n", + "gplot.plot_continent_ocean_boundaries(ax, color=\"0.98\")\n", + "gplot.plot_coastlines(ax, color=\"0.9\")\n", + "gplot.plot_all_topological_sections(\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"red\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + ")\n", + "plt.title(f\"Global surface hotspot & plume locations at {time} Ma\")\n", + "ax.scatter(\n", + " plon,\n", + " plat,\n", + " transform=ccrs.PlateCarree(),\n", + " marker=\"o\",\n", + " color=\"greenyellow\",\n", + " edgecolor=\"k\",\n", + " s=30,\n", + " zorder=2,\n", + ")\n", + "ax.set_global()" ] }, { @@ -406,7 +448,7 @@ "source": [ "### Dataset sources/citations\n", "\n", - "GPML feature topology data used for this notebook have been sourced from EarthByte's GPlates 2.3 software and dataset database: https://www.earthbyte.org/gplates-2-3-software-and-data-sets/\n", + "GPML feature topology data used for this notebook have been sourced from EarthByte's [GPlates 2.3 software and dataset](https://www.earthbyte.org/gplates-2-3-software-and-data-sets/).\n", "\n", "__Global seafloor fabric:__\n", "- Matthews, K.J., Müller, R.D., Wessel, P. and Whittaker, J.M., 2011. The tectonic fabric of the ocean basins. Journal of Geophysical Research, 116(B12): B12109, DOI: 10.1029/2011JB008413.\n", @@ -422,19 +464,11 @@ "__Surface hotspot + plume locations:__\n", "- Whittaker, J., Afonso, J., Masterton, S., Müller, R., Wessel, P., Williams, S., and Seton, M., 2015, Long-term interaction between mid-ocean ridges and mantle plumes: Nature Geoscience, v. 8, no. 6, p. 479-483, doi: 10.1038/ngeo2437." ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9884664a", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -448,7 +482,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/06-Rasters.ipynb b/Notebooks/06-Rasters.ipynb index 62038f46..70a62f12 100644 --- a/Notebooks/06-Rasters.ipynb +++ b/Notebooks/06-Rasters.ipynb @@ -9,12 +9,13 @@ "\n", "In this notebook, we will demonstrate how to use the gplately.Raster class to:\n", "\n", - "- Reconstruct rasters back in time\n", + "- Create rasters from scattered geographic points\n", + "- Download time-dependent rasters\n", "- Plot rasters\n", - "- Resize and respace rasters\n", - "- Linear interpolation rasters\n", - "- Query raster\n", - "- Clip raster by extent\n" + "- Resize and resample rasters\n", + "- Reconstruct rasters back in time\n", + "- Query rasters using linear interpolation or a spatial tree search\n", + "- Clip rasters by extent\n" ] }, { @@ -29,9 +30,8 @@ "import gplately\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from plate_model_manager import PlateModelManager, PresentDayRasterManager\n", - "from gplately.utils.log_utils import turn_on_debug_logging\n", - "#turn_on_debug_logging()" + "import xarray as xr\n", + "from plate_model_manager import PresentDayRasterManager,PlateModelManager" ] }, { @@ -39,7 +39,7 @@ "id": "a6c10d5f-6ff6-48f2-ad9e-7665625d509a", "metadata": {}, "source": [ - "Use [`PlateModelManager`](https://gplates.github.io/plate-model-manager/latest/) to download plate tectonic models, and create [`PlateReconstruction`](https://gplates.github.io/gplately/reconstruction.html#gplately.reconstruction.PlateReconstruction) and [`PlotTopologies`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.PlotTopologies.html) objects for the [Muller et al. (2019)](https://doi.org/10.1029/2018TC005462) plate tectonic model." + "Use [`PlateModelManager`](https://gplates.github.io/plate-model-manager/latest/) to download plate tectonic models, and create [`PlateReconstruction`](https://gplates.github.io/gplately/reconstruction.html#gplately.reconstruction.PlateReconstruction) and [`PlotTopologies`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.PlotTopologies.html) objects." ] }, { @@ -49,17 +49,16 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture cap\n", - "\n", - "muller2019_model = PlateModelManager().get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "static_polygons = muller2019_model.get_static_polygons()\n", + "model_name = \"Zahirovic2022\"\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=\"plate-model-repo\")\n", + "assert pmm_model is not None, f\"Failed to load the {model_name} plate model.\"\n", + "rotation_model = pmm_model.get_rotation_model()\n", + "topology_features = pmm_model.get_topologies()\n", + "static_polygons = pmm_model.get_static_polygons()\n", "\n", - "coastlines = muller2019_model.get_layer('Coastlines')\n", - "continents = muller2019_model.get_layer('ContinentalPolygons')\n", - "COBs = muller2019_model.get_layer('COBs')\n", + "coastlines = pmm_model.get_layer(\"Coastlines\")\n", + "continents = pmm_model.get_layer(\"ContinentalPolygons\")\n", + "COBs = pmm_model.get_layer(\"COBs\")\n", "\n", "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", "gplot = gplately.PlotTopologies(model, coastlines, continents, COBs)" @@ -70,13 +69,13 @@ "id": "e801673c-ad33-49b5-bcea-6e2b6954126a", "metadata": {}, "source": [ - "Alternatively, you can use the gplately.auxiliary.get_gplot() function to get the `PlotTopologies` and `PlateReconstruction` objects.\n", + "Alternatively, you can use the `gplately.auxiliary.get_gplot()` function to get the `PlotTopologies` and `PlateReconstruction` objects.\n", "\n", "```Python\n", "from gplately.auxiliary import get_gplot, get_plate_reconstruction\n", "\n", "# use the auxiliary function to create PlotTopologies and PlateReconstruction objects\n", - "gplot = get_gplot(\"Muller2019\") # the PlotTopologies object\n", + "gplot = get_gplot(model_name) # the PlotTopologies object\n", "model = gplot.plate_reconstruction # the PlateReconstruction object\n", "```" ] @@ -97,13 +96,20 @@ "outputs": [], "source": [ "with warnings.catch_warnings():\n", - " warnings.filterwarnings('ignore', category=RuntimeWarning)\n", + " warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", " import pygmt\n", - " \n", - " relief = pygmt.datasets.load_earth_relief(\n", - " resolution=\"10m\", region=[-180, 180, -80, 80]\n", - " )\n", - " \n", + "\n", + " relief_filename = \"earth_relief_10m.nc\"\n", + " if os.path.isfile(relief_filename):\n", + " relief = xr.open_dataarray(relief_filename)\n", + " print(f\"Loaded relief data from {relief_filename}\")\n", + " else:\n", + " relief = pygmt.datasets.load_earth_relief(\n", + " resolution=\"10m\", region=[-180, 180, -80, 80]\n", + " )\n", + " print(f\"Downloaded relief data and saved to {relief_filename}\")\n", + " relief.to_netcdf(relief_filename)\n", + "\n", " # Randomly subsample the regular grid into \"scattered\" points.\n", " # These points will be used to demonstrate the functionality of the Raster.from_points() method later.\n", " rng = np.random.default_rng(0)\n", @@ -111,11 +117,11 @@ " n_points = 5000\n", " iy = rng.integers(0, ny, n_points)\n", " ix = rng.integers(0, nx, n_points)\n", - " \n", + "\n", " lon = relief.lon.values[ix]\n", " lat = relief.lat.values[iy]\n", " val = relief.values[iy, ix]\n", - " \n", + "\n", " # Create a Raster from the points\n", " raster = gplately.Raster.from_points(\n", " lon=lon,\n", @@ -123,7 +129,7 @@ " values=val,\n", " spacing=\"0.5d\", # dataset covers only a small region, so use fine spacing\n", " )\n", - " \n", + "\n", " fig = pygmt.Figure()\n", " raster.plot(ax_or_fig=fig, use_gmt=True)\n", " fig.plot(\n", @@ -151,11 +157,11 @@ "id": "ac0fab94", "metadata": {}, "source": [ - "Let's use [`PlateModelManager`](https://gplates.github.io/plate-model-manager/latest/) to download Muller et al. 2019 netCDF age grids. \n", + "Let's use [`PlateModelManager`](https://gplates.github.io/plate-model-manager/latest/) to download netCDF age grids. \n", "\n", "There is a unique age grid for each millionth year - let's access the 0 Ma age grid by passing `time` to the [`get_raster`](https://gplates.github.io/plate-model-manager/latest/plate_model_manager.html#plate_model_manager.PlateModel.get_raster) function. \n", "\n", - "We can then create a GPlately `Raster` object(`muller_2019_age_grid`) from the raster file." + "We can then create a GPlately `Raster` object from the raster file." ] }, { @@ -166,12 +172,12 @@ "outputs": [], "source": [ "time = 0 # Ma\n", - "muller_2019_age_grid = gplately.Raster(\n", - " data=muller2019_model.get_raster(\"AgeGrids\", time),\n", + "age_grid_raster = gplately.Raster(\n", + " data=pmm_model.get_raster(\"AgeGrids\", time),\n", " plate_reconstruction=model,\n", - " extent=[-180, 180, -90, 90],\n", - " )\n", - "test=gplately.Raster(muller_2019_age_grid)" + " extent=(-180, 180, -90, 90),\n", + ")\n", + "test = gplately.Raster(age_grid_raster)" ] }, { @@ -180,7 +186,7 @@ "metadata": {}, "source": [ "## Plot Raster\n", - "The `muller_2019_age_grid` is a [`Raster`](https://gplates.github.io/gplately/grids.html#gplately.grids.Raster) object - this object allows us to work with age grids and other rasters. Let's visualise the data with [`imshow`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html)." + "The `age_grid_raster` is a [`Raster`](https://gplates.github.io/gplately/grids.html#gplately.grids.Raster) object - this object allows us to work with age grids and other rasters. Let's visualise the data with [`imshow`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.imshow.html)." ] }, { @@ -193,15 +199,17 @@ "fig = plt.figure(figsize=(6, 6))\n", "ax = fig.add_subplot(111, projection=ccrs.PlateCarree())\n", "ax.coastlines()\n", - "cpt_file='agegrid.cpt'\n", + "cpt_file = \"agegrid.cpt\"\n", "if not os.path.isfile(cpt_file):\n", " import urllib.request\n", + "\n", " urllib.request.urlretrieve(\n", " \"https://raw.githubusercontent.com/GPlates/gplately/refs/heads/master/tests-dir/unittest/create-age-grids-video/agegrid.cpt\",\n", " cpt_file,\n", " )\n", - "from gplately.mapping.gmt_cpt import get_cmap_from_gmt_cpt\n", - "muller_2019_age_grid.plot(ax, cmap=get_cmap_from_gmt_cpt(cpt_file))# Raster.plot() method\n", + "from gplately.plot.gmt_cpt import get_cmap_from_gmt_cpt\n", + "\n", + "age_grid_raster.plot(ax, cmap=get_cmap_from_gmt_cpt(cpt_file)) # Raster.plot() method\n", "ax.set_title(\"Map Plotted by Cartopy\")\n", "plt.show()" ] @@ -211,7 +219,7 @@ "id": "da1ff4b0-a4ff-4fff-9ae7-6dc11ef377de", "metadata": {}, "source": [ - "Use the built-in PyGMT plot engine" + "Use the built-in PyGMT plot engine." ] }, { @@ -222,13 +230,20 @@ "outputs": [], "source": [ "with warnings.catch_warnings():\n", - " warnings.filterwarnings('ignore', category=RuntimeWarning)\n", - " \n", + " warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", " from gplately.auxiliary import get_pygmt_basemap_figure\n", - " \n", - " fig = get_pygmt_basemap_figure(projection=\"N180/10c\", region=\"d\", frame=[\"xafg30\", \"yafg30\"], title=\"Map Plotted by PyGMT\")\n", + "\n", + " fig = get_pygmt_basemap_figure(\n", + " projection=\"N180/10c\",\n", + " region=\"d\",\n", + " frame=[\"xafg30\", \"yafg30\"],\n", + " title=\"Map Plotted by PyGMT\",\n", + " )\n", " fig.coast(land=\"darkgreen\")\n", - " muller_2019_age_grid.plot(fig, cmap=cpt_file, nan_transparent=True, use_gmt=True) # Raster.plot() method\n", + " age_grid_raster.plot(\n", + " fig, cmap=cpt_file, nan_transparent=True, use_gmt=True\n", + " ) # Raster.plot() method\n", " fig.show(crop=\"+m0.4c\")" ] }, @@ -237,7 +252,7 @@ "id": "733a2b3a", "metadata": {}, "source": [ - "Let's plot this netCDF grid along with coastlines, mid-ocean ridges and subduction zones (with teeth) resolved from the Muller et al. 2019 plate model." + "Let's plot this netCDF grid along with coastlines, mid-ocean ridges and subduction zones (with teeth)." ] }, { @@ -248,23 +263,23 @@ "outputs": [], "source": [ "with warnings.catch_warnings():\n", - " warnings.filterwarnings('ignore', category=UserWarning)\n", - " \n", + " warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "\n", " fig = plt.figure(figsize=(6, 6))\n", " ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", - " \n", + "\n", " gplot.time = time\n", - " \n", - " muller_2019_age_grid.imshow(ax, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200)\n", - " gplot.plot_coastlines(ax, edgecolor='none', facecolor='grey', alpha=0.3)\n", + "\n", + " age_grid_raster.imshow(ax, cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=200)\n", + " gplot.plot_coastlines(ax, edgecolor=\"none\", facecolor=\"grey\", alpha=0.3)\n", " gplot.plot_all_topological_sections(\n", - " ax,\n", - " plot_subduction_teeth=True,\n", - " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", - " ridge_kwargs={\"color\": \"black\", \"linewidth\": 1.0},\n", - " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", - " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", - " )\n", + " ax,\n", + " plot_subduction_teeth=True,\n", + " other_kwargs={\"color\": \"grey\", \"linewidth\": 0.8},\n", + " ridge_kwargs={\"color\": \"black\", \"linewidth\": 1.0},\n", + " transform_kwargs={\"color\": \"green\", \"linewidth\": 1.0},\n", + " trench_kwargs={\"color\": \"blue\", \"linewidth\": 1.0},\n", + " )\n", " ax.set_title(f\"{time} Ma\")\n", " plt.show()" ] @@ -276,7 +291,7 @@ "source": [ "## Resize and Resample Raster\n", "\n", - "Let's resize and resample the the present-day Muller et al. 2019 agegrid.\n", + "Let's resize and resample the present-day age grid.\n", "\n", "- [`resize`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.Raster.html#gplately.Raster.resize) - provide the number of points in each direction to resize the raster (e.g. 100 cols by 200 rows)\n", "- [`resample`](https://gplates.github.io/gplately/latest/sphinx/html/generated/gplately.Raster.html#gplately.Raster.resample) - provide the grid spacing in each direction (e.g. 0.1 degrees by 0.2 degrees)" @@ -289,38 +304,40 @@ "metadata": {}, "outputs": [], "source": [ - "muller_2019_age_grid = gplately.Raster(\n", - " data=muller2019_model.get_raster(\"AgeGrids\", time),\n", + "age_grid_raster = gplately.Raster(\n", + " data=pmm_model.get_raster(\"AgeGrids\", time),\n", " extent=[-180, 180, -90, 90],\n", - " )\n", + ")\n", "\n", "# Set grid size in x and y directions\n", - "age_grid_361_181 = muller_2019_age_grid.resize(361, 181)\n", - "age_grid_2_degree_space = muller_2019_age_grid.resample(spacingX=2, spacingY=2)\n", + "age_grid_361_181 = age_grid_raster.resize(361, 181)\n", + "age_grid_2_degree_space = age_grid_raster.resample(spacingX=2, spacingY=2)\n", "\n", - "fig = plt.figure(figsize=(16,8), dpi=100)\n", + "fig = plt.figure(figsize=(16, 8), dpi=100)\n", "\n", "ax_1 = fig.add_subplot(221, projection=ccrs.PlateCarree())\n", "ax_1.coastlines()\n", - "muller_2019_age_grid.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", - "ax_1.set_title(f\"Orignal Age Grid {muller_2019_age_grid.shape}\")\n", + "age_grid_raster.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0)\n", + "ax_1.set_title(f\"Original Age Grid {age_grid_raster.shape}\")\n", "\n", "ax_2 = fig.add_subplot(222, projection=ccrs.PlateCarree())\n", "ax_2.coastlines()\n", - "age_grid_361_181.plot(ax=ax_2, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", + "age_grid_361_181.plot(ax=ax_2, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0)\n", "ax_2.set_title(f\"Age Grid Resized {age_grid_361_181.shape}\")\n", "\n", "ax_3 = fig.add_subplot(223, projection=ccrs.PlateCarree())\n", "ax_3.coastlines()\n", - "age_grid_2_degree_space.plot(ax=ax_3, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", + "age_grid_2_degree_space.plot(\n", + " ax=ax_3, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0\n", + ")\n", "ax_3.set_title(f\"Age Grid Resampled {age_grid_2_degree_space.shape}\")\n", "\n", - "muller_2019_age_grid.resize(91, 46, inplace=True)\n", + "age_grid_raster.resize(91, 46, inplace=True)\n", "\n", "ax_4 = fig.add_subplot(224, projection=ccrs.PlateCarree())\n", "ax_4.coastlines()\n", - "muller_2019_age_grid.plot(ax=ax_4, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", - "ax_4.set_title(f\"Age Grid Resized In-Place {muller_2019_age_grid.shape}\")\n", + "age_grid_raster.plot(ax=ax_4, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0)\n", + "ax_4.set_title(f\"Age Grid Resized In-Place {age_grid_raster.shape}\")\n", "\n", "plt.show()" ] @@ -345,12 +362,15 @@ "outputs": [], "source": [ "with warnings.catch_warnings():\n", - " warnings.filterwarnings('ignore')\n", + " warnings.filterwarnings(\"ignore\")\n", "\n", " from matplotlib import image\n", - " etopo = gplately.Raster(data=image.imread(PresentDayRasterManager().get_raster(\"ETOPO1_tif\"))) # This returns ETOPO1 as a `Raster` object.\n", + "\n", + " etopo = gplately.Raster(\n", + " data=image.imread(PresentDayRasterManager().get_raster(\"ETOPO1_tif\"))\n", + " ) # This returns ETOPO1 as a `Raster` object.\n", " etopo.lats = etopo.lats[::-1]\n", - " \n", + "\n", " print(np.shape(etopo))" ] }, @@ -391,7 +411,7 @@ "metadata": {}, "outputs": [], "source": [ - "etopo_downscaled = etopo.resample(0.5,0.5)\n", + "etopo_downscaled = etopo.resample(0.5, 0.5)\n", "print(etopo_downscaled.shape)\n", "\n", "fig = plt.figure(figsize=(6, 6))\n", @@ -419,7 +439,7 @@ "metadata": {}, "outputs": [], "source": [ - "# use the downsample ETOPO1 so that the reconstructon won't take too long for this demonstration\n", + "# use the downsampled ETOPO1 so that the reconstruction won't take too long for this demonstration\n", "# assign a plate reconstruction in order to reconstruct the raster\n", "etopo_downscaled.plate_reconstruction = model\n", "\n", @@ -459,7 +479,7 @@ "id": "a3948752", "metadata": {}, "source": [ - "By default, [`Raster.reconstruct`]([`reconstruct`](https://gplates.github.io/gplately/grids.html#gplately.grids.Raster.reconstruct) uses `self.plate_reconstruction.static_polygons` to assign plate IDs to grid points. To override this behaviour, pass any collection of `pygplates.Feature` (e.g. `list`, `pygplates.FeatureCollection`, etc) to the `partitioning_features` argument." + "By default, [`Raster.reconstruct`](https://gplates.github.io/gplately/grids.html#gplately.grids.Raster.reconstruct) uses `self.plate_reconstruction.static_polygons` to assign plate IDs to grid points. To override this behaviour, pass any collection of `pygplates.Feature` (e.g. `list`, `pygplates.FeatureCollection`, etc) to the `partitioning_features` argument." ] }, { @@ -469,7 +489,9 @@ "metadata": {}, "outputs": [], "source": [ - "etopo_reconstructed = etopo_downscaled.reconstruct(140, partitioning_features=continents, threads=4, fill_value=\"grey\")\n", + "etopo_reconstructed = etopo_downscaled.reconstruct(\n", + " 140, partitioning_features=continents, threads=4, fill_value=\"grey\"\n", + ")\n", "etopo_reconstructed.plot(projection=ccrs.Orthographic(0, -80))\n", "plt.gca().set_title(f\"Reconstructed to {etopo_reconstructed.time} Ma\")" ] @@ -479,9 +501,9 @@ "id": "f2856502", "metadata": {}, "source": [ - "## Reverse reconstructions\n", + "## Reverse Reconstructions\n", "\n", - "Rasters can be also be reverse reconstructed forward in time! Some data will be lost during the reconstruction because those data don't exist at 50Ma." + "Rasters can also be reverse reconstructed forward in time! Some data will be lost during the reconstruction because that data doesn't exist at 50 Ma." ] }, { @@ -491,25 +513,34 @@ "metadata": {}, "outputs": [], "source": [ - "assert etopo_downscaled.shape[2]==3, \"Must be a RGB image\"\n", + "assert etopo_downscaled.shape[2] == 3, \"Must be a RGB image\"\n", "\n", "etopo_50_ma = etopo_downscaled.reconstruct(50, fill_value=\"white\", threads=4)\n", - "etopo_reversed_0_ma = etopo_50_ma.reconstruct(0, fill_value=(0,0,0), threads=4)\n", + "etopo_reversed_0_ma = etopo_50_ma.reconstruct(0, fill_value=(0, 0, 0), threads=4)\n", "\n", - "fig, axs = plt.subplots(2, 2, figsize=(10, 5), subplot_kw={\"projection\": ccrs.Mollweide(central_longitude=0)})\n", + "fig, axs = plt.subplots(\n", + " 2,\n", + " 2,\n", + " figsize=(10, 5),\n", + " subplot_kw={\"projection\": ccrs.Mollweide(central_longitude=0)},\n", + ")\n", "etopo_downscaled.plot(ax=axs[0][0])\n", "etopo_50_ma.plot(ax=axs[0][1])\n", "etopo_reversed_0_ma.plot(ax=axs[1][0])\n", "\n", - "use_spatial_tree = True # set this to False to use Linear Interpolation to fill the gaps. (much slower!!)\n", + "use_spatial_tree = True # set this to False to use Linear Interpolation to fill the gaps. (much slower!!)\n", "if use_spatial_tree:\n", - " etopo_reversed_0_ma.fill_gaps(invalid_value=(0,0,0), use_spatial_tree=True).plot(ax=axs[1][1])\n", + " etopo_reversed_0_ma.fill_gaps(invalid_value=(0, 0, 0), use_spatial_tree=True).plot(\n", + " ax=axs[1][1]\n", + " )\n", "else:\n", - " etopo_reversed_0_ma.fill_gaps(invalid_value=(0,0,0), method=\"linear\").plot(ax=axs[1][1])\n", + " etopo_reversed_0_ma.fill_gaps(invalid_value=(0, 0, 0), method=\"linear\").plot(\n", + " ax=axs[1][1]\n", + " )\n", "\n", "axs[0][0].set_title(\"Original Present Day\")\n", - "axs[0][1].set_title(\"Reconstructed to 50Ma\")\n", - "axs[1][0].set_title(\"Reverse from 50Ma to Present Day\")\n", + "axs[0][1].set_title(\"Reconstructed to 50 Ma\")\n", + "axs[1][0].set_title(\"Reverse from 50 Ma to Present Day\")\n", "if use_spatial_tree:\n", " axs[1][1].set_title(\"Gaps Filled By Nearest Interpolation\")\n", "else:\n", @@ -551,13 +582,17 @@ "# plot\n", "fig = plt.figure(figsize=(10, 10))\n", "ax_1 = fig.add_subplot(121, projection=ccrs.Mollweide(central_longitude=20))\n", - "im = etopo_nc_reconstructed.plot(ax_1, cmap=gplately.get_topo_cmap(), vmin=-10927,vmax=8726)\n", - "fig.colorbar(im, ax=ax_1, pad=0.05, shrink=0.7, orientation='horizontal')\n", + "im = etopo_nc_reconstructed.plot(\n", + " ax_1, cmap=gplately.get_topo_cmap(), vmin=-10927, vmax=8726\n", + ")\n", + "fig.colorbar(im, ax=ax_1, pad=0.05, shrink=0.7, orientation=\"horizontal\")\n", "ax_1.set_title(f\"ETOPO1 NetCDF at {etopo_nc_reconstructed.time} Ma\")\n", "\n", "ax_2 = fig.add_subplot(122, projection=ccrs.Mollweide(central_longitude=20))\n", - "im = etopo_nc_reconstructed.fill_gaps(use_gmt=True).plot(ax_2, cmap=gplately.get_topo_cmap(), vmin=-10927,vmax=8726)\n", - "fig.colorbar(im, ax=ax_2, pad=0.05, shrink=0.7, orientation='horizontal')\n", + "im = etopo_nc_reconstructed.fill_gaps(use_gmt=True).plot(\n", + " ax_2, cmap=gplately.get_topo_cmap(), vmin=-10927, vmax=8726\n", + ")\n", + "fig.colorbar(im, ax=ax_2, pad=0.05, shrink=0.7, orientation=\"horizontal\")\n", "ax_2.set_title(f\"Gaps Filled by Nearest using pygmt.grdfill()\")\n", "\n", "plt.show()" @@ -571,7 +606,9 @@ "outputs": [], "source": [ "# Save the reconstructed ETOPO grid to a netCDF file\n", - "etopo_nc_reconstructed.save_to_netcdf4(f\"reconstructed_etopo_{etopo_nc_reconstructed.time}.nc\",)" + "etopo_nc_reconstructed.save_to_netcdf4(\n", + " f\"reconstructed_etopo_{etopo_nc_reconstructed.time}.nc\",\n", + ")" ] }, { @@ -591,32 +628,38 @@ "metadata": {}, "outputs": [], "source": [ - "fig = plt.figure(figsize=(12,3), dpi=100)\n", + "fig = plt.figure(figsize=(12, 3), dpi=100)\n", "\n", - "ax_1 = fig.add_subplot(131, projection=ccrs.PlateCarree(central_longitude = 0))\n", + "ax_1 = fig.add_subplot(131, projection=ccrs.PlateCarree(central_longitude=0))\n", "etopo_downscaled.plot(ax=ax_1)\n", "\n", - "extent = (-100,100,-50,50)\n", - "ax_2 = fig.add_subplot(132, projection=ccrs.PlateCarree(central_longitude = 0))\n", - "xx, yy = np.meshgrid(np.linspace(extent[0], extent[1], 100), np.linspace(extent[2], extent[3], 50))\n", - "shape=xx.shape\n", + "extent = (-100, 100, -50, 50)\n", + "ax_2 = fig.add_subplot(132, projection=ccrs.PlateCarree(central_longitude=0))\n", + "xx, yy = np.meshgrid(\n", + " np.linspace(extent[0], extent[1], 100), np.linspace(extent[2], extent[3], 50)\n", + ")\n", + "shape = xx.shape\n", "xx = xx.flatten()\n", - "yy= yy.flatten()\n", - "values = etopo_downscaled.query(lons=xx, lats=yy, interpolation_method = \"linear\")\n", + "yy = yy.flatten()\n", + "values = etopo_downscaled.query(lons=xx, lats=yy, interpolation_method=\"linear\")\n", "ax_2.set_extent(extent, crs=ccrs.PlateCarree())\n", "ax_2.set_facecolor(\"white\")\n", "ax_2.scatter(\n", - " xx,\n", - " yy,\n", - " c=values/ 255.0,\n", - " marker=\"o\",\n", - " s=3.5,\n", - " transform=ccrs.PlateCarree(),\n", - " )\n", + " xx,\n", + " yy,\n", + " c=values / 255.0,\n", + " marker=\"o\",\n", + " s=3.5,\n", + " transform=ccrs.PlateCarree(),\n", + ")\n", "\n", - "ax_3 = fig.add_subplot(133, projection=ccrs.PlateCarree(central_longitude = 0))\n", + "ax_3 = fig.add_subplot(133, projection=ccrs.PlateCarree(central_longitude=0))\n", "ax_3.set_global()\n", - "ax_3.imshow(np.flipud(np.reshape(values,(shape[0],shape[1],3))),transform=ccrs.PlateCarree(),extent=extent)\n", + "ax_3.imshow(\n", + " np.flipud(np.reshape(values, (shape[0], shape[1], 3))),\n", + " transform=ccrs.PlateCarree(),\n", + " extent=extent,\n", + ")\n", "\n", "ax_1.set_title(f\"Original Image\")\n", "ax_2.set_title(f\"Values Plotted by scatter()\")\n", @@ -631,7 +674,7 @@ "id": "33f06a32-a41c-4c7b-bc70-e492f1a3f63b", "metadata": {}, "source": [ - "This example demonstrates how to query nearest values from a Raster. We'll find which subduction zones form continental arcs by using continental mask raster and query the raster with points projected 250 km from the trench (in the direction of the subducting plate) to determine if they are inside a continent." + "This example demonstrates how to query nearest values from a Raster. We'll find which subduction zones form continental arcs by using a continental mask raster, and querying that raster with points projected 250 km from the trench (in the direction of the subducting plate) to determine if they are inside a continent." ] }, { @@ -645,22 +688,23 @@ "continental_mask_file = os.path.join(\"NotebookFiles\", \"continental_grid_0.nc\")\n", "if not os.path.isfile(continental_mask_file):\n", " import urllib.request\n", + "\n", " urllib.request.urlretrieve(\n", " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/continental_grid_0.nc\",\n", " continental_mask_file,\n", " )\n", - " \n", + "\n", "continental_raster = gplately.Raster(continental_mask_file, model)\n", "\n", - "# tessellate trenches and extract subduction polarity angle, and the lat-lon coordinates\n", + "# tessellate trenches and extract the subduction polarity angle and the lat-lon coordinates\n", "trench_data = model.tessellate_subduction_zones(time)\n", "trench_normal_azimuthal_angle = trench_data[:, 7]\n", "trench_pt_lon = trench_data[:, 0]\n", "trench_pt_lat = trench_data[:, 1]\n", - " \n", + "\n", "# calculate 250 km arc distance\n", "arc_distance = 250 / (gplately.tools.geocentric_radius(trench_pt_lat) / 1e3)\n", - " \n", + "\n", "# Lat and lon coordinates of all trench points after being projected out 250 km in the direction of subduction.\n", "dlon = arc_distance * np.sin(np.radians(trench_normal_azimuthal_angle))\n", "dlat = arc_distance * np.cos(np.radians(trench_normal_azimuthal_angle))\n", @@ -673,7 +717,7 @@ "id": "d851bb07", "metadata": {}, "source": [ - "Now use these projected trench points to query the nearest cells in the continental raster using [`Raster.query`](https://gplates.github.io/gplately/grids.html#gplately.Raster.query)" + "Now use these projected trench points to query the nearest cells in the continental raster using [`Raster.query`](https://gplates.github.io/gplately/grids.html#gplately.Raster.query)." ] }, { @@ -684,9 +728,11 @@ "outputs": [], "source": [ "# Query the raster with the projected trench points\n", - "sampled_points = continental_raster.query(lons=ilon, lats=ilat, interpolation_method='nearest')\n", + "sampled_points = continental_raster.query(\n", + " lons=ilon, lats=ilat, interpolation_method=\"nearest\"\n", + ")\n", "\n", - "# The cells of land in the continental raster are 1. Now find all the inland points. \n", + "# The cells of land in the continental raster are 1. Now find all the inland points.\n", "in_raster_indices = sampled_points > 0\n", "\n", "# Get the lat-lon coordinates of the in_raster points\n", @@ -712,13 +758,16 @@ "fig = plt.figure(figsize=(8, 6), dpi=100)\n", "ax1 = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", "gplot.time = time\n", - "gplot.plot_grid_from_netCDF(ax1, continental_mask_file, cmap=\"twilight\", alpha=0.5, vmin=0, vmax=200)\n", - "gplot.plot_coastlines(ax1, edgecolor='k', facecolor='1', alpha=0.1)\n", - "gplot.plot_trenches(ax1, color='r', zorder=5) # Plot the original trench points in Red\n", + "gplot.plot_grid_from_netCDF(\n", + " ax1, continental_mask_file, cmap=\"twilight\", alpha=0.5, vmin=0, vmax=200\n", + ")\n", + "gplot.plot_coastlines(ax1, edgecolor=\"k\", facecolor=\"1\", alpha=0.1)\n", + "gplot.plot_trenches(ax1, color=\"r\", zorder=5) # Plot the original trench points in Red\n", "\n", "# Plot the projected inland points in Blue\n", "ax1.plot(\n", - " lon_in, lat_in,\n", + " lon_in,\n", + " lat_in,\n", " linestyle=\"none\",\n", " marker=\"o\",\n", " markersize=0.25,\n", @@ -740,7 +789,7 @@ " \"Arc segments in \\ncontinental grids\",\n", "]\n", "\n", - "plt.legend(handles, labels, loc=\"lower left\",bbox_to_anchor=(0., -0.05))\n", + "plt.legend(handles, labels, loc=\"lower left\", bbox_to_anchor=(0.0, -0.05))\n", "plt.show()" ] }, @@ -761,14 +810,15 @@ "metadata": {}, "outputs": [], "source": [ - "age_grid_raster = gplately.Raster(data=muller2019_model.get_raster(\"AgeGrids\", 0))\n", + "age_grid_raster = gplately.Raster(data=pmm_model.get_raster(\"AgeGrids\", 0))\n", "\n", "# plot the age grid raster and black sample points\n", - "fig = plt.figure(figsize=(10,10), dpi=100)\n", - "ax_1 = fig.add_subplot(121, projection=ccrs.Mollweide(central_longitude = 0))\n", - "age_grid_raster.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", + "fig = plt.figure(figsize=(10, 10), dpi=100)\n", + "ax_1 = fig.add_subplot(121, projection=ccrs.Mollweide(central_longitude=0))\n", + "age_grid_raster.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0)\n", "ax_1.plot(\n", - " lon_in, lat_in,\n", + " lon_in,\n", + " lat_in,\n", " linestyle=\"none\",\n", " marker=\"o\",\n", " markersize=0.25,\n", @@ -779,29 +829,36 @@ "ax_1.set_title(\"Age Grid and Black Sample Points\")\n", "\n", "# plot the data being retrieved by raster query\n", - "ax_2 = fig.add_subplot(122, projection=ccrs.Mollweide(central_longitude = 0))\n", + "ax_2 = fig.add_subplot(122, projection=ccrs.Mollweide(central_longitude=0))\n", "ax_2.set_global()\n", - "xx=lon_in\n", - "yy=lat_in\n", + "xx = lon_in\n", + "yy = lat_in\n", "\n", "# You may set the `region_of_interest` to a smaller value, such as 100, to see what will happen.\n", - "# You will see less points being plotted because there are no valid data within 100km for some sample points.\n", - "roi = 500 #km\n", - "values = age_grid_raster.query(lons=xx, lats=yy,region_of_interest=roi)\n", + "# You will see fewer points being plotted because there are no valid data within 100 km for some sample points.\n", + "roi = 500 # km\n", + "values = age_grid_raster.query(lons=xx, lats=yy, region_of_interest=roi)\n", "\n", "ax_2.scatter(\n", - " xx,\n", - " yy,\n", - " c=values,\n", - " marker=\"o\",\n", - " s=0.25,\n", - " transform=ccrs.PlateCarree(),\n", - " cmap=get_cmap_from_gmt_cpt(cpt_file),\n", - " vmax=200,\n", - " vmin=0,\n", - " )\n", + " xx,\n", + " yy,\n", + " c=values,\n", + " marker=\"o\",\n", + " s=0.25,\n", + " transform=ccrs.PlateCarree(),\n", + " cmap=get_cmap_from_gmt_cpt(cpt_file),\n", + " vmax=200,\n", + " vmin=0,\n", + ")\n", "\n", - "gl = ax_2.gridlines(crs=ccrs.PlateCarree(), draw_labels=False, linewidth=1, color='gray', alpha=0.5, linestyle='--')\n", + "gl = ax_2.gridlines(\n", + " crs=ccrs.PlateCarree(),\n", + " draw_labels=False,\n", + " linewidth=1,\n", + " color=\"gray\",\n", + " alpha=0.5,\n", + " linestyle=\"--\",\n", + ")\n", "ax_2.set_title(f\"Sampled Age Values within {roi} KM\")\n", "fig.tight_layout()\n", "plt.show()" @@ -822,17 +879,28 @@ "metadata": {}, "outputs": [], "source": [ - "fig, axs = plt.subplots(1,2,figsize=(10,8), \n", - " gridspec_kw={'width_ratios': [2, 1]},\n", - " subplot_kw={'projection': ccrs.PlateCarree()})\n", - "#fig.tight_layout()\n", - "ax_1=axs[0]\n", - "ax_2=axs[1]\n", + "fig, axs = plt.subplots(\n", + " 1,\n", + " 2,\n", + " figsize=(10, 8),\n", + " gridspec_kw={\"width_ratios\": [2, 1]},\n", + " subplot_kw={\"projection\": ccrs.PlateCarree()},\n", + ")\n", + "# fig.tight_layout()\n", + "ax_1 = axs[0]\n", + "ax_2 = axs[1]\n", "\n", "# plot the original age grid raster\n", - "age_grid_raster.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file),vmax=200, vmin=0)\n", - "ax_1.set_title(\"Orignal Age Grid\")\n", - "gl = ax_1.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='--')\n", + "age_grid_raster.plot(ax=ax_1, cmap=get_cmap_from_gmt_cpt(cpt_file), vmax=200, vmin=0)\n", + "ax_1.set_title(\"Original Age Grid\")\n", + "gl = ax_1.gridlines(\n", + " crs=ccrs.PlateCarree(),\n", + " draw_labels=True,\n", + " linewidth=1,\n", + " color=\"gray\",\n", + " alpha=0.5,\n", + " linestyle=\"--\",\n", + ")\n", "gl.right_labels = False\n", "gl.top_labels = False\n", "\n", @@ -848,7 +916,14 @@ " vmin=0,\n", ")\n", "ax_2.set_title(\"Clipped Age Grid\")\n", - "gl = ax_2.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='--')\n", + "gl = ax_2.gridlines(\n", + " crs=ccrs.PlateCarree(),\n", + " draw_labels=True,\n", + " linewidth=1,\n", + " color=\"gray\",\n", + " alpha=0.5,\n", + " linestyle=\"--\",\n", + ")\n", "gl.left_labels = False\n", "gl.top_labels = False\n", "plt.show()" @@ -857,7 +932,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -871,12 +946,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" - }, - "vscode": { - "interpreter": { - "hash": "a10fed8c503fa0e7abcec38684bcaa5ab84af52f4a155e8c08912d91252721a5" - } + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/07-WorkingWithPlateTectonicStats.ipynb b/Notebooks/07-WorkingWithPlateTectonicStats.ipynb index 4c455be1..ed29022a 100644 --- a/Notebooks/07-WorkingWithPlateTectonicStats.ipynb +++ b/Notebooks/07-WorkingWithPlateTectonicStats.ipynb @@ -20,9 +20,7 @@ "- Mean global trench velocities (cm/yr)\n", "- Global trench velocity standard deviation (cm/yr)\n", "\n", - "for a given plate model (a GPlately `PlateReconstruction` object) and a `reconstruction_time`. \n", - "\n", - "First, let's load our modules:" + "for a given plate model (a GPlately `PlateReconstruction` object) and a `reconstruction_time`. " ] }, { @@ -33,13 +31,16 @@ "outputs": [], "source": [ "import gplately\n", + "from pathlib import Path\n", "import numpy as np\n", - "import pygplates\n", - "import glob, os\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", - "import cartopy.crs as ccrs\n", - "from plate_model_manager import PlateModelManager" + "from scipy.ndimage import gaussian_filter\n", + "from plate_model_manager import PlateModelManager\n", + "\n", + "data_dir = Path(\"7-Working-with-Plate-Tectonic-Stats-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)" ] }, { @@ -47,21 +48,12 @@ "id": "05bde438", "metadata": {}, "source": [ - "Define `get_plate_tectonic_stats` and obtain plate tectonic stats for the Muller et al. 2019 model." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c79de52", - "metadata": {}, - "outputs": [], - "source": [ - "muller2019_model = PlateModelManager().get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "model = gplately.PlateReconstruction(\n", - " muller2019_model.get_rotation_model(), \n", - " muller2019_model.get_topologies(), \n", - " muller2019_model.get_static_polygons())" + "Define `get_plate_tectonic_stats` below, then use it to calculate plate tectonic stats for all four plate reconstruction models used in this notebook:\n", + "\n", + "1. Müller et al. 2019\n", + "2. Müller et al. 2016\n", + "3. Merdith et al. 2021\n", + "4. Zahirovic et al. 2022" ] }, { @@ -117,149 +109,201 @@ " # ----------------------- MID-OCEAN RIDGES ------------------------------\n", " # Calculate mid ocean ridge stats with GPlately\n", " ridge_data = model.tessellate_mid_ocean_ridges(reconstruction_time)\n", - " \n", + "\n", " if ridge_data is None:\n", - " return \n", + " return\n", + "\n", + " # Ignore data of ridge segments with negative velocities\n", + " ridge_data = ridge_data[ridge_data[:, 2] >= 0]\n", "\n", - " # Ignore data of ridge segments with negative velocities \n", - " ridge_data = ridge_data[ridge_data[:,2] >= 0]\n", - " \n", " # Latitudes and longitudes of points along ridge segments\n", - " ridge_lon = ridge_data[:,0]\n", - " ridge_lat = ridge_data[:,1]\n", - " \n", + " ridge_lon = ridge_data[:, 0]\n", + " ridge_lat = ridge_data[:, 1]\n", + "\n", " # Global mid-ocean ridge length at this reconstruction time (km)\n", - " ridge_len = sum(np.radians(ridge_data[:,3]) * gplately.tools.geocentric_radius(ridge_data[:,1])) * 1e-3\n", - " \n", + " ridge_len = (\n", + " sum(\n", + " np.radians(ridge_data[:, 3])\n", + " * gplately.tools.geocentric_radius(ridge_data[:, 1])\n", + " )\n", + " * 1e-3\n", + " )\n", + "\n", " # Mean ridge spreading velocity + its standard deviation (in cm/year)\n", - " ridge_vel = ridge_data[:,2] # spreading velocities of ridge segments in cm/yr\n", - " ridge_vel_mean = np.mean(ridge_vel) # mean global spreading velocity amongst all ridge segments in cm/yr\n", - " ridge_vel_std = np.std(ridge_vel) # standard deviation\n", - " \n", + " ridge_vel = ridge_data[:, 2] # spreading velocities of ridge segments in cm/yr\n", + " ridge_vel_mean = np.mean(\n", + " ridge_vel\n", + " ) # mean global spreading velocity amongst all ridge segments in cm/yr\n", + " ridge_vel_std = np.std(ridge_vel) # standard deviation\n", + "\n", " # Ridge surface area (km^2/yr)\n", " # Convert ridge velocities from cm/yr to m/yr; ridge lengths are already in km\n", " ridge_surface_area = ridge_vel_mean * 1e-5 * ridge_len\n", - " \n", - " \n", + "\n", " # ----------------------- SUBDUCTION ZONES ------------------------------\n", " # Calculate subduction convergence stats with GPlately\n", " subduction_data = model.tessellate_subduction_zones(reconstruction_time)\n", - " \n", + "\n", " # Latitudes and longitudes of points along trench segments\n", - " subduction_lon = subduction_data[:,0]\n", - " subduction_lat = subduction_data[:,1]\n", - " \n", + " subduction_lon = subduction_data[:, 0]\n", + " subduction_lat = subduction_data[:, 1]\n", + "\n", " # calculate geocentric earth radius from latitude\n", " earth_radius = gplately.tools.geocentric_radius(subduction_lat)\n", - " \n", + "\n", " # Global subduction zone length at this reconstruction time (km)\n", - " subduction_len = np.sum(np.radians(subduction_data[:,6]) * earth_radius) * 1e-3 \n", - " \n", + " subduction_len = np.sum(np.radians(subduction_data[:, 6]) * earth_radius) * 1e-3\n", + "\n", " # Ensure convergence velocities are positive\n", - " subduction_data[:,2] = np.clip(subduction_data[:,2], 0.0, 1e99)\n", - " \n", + " subduction_data[:, 2] = np.clip(subduction_data[:, 2], 0.0, 1e99)\n", + "\n", " # Multiply convergence velocities by the cosine of the subduction obliquity angle to get\n", " # subduction velocities (cm/yr)\n", - " subduction_vel = np.fabs(subduction_data[:,2]) * np.cos(np.radians(subduction_data[:,3]))\n", - " \n", + " subduction_vel = np.fabs(subduction_data[:, 2]) * np.cos(\n", + " np.radians(subduction_data[:, 3])\n", + " )\n", + "\n", " # Global mean subduction velocity and stdev of all trench segments (cm/year)\n", " subd_vel_mean, subd_vel_std = np.mean(subduction_vel), np.std(subduction_vel)\n", "\n", - " # Absolute velocity(cm/year) of the trench’s motion in the orthogonal direction towards the overriding plate. \n", + " # Absolute velocity(cm/year) of the trench’s motion in the orthogonal direction towards the overriding plate.\n", " # Negative if moving towards the overriding plate (Trench advance)\n", " # Positive if moving away from the overriding plate (Trench retreat)\n", " # The purpose of \"-np.fabs()\" is to allow \"np.cos()\" to produce the correct sign of value(negative or positive).\n", - " trench_absolute_vel = -np.fabs(subduction_data[:,4]) * np.cos(np.radians(subduction_data[:,5]))\n", + " trench_absolute_vel = -np.fabs(subduction_data[:, 4]) * np.cos(\n", + " np.radians(subduction_data[:, 5])\n", + " )\n", "\n", " # Global mean and standard deviation of trench velocity(cm/year) of all trench segments\n", - " trench_abs_vel_mean, trench_abs_vel_std = np.mean(trench_absolute_vel), np.std(trench_absolute_vel)\n", - " \n", + " trench_abs_vel_mean, trench_abs_vel_std = np.mean(trench_absolute_vel), np.std(\n", + " trench_absolute_vel\n", + " )\n", + "\n", " # Area subducted by trenches over 1 yr (km^2/yr)\n", " # Convert subduction velocities from cm/yr to km/yr; trench lengths are already in km.\n", " subd_surface_area = subd_vel_mean * 1e-5 * subduction_len\n", "\n", " # Use gplately.PlateReconstruction.crustal_production_destruction_rate() to get a more\n", " # accurate value for global crustal production/destruction rate.\n", - " total_crustal_production_rate_km_2_per_yr, total_crustal_destruction_rate_km_2_per_yr = (\n", - " model.crustal_production_destruction_rate(time)\n", - " )\n", + " (\n", + " total_crustal_production_rate_km_2_per_yr,\n", + " total_crustal_destruction_rate_km_2_per_yr,\n", + " ) = model.crustal_production_destruction_rate(time)\n", " ridge_surface_area = total_crustal_production_rate_km_2_per_yr\n", " subd_surface_area = total_crustal_destruction_rate_km_2_per_yr\n", "\n", " # Return a set of boundary stats in a tuple\n", " data = (\n", - " reconstruction_time, ridge_len, ridge_vel_mean, ridge_vel_std,\n", - " ridge_surface_area, subduction_len, subd_vel_mean, subd_vel_std, subd_surface_area,\n", - " trench_abs_vel_mean, trench_abs_vel_std\n", + " reconstruction_time,\n", + " ridge_len,\n", + " ridge_vel_mean,\n", + " ridge_vel_std,\n", + " ridge_surface_area,\n", + " subduction_len,\n", + " subd_vel_mean,\n", + " subd_vel_std,\n", + " subd_surface_area,\n", + " trench_abs_vel_mean,\n", + " trench_abs_vel_std,\n", " )\n", - " \n", + "\n", " return data" ] }, { "cell_type": "markdown", - "id": "e3419083", + "id": "compute-all-models-md", "metadata": {}, "source": [ - "Let's obtain Muller2019 plate tectonic stats from 250Ma to present day." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1007b4a1", - "metadata": {}, - "outputs": [], - "source": [ - "# Create a time array\n", - "min_time = 0 \n", - "max_time = 249 #time 250 is no good\n", - "time_array = np.arange(max_time,min_time-1, -1)\n", - "\n", - "# Initialise data ndarray\n", - "data_array = np.zeros((11,time_array.size))\n", - "\n", - "# Get plate tectonic stats for each reconstruction time\n", - "for t, time in enumerate(time_array):\n", - " data = get_plate_tectonic_stats(model, time)\n", - " if data:\n", - " data_array[:,t] = data\n", - " gplately.tools.update_progress((max_time-time)/(max_time-min_time))\n", - "print(\"Done calculating stats!\")" - ] - }, - { - "cell_type": "markdown", - "id": "e89e7e40", - "metadata": {}, - "source": [ - "### Save plate tectonic stats to a CSV file" + "### Compute stats for each plate model, and save to CSV\n", + "\n", + "Each model supports a different maximum reconstruction time (Müller et al. 2019, for example, only extends back to 249 Ma, not 250 Ma). We loop over all four models, use `get_plate_tectonic_stats` to build a full time series of statistics for each, and save each one to its own CSV file." ] }, { "cell_type": "code", "execution_count": null, - "id": "87032bcc", + "id": "compute-all-models", "metadata": {}, "outputs": [], "source": [ - "headers = ['Time (Ma)',\n", - "'Global mid-ocean ridge lengths (km)',\n", - "'Mean global ridge velocities (cm/yr)',\n", - "'Global ridge velocity standard deviation (cm/yr)',\n", - "'Surface area of crust produced at ridges (km^2/yr)',\n", - "'Global subduction zone lengths (km)',\n", - "'Mean global subduction velocities (cm/yr)',\n", - "'Global subduction velocity standard deviation (cm/yr)',\n", - "'Surface area of crust subducted at trenches (km^2/yr)',\n", - "'Mean global trench velocities (cm/yr)',\n", - "'Global trench velocity standard deviation (cm/yr)']\n", - "\n", - "# Turn data array into a pandas dataframe.\n", - "muller19_df = pd.DataFrame(np.column_stack(data_array), columns = headers)\n", - "\n", - "# Save dataframe to CSV\n", - "muller19_df.to_csv('./NotebookFiles/Muller2019-PlateTectonicStats.csv', encoding='utf-8', index=False)" + "# Plate reconstruction models used in this notebook, and the maximum time (Ma) each one supports.\n", + "MODEL_CONFIGS = {\n", + " \"muller19\": {\n", + " \"pmm_name\": \"Muller2019\",\n", + " \"label\": \"Muller et al. 2019\",\n", + " \"max_time\": 249,\n", + " },\n", + " \"muller16\": {\n", + " \"pmm_name\": \"Muller2016\",\n", + " \"label\": \"Muller et al. 2016\",\n", + " \"max_time\": 230,\n", + " },\n", + " \"merdith21\": {\n", + " \"pmm_name\": \"Merdith2021\",\n", + " \"label\": \"Merdith et al. 2021\",\n", + " \"max_time\": 250,\n", + " },\n", + " \"zahirovic22\": {\n", + " \"pmm_name\": \"Zahirovic2022\",\n", + " \"label\": \"Zahirovic et al. 2022\",\n", + " \"max_time\": 250,\n", + " },\n", + "}\n", + "\n", + "# Columns match the order of the tuple returned by get_plate_tectonic_stats()\n", + "STAT_COLUMNS = [\n", + " \"Time (Ma)\",\n", + " \"Global mid-ocean ridge lengths (km)\",\n", + " \"Mean global ridge velocities (cm/yr)\",\n", + " \"Global ridge velocity standard deviation (cm/yr)\",\n", + " \"Surface area of crust produced at ridges (km^2/yr)\",\n", + " \"Global subduction zone lengths (km)\",\n", + " \"Mean global subduction velocities (cm/yr)\",\n", + " \"Global subduction velocity standard deviation (cm/yr)\",\n", + " \"Surface area of crust subducted at trenches (km^2/yr)\",\n", + " \"Mean global trench velocities (cm/yr)\",\n", + " \"Global trench velocity standard deviation (cm/yr)\",\n", + "]\n", + "\n", + "pmm = PlateModelManager()\n", + "model_dfs = {} # key -> DataFrame of plate tectonic stats, one entry per model\n", + "done_labels = []\n", + "\n", + "for key, cfg in MODEL_CONFIGS.items():\n", + " pmm_model = pmm.get_model(cfg[\"pmm_name\"], data_dir=\"plate-model-repo\")\n", + " reconstruction = gplately.PlateReconstruction(\n", + " pmm_model.get_rotation_model(),\n", + " pmm_model.get_topologies(),\n", + " pmm_model.get_static_polygons(),\n", + " )\n", + "\n", + " time_array = np.arange(cfg[\"max_time\"], -1, -1)\n", + " data_array = np.zeros((len(STAT_COLUMNS), time_array.size))\n", + " for t, time in enumerate(time_array):\n", + " stats = get_plate_tectonic_stats(reconstruction, time)\n", + " if stats:\n", + " data_array[:, t] = stats\n", + "\n", + " frac = (cfg[\"max_time\"] - time) / cfg[\"max_time\"] if cfg[\"max_time\"] else 1.0\n", + " gplately.tools.update_progress(frac)\n", + "\n", + " status = \", \".join(done_labels) if done_labels else \"none yet\"\n", + " print(f\"Working on: {cfg['label']} | Completed: {status}\")\n", + "\n", + " df = pd.DataFrame(np.column_stack(data_array), columns=STAT_COLUMNS)\n", + " df.to_csv(\n", + " output_dir / f\"{cfg['pmm_name']}-PlateTectonicStats.csv\",\n", + " encoding=\"utf-8\",\n", + " index=False,\n", + " )\n", + " model_dfs[key] = df\n", + " done_labels.append(cfg[\"label\"])\n", + "\n", + "# Final summary\n", + "print(\"\\nFinished computing plate tectonic stats for:\")\n", + "for label in done_labels:\n", + " print(f\"- {label}\")" ] }, { @@ -278,201 +322,120 @@ "metadata": {}, "outputs": [], "source": [ - "# Smooth data with a Gaussian filter\n", - "from scipy.ndimage import gaussian_filter\n", + "muller19_df = model_dfs[\"muller19\"]\n", "\n", - "# Get stats from the data frame\n", - "reconstruction_times = muller19_df['Time (Ma)'].to_list()\n", - "ridge_vels_mean = muller19_df['Mean global ridge velocities (cm/yr)'].to_list()\n", - "ridge_vels_std = muller19_df['Global ridge velocity standard deviation (cm/yr)'].to_list()\n", - "subd_vels_mean = muller19_df['Mean global subduction velocities (cm/yr)'].to_list()\n", - "subd_vels_std = muller19_df['Global subduction velocity standard deviation (cm/yr)'].to_list()\n", + "reconstruction_times = muller19_df[\"Time (Ma)\"]\n", + "ridge_vels_mean = muller19_df[\"Mean global ridge velocities (cm/yr)\"]\n", + "ridge_vels_std = muller19_df[\"Global ridge velocity standard deviation (cm/yr)\"]\n", + "subd_vels_mean = muller19_df[\"Mean global subduction velocities (cm/yr)\"]\n", + "subd_vels_std = muller19_df[\"Global subduction velocity standard deviation (cm/yr)\"]\n", "\n", - "# Use a Gaussian filter\n", "ridge_vels_mean_smoothed = gaussian_filter(ridge_vels_mean, sigma=2)\n", "subd_vels_mean_smoothed = gaussian_filter(subd_vels_mean, sigma=2)\n", "\n", - "# Plotting functions\n", "fig = plt.figure(figsize=(8, 4), dpi=200)\n", - "ax1 = fig.add_subplot(111, xlim=(250,0), ylim=(0,18), xlabel='Age (Ma)', ylabel=\"Rate (cm/yr)\",\n", - " title=\"Subduction zone convergence and ridge spreading rates (cm/yr):\\nMüller et al 2019\")\n", + "ax1 = fig.add_subplot(\n", + " 111,\n", + " xlim=(250, 0),\n", + " ylim=(0, 18),\n", + " xlabel=\"Age (Ma)\",\n", + " ylabel=\"Rate (cm/yr)\",\n", + " title=\"Subduction zone convergence and ridge spreading rates (cm/yr):\\nMüller et al 2019\",\n", + ")\n", "\n", "ax1.plot(reconstruction_times, ridge_vels_mean_smoothed, label=\"Ridge spreading rate\")\n", - "ax1.fill_between(reconstruction_times, \n", - " gaussian_filter(ridge_vels_mean_smoothed-ridge_vels_std, sigma=2), \n", - " gaussian_filter(ridge_vels_mean_smoothed+ridge_vels_std, sigma=2),\n", - " edgecolor='k', color='C0', alpha=0.2)\n", - "\n", - "ax1.plot(reconstruction_times, subd_vels_mean_smoothed, label=\"Subduction convergence rate\") \n", - "ax1.fill_between(reconstruction_times, \n", - " gaussian_filter(subd_vels_mean_smoothed-subd_vels_std, sigma=2), \n", - " gaussian_filter(subd_vels_mean_smoothed+subd_vels_std, sigma=2),\n", - " edgecolor='k', color='C1', alpha=0.2)\n", - "plt.legend(loc=\"upper right\", frameon=False)" - ] - }, - { - "cell_type": "markdown", - "id": "17a6d07a", - "metadata": {}, - "source": [ - "### Calculate plate tectonic stats of other plate models\n", - "We repeat the workflow above to calculate the plate tectonic stats of three additional plate reconstruction models:\n", - "\n", - "1. Muller et al. 2016\n", - "2. Merdith et al. 2021\n", - "3. Zahirovic et al. 2022\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "02e6b018", - "metadata": {}, - "outputs": [], - "source": [ - "# MULLER ET AL 2016\n", - "# Create a time array\n", - "min_time = 0 \n", - "max_time = 230\n", - "muller16_time_array = np.arange(max_time,min_time-1, -1)\n", - "\n", - "# Initialise data ndarray\n", - "muller16_data = np.zeros((11,muller16_time_array.size))\n", - "\n", - "# Create the PlateReconstruction object with Muller et al 2016\n", - "m = PlateModelManager().get_model(\"Muller2016\", data_dir=\"plate-model-repo\")\n", - "muller2016 = gplately.PlateReconstruction(\n", - " m.get_rotation_model(),\n", - " m.get_topologies(),\n", - " m.get_static_polygons(),\n", + "ax1.fill_between(\n", + " reconstruction_times,\n", + " gaussian_filter(ridge_vels_mean_smoothed - ridge_vels_std, sigma=2),\n", + " gaussian_filter(ridge_vels_mean_smoothed + ridge_vels_std, sigma=2),\n", + " edgecolor=\"k\",\n", + " color=\"C0\",\n", + " alpha=0.2,\n", ")\n", "\n", - "# Get plate tectonic stats for Muller et al 2016\n", - "for t, time in enumerate(muller16_time_array):\n", - " muller16_data[:,t] = get_plate_tectonic_stats(muller2016, time)\n", - " #print(\"Calculated stats for {} Ma!\".format(time))\n", - " gplately.tools.update_progress((max_time-time)/(max_time-min_time))\n", - "print(\"Done calculating stats!\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "84c8ebcf", - "metadata": {}, - "outputs": [], - "source": [ - "# Turn the Muller et al 2016 plate tectoncic stats array into a pandas dataframe.\n", - "muller16_df = pd.DataFrame(np.column_stack(muller16_data), columns = headers)\n", - "\n", - "# Save dataframe to CSV\n", - "muller16_df.to_csv(\"./NotebookFiles/Muller2016-PlateTectonicStats.csv\", encoding='utf-8', index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e9c7afec", - "metadata": {}, - "outputs": [], - "source": [ - "# MERDITH 2021\n", - "# Create a time array\n", - "min_time = 0 \n", - "max_time = 250\n", - "merdith21_time_array = np.arange(max_time,min_time-1, -1)\n", - "\n", - "# Initialise data ndarray\n", - "merdith21_data = np.zeros((11,merdith21_time_array.size))\n", - "\n", - "# Create the PlateReconstruction object with Merdith 2021\n", - "m = PlateModelManager().get_model(\"Merdith2021\", data_dir=\"plate-model-repo\")\n", - "merdith2021 = gplately.PlateReconstruction(\n", - " m.get_rotation_model(),\n", - " m.get_topologies(),\n", - " m.get_static_polygons(),\n", + "ax1.plot(\n", + " reconstruction_times, subd_vels_mean_smoothed, label=\"Subduction convergence rate\"\n", ")\n", - "\n", - "# Get plate tectonic stats for Merdith 2021\n", - "for t, time in enumerate(merdith21_time_array):\n", - " merdith21_data[:,t] = get_plate_tectonic_stats(merdith2021, time)\n", - " #print(\"Calculated stats for {} Ma!\".format(time))\n", - " gplately.tools.update_progress((max_time-time)/(max_time-min_time))\n", - "print(\"Done calculating stats!\")" + "ax1.fill_between(\n", + " reconstruction_times,\n", + " gaussian_filter(subd_vels_mean_smoothed - subd_vels_std, sigma=2),\n", + " gaussian_filter(subd_vels_mean_smoothed + subd_vels_std, sigma=2),\n", + " edgecolor=\"k\",\n", + " color=\"C1\",\n", + " alpha=0.2,\n", + ")\n", + "plt.legend(loc=\"upper right\", frameon=False)\n", + "plt.show()" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "8ed2f785", + "cell_type": "markdown", + "id": "33a85cf2", "metadata": {}, - "outputs": [], "source": [ - "# Turn the Merdith 2021 plate tectoncic stats array into a pandas dataframe.\n", - "merdith21_df = pd.DataFrame(np.column_stack(merdith21_data), columns = headers)\n", + "## Visualising plate tectonic stats\n", "\n", - "# Save dataframe to CSV\n", - "merdith21_df.to_csv(\"./NotebookFiles/Merdith2021-PlateTectonicStats.csv\", encoding='utf-8', index=False)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "82f21113-541b-4993-bee6-2e428f6070b7", - "metadata": {}, - "outputs": [], - "source": [ - "# Zahirovic et al. 2022\n", - "# Create a time array\n", - "min_time = 0 \n", - "max_time = 250\n", - "zahirovic22_time_array = np.arange(max_time,min_time-1, -1)\n", - "\n", - "# Initialise data ndarray\n", - "zahirovic22_data = np.zeros((11, zahirovic22_time_array.size))\n", - "\n", - "# Create the PlateReconstruction object with Zahirovic et al. 2022\n", - "m = PlateModelManager().get_model(\"Zahirovic2022\", data_dir=\"plate-model-repo\")\n", - "zahirovic2022 = gplately.PlateReconstruction(\n", - " m.get_rotation_model(),\n", - " m.get_topologies(),\n", - " m.get_static_polygons(),\n", - ")\n", + "Read and plot data from the plate tectonic stats dataframes we just created for all four plate models, using the **pandas** library.\n", "\n", - "# Get plate tectonic stats for Merdith 2021\n", - "for t, time in enumerate(zahirovic22_time_array):\n", - " zahirovic22_data[:,t] = get_plate_tectonic_stats(zahirovic2022, time)\n", - " #print(\"Calculated stats for {} Ma!\".format(time))\n", - " gplately.tools.update_progress((max_time-time)/(max_time-min_time))\n", - "print(\"Done calculating stats!\")" + "### Global mid-ocean ridge and subduction zone lengths" ] }, { "cell_type": "code", "execution_count": null, - "id": "1454cb90-79ed-4de4-8024-9ffc0b20424e", + "id": "plot-helpers", "metadata": {}, "outputs": [], "source": [ - "# Turn the Zahirovic et al. 2022 plate tectoncic stats array into a pandas dataframe.\n", - "zahirovic22_df = pd.DataFrame(np.column_stack(zahirovic22_data), columns = headers)\n", - "\n", - "# Save dataframe to CSV\n", - "zahirovic22_df.to_csv(\"./NotebookFiles/Zahirovic2022-PlateTectonicStats.csv\", encoding='utf-8', index=False)" - ] - }, - { - "cell_type": "markdown", - "id": "33a85cf2", - "metadata": {}, - "source": [ - "## Visualising plate tectonic stats\n", - "\n", - "Read and plot data from the plate tectonic stats dataframes we just created for the Muller et al. 2016 and Merdith 2021 plate models using the **pandas** library.\n", - "\n", - "### Global mid-ocean ridge and subduction zone lengths" + "# Consistent per-model plotting style, reused across all comparison plots below.\n", + "MODEL_STYLE = {\n", + " \"muller19\": dict(color=\"k\", linestyle=\":\", alpha=1.0, label=\"Muller et al. 2019\"),\n", + " \"muller16\": dict(color=\"k\", linestyle=\"-\", alpha=0.7, label=\"Muller et al. 2016\"),\n", + " \"merdith21\": dict(color=\"k\", linestyle=\"-\", alpha=0.3, label=\"Merdith et al. 2021\"),\n", + " \"zahirovic22\": dict(\n", + " color=\"k\", linestyle=\"-.\", alpha=0.5, label=\"Zahirovic et al. 2022\"\n", + " ),\n", + "}\n", + "\n", + "\n", + "def plot_model_stat(ax, column, sigma=1, sd_column=None):\n", + " \"\"\"Plot `column` from every model's dataframe on `ax`, using a consistent style per model.\n", + "\n", + " If `sd_column` is given, also draws a shaded +/- 1 standard deviation band per model.\n", + " \"\"\"\n", + " for key, df in model_dfs.items():\n", + " time = df[\"Time (Ma)\"]\n", + " values = df[column]\n", + " ax.plot(time, gaussian_filter(values, sigma=sigma), **MODEL_STYLE[key])\n", + " if sd_column is not None:\n", + " sd = df[sd_column]\n", + " ax.fill_between(\n", + " time,\n", + " gaussian_filter(values - sd, sigma=2),\n", + " gaussian_filter(values + sd, sigma=2),\n", + " edgecolor=\"k\",\n", + " color=\"k\",\n", + " alpha=0.05,\n", + " )\n", + "\n", + "\n", + "def style_axis(ax, bottom=False):\n", + " \"\"\"Apply the tick styling used consistently across the comparison plots below.\"\"\"\n", + " ax.tick_params(direction=\"in\", length=5, top=True, right=True)\n", + " if bottom:\n", + " ax.tick_params(axis=\"x\", which=\"major\", direction=\"inout\", length=5)\n", + " ax.tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", + " ax.minorticks_on()\n", + "\n", + "\n", + "def finalize_time_axes(axes, max_time, labelpad=None):\n", + " \"\"\"Share the x-axis across stacked panels: hide tick labels except on the bottom panel.\"\"\"\n", + " for i, ax in enumerate(axes):\n", + " ax.set_xlim(max_time, 0)\n", + " if i < len(axes) - 1:\n", + " ax.set_xticklabels([])\n", + " else:\n", + " ax.set_xlabel(\"Age (Ma)\", labelpad=labelpad)" ] }, { @@ -482,61 +445,23 @@ "metadata": {}, "outputs": [], "source": [ - "fig, axes = plt.subplots(2, 1, figsize=(8,6), dpi=200)\n", + "fig, axes = plt.subplots(2, 1, figsize=(8, 6), dpi=200)\n", "fig.subplots_adjust(hspace=0.05)\n", - "n_axes = len(axes)\n", - "\n", - "# Access subduction zone and ridge lengths from pandas dataframes\n", - "column_name = 'Global subduction zone lengths (km)'\n", - "muller19_trench_lengths = muller19_df[column_name].to_list()\n", - "muller16_trench_lengths = muller16_df[column_name].to_list()\n", - "merdith21_trench_lengths = merdith21_df[column_name].to_list()\n", - "zahirovic22_trench_lengths = zahirovic22_df[column_name].to_list()\n", - "\n", - "column_name = 'Global mid-ocean ridge lengths (km)'\n", - "muller19_ridge_lengths = muller19_df[column_name].to_list()\n", - "muller16_ridge_lengths = muller16_df[column_name].to_list()\n", - "merdith21_ridge_lengths = merdith21_df[column_name].to_list()\n", - "zahirovic22_ridge_lengths = zahirovic22_df[column_name].to_list()\n", - "\n", - "# Prepare different time arrays for each model as Muller 2016 stats start at 230 Ma, while \n", - "# the others start at 250 Ma\n", - "muller19_time_array = muller19_df['Time (Ma)'].to_list()\n", - "muller16_time_array = muller16_df['Time (Ma)'].to_list()\n", - "merdith21_time_array = merdith21_df['Time (Ma)'].to_list()\n", - "zahirovic22_time_array = zahirovic22_df['Time (Ma)'].to_list()\n", - "\n", - "\n", - "# Subduction zone length plot\n", - "axes[0].set_ylabel('Subduction zone\\nlengths (km)')\n", + "\n", + "axes[0].set_ylabel(\"Subduction zone\\nlengths (km)\")\n", "axes[0].set_title(\"(a)\", x=-0.1, y=1, fontsize=\"12\")\n", - "axes[0].plot(muller19_time_array, gaussian_filter(muller19_trench_lengths, sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[0].plot(muller16_time_array, gaussian_filter(muller16_trench_lengths,sigma=1), c='k', alpha = 0.7, label='Muller et al. 2016')\n", - "axes[0].plot(merdith21_time_array, gaussian_filter(merdith21_trench_lengths,sigma=1), c='k', alpha = 0.3, label=\"Merdith 2021\")\n", - "axes[0].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_trench_lengths,sigma=1), c='k', alpha = 0.7, linestyle=\"-.\", label=\"Zahirovic 2022\")\n", - "axes[0].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[0].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[0].minorticks_on()\n", - "\n", - "# Mid-ocean ridge length plot\n", - "axes[1].set_ylabel('Mid-ocean ridge\\nlengths (km)')\n", + "plot_model_stat(axes[0], \"Global subduction zone lengths (km)\")\n", + "style_axis(axes[0])\n", + "\n", + "axes[1].set_ylabel(\"Mid-ocean ridge\\nlengths (km)\")\n", "axes[1].set_title(\"(b)\", x=-0.1, y=1, fontsize=\"12\")\n", - "axes[1].plot(muller19_time_array, gaussian_filter(muller19_ridge_lengths,sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[1].plot(muller16_time_array, gaussian_filter(muller16_ridge_lengths,sigma=1), c='k', alpha = 0.7, label=\"Muller et al. 2016\")\n", - "axes[1].plot(merdith21_time_array, gaussian_filter(merdith21_ridge_lengths,sigma=1), c='k', alpha = 0.3, label=\"Merdith 2021\")\n", - "axes[1].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_ridge_lengths,sigma=1), c='k', alpha = 0.7, linestyle=\"-.\", label=\"Zahirovic 2022\")\n", - "axes[1].legend(bbox_to_anchor=(0.9, -0.2), ncol = 3, frameon=False)\n", - "axes[1].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[1].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[1].minorticks_on()\n", - "\n", - "# Place tick labels only on the bottom plot\n", - "for i, ax in enumerate(axes):\n", - " ax.set_xlim(max_time, min_time)\n", - " if i < n_axes-1:\n", - " ax.set_xticklabels([])\n", - " else:\n", - " ax.set_xlabel(\"Age (Ma)\")" + "plot_model_stat(axes[1], \"Global mid-ocean ridge lengths (km)\")\n", + "axes[1].legend(bbox_to_anchor=(0.9, -0.2), ncol=3, frameon=False)\n", + "style_axis(axes[1], bottom=True)\n", + "\n", + "max_time = max(df[\"Time (Ma)\"].max() for df in model_dfs.values())\n", + "finalize_time_axes(axes, max_time)\n", + "plt.show()" ] }, { @@ -554,164 +479,46 @@ "metadata": {}, "outputs": [], "source": [ - "fig, axes = plt.subplots(3, 1, figsize=(8,9), dpi=200)\n", + "fig, axes = plt.subplots(3, 1, figsize=(8, 9), dpi=200)\n", "fig.subplots_adjust(hspace=0.05)\n", - "n_axes = len(axes)\n", - "\n", - "# Access subduction convergence and ridge spreading rates (along with their standard deviation) from the dataframes\n", - "# Turn lists into numpy arrays.\n", - "column_name = 'Mean global ridge velocities (cm/yr)'\n", - "muller19_subduction_rate = np.array(muller19_df[column_name].to_list())\n", - "muller16_subduction_rate = np.array(muller16_df[column_name].to_list())\n", - "merdith21_subduction_rate = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_subduction_rate = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Global ridge velocity standard deviation (cm/yr)'\n", - "muller19_subduction_rate_sd = np.array(muller19_df[column_name].to_list())\n", - "muller16_subduction_rate_sd = np.array(muller16_df[column_name].to_list())\n", - "merdith21_subduction_rate_sd = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_subduction_rate_sd = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Mean global subduction velocities (cm/yr)'\n", - "muller19_spreading_rate = np.array(muller19_df[column_name].to_list())\n", - "muller16_spreading_rate = np.array(muller16_df[column_name].to_list())\n", - "merdith21_spreading_rate = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_spreading_rate = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Global subduction velocity standard deviation (cm/yr)'\n", - "muller19_spreading_rate_sd = np.array(muller19_df[column_name].to_list())\n", - "muller16_spreading_rate_sd = np.array(muller16_df[column_name].to_list())\n", - "merdith21_spreading_rate_sd = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_spreading_rate_sd = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Mean global trench velocities (cm/yr)'\n", - "muller19_trench_rate = np.array(muller19_df[column_name].to_list())\n", - "muller16_trench_rate = np.array(muller16_df[column_name].to_list())\n", - "merdith21_trench_rate = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_trench_rate = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Global trench velocity standard deviation (cm/yr)'\n", - "muller19_trench_rate_sd = np.array(muller19_df[column_name].to_list())\n", - "muller16_trench_rate_sd = np.array(muller16_df[column_name].to_list())\n", - "merdith21_trench_rate_sd = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_trench_rate_sd = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "# Subduction convergence rates with standard deviation\n", - "axes[0].set_ylabel('Ridge spreading \\n rate (cm/yr)', fontsize=\"12\")\n", + "\n", + "axes[0].set_ylabel(\"Ridge spreading \\n rate (cm/yr)\", fontsize=\"12\")\n", "axes[0].yaxis.set_label_coords(-0.125, 0.45)\n", "axes[0].set_title(\"(a)\", x=-0.2, y=0.9, fontsize=\"12\")\n", "axes[0].set_ylim([0, 18])\n", - "# Muller et al. 2019\n", - "axes[0].plot(muller19_time_array, gaussian_filter(muller19_subduction_rate, sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[0].fill_between(muller19_time_array, \n", - " gaussian_filter(muller19_subduction_rate-muller19_subduction_rate_sd, sigma=2), \n", - " gaussian_filter(muller19_subduction_rate+muller19_subduction_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "# Muller et al. 2016\n", - "axes[0].plot(muller16_time_array, gaussian_filter(muller16_subduction_rate,sigma=1), c='k', alpha = 0.7, label='Muller et al. 2016')\n", - "axes[0].fill_between(muller16_time_array, \n", - " gaussian_filter(muller16_subduction_rate-muller16_subduction_rate_sd, sigma=2), \n", - " gaussian_filter(muller16_subduction_rate+muller16_subduction_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "# Merdith 2021\n", - "axes[0].plot(merdith21_time_array, gaussian_filter(merdith21_subduction_rate,sigma=1), c='k', alpha = 0.3, label=\"Merdith et al. 2021\")\n", - "axes[0].fill_between(merdith21_time_array, \n", - " gaussian_filter(merdith21_subduction_rate-merdith21_subduction_rate_sd, sigma=2), \n", - " gaussian_filter(merdith21_subduction_rate+merdith21_subduction_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "# Zahirovic 2022\n", - "axes[0].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_subduction_rate,sigma=1), c='k', alpha = 0.7, linestyle=\"-.\", label=\"Zahirovic22 et al. 2022\")\n", - "axes[0].fill_between(merdith21_time_array, \n", - " gaussian_filter(merdith21_subduction_rate-merdith21_subduction_rate_sd, sigma=2), \n", - " gaussian_filter(merdith21_subduction_rate+merdith21_subduction_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "# Tick labels\n", - "axes[0].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[0].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[0].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[0].minorticks_on()\n", - "\n", - "\n", - "# Mid-ocean ridge spreading rates with standard deviation\n", - "axes[1].set_ylabel('Subduction convergence \\n rate (cm/yr)', fontsize=\"12\")\n", + "plot_model_stat(\n", + " axes[0],\n", + " \"Mean global ridge velocities (cm/yr)\",\n", + " sd_column=\"Global ridge velocity standard deviation (cm/yr)\",\n", + ")\n", + "style_axis(axes[0])\n", + "\n", + "axes[1].set_ylabel(\"Subduction convergence \\n rate (cm/yr)\", fontsize=\"12\")\n", "axes[1].yaxis.set_label_coords(-0.125, 0.45)\n", "axes[1].set_title(\"(b)\", x=-0.2, y=0.9, fontsize=\"12\")\n", "axes[1].set_ylim([0, 18])\n", - "axes[1].plot(muller19_time_array, gaussian_filter(muller19_spreading_rate,sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[1].fill_between(muller19_time_array, \n", - " gaussian_filter(muller19_spreading_rate-muller19_spreading_rate_sd, sigma=2), \n", - " gaussian_filter(muller19_spreading_rate+muller19_spreading_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[1].plot(muller16_time_array, gaussian_filter(muller16_spreading_rate,sigma=1), c='k', alpha = 0.7, label=\"Muller et al. 2016\")\n", - "axes[1].fill_between(muller16_time_array, \n", - " gaussian_filter(muller16_spreading_rate-muller16_spreading_rate_sd, sigma=2), \n", - " gaussian_filter(muller16_spreading_rate+muller16_spreading_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[1].plot(merdith21_time_array, gaussian_filter(merdith21_spreading_rate,sigma=1), c='k', alpha = 0.3, label=\"Merdith et al. 2021\")\n", - "axes[1].fill_between(merdith21_time_array, \n", - " gaussian_filter(merdith21_spreading_rate-merdith21_spreading_rate_sd, sigma=2), \n", - " gaussian_filter(merdith21_spreading_rate+merdith21_spreading_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[1].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_spreading_rate,sigma=1), c='k', alpha = 0.5, linestyle=\"-.\", label=\"Zahirovic et al. 2022\")\n", - "axes[1].fill_between(merdith21_time_array, \n", - " gaussian_filter(zahirovic22_spreading_rate - zahirovic22_spreading_rate_sd, sigma=2), \n", - " gaussian_filter(zahirovic22_spreading_rate + zahirovic22_spreading_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[1].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[1].tick_params(axis=\"x\", which=\"major\", direction=\"inout\", length=5)\n", - "axes[1].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[1].minorticks_on()\n", - "\n", - " \n", - "# trench velocities with standard deviation\n", - "axes[2].set_ylabel('Trench Velocities (cm/yr)', fontsize=\"12\")\n", + "plot_model_stat(\n", + " axes[1],\n", + " \"Mean global subduction velocities (cm/yr)\",\n", + " sd_column=\"Global subduction velocity standard deviation (cm/yr)\",\n", + ")\n", + "style_axis(axes[1])\n", + "\n", + "axes[2].set_ylabel(\"Trench Velocities (cm/yr)\", fontsize=\"12\")\n", "axes[2].yaxis.set_label_coords(-0.125, 0.45)\n", "axes[2].set_title(\"(c)\", x=-0.2, y=0.9, fontsize=\"12\")\n", "axes[2].set_ylim([-9.5, 13.5])\n", - "axes[2].plot(muller19_time_array, gaussian_filter(muller19_trench_rate,sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[2].fill_between(muller19_time_array, \n", - " gaussian_filter(muller19_trench_rate - muller19_trench_rate_sd, sigma=2), \n", - " gaussian_filter(muller19_trench_rate + muller19_trench_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[2].plot(muller16_time_array, gaussian_filter(muller16_trench_rate,sigma=1), c='k', alpha = 0.7, label=\"Muller et al. 2016\")\n", - "axes[2].fill_between(muller16_time_array, \n", - " gaussian_filter(muller16_trench_rate - muller16_trench_rate_sd, sigma=2), \n", - " gaussian_filter(muller16_trench_rate + muller16_trench_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[2].plot(merdith21_time_array, gaussian_filter(merdith21_spreading_rate,sigma=1), c='k', alpha = 0.3, label=\"Merdith et al. 2021\")\n", - "axes[2].fill_between(merdith21_time_array, \n", - " gaussian_filter(merdith21_trench_rate - merdith21_trench_rate_sd, sigma=2), \n", - " gaussian_filter(merdith21_trench_rate + merdith21_trench_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "axes[2].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_trench_rate,sigma=1), c='k', alpha = 0.5, linestyle=\"-.\", label=\"Zahirovic et al. 2022\")\n", - "axes[2].fill_between(merdith21_time_array, \n", - " gaussian_filter(zahirovic22_trench_rate - zahirovic22_trench_rate_sd, sigma=2), \n", - " gaussian_filter(zahirovic22_trench_rate + zahirovic22_trench_rate_sd, sigma=2),\n", - " edgecolor='k', color='k', alpha=0.05)\n", - "\n", - "\n", - "axes[2].legend(bbox_to_anchor=(0.9, -0.25), ncol = 3, frameon=False)\n", - "axes[2].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[2].tick_params(axis=\"x\", which=\"major\", direction=\"inout\", length=5)\n", - "axes[2].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[2].minorticks_on()\n", - "\n", - "# Place tick labels on the bottom plot only.\n", - "for i, ax in enumerate(axes):\n", - " ax.set_xlim(max_time, min_time)\n", - " if i < n_axes-1:\n", - " ax.set_xticklabels([])\n", - " else:\n", - " ax.set_xlabel(\"Age (Ma)\", labelpad=10)\n" + "plot_model_stat(\n", + " axes[2],\n", + " \"Mean global trench velocities (cm/yr)\",\n", + " sd_column=\"Global trench velocity standard deviation (cm/yr)\",\n", + ")\n", + "axes[2].legend(bbox_to_anchor=(0.9, -0.25), ncol=3, frameon=False)\n", + "style_axis(axes[2], bottom=True)\n", + "\n", + "max_time = max(df[\"Time (Ma)\"].max() for df in model_dfs.values())\n", + "finalize_time_axes(axes, max_time, labelpad=10)\n", + "plt.show()" ] }, { @@ -729,81 +536,33 @@ "metadata": {}, "outputs": [], "source": [ - "fig, axes = plt.subplots(2, 1, figsize=(8,6), dpi=200)\n", + "fig, axes = plt.subplots(2, 1, figsize=(8, 6), dpi=200)\n", "fig.subplots_adjust(hspace=0.05)\n", - "n_axes = len(axes)\n", - "\n", - "\n", - "# Access crustal production and destruction rates from the dataframes\n", - "# Turn lists into numpy arrays.\n", - "column_name = 'Surface area of crust produced at ridges (km^2/yr)'\n", - "muller19_crustal_production_rate = np.array(muller19_df[column_name].to_list())\n", - "muller16_crustal_production_rate = np.array(muller16_df[column_name].to_list())\n", - "merdith21_crustal_production_rate = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_crustal_production_rate = np.array(zahirovic22_df[column_name].to_list())\n", - "\n", - "column_name = 'Surface area of crust subducted at trenches (km^2/yr)'\n", - "muller19_crustal_destruction_rate = np.array(muller19_df[column_name].to_list())\n", - "muller16_crustal_destruction_rate = np.array(muller16_df[column_name].to_list())\n", - "merdith21_crustal_destruction_rate = np.array(merdith21_df[column_name].to_list())\n", - "zahirovic22_crustal_destruction_rate = np.array(zahirovic22_df[column_name].to_list())\n", "\n", - "\n", - "# Crustal production rates\n", - "axes[0].set_ylabel('Crustal production \\n rate (km$^2$/yr)', fontsize=\"12\")\n", + "axes[0].set_ylabel(\"Crustal production \\n rate (km$^2$/yr)\", fontsize=\"12\")\n", "axes[0].yaxis.set_label_coords(-0.125, 0.45)\n", "axes[0].set_title(\"(a)\", x=-0.2, y=0.9, fontsize=\"12\")\n", + "plot_model_stat(axes[0], \"Surface area of crust produced at ridges (km^2/yr)\")\n", + "style_axis(axes[0])\n", "\n", - "axes[0].plot(muller19_time_array, gaussian_filter(muller19_crustal_production_rate, sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[0].plot(muller16_time_array, gaussian_filter(muller16_crustal_production_rate,sigma=1), c='k', alpha = 0.7, label='Muller et al. 2016')\n", - "axes[0].plot(merdith21_time_array, gaussian_filter(merdith21_crustal_production_rate,sigma=1), c='k', alpha = 0.3, label=\"Merdith et al. 2021\")\n", - "axes[0].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_crustal_production_rate,sigma=1), c='k', linestyle=\"-.\", alpha = 0.5, label=\"Zahirovic et al. 2022\")\n", - "\n", - "# Tick labels\n", - "axes[0].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[0].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[0].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[0].minorticks_on()\n", - "\n", - "\n", - "# Crustal destruction rates\n", - "axes[1].set_ylabel('Crustal destruction \\n rate (km$^2$/yr)', fontsize=\"12\")\n", + "axes[1].set_ylabel(\"Crustal destruction \\n rate (km$^2$/yr)\", fontsize=\"12\")\n", "axes[1].yaxis.set_label_coords(-0.125, 0.45)\n", "axes[1].set_title(\"(b)\", x=-0.2, y=0.9, fontsize=\"12\")\n", - "axes[1].plot(muller19_time_array, gaussian_filter(muller19_crustal_destruction_rate,sigma=1), c='k', linestyle=\":\", label=\"Muller et al. 2019\")\n", - "axes[1].plot(muller16_time_array, gaussian_filter(muller16_crustal_destruction_rate,sigma=1), c='k', alpha = 0.7, label=\"Muller et al. 2016\")\n", - "axes[1].plot(merdith21_time_array, gaussian_filter(merdith21_crustal_destruction_rate,sigma=1), c='k', alpha = 0.3, label=\"Merdith et al. 2021\")\n", - "axes[1].plot(zahirovic22_time_array, gaussian_filter(zahirovic22_crustal_destruction_rate,sigma=1), c='k', linestyle=\"-.\", alpha = 0.5, label=\"Zahirovic et al. 2022\")\n", - "\n", - "axes[1].legend(bbox_to_anchor=(0.9, -0.25), ncol = 3, frameon=False)\n", - "axes[1].tick_params(direction=\"in\", length=5, top=True, right=True)\n", - "axes[1].tick_params(axis=\"x\", which=\"major\", direction=\"inout\", length=5)\n", - "axes[1].tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", - "axes[1].minorticks_on()\n", - "\n", - "# Place tick labels on the bottom plot only.\n", - "for i, ax in enumerate(axes):\n", - " ax.set_xlim(max_time, min_time)\n", - " if i < n_axes-1:\n", - " ax.set_xticklabels([])\n", - " else:\n", - " ax.set_xlabel(\"Age (Ma)\", labelpad=10)\n", - "\n", - "fig.savefig(\"crustal_production_destruction.pdf\", bbox_inches='tight')" + "plot_model_stat(axes[1], \"Surface area of crust subducted at trenches (km^2/yr)\")\n", + "axes[1].legend(bbox_to_anchor=(0.9, -0.25), ncol=3, frameon=False)\n", + "style_axis(axes[1], bottom=True)\n", + "\n", + "max_time = max(df[\"Time (Ma)\"].max() for df in model_dfs.values())\n", + "finalize_time_axes(axes, max_time, labelpad=10)\n", + "\n", + "fig.savefig(output_dir / \"crustal_production_destruction.pdf\", bbox_inches=\"tight\")\n", + "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d540bb1f", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -817,7 +576,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/08-PredictingSlabFlux.ipynb b/Notebooks/08-PredictingSlabFlux.ipynb index 5e4be151..aaf31553 100644 --- a/Notebooks/08-PredictingSlabFlux.ipynb +++ b/Notebooks/08-PredictingSlabFlux.ipynb @@ -4,16 +4,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# 8 - Predict slab flux\n", + "# 8 - Predicting slab flux\n", "\n", - "Predict the slab flux of subducting oceanic lithosphere using the thickness of the subducting plate calculated by plate models of lithospheric cooling (from [Grose, 2012](https://doi.org/10.1016/j.epsl.2012.03.037)), the convervenge velocity, and the trench segment length.\n", + "Estimate slab flux of subducting oceanic lithosphere using plate thickness from lithospheric cooling models ([Grose, 2012](https://doi.org/10.1016/j.epsl.2012.03.037)), convergence velocity, and trench-segment length.\n", "\n", "## Data packages\n", "\n", - "A plate reconstruction and corresponding age grids of the seafloor are required to predict slab dip. These may be downloaded from https://www.earthbyte.org/gplates-2-3-software-and-data-sets/\n", - "\n", - "The calculation has been tested on [Clennett _et al._ (2020)](https://doi.org/10.1029/2020GC009117) and [Müller _et al._ (2019)](https://doi.org/10.1029/2018TC005462) plate reconstructions but should also work fine for other plate reconstructions.\n", + "A plate reconstruction and corresponding seafloor age grids are required to estimate slab flux. These can be downloaded from https://www.earthbyte.org/gplates-2-3-software-and-data-sets/\n", "\n", + "The workflow below has been tested with the [Clennett _et al._ (2020)](https://doi.org/10.1029/2020GC009117) and [Müller _et al._ (2019)](https://doi.org/10.1029/2018TC005462) reconstructions, and should also work with other reconstruction models.\n", "\n", "#### References\n", "\n", @@ -28,19 +27,22 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", "import gplately\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from scipy.ndimage import gaussian_filter\n", - "from plate_model_manager import PlateModelManager" + "from plate_model_manager import PlateModelManager\n", + "\n", + "# This workflow takes long time to finish. For a quick run, set the flag below to True\n", + "quick_run = False\n", + "#quick_run = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Let's compare subduction zone data between two plate models: Müller et al. 2016 and Müller et al. 2019." + "We compare subduction-zone data between two plate reconstructions: Müller _et al._ (2016) and Müller _et al._ (2019)." ] }, { @@ -51,29 +53,26 @@ "source": [ "pm_manager = PlateModelManager()\n", "\n", - "muller2019_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "rotation_model = muller2019_model.get_rotation_model()\n", - "topology_features = muller2019_model.get_topologies()\n", - "model = gplately.PlateReconstruction(rotation_model, topology_features)\n", - "\n", - "muller2016_model = pm_manager.get_model(\"Muller2016\", data_dir=\"plate-model-repo\")\n", - "rotation_model2 = muller2016_model.get_rotation_model()\n", - "topology_features2 = muller2016_model.get_topologies()\n", - "model2 = gplately.PlateReconstruction(rotation_model2, topology_features2)\n", + "pmm_model_1 = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", + "assert pmm_model_1 is not None, \"Failed to load the Muller2019 plate model.\"\n", + "rotation_model_1 = pmm_model_1.get_rotation_model()\n", + "topology_features_1 = pmm_model_1.get_topologies()\n", + "model_1 = gplately.PlateReconstruction(rotation_model_1, topology_features_1)\n", "\n", - "# Tessellate the subduction zones to 0.5 degrees.\n", - "tessellation_threshold_radians = np.radians(0.05)\n", - "\n", - "extent_globe = [-180,180,-90,90]" + "pmm_model_2 = pm_manager.get_model(\"Muller2016\", data_dir=\"plate-model-repo\")\n", + "assert pmm_model_2 is not None, \"Failed to load the Muller2016 plate model.\"\n", + "rotation_model_2 = pmm_model_2.get_rotation_model()\n", + "topology_features_2 = pmm_model_2.get_topologies()\n", + "model_2 = gplately.PlateReconstruction(rotation_model_2, topology_features_2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Get kinematic data\n", + "## Retrieve kinematic data\n", "\n", - "We extract plate kinematic data for the present-day configuration of subduction zones to calculate the dip angle of subducting slabs." + "We extract present-day subduction-zone kinematics to estimate slab flux from subducting plate age, convergence rate, and trench-segment length." ] }, { @@ -83,55 +82,67 @@ "outputs": [], "source": [ "def get_subduction_volume_rate(pmm_model, model, reconstruction_time):\n", - " \n", - " # calculate subduction convergence with gplately\n", + " # Calculate subduction convergence using GPlately.\n", " subduction_data = model.tessellate_subduction_zones(\n", - " reconstruction_time, \n", - " output_subducting_absolute_velocity_components=True\n", + " reconstruction_time, output_subducting_absolute_velocity_components=True\n", " )\n", "\n", - " subduction_lon = subduction_data[:,0]\n", - " subduction_lat = subduction_data[:,1]\n", - " subduction_angle = subduction_data[:,3]\n", - " subduction_norm = subduction_data[:,7]\n", - " subduction_pid_sub = subduction_data[:,8]\n", - " subduction_pid_over= subduction_data[:,9]\n", - " subduction_length = np.radians(subduction_data[:,6])*gplately.tools.geocentric_radius(subduction_data[:,1])\n", - " subduction_convergence = np.fabs(subduction_data[:,2])*1e-2 * np.cos(np.radians(subduction_data[:,3]))\n", - " subduction_migration = np.fabs(subduction_data[:,4])*1e-2 * np.cos(np.radians(subduction_data[:,5]))\n", - " subduction_plate_vel = subduction_data[:,10]\n", + " subduction_lon = subduction_data[:, 0]\n", + " subduction_lat = subduction_data[:, 1]\n", + " subduction_angle = subduction_data[:, 3]\n", + " subduction_norm = subduction_data[:, 7]\n", + " subduction_pid_sub = subduction_data[:, 8]\n", + " subduction_pid_over = subduction_data[:, 9]\n", + " subduction_length = np.radians(\n", + " subduction_data[:, 6]\n", + " ) * gplately.tools.geocentric_radius(subduction_data[:, 1])\n", + " subduction_convergence = (\n", + " np.fabs(subduction_data[:, 2])\n", + " * 1e-2\n", + " * np.cos(np.radians(subduction_data[:, 3]))\n", + " )\n", + " subduction_migration = (\n", + " np.fabs(subduction_data[:, 4])\n", + " * 1e-2\n", + " * np.cos(np.radians(subduction_data[:, 5]))\n", + " )\n", + " subduction_plate_vel = subduction_data[:, 10]\n", "\n", - " # remove entries that have \"negative\" subduction\n", - " # this occurs when the subduction obliquity is greater than 90 degrees\n", + " # Remove entries with effectively negative subduction.\n", + " # This occurs when subduction obliquity is greater than 90 degrees.\n", " subduction_convergence = np.clip(subduction_convergence, 0, 1e99)\n", "\n", - " # sample AgeGrid for current timestep\n", + " # Sample the AgeGrid for the current timestep.\n", "\n", - " # returns a Raster object\n", + " # Returns a Raster object.\n", " graster = gplately.Raster(\n", " data=pmm_model.get_raster(\"AgeGrids\", reconstruction_time),\n", " plate_reconstruction=model,\n", - " extent=[-180, 180, -90, 90],\n", - " )\n", + " extent=(-180, 180, -90, 90),\n", + " )\n", " graster.fill_NaNs(inplace=True)\n", " age_interp = graster.interpolate(subduction_lon, subduction_lat)\n", "\n", " subduction_age = age_interp\n", " thickness = gplately.tools.plate_isotherm_depth(age_interp)\n", "\n", - " # calculate subduction volume rate - m * m * m/yr\n", - " subduction_vol_rate = thickness*subduction_length*subduction_convergence # integrated along subduction len\n", - " subduction_vol_rate *= 1e-9 # convert m^3/yr to km^3/yr\n", + " # Calculate subduction volume rate in m^3/yr.\n", + " subduction_vol_rate = (\n", + " thickness * subduction_length * subduction_convergence\n", + " ) # Integrated along total subduction-segment length.\n", + " subduction_vol_rate *= 1e-9 # Convert m^3/yr to km^3/yr.\n", "\n", - " mean_plate_thickness = thickness.mean()\n", - " mean_subduction_segment_length = subduction_length.sum()\n", + " mean_plate_thickness = thickness.mean()\n", + " mean_subduction_segment_length = subduction_length.sum()\n", " mean_subduction_convergence_rate = subduction_convergence.mean()\n", - " total_subduction_volume_rate = subduction_vol_rate.sum()\n", - " \n", - " return (mean_plate_thickness,\n", - " mean_subduction_segment_length,\n", - " mean_subduction_convergence_rate,\n", - " total_subduction_volume_rate)" + " total_subduction_volume_rate = subduction_vol_rate.sum()\n", + "\n", + " return (\n", + " mean_plate_thickness,\n", + " mean_subduction_segment_length,\n", + " mean_subduction_convergence_rate,\n", + " total_subduction_volume_rate,\n", + " )" ] }, { @@ -142,16 +153,20 @@ }, "outputs": [], "source": [ - "# Calculate the total subduction volume rate (km^3/yr) per timestep for each plate model\n", - "reconstruction_times = np.arange(0,231)\n", + "# Calculate total subduction volume rate (km^3/yr) at each timestep for each model.\n", "\n", - "# Muller 2019 model\n", - "thk2019 = np.zeros(reconstruction_times.size) # mean plate thickness\n", - "len2019 = np.zeros(reconstruction_times.size) # subduction zone length\n", - "vel2019 = np.zeros(reconstruction_times.size) # mean convergence velocity\n", - "vol2019 = np.zeros(reconstruction_times.size) # subduction flux\n", + "reconstruction_times = np.arange(0, 231)\n", + "if quick_run:\n", + " # This workflow takes long time to finish. For debug purpose, use the \"reconstruction_times\" below\n", + " reconstruction_times = np.array([0, 230])\n", "\n", - "# Muller 2016 model\n", + "# Müller 2019 model\n", + "thk2019 = np.zeros(reconstruction_times.size) # mean plate thickness\n", + "len2019 = np.zeros(reconstruction_times.size) # subduction-zone length\n", + "vel2019 = np.zeros(reconstruction_times.size) # mean convergence velocity\n", + "vol2019 = np.zeros(reconstruction_times.size) # subduction flux\n", + "\n", + "# Müller 2016 model\n", "thk2016 = np.zeros(reconstruction_times.size)\n", "len2016 = np.zeros(reconstruction_times.size)\n", "vel2016 = np.zeros(reconstruction_times.size)\n", @@ -159,11 +174,15 @@ "\n", "\n", "for t, time in enumerate(reconstruction_times):\n", - " thk2019[t], len2019[t], vel2019[t], vol2019[t] = get_subduction_volume_rate(muller2019_model, model, time)\n", - " thk2016[t], len2016[t], vel2016[t], vol2016[t] = get_subduction_volume_rate(muller2016_model, model2, time)\n", + " thk2019[t], len2019[t], vel2019[t], vol2019[t] = get_subduction_volume_rate(\n", + " pmm_model_1, model_1, time\n", + " )\n", + " thk2016[t], len2016[t], vel2016[t], vol2016[t] = get_subduction_volume_rate(\n", + " pmm_model_2, model_2, time\n", + " )\n", "\n", - " gplately.tools.update_progress(time/reconstruction_times.size)\n", - "gplately.tools.update_progress(1) " + " gplately.tools.update_progress(time / reconstruction_times.size)\n", + "gplately.tools.update_progress(1)" ] }, { @@ -172,9 +191,9 @@ "source": [ "### Parallel processing\n", "\n", - "GPlately supports parallel processing to distribute tasks over multiple processors. We recommend the [`joblib`](https://joblib.readthedocs.io/en/latest/) package to efficiently manage parallel resources. Below we demonstrate how the `get_subduction_volume_rate` function we defined above can be executed over multiple processors.\n", + "GPlately supports parallel execution across multiple CPU cores. Here we use [`joblib`](https://joblib.readthedocs.io/en/latest/) to run `get_subduction_volume_rate` across many timesteps efficiently.\n", "\n", - "> Note: On Windows platforms it appears `joblib` is running slower in parallel than in serial. So we disable parallel processing (on Windows) with `n_jobs=None`." + "> Note: On Windows, `joblib` may run slower in parallel than in serial for this workflow. We therefore disable parallel processing on Windows by setting `n_jobs=None`." ] }, { @@ -186,16 +205,19 @@ "from joblib import Parallel, delayed\n", "import platform\n", "\n", - "# Use serial processing on Windows (runs slower in parallel than in serial for some reason). \n", - "if platform.system() == 'Windows':\n", + "# Use serial processing on Windows (parallel can be slower for this workflow).\n", + "if platform.system() == \"Windows\":\n", " n_jobs = None\n", "else:\n", - " n_jobs = -3 # use all CPUs except 2\n", + " n_jobs = -3 # Use all CPUs except two.\n", "\n", - "# Use Loky Backend\n", - "parallel = Parallel(n_jobs=n_jobs, backend='loky', verbose=1)\n", + "# Use the Loky backend.\n", + "parallel = Parallel(n_jobs=n_jobs, backend=\"loky\", verbose=1)\n", "\n", - "reconstruction_times = np.arange(0, 231)" + "reconstruction_times = np.arange(0, 231)\n", + "if quick_run:\n", + " # This workflow takes long time to finish. For debug purpose, use the \"reconstruction_times\" below\n", + " reconstruction_times = np.array([0, 230])" ] }, { @@ -204,8 +226,14 @@ "metadata": {}, "outputs": [], "source": [ - "muller_2016_data = parallel(delayed(get_subduction_volume_rate)(muller2016_model, model2, time) for time in reconstruction_times)\n", - "muller_2019_data = parallel(delayed(get_subduction_volume_rate)(muller2019_model, model, time) for time in reconstruction_times)" + "muller_2016_data = parallel(\n", + " delayed(get_subduction_volume_rate)(pmm_model_2, model_2, time)\n", + " for time in reconstruction_times\n", + ")\n", + "muller_2019_data = parallel(\n", + " delayed(get_subduction_volume_rate)(pmm_model_1, model_1, time)\n", + " for time in reconstruction_times\n", + ")" ] }, { @@ -214,7 +242,7 @@ "metadata": {}, "outputs": [], "source": [ - "# unpack numpy arrays\n", + "# Unpack arrays.\n", "thk2016, len2016, vel2016, vol2016 = np.array(muller_2016_data).T\n", "thk2019, len2019, vel2019, vol2019 = np.array(muller_2019_data).T" ] @@ -223,7 +251,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Plot slab flux" + "### Plot slab flux through time" ] }, { @@ -232,34 +260,38 @@ "metadata": {}, "outputs": [], "source": [ - "# Plot this data using a Gaussian filter\n", - "fig = plt.figure(figsize=(12,6), dpi=300)\n", + "# Plot the slab-flux time series with light Gaussian smoothing.\n", + "fig = plt.figure(figsize=(12, 6), dpi=300)\n", "\n", "muller2016_volumes_smoothed = gaussian_filter(vol2016, sigma=1)\n", "muller2019_volumes_smoothed = gaussian_filter(vol2019, sigma=1)\n", - "plt.plot(reconstruction_times, muller2016_volumes_smoothed,\n", - " color=\"k\", label=\"Müller et al. (2016)\")\n", - "plt.plot(reconstruction_times, muller2019_volumes_smoothed,\n", - " linestyle=\"--\", alpha=0.5, color=\"k\", label=\"Müller et al. (2019)\")\n", - " \n", + "plt.plot(\n", + " reconstruction_times,\n", + " muller2016_volumes_smoothed,\n", + " color=\"k\",\n", + " label=\"Müller et al. (2016)\",\n", + ")\n", + "plt.plot(\n", + " reconstruction_times,\n", + " muller2019_volumes_smoothed,\n", + " linestyle=\"--\",\n", + " alpha=0.5,\n", + " color=\"k\",\n", + " label=\"Müller et al. (2019)\",\n", + ")\n", + "\n", "# Plot settings\n", - "plt.title(\"Total subduction volume rate per Ma\")\n", - "plt.xlabel('Time (Ma)')\n", - "plt.ylabel('km$^3$/yr')\n", - "plt.legend(loc='upper center', bbox_to_anchor=(0.5, -0.1), ncol=6)" + "plt.title(\"Total subduction flux through time\")\n", + "plt.xlabel(\"Time (Ma)\")\n", + "plt.ylabel(\"Subduction flux (km$^3$/yr)\")\n", + "plt.legend(loc=\"upper center\", bbox_to_anchor=(0.5, -0.1), ncol=2)\n", + "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -273,12 +305,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.11" - }, - "vscode": { - "interpreter": { - "hash": "a10fed8c503fa0e7abcec38684bcaa5ab84af52f4a155e8c08912d91252721a5" - } + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/09-CreatingMotionPathsAndFlowlines.ipynb b/Notebooks/09-CreatingMotionPathsAndFlowlines.ipynb index 2d490b18..c3e9a01b 100644 --- a/Notebooks/09-CreatingMotionPathsAndFlowlines.ipynb +++ b/Notebooks/09-CreatingMotionPathsAndFlowlines.ipynb @@ -9,9 +9,9 @@ "\n", "In this notebook, we will show how GPlately's `PlateReconstruction` object:\n", "- Creates a motion path to illustrate the trajectory of a tectonic plate through geological time, and\n", - "- Creates a flowlines to track the motion of tectonic plates from spreading features like mid-ocean ridges.\n", + "- Creates flowlines to track the motion of tectonic plates from spreading features like mid-ocean ridges.\n", "\n", - "Also, the rate of motion through time, of the motion path and flowlines, are quantified." + "Also, the rate of motion through time, of the motion path and flowlines, is quantified." ] }, { @@ -22,22 +22,21 @@ "outputs": [], "source": [ "import gplately\n", - "\n", + "import os, warnings\n", + "from pathlib import Path\n", "import numpy as np\n", - "import pygplates\n", - "import glob, os\n", "import matplotlib.pyplot as plt\n", - "import matplotlib.axes as axs\n", "import cartopy.crs as ccrs\n", - "import matplotlib\n", - "from matplotlib import image\n", "from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter\n", "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "from shapely.geometry import LineString\n", - "import geopandas as gpd\n", "from plate_model_manager import PlateModelManager, PresentDayRasterManager\n", "\n", - "%matplotlib inline" + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", + "data_dir = Path(\"9-Creating-Motion-Paths-and-Flowlines-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)" ] }, { @@ -60,20 +59,25 @@ "outputs": [], "source": [ "# Download Matthews2016 model\n", - "pm_manager = PlateModelManager()\n", - "matthews2016_model = pm_manager.get_model(\"Matthews2016\", data_dir=\"plate-model-repo\")\n", + "matthews2016_model = PlateModelManager().get_model(\n", + " \"Matthews2016\", data_dir=\"plate-model-repo\"\n", + ")\n", "\n", "rotation_model = matthews2016_model.get_rotation_model()\n", "topology_features = matthews2016_model.get_topologies()\n", "static_polygons = matthews2016_model.get_static_polygons()\n", "\n", - "coastlines = matthews2016_model.get_layer('Coastlines')\n", + "coastlines = matthews2016_model.get_layer(\"Coastlines\")\n", "\n", "# Create a PlateReconstruction object\n", - "model = gplately.reconstruction.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", + "model = gplately.reconstruction.PlateReconstruction(\n", + " rotation_model, topology_features, static_polygons\n", + ")\n", "\n", "# Create a PlotTopologies object\n", - "gplot = gplately.PlotTopologies(model, coastlines=coastlines, continents=None, COBs=None, time=0)" + "gplot = gplately.PlotTopologies(\n", + " model, coastlines=coastlines, continents=None, COBs=None, time=0\n", + ")" ] }, { @@ -83,7 +87,7 @@ "source": [ "### 1. The motion path of a Hawaiian-Emperor chain seed point\n", "\n", - "The longitude and latitude coordinates of present-day Hawaii is (-155, 19). \n", + "The longitude and latitude coordinates of present-day Hawaii are (-155, 19). \n", "The easiest way to understand _motion paths_ is as a hot spot trail, where we want to trace the path that a hot spot in the relatively stationary reference frame (`moving_plate_ID = 2`) imprints on the overriding Pacific plate (`relative_plate_ID = 901`). This 901-2 reconstruction ID pair is from Wessel and Kroenke's (2008) WK08-A model. \n", "\n", "#### Step plots of rates of motion\n", @@ -108,10 +112,12 @@ "relative_plate_ID = 901\n", "\n", "# Create the time array for the motion path - must be float\n", - "start_reconstruction_time = 0.\n", - "time_step = 2.\n", - "max_reconstruction_time = 100.\n", - "time_array = np.arange(start_reconstruction_time, max_reconstruction_time + time_step, time_step)\n", + "start_reconstruction_time = 0.0\n", + "time_step = 2.0\n", + "max_reconstruction_time = 100.0\n", + "time_array = np.arange(\n", + " start_reconstruction_time, max_reconstruction_time + time_step, time_step\n", + ")\n", "\n", "# Get the latitudes and longitudes of all points along the motion path\n", "rlons, rlats, rtimes, rates_of_motion = model.create_motion_path(\n", @@ -121,7 +127,8 @@ " plate_id=moving_plate_ID,\n", " relative_plate_id=relative_plate_ID,\n", " return_times=True,\n", - " return_rate_of_motion=True)" + " return_rate_of_motion=True,\n", + ")" ] }, { @@ -140,10 +147,11 @@ "outputs": [], "source": [ "from matplotlib import image\n", - "raster_manager = PresentDayRasterManager()\n", + "\n", "etopo_tif_img = gplately.Raster(\n", - " data=image.imread(raster_manager.get_raster(\"ETOPO1_tif\")),\n", - " plate_reconstruction=model)\n", + " data=image.imread(PresentDayRasterManager().get_raster(\"ETOPO1_tif\")),\n", + " plate_reconstruction=model,\n", + ")\n", "etopo_tif_img.resample(0.25, 0.25, inplace=True)\n", "etopo_tif_img.lats = etopo_tif_img.lats[::-1]" ] @@ -157,15 +165,18 @@ "source": [ "from matplotlib.colors import ListedColormap\n", "\n", + "\n", "def plot_motion_path(rlons, rlats, rtimes, rates_of_motion, time=0):\n", " # --- Create figure\n", - " fig = plt.figure(figsize=(12,5), dpi=200)\n", - " plt.suptitle(\"Motion path of the Hawaiian-Emperor Chain (at {} Ma)\".format(time), weight=\"demi\")\n", + " fig = plt.figure(figsize=(12, 5), dpi=200)\n", + " plt.suptitle(\n", + " f\"Motion path of the Hawaiian-Emperor Chain (at {time} Ma)\", weight=\"demi\"\n", + " )\n", " gs = fig.add_gridspec(1, 7)\n", - " \n", + "\n", " ax = fig.add_subplot(gs[0, :4], projection=ccrs.Mercator(central_longitude=180))\n", " ax.set_title(\"Motion paths\", fontsize=14)\n", - " \n", + "\n", " # --- Plot tick labels\n", " ax.set_xticks(np.arange(0, 360, 20), crs=ccrs.PlateCarree())\n", " ax.set_yticks(np.arange(-90, 100, 10), crs=ccrs.PlateCarree())\n", @@ -173,64 +184,92 @@ " ax.set_yticks(np.arange(-90, 90, 5), minor=True, crs=ccrs.PlateCarree())\n", " lon_formatter = LongitudeFormatter(zero_direction_label=True)\n", " lat_formatter = LatitudeFormatter()\n", - " ax.xaxis.tick_bottom() # labelled ticks on top\n", + " ax.xaxis.tick_bottom() # labelled ticks on bottom\n", " ax.xaxis.set_major_formatter(lon_formatter)\n", " ax.yaxis.set_major_formatter(lat_formatter)\n", - " ax.xaxis.set_ticks_position('both') # ticks on both top/bottom\n", - " ax.yaxis.set_ticks_position('both') # ticks on both left/right\n", + " ax.xaxis.set_ticks_position(\"both\") # ticks on both top/bottom\n", + " ax.yaxis.set_ticks_position(\"both\") # ticks on both left/right\n", " ax.grid()\n", - " \n", + "\n", " # --- Limit map extent\n", " lon_min = 130\n", " lon_max = 220\n", " lat_min = 0\n", " lat_max = 60\n", - " ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())\n", + " ax.set_extent((lon_min, lon_max, lat_min, lat_max), crs=ccrs.PlateCarree())\n", "\n", " gplot.time = time\n", - " \n", + "\n", " # --- Plot filled coastlines\n", " gplot.plot_coastlines(ax, facecolor=\"white\", edgecolor=None, zorder=2)\n", - " \n", + "\n", " # ---- Plot bathymetry\n", - " etopo_tif_img.reconstruct(time=time, fill_value='darkblue').imshow(alpha=0.5, zorder=1)\n", - " \n", - " # --- Make sure negtive motion path longitudes are wrapped correctly to the dateline\n", + " etopo_tif_img.reconstruct(time=time, fill_value=\"darkblue\").imshow(\n", + " alpha=0.5, zorder=1\n", + " )\n", + "\n", + " # --- Make sure negative motion path longitudes are wrapped correctly to the dateline\n", " rlons = gplately.tools.correct_longitudes_for_dateline(rlons)\n", - " \n", + "\n", " # --- plot motion path\n", - " colors = ['yellow']\n", " # The mapping of 'rtimes' to colours should not change.\n", " # Ie, each time should map to the same colour (regardless of the time range).\n", " rtimes_min, rtimes_max = rtimes[0], rtimes[-1]\n", - " cmap = ListedColormap(plt.cm.inferno(np.linspace(rtimes_min/rtimes_max, 1.0, 256)))\n", + " cmap = ListedColormap(\n", + " plt.cm.inferno(np.linspace(rtimes_min / rtimes_max, 1.0, 256))\n", + " )\n", " for i in range(len(rlons)):\n", - " ax.plot(rlons[i], rlats[i], colors[i], linewidth=0.75, transform=ccrs.PlateCarree(), zorder=3)\n", - " mp = ax.scatter(rlons[i], rlats[i], 100, marker='.', c=rtimes, cmap=cmap, edgecolor='k',\n", - " transform=ccrs.PlateCarree(), vmin=rtimes_min, vmax=rtimes_max, zorder=4)\n", - " \n", + " ax.plot(\n", + " rlons[i],\n", + " rlats[i],\n", + " \"yellow\",\n", + " linewidth=0.75,\n", + " transform=ccrs.PlateCarree(),\n", + " zorder=3,\n", + " )\n", + " mp = ax.scatter(\n", + " rlons[i],\n", + " rlats[i],\n", + " 100,\n", + " marker=\".\",\n", + " c=rtimes,\n", + " cmap=cmap,\n", + " edgecolor=\"k\",\n", + " transform=ccrs.PlateCarree(),\n", + " vmin=rtimes_min,\n", + " vmax=rtimes_max,\n", + " zorder=4,\n", + " )\n", + "\n", " # --- Set a colorbar to help visualise the passage of every Xth Myr.\n", " divider = make_axes_locatable(ax)\n", " cax = divider.new_horizontal(size=\"4%\", pad=0.4, axes_class=plt.Axes)\n", " fig.add_axes(cax)\n", " cbar = plt.colorbar(mp, cax=cax, orientation=\"vertical\")\n", - " cbar.set_label('Age (Ma)', fontsize=12)\n", + " cbar.set_label(\"Age (Ma)\", fontsize=12)\n", " cbar.ax.minorticks_on()\n", "\n", " # --- Plot times and rates of motion on 2nd subplot\n", - " p = fig.add_subplot(gs[0, 5:], xlabel='Time (Ma)', ylabel='Rate of motion (cm/yr)',\n", - " xlim=[rtimes.max(), 0],ylim=[0.0,12.0])\n", + " p = fig.add_subplot(\n", + " gs[0, 5:],\n", + " xlabel=\"Time (Ma)\",\n", + " ylabel=\"Rate of motion (cm/yr)\",\n", + " xlim=[rtimes.max(), 0],\n", + " ylim=[0.0, 12.0],\n", + " )\n", " p.set_title(\"Rate of motion\")\n", " p.tick_params(direction=\"in\", length=5, top=True, right=True)\n", - " p.tick_params(direction=\"in\", which='minor', length=2.5, top=True, right=True)\n", + " p.tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", " p.minorticks_on()\n", " # For each seed point plot its rate of motion.\n", " for rate_of_motion in rates_of_motion:\n", " plt.stairs(rate_of_motion, rtimes)\n", "\n", - " plt.show()\n", - "\n", - " fig.savefig(\"Hawaii_Emperor_motion_path_{}Ma.pdf\".format(time), bbox_inches='tight')" + " fig.savefig(\n", + " os.path.join(output_dir, f\"Hawaii_Emperor_motion_path_{time}Ma.pdf\"),\n", + " bbox_inches=\"tight\",\n", + " )\n", + " plt.show()" ] }, { @@ -285,7 +324,8 @@ " # Note that we specify 'relative_plate_id' and leave 'anchor_plate_id' as default (of 'model')...\n", " relative_plate_id=relative_plate_ID,\n", " return_times=True,\n", - " return_rate_of_motion=True)\n", + " return_rate_of_motion=True,\n", + ")\n", "\n", "plot_motion_path(rlons, rlats, rtimes, rates_of_motion, time=time)" ] @@ -322,7 +362,8 @@ " # Note that we specify 'relative_plate_id' and leave 'anchor_plate_id' as default (of 'model')...\n", " relative_plate_id=relative_plate_ID,\n", " return_times=True,\n", - " return_rate_of_motion=True)\n", + " return_rate_of_motion=True,\n", + ")\n", "\n", "plot_motion_path(rlons, rlats, rtimes, rates_of_motion, time=time)" ] @@ -373,7 +414,8 @@ " # Note that we specify 'relative_plate_id' and leave 'anchor_plate_id' as default (of 'model')...\n", " relative_plate_id=relative_plate_ID,\n", " return_times=True,\n", - " return_rate_of_motion=True)\n", + " return_rate_of_motion=True,\n", + ")\n", "\n", "plot_motion_path(rlons, rlats, rtimes, rates_of_motion, time=time)" ] @@ -398,26 +440,28 @@ "source": [ "# Longitudes and latitudes of points along the South Atlantic mid ocean ridge\n", "lons = np.array([-15.4795, -14.9688, -14.0632, -17.7739])\n", - "lats = np.array([-1.60584,-11.9764, -25.7209, -35.7228])\n", + "lats = np.array([-1.60584, -11.9764, -25.7209, -35.7228])\n", "\n", "# Left and right plate IDs\n", "left_plate_ID = 201\n", "right_plate_ID = 701\n", "\n", "# Constructing the time array\n", - "time_step = 5.\n", + "time_step = 5.0\n", "max_reconstruction_time = 120\n", - "time_array = np.arange(0, max_reconstruction_time+time_step, time_step)\n", + "time_array = np.arange(0, max_reconstruction_time + time_step, time_step)\n", "\n", "# Generate the latitudes and longitude coordinates of the flowlines to the left and right of the MOR!\n", - "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = model.create_flowline(\n", - " lons,\n", - " lats,\n", - " time_array,\n", - " left_plate_ID,\n", - " right_plate_ID,\n", - " return_times=True,\n", - " return_rate_of_motion=True,\n", + "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = (\n", + " model.create_flowline(\n", + " lons,\n", + " lats,\n", + " time_array,\n", + " left_plate_ID,\n", + " right_plate_ID,\n", + " return_times=True,\n", + " return_rate_of_motion=True,\n", + " )\n", ")" ] }, @@ -436,15 +480,20 @@ "metadata": {}, "outputs": [], "source": [ - "def plot_flowline(left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=0):\n", + "def plot_flowline(\n", + " left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=0\n", + "):\n", " # --- Create figure\n", - " fig = plt.figure(figsize=(12,5), dpi=200)\n", - " plt.suptitle(\"Flowlines of seed points along a South-Atlantic mid-ocean ridge (at {} Ma)\".format(time), weight=\"demi\")\n", + " fig = plt.figure(figsize=(12, 5), dpi=200)\n", + " plt.suptitle(\n", + " f\"Flowlines of seed points along a South-Atlantic mid-ocean ridge (at {time} Ma)\",\n", + " weight=\"demi\",\n", + " )\n", " gs = fig.add_gridspec(1, 7)\n", - " \n", + "\n", " ax = fig.add_subplot(gs[0, :4], projection=ccrs.Mercator())\n", " ax.set_title(\"Flowlines\", fontsize=14)\n", - " \n", + "\n", " # --- Plot tick labels. This doesn't work for all projections in cartopy yet...\n", " ax.set_xticks(np.arange(-180, 190, 10), crs=ccrs.PlateCarree())\n", " ax.set_yticks(np.arange(-90, 95, 5), crs=ccrs.PlateCarree())\n", @@ -452,77 +501,121 @@ " ax.set_yticks(np.arange(-90, 90, 2.5), minor=True, crs=ccrs.PlateCarree())\n", " lon_formatter = LongitudeFormatter(zero_direction_label=True)\n", " lat_formatter = LatitudeFormatter()\n", - " ax.xaxis.tick_bottom() # labelled ticks on top\n", + " ax.xaxis.tick_bottom() # labelled ticks on bottom\n", " ax.xaxis.set_major_formatter(lon_formatter)\n", " ax.yaxis.set_major_formatter(lat_formatter)\n", - " ax.xaxis.set_ticks_position('both') # ticks on both top/bottom\n", - " ax.yaxis.set_ticks_position('both') # ticks on both left/right\n", - " \n", + " ax.xaxis.set_ticks_position(\"both\") # ticks on both top/bottom\n", + " ax.yaxis.set_ticks_position(\"both\") # ticks on both left/right\n", + "\n", " # --- Limit map extent\n", " lon_min = -60\n", " lon_max = 30\n", - " lat_min = -40.\n", + " lat_min = -40.0\n", " lat_max = 10\n", - " ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())\n", - " \n", + " ax.set_extent((lon_min, lon_max, lat_min, lat_max), crs=ccrs.PlateCarree())\n", + "\n", " # ---- Plot bathymetry\n", - " etopo_tif_img.reconstruct(time=time, fill_value='darkblue').imshow(alpha=0.3, zorder=1)\n", - " \n", + " etopo_tif_img.reconstruct(time=time, fill_value=\"darkblue\").imshow(\n", + " alpha=0.3, zorder=1\n", + " )\n", + "\n", " gplot.time = time\n", - " \n", + "\n", " # --- Plot filled coastlines\n", " gplot.plot_coastlines(ax, facecolor=\"white\", edgecolor=None, zorder=2)\n", - " \n", + "\n", " gplot.plot_ridges(ax, color=\"r\", linewidth=1)\n", " gplot.plot_transforms(ax, color=\"r\", linewidth=1)\n", - " \n", + "\n", " # --- Make sure flowline longitudes are wrapped correctly to the dateline\n", - " #left_rlons = gplately.tools.correct_longitudes_for_dateline(left_rlons)\n", - " #right_rlons = gplately.tools.correct_longitudes_for_dateline(right_rlons)\n", - " \n", + " # left_rlons = gplately.tools.correct_longitudes_for_dateline(left_rlons)\n", + " # right_rlons = gplately.tools.correct_longitudes_for_dateline(right_rlons)\n", + "\n", " # The mapping of 'rtimes' to colours should not change.\n", " # Ie, each time should map to the same colour (regardless of the time range).\n", " rtimes_min, rtimes_max = rtimes[0], rtimes[-1]\n", - " cmap = ListedColormap(plt.cm.inferno(np.linspace(rtimes_min/rtimes_max, 1.0, 256)))\n", - " \n", - " # --- Iterate over the reconstructed flowlines. Each seed point results in a 'left' and 'right' flowline \n", - " for i in range(len(lons)):\n", - " \n", + " cmap = ListedColormap(\n", + " plt.cm.inferno(np.linspace(rtimes_min / rtimes_max, 1.0, 256))\n", + " )\n", + "\n", + " # --- Iterate over the reconstructed flowlines. Each seed point results in a 'left' and 'right' flowline\n", + " for i in range(len(left_rlons)):\n", " # plot left flowlines as circles\n", - " ax.plot(left_rlons[i], left_rlats[i], color=\"C{}\".format(i), linewidth=2, transform=ccrs.PlateCarree(), zorder=3)\n", - " fl = ax.scatter(left_rlons[i], left_rlats[i], s=50, marker='D', c=rtimes, cmap=cmap,\n", - " transform=ccrs.PlateCarree(), edgecolor=\"C{}\".format(i),\n", - " vmin=rtimes_min, vmax=rtimes_max, zorder=4)\n", - " \n", + " ax.plot(\n", + " left_rlons[i],\n", + " left_rlats[i],\n", + " color=\"C{}\".format(i),\n", + " linewidth=2,\n", + " transform=ccrs.PlateCarree(),\n", + " zorder=3,\n", + " )\n", + " fl = ax.scatter(\n", + " left_rlons[i],\n", + " left_rlats[i],\n", + " s=50,\n", + " marker=\"D\",\n", + " c=rtimes,\n", + " cmap=cmap,\n", + " transform=ccrs.PlateCarree(),\n", + " edgecolor=\"C{}\".format(i),\n", + " vmin=rtimes_min,\n", + " vmax=rtimes_max,\n", + " zorder=4,\n", + " )\n", + "\n", " # plot right flowlines as stars\n", - " ax.plot(right_rlons[i], right_rlats[i], color=\"C{}\".format(i), linewidth=2, transform=ccrs.PlateCarree(), zorder=3)\n", - " ax.scatter(right_rlons[i], right_rlats[i], s=50, marker='s', c=rtimes, cmap=cmap,\n", - " transform=ccrs.PlateCarree(), edgecolor=\"C{}\".format(i),\n", - " vmin=rtimes_min, vmax=rtimes_max, zorder=4)\n", - " \n", + " ax.plot(\n", + " right_rlons[i],\n", + " right_rlats[i],\n", + " color=\"C{}\".format(i),\n", + " linewidth=2,\n", + " transform=ccrs.PlateCarree(),\n", + " zorder=3,\n", + " )\n", + " ax.scatter(\n", + " right_rlons[i],\n", + " right_rlats[i],\n", + " s=50,\n", + " marker=\"s\",\n", + " c=rtimes,\n", + " cmap=cmap,\n", + " transform=ccrs.PlateCarree(),\n", + " edgecolor=\"C{}\".format(i),\n", + " vmin=rtimes_min,\n", + " vmax=rtimes_max,\n", + " zorder=4,\n", + " )\n", + "\n", " # --- Set a colorbar to help visualise the passage of every Xth Myr.\n", " divider = make_axes_locatable(ax)\n", " cax = divider.new_horizontal(size=\"4%\", pad=0.4, axes_class=plt.Axes)\n", " fig.add_axes(cax)\n", " cbar = plt.colorbar(fl, cax=cax, orientation=\"vertical\")\n", - " cbar.set_label('Age (Ma)', fontsize=12)\n", + " cbar.set_label(\"Age (Ma)\", fontsize=12)\n", " cbar.ax.minorticks_on()\n", "\n", " # --- Plot times and rates of motion on 2nd subplot\n", - " p = fig.add_subplot(gs[0, 5:], xlabel='Time (Ma)', ylabel='Spreading Rate (cm/yr)',\n", - " xlim=[rtimes.max(), 0],ylim=[0.0,6.0])\n", - " \n", + " p = fig.add_subplot(\n", + " gs[0, 5:],\n", + " xlabel=\"Time (Ma)\",\n", + " ylabel=\"Spreading rate (cm/yr)\",\n", + " xlim=[rtimes.max(), 0],\n", + " ylim=[0.0, 6.0],\n", + " )\n", + "\n", " p.set_title(\"Flowline spreading rates\")\n", " p.tick_params(direction=\"in\", length=5, top=True, right=True)\n", - " p.tick_params(direction=\"in\", which='minor', length=2.5, top=True, right=True)\n", + " p.tick_params(direction=\"in\", which=\"minor\", length=2.5, top=True, right=True)\n", " p.minorticks_on()\n", " # For each seed point plot its rate of motion.\n", " for rate_of_motion in rates_of_motion:\n", " plt.stairs(rate_of_motion, rtimes)\n", "\n", - " plt.show()\n", - "\n", - " fig.savefig(\"Flowlines_South_Atlantic_{}Ma.pdf\".format(time), bbox_inches='tight')" + " fig.savefig(\n", + " os.path.join(output_dir, f\"Flowlines_South_Atlantic_{time}Ma.pdf\"),\n", + " bbox_inches=\"tight\",\n", + " )\n", + " plt.show()" ] }, { @@ -565,19 +658,23 @@ "time = 55\n", "\n", "# Reconstruct the flowline to a past time.\n", - "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = model.create_flowline(\n", - " lons,\n", - " lats,\n", - " time_array,\n", - " left_plate_ID,\n", - " right_plate_ID,\n", - " # Specify the reconstruction time...\n", - " to_time=time,\n", - " return_times=True,\n", - " return_rate_of_motion=True,\n", + "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = (\n", + " model.create_flowline(\n", + " lons,\n", + " lats,\n", + " time_array,\n", + " left_plate_ID,\n", + " right_plate_ID,\n", + " # Specify the reconstruction time...\n", + " to_time=time,\n", + " return_times=True,\n", + " return_rate_of_motion=True,\n", + " )\n", ")\n", "\n", - "plot_flowline(left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time)" + "plot_flowline(\n", + " left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time\n", + ")" ] }, { @@ -605,17 +702,21 @@ "gpts = gplately.Points(model, lons, lats, time=0)\n", "\n", "# Reconstruct the flowline to a past time using our 'Points' object.\n", - "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = gpts.flowline(\n", - " time_array,\n", - " left_plate_ID,\n", - " right_plate_ID,\n", - " # Specify the reconstruction time...\n", - " time=time,\n", - " return_times=True,\n", - " return_rate_of_motion=True,\n", + "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = (\n", + " gpts.flowline(\n", + " time_array,\n", + " left_plate_ID,\n", + " right_plate_ID,\n", + " # Specify the reconstruction time...\n", + " time=time,\n", + " return_times=True,\n", + " return_rate_of_motion=True,\n", + " )\n", ")\n", "\n", - "plot_flowline(left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time)" + "plot_flowline(\n", + " left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time\n", + ")" ] }, { @@ -647,27 +748,31 @@ "\n", "# Create the flowlines using our reconstructed seed locations at 'initial_seed_time'.\n", "# Note, however, that the flowlines are reconstructed to 'time'.\n", - "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = model.create_flowline(\n", - " initial_lons,\n", - " initial_lats,\n", - " time_array,\n", - " left_plate_ID,\n", - " right_plate_ID,\n", - " # Specify the initial time of the seed points...\n", - " from_time=initial_seed_time,\n", - " # Specify the reconstruction time...\n", - " to_time=time,\n", - " return_times=True,\n", - " return_rate_of_motion=True,\n", + "left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion = (\n", + " model.create_flowline(\n", + " initial_lons,\n", + " initial_lats,\n", + " time_array,\n", + " left_plate_ID,\n", + " right_plate_ID,\n", + " # Specify the initial time of the seed points...\n", + " from_time=initial_seed_time,\n", + " # Specify the reconstruction time...\n", + " to_time=time,\n", + " return_times=True,\n", + " return_rate_of_motion=True,\n", + " )\n", ")\n", "\n", - "plot_flowline(left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time)" + "plot_flowline(\n", + " left_rlons, left_rlats, right_rlons, right_rlats, rtimes, rates_of_motion, time=time\n", + ")" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -681,7 +786,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.1" + "version": "3.14.2" } }, "nbformat": 4, diff --git a/Notebooks/10-SeafloorGrids.ipynb b/Notebooks/10-SeafloorGrids.ipynb index b74e6aac..dac71106 100644 --- a/Notebooks/10-SeafloorGrids.ipynb +++ b/Notebooks/10-SeafloorGrids.ipynb @@ -1,649 +1,677 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "6a6c21c7", - "metadata": {}, - "source": [ - "## 10 - Seafloor Grids\n", - "\n", - "An adaptation of [agegrid-01](https://github.com/siwill22/agegrid-0.1) written by Simon Williams, Nicky Wright and John Cannon for gridding general z-values onto seafloor basin points using GPlately.\n", - "\n", - "This notebook demonstrates how to generate seafloor age and spreading-rate grids through time. The figure below, generated by this notebook, shows seafloor age at 65 Ma. You may adjust the time parameter to generate grids at different geological times.\n", - "\n", - "![Figure: seafloor_age_65Ma.png](https://gplates.github.io/gplately/latest/sphinx/html/_images/seafloor_age_65Ma.png)\n", - "\n", - "### Citation:\n", - "Simon Williams, Nicky M. Wright, John Cannon, Nicolas Flament, R. Dietmar Müller, Reconstructing seafloor age distributions in lost ocean basins, Geoscience Frontiers, Volume 12, Issue 2, 2021, Pages 769-780, ISSN 1674-9871,\n", - "https://doi.org/10.1016/j.gsf.2020.06.004." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db118eb4", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "#\n", - "# Setting GPLATELY_DEBUG to a value above 100 will create the following debug files\n", - "# during seafloor gridding (that can be loaded into GPlates):\n", - "#\n", - "# - A separate debug file for each time (from 'max_time' to 'min_time) containing the seed points\n", - "# reconstructed from that time to 'min_time'.\n", - "# * The debug file with \"initial_ocean_basin\" in the filename contains reconstructions of the initial ocean basin seed points, and\n", - "# * each debug file with \"mid_ocean_ridge\" in the filename contains reconstructions of the seed points created along mid-ocean ridges\n", - "# that formed at that time specified in the filename.\n", - "# - One big debug file containing the reconstructions of ALL seed points created at ALL times.\n", - "#\n", - "# These files are located in the 'debug' sub-directory of the save directory.\n", - "#\n", - "#os.environ[\"GPLATELY_DEBUG\"] = \"200\"\n", - "\n", - "import gplately\n", - "\n", - "import pygplates\n", - "import glob\n", - "import matplotlib.pyplot as plt\n", - "import cartopy.crs as ccrs\n", - "from plate_model_manager import PlateModelManager" - ] - }, - { - "cell_type": "markdown", - "id": "9a4bddee", - "metadata": {}, - "source": [ - "### Define a rotation model, topology features and continents for the `PlateReconstruction` model\n", - "There are two ways to do this. To use local files, set `use_local_files = True`. To use `PlateModelManager`, set `use_local_files = False`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b571bf94", - "metadata": {}, - "outputs": [], - "source": [ - "use_local_files = False" - ] - }, - { - "cell_type": "markdown", - "id": "4936baad", - "metadata": {}, - "source": [ - "#### 1) Manually pointing to files" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e5319664", - "metadata": {}, - "outputs": [], - "source": [ - "if use_local_files:\n", - " # Method 1: manually point to files\n", - " # you can download the model files at \n", - " # https://www.earthbyte.org/webdav/ftp/Data_Collections/Merdith_etal_2021_ESR/SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4.zip\n", - " input_directory = \"./SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4\"\n", - " \n", - " rotation_model = glob.glob(os.path.join(input_directory, '*.rot'))\n", - " static_polygons = input_directory+\"/shapes_static_polygons_Merdith_et_al.gpml\"\n", - " topology_features = [\n", - " input_directory+\"/250-0_plate_boundaries_Merdith_et_al.gpml\",\n", - " input_directory+\"/410-250_plate_boundaries_Merdith_et_al.gpml\",\n", - " input_directory+\"/1000-410-Convergence_Merdith_et_al.gpml\",\n", - " input_directory+\"/1000-410-Divergence_Merdith_et_al.gpml\",\n", - " input_directory+\"/1000-410-Topologies_Merdith_et_al.gpml\",\n", - " input_directory+\"/1000-410-Transforms_Merdith_et_al.gpml\",\n", - " input_directory+\"/TopologyBuildingBlocks_Merdith_et_al.gpml\"\n", - " ]\n", - " \n", - " continents = input_directory+\"/shapes_continents.gpml\"\n", - " coastlines = input_directory+\"/shapes_coastlines_Merdith_et_al_v2.gpmlz\"\n", - " COBs = None\n", - "\n", - " from pathlib import Path\n", - " if not Path(input_directory).is_dir():\n", - " raise FileNotFoundError(f\"The input directory does not exist: {input_directory}. Ensure your files are in the correct locations or download the example input files at https://www.earthbyte.org/webdav/ftp/Data_Collections/Merdith_etal_2021_ESR/SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4.zip\")\n", - " " - ] - }, - { - "cell_type": "markdown", - "id": "1519da7c", - "metadata": {}, - "source": [ - "#### 2) Using `PlateModelManager`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3ebf9727", - "metadata": {}, - "outputs": [], - "source": [ - "if not use_local_files:\n", - " # Method 2: use PlateModelManager\n", - " pm_manager = PlateModelManager()\n", - " plate_model = pm_manager.get_model(\"Merdith2021\", data_dir=\"plate-model-repo\")\n", - " \n", - " rotation_model = plate_model.get_rotation_model()\n", - " topology_features = plate_model.get_topologies()\n", - " static_polygons = plate_model.get_static_polygons()\n", - " \n", - " continents = plate_model.get_layer('ContinentalPolygons') " - ] - }, - { - "cell_type": "markdown", - "id": "3560443e", - "metadata": {}, - "source": [ - "## The SeafloorGrid object\n", - "...is a collection of methods to generate seafloor grids.\n", - "\n", - "The critical input parameters are:\n", - "\n", - "### Plate model parameters\n", - "* **`plate_reconstruction`**: The gplately `PlateReconstruction` object defined in the cell below. This object is a collection of methods for calculating plate tectonic stats through geological time.\n", - "* **`plot_topologies`**: The gplately `PlotTopologies` object defined in the cell below. This object is a collection of methods for resolving geological features needed for gridding to a certain geological time.\n", - "\n", - "#### Define the `PlateReconstruction` and `PlotTopologies` objects" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "12bec53e", - "metadata": {}, - "outputs": [], - "source": [ - "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", - "\n", - "gplot = gplately.PlotTopologies(model, continents=continents)" - ] - }, - { - "cell_type": "markdown", - "id": "57d6e8b2", - "metadata": {}, - "source": [ - "### Time parameters\n", - "* **`max_time`**: The first time step to start generating age grids. This is the time step where we define certain **gridding initial conditions**, which will be explained below.\n", - "* **`min_time`**: The final step to generate age grids. This is the time when recursive reconstructions starting from `max_time` stop.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "17add2d7", - "metadata": {}, - "outputs": [], - "source": [ - "max_time = 410.\n", - "min_time = 65." - ] - }, - { - "cell_type": "markdown", - "id": "703d2315", - "metadata": {}, - "source": [ - "### Ridge resolution parameters\n", - "With each reconstruction time step, mid-ocean ridge segments emerge and spread across the ocean floor. In `gplately`, these ridges are lines, or a tessellation of infinitesimal points. The spatio-temporal resolution of these ridge points can be controlled by two parameters:\n", - "* **`ridge_time_step`**: The \"delta time\" or time increment between successive resolutions of ridges, and hence successive grids. By default this is 1Myr, so grids are produced every millionth year.\n", - "* **`ridge_sampling`**: This controls the geographical resolution (in degrees) with which ridge lines are partitioned into points. The larger this is, the larger the spacing between successive ridge points, and hence the smaller the number of ridge points at each timestep. By default this is 0.5 degrees, so points are spaced 0.5 degrees apart. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b959a7b4", - "metadata": {}, - "outputs": [], - "source": [ - "ridge_time_step = 5. # We set the time step to 5 Myr to reduce the notebook’s runtime.\n", - "ridge_sampling = 0.5" - ] - }, - { - "cell_type": "markdown", - "id": "c1c20f7d", - "metadata": {}, - "source": [ - "***\n", - "### Grid resolution parameters\n", - "The ridge resolution parameters mentioned above take care of the spatio-temporal positioning of ridge features/points. However, these ridge points need to be interpolated onto regular grids - separate parameters are needed for these grids. \n", - "\n", - "#### For the `max_time` initial condition\n", - "At `max_time`, i.e. 410Ma, the `PlateReconstruction` model has not been initialised at 411Ma and any time(s) before that to sculpt the geological history before `max_time`. In terms of the workflow, all geological history before `max_time` is unknown. Thus, the global seafloor point distriubtion, and each point's spreading rate and age must be manually defined. \n", - "\n", - "The initial point distribution is an icosahedral point mesh, made using [`stripy`](https://github.com/underworldcode/stripy/tree/294354c00dd72e085a018e69c345d9353c6fafef).\n", - "\n", - "* **`refinement_levels`**: A unitless integer that controls the number of points in the `max_time` icosahedral point mesh.\n", - " \n", - " 6 is the default. Any higher will result in a finer mesh.\n", - " \n", - " \n", - "* **`initial_ocean_mean_spreading_rate`** (in units of mm/yr or km/myr): Since the geological history before and at `max_time` is unknown, we will need to manually define the spreading rates and ages of all ocean points. This is manually set to a uniform spreading rate of 75 (mm/yr or km/myr). Each point's age is equal to its proximity to the nearest mid ocean ridge (assuming that ridge is the source of the point) divided by half this uniform spreading rate (half to account for spreading to the left and right of a ridge). \n", - "\n", - "#### For the interpolated regular grids\n", - "The regular grid on which all data is interpolated has a resolution that can be controlled by:\n", - "\n", - "* **`grid_spacing`**: The degree spacing between successive nodes in the grid. By default, this is 0.1 degrees. **Acceptable degree spacings are 0.1, 0.25, 0.5, 0.75, or 1 degree(s)** because these allow cleanly divisible grid nodes across global latitude and longitude extents. Anything greater than 1 will be rounded to 1, and anything between these acceptable spacings will be rounded to the nearest acceptable spacing. \n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ec00f36", - "metadata": {}, - "outputs": [], - "source": [ - "refinement_levels = 6\n", - "initial_ocean_mean_spreading_rate = 50.\n", - "\n", - "# Gridding parameter\n", - "grid_spacing = 0.25\n", - "\n", - "extent=[-180,180,-90,90]" - ] - }, - { - "cell_type": "markdown", - "id": "8464bfb4", - "metadata": {}, - "source": [ - "***\n", - "### File saving and naming parameters\n", - "When grids are produced, they are saved to:\n", - "* **`save_directory`**: A string to a directory that must exist already.\n", - "\n", - "These files are named according to:\n", - "* **`file_collection`**: A string used to help with naming all output files. \n", - "***" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "106b61f5", - "metadata": {}, - "outputs": [], - "source": [ - "# continent masks, initial ocean seed points, and gridding input files are kept here\n", - "output_parent_directory = os.path.join(\n", - " \"NotebookFiles\",\n", - " \"Notebook10\",\n", - ")\n", - "save_directory = os.path.join(\n", - " output_parent_directory,\n", - " \"seafloor_grid_output\",\n", - ")\n", - "print(f\"The ouput files created by this notebook will be saved in folder {save_directory}.\" )\n", - "\n", - "# A string to help name files according to a plate model \"Merdith2021\"\n", - "file_collection = \"Merdith2021\"\n" - ] - }, - { - "cell_type": "markdown", - "id": "a328fa6d", - "metadata": {}, - "source": [ - "***\n", - "### Continent collision\n", - "\n", - "One way an oceanic point can be deleted from the ocean basin at a certain timestep is if it is collides into continental crust. At each timestep continental crust is represented as a **continental mask** which is a binary grid that partitions continental regions from oceanic regions. By default, continental masks are generated from the reconstructed continental polygons (where the continent polygons features are specified with ``continent_polygon_features``). But they can also be generated using [continent contouring](https://github.com/EarthByte/continent-contouring) (by specifying ``use_continent_contouring=True``) or you can explicitly provide them as your own continent masks (by specifying them with ``continent_mask_filename``).\n", - "\n", - "### Subduction parameters\n", - "Another way an oceanic point can be deleted from the ocean basin at a certain timestep is if it is approaching a plate boundary such that a **velocity and displacement test** is passed:\n", - "\n", - "#### Velocity test\n", - "First, given the trajectory of a point at the next time step, a point will cross a subducting plate boundary (and thus will be deleted) if the difference between its velocity on its current plate AND the velocity it will have on the other plate is greater than this velocity difference is higher than a specified **threshold delta velocity in kms/Myr**. \n", - "\n", - "#### Displacement test\n", - "If the proximity of the point's previous time position to the plate boundary it is approaching is higher than a set distance threshold (**threshold distance to boundary per Myr in kms/Myr**), then the point is far enough away from the boundary that it cannot be subducted or consumed by it, and \n", - "hence the point is still active.\n", - "\n", - "The parameter needed to encapsulate these thresholds is:\n", - "* **`subduction_collision_parameters`**: This a tuple with two elements, by default (5.0, 10.0), for the (threshold velocity delta in kms/my, threshold distance to boundary per My in kms/my)\n", - "***" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "68450bf6", - "metadata": {}, - "outputs": [], - "source": [ - "continent_polygon_features=continents\n", - "subduction_collision_parameters=(5.0, 10.0)" - ] - }, - { - "cell_type": "markdown", - "id": "bd978b09", - "metadata": {}, - "source": [ - "***\n", - "### Methodology parameters\n", - "* **`resume_from_checkpoints`**: Gridding can take a while (depending on the number of CPUs used). If this is changed to `True`, after gridding was interrupted, then rerunning the gridding cell will resume from where it left off.\n", - "* **`nprocs`**: The number of CPUs to use for parts of the code that are parallelized. The default is `-2` which uses all CPUs except one (to keep the system responsive).\n", - "***" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1a6fbee", - "metadata": {}, - "outputs": [], - "source": [ - "# Methodology parameters\n", - "resume_from_checkpoints = False\n", - "nprocs = -2" - ] - }, - { - "cell_type": "markdown", - "id": "5038c7e6", - "metadata": {}, - "source": [ - "Use all parameters to define `SeafloorGrid`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ed5fc9e9", - "metadata": {}, - "outputs": [], - "source": [ - "# The SeafloorGrid object with all aforementioned parameters\n", - "seafloorgrid = gplately.SeafloorGrid(\n", - " \n", - " plate_reconstruction = model, \n", - " \n", - " # Time parameters\n", - " max_time = max_time,\n", - " min_time = min_time,\n", - " \n", - " # Ridge tessellation parameters\n", - " ridge_time_step = ridge_time_step,\n", - " ridge_sampling = ridge_sampling,\n", - " \n", - " # Gridding parameters\n", - " grid_spacing = grid_spacing,\n", - "\n", - " extent = extent,\n", - " \n", - " # Naming parameters\n", - " save_directory = save_directory,\n", - " file_collection = file_collection,\n", - " \n", - " # Initial condition parameters\n", - " refinement_levels = refinement_levels,\n", - " initial_ocean_mean_spreading_rate = initial_ocean_mean_spreading_rate,\n", - " \n", - " # Subduction parameters\n", - " subduction_collision_parameters = subduction_collision_parameters,\n", - " \n", - " # Methodology parameters\n", - " resume_from_checkpoints = resume_from_checkpoints,\n", - "\n", - " # Continental polygons.\n", - " continent_polygon_features = continent_polygon_features,\n", - "\n", - " nprocs = nprocs\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "ab3cbf87", - "metadata": {}, - "source": [ - "## How to use `SeafloorGrid`\n", - "\n", - "### 1) Run `seafloorgrid.reconstruct_by_topologies()`\n", - "Running `seafloorgrid.reconstruct_by_topologies()` prepares all ocean basin points and their seafloor ages and spreading rates for gridding, and reconstructs all active points per timestep to form the grids per timestep.\n", - "\n", - "#### Continental masks\n", - "A netCDF4 continent mask is produced for each _time_ from `max_time` to `min_time`.\n", - "\n", - "#### Initial ocean basin\n", - "At `max_time`, an initial ocean seed point icosahedral mesh fills the ocean basin. Each initial seed point is allocated an age based on its distance from the nearest mid-ocean ridge. And these are reconstructed to `min_time` to produce an `.npz` file (containing reconstructed seed point data for seed points created at `max_time`).\n", - "\n", - "#### Mid-ocean ridges\n", - "At each successive timestep (`ridge_time_step`), new seed points emerge from spreading ridges, and they are allocated their own spreading rates. The seed points emerging at a particular _time_ are reconstructed to `min_time` to produce an `.npz` file (containing reconstructed seed point data for seed points created at that _time_). This results in one `.npz` file for each _time_ from `max_time - ridge_time_step` to `min_time`.\n", - "\n", - "#### Seafloor age and spreading rate\n", - "After all seed points (initial and mid-ocean ridge) have been reconstructed to `min_time`, then one `.npz` file is generated for each _time_ from `max_time` to `min_time`. These files contain the gridding input that will later be used to generate the seafloor age and spreading rate grids.\n", - "\n", - "#### Resuming an interrupted gridding run\n", - "*If the `resume_from_checkpoints` parameter is passed as `True`, after `seafloorgrid.reconstruct_by_topologies()` was interrupted, then you can rerun the cell. The workflow will pick up from where it left off (provided the continent mask files and reconstructed seed point data files have not been erased).*\n", - "\n", - "To overwrite all files in `save_directory`, pass `resume_from_checkpoints` as `False` (this is the default behaviour).\n", - "\n", - "#### Reconstruct by topologies\n", - "The `ReconstructByTopologies` object (written by Simon Williams, Nicky Wright, and John Cannon) handles the reconstruction of seed points. `ReconstructByTopologies` (RBT) identifies active points on the ocean basin per timestep. It works as follows:\n", - "\n", - "If an ocean point on one plate ID transitions into another rigid plate ID at the next timestep, RBT calculates the point's velocity difference between both plates. The point **may** have subducted/collided with a continent at this boundary if this velocity difference is higher than a set velocity threshold. To ascertain whether the point should indeed be deactivated, a second test is conducted: RBT checks the previous time position of the point and calculates this point’s proximity to the boundary of the plate ID polygon it is approaching. If this distance is higher than a set distance threshold, then the point is far enough away from the boundary that it cannot be subducted or consumed by it and hence the point is still active. Else, it is deactivated/deleted.\n", - "\n", - "Once all active points and their z-values are identified, they are written to the gridding input file (.npz) for that timestep.\n", - "\n", - "Below is an example of the initial condition: the ocean basin is populated with an `intial_mean_ocean_spreading_rate` of 50 mm/yr at a `max_time` of 410Ma. Reconstruction over 10 Myr to 400 Ma sees new seed points emerging from ridges with their own plate-model-ascribed spreading rates.\n", - "\n", - "![init_condition](https://raw.githubusercontent.com/GPlates/gplately/master/Notebooks/NotebookFiles/Notebook10/Merdith2021_initial_sr_conditions.png)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "143ea077", - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "import time\n", - "\n", - "start = time.time()\n", - "seafloorgrid.reconstruct_by_topologies()\n", - "end = time.time()\n", - "duration = datetime.timedelta(seconds=end - start)\n", - "print(\"Duration: {}\".format(duration))" - ] - }, - { - "cell_type": "markdown", - "id": "a0a0ca60", - "metadata": {}, - "source": [ - "### 2) Run `seafloorgrid.lat_lon_z_to_netCDF` to write grids to netCDF\n", - "Calling `seafloorgrid.lat_lon_z_to_netCDF` grids one set of z-data per latitude-longitude pair from each timestep's gridding input file (produced in `seafloorgrid.reconstruct_by_topologies()`). Grids are in netCDF format.\n", - "\n", - "The desired z-data to grid is identified using a `zval_name`. \n", - "For example, seafloor age grids can be produced using `SEAFLOOR_AGE`, and spreading rate grids are `SPREADING_RATE`. \n", - "\n", - "Use `unmasked = True` to output both the masked and unmasked versions of the grids." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "891183f3", - "metadata": {}, - "outputs": [], - "source": [ - "seafloorgrid.lat_lon_z_to_netCDF(\"SEAFLOOR_AGE\", unmasked=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d51a41a5", - "metadata": {}, - "outputs": [], - "source": [ - "seafloorgrid.lat_lon_z_to_netCDF(\"SPREADING_RATE\", unmasked=True)" - ] - }, - { - "cell_type": "markdown", - "id": "8213b46a", - "metadata": {}, - "source": [ - "### Plotting a sample age grid and spreading rate grid\n", - "Read one netCDF grid using GPlately's `Raster` object from `grids`, and plot it using the `PlotTopologies` object.\n", - "\n", - "Notice the evolution of seafloor spreading rate from the initial value set with `initial_ocean_mean_spreading_rate`. Eventually, this initial uniform spreading rate will be phased out with sufficient recursive reconstruction." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06a92ce9-ecea-49b9-9c99-c33438e75379", - "metadata": {}, - "outputs": [], - "source": [ - "time = min_time\n", - "# set the plot_unmasked_grid variable to True if you would like to see the unmasked age grid.\n", - "plot_unmasked_grid = False\n", - "\n", - "# age grids\n", - "if not plot_unmasked_grid:\n", - " agegrid_filename = f\"{seafloorgrid.save_directory}/SEAFLOOR_AGE/{seafloorgrid.file_collection}_SEAFLOOR_AGE_grid_{time:0.2f}Ma.nc\"\n", - "else:\n", - " agegrid_filename = f\"{seafloorgrid.save_directory}/SEAFLOOR_AGE/{seafloorgrid.file_collection}_SEAFLOOR_AGE_grid_unmasked_{time:0.2f}Ma.nc\"\n", - "\n", - "age_grid = gplately.grids.Raster(agegrid_filename)\n", - "\n", - "# Prepare plots\n", - "fig = plt.figure(figsize=(8,4), dpi=240, linewidth=1)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", - "\n", - "gplot.time = time\n", - "plt.title(f\"Seafloor Age at {int(time)} Ma (Model:{seafloorgrid.file_collection})\")\n", - "\n", - "agegrid_cmap = \"YlGnBu\"\n", - "agegrid_cpt_file = \"NotebookFiles/agegrid.cpt\"\n", - "if not os.path.isfile(agegrid_cpt_file):\n", - " print(f\"The file {agegrid_cpt_file} does not exist locally. It can be found at https://raw.githubusercontent.com/GPlates/gplately/refs/heads/master/Notebooks/NotebookFiles/agegrid.cpt\")\n", - " print(f\"The '{agegrid_cmap}' will be used to plot the age grid.\")\n", - " agegrid_cpt_file = None\n", - "if agegrid_cpt_file:\n", - " try:\n", - " from gplately.mapping.gmt_cpt import get_cmap_from_gmt_cpt\n", - " agegrid_cmap=get_cmap_from_gmt_cpt(agegrid_cpt_file)\n", - " except ImportError as e:\n", - " print(\"To use GMT cpt file, ensure you are using GPlately version 2.1 or higher.\")\n", - " print(e)\n", - "\n", - "im = gplot.plot_grid(\n", - " ax, \n", - " age_grid.data, \n", - " cmap = agegrid_cmap,\n", - " vmin = 0, \n", - " vmax =410,\n", - ")\n", - "gplot.plot_ridges(ax,linewidth=1)\n", - "if not plot_unmasked_grid:\n", - " gplot.plot_continents(ax, edgecolor=\"grey\", facecolor=\"lightgrey\", linewidth=0.5)\n", - "plt.colorbar(im, label=\"Seafloor Age (Myr)\", shrink=0.5, pad=0.05)\n", - "\n", - "# Save figure\n", - "plt.savefig(\n", - " f\"{output_parent_directory}/{seafloorgrid.file_collection}_seafloor_age_{int(time)}Ma.png\",\n", - " bbox_inches = 'tight',\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "56e03e39", - "metadata": {}, - "outputs": [], - "source": [ - "time = min_time\n", - "# set the plot_unmasked_grid variable to True if you would like to see the unmasked spreading rate grid.\n", - "plot_unmasked_grid = False\n", - "\n", - "# spreading rate grids\n", - "if not plot_unmasked_grid:\n", - " srgrid_filename = f\"{seafloorgrid.save_directory}/SPREADING_RATE/{seafloorgrid.file_collection}_SPREADING_RATE_grid_{time:0.2f}Ma.nc\"\n", - "else:\n", - " srgrid_filename = f\"{seafloorgrid.save_directory}/SPREADING_RATE/{seafloorgrid.file_collection}_SPREADING_RATE_grid_unmasked_{time:0.2f}Ma.nc\"\n", - "\n", - "sr_grid = gplately.grids.Raster(srgrid_filename)\n", - "\n", - "# Prepare plots\n", - "fig = plt.figure(figsize=(8,4), dpi=240, linewidth=1)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", - "\n", - "gplot.time = time\n", - "plt.title(f\"Seafloor Spreading Rate at {int(time)} Ma (Model:{seafloorgrid.file_collection})\")\n", - "\n", - "spreading_rate_cmap = \"magma\"\n", - "spreading_rate_cpt_file = \"NotebookFiles/spreading_full_rate.cpt\"\n", - "if not os.path.isfile(spreading_rate_cpt_file):\n", - " print(f\"The file {spreading_rate_cpt_file} does not exist locally. It can be found at https://raw.githubusercontent.com/GPlates/gplately/refs/heads/master/Notebooks/NotebookFiles/spreading_full_rate.cpt\")\n", - " print(f\"The '{spreading_rate_cmap}' will be used to plot the spreading rate grid.\")\n", - " spreading_rate_cpt_file = None\n", - "if spreading_rate_cpt_file:\n", - " try:\n", - " from gplately.mapping.gmt_cpt import get_cmap_from_gmt_cpt\n", - " spreading_rate_cmap=get_cmap_from_gmt_cpt(spreading_rate_cpt_file)\n", - " except ImportError as e:\n", - " print(\"To use GMT cpt file, ensure you are using GPlately version 2.1 or higher.\")\n", - " print(e)\n", - "\n", - "im = gplot.plot_grid(\n", - " ax, \n", - " sr_grid.data, \n", - " cmap = spreading_rate_cmap,\n", - " vmin = 0, \n", - " vmax =200,\n", - ")\n", - "gplot.plot_ridges(ax, linewidth=1)\n", - "if not plot_unmasked_grid:\n", - " gplot.plot_continents(ax, edgecolor=\"grey\", facecolor=\"lightgrey\", linewidth=0.5)\n", - "plt.colorbar(im, label=\"Spreading rate (mm/yr)\", shrink=0.5, pad=0.05, ticks=[0,50,100,150,200])\n", - "\n", - "# Save figure\n", - "plt.savefig(\n", - " f\"{output_parent_directory}/{seafloorgrid.file_collection}_seafloor_spreading_rate_{int(time)}Ma.png\",\n", - " bbox_inches = 'tight',\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6a6c21c7", + "metadata": {}, + "source": [ + "## 10 - Seafloor Grids\n", + "\n", + "An adaptation of [agegrid-0.1](https://github.com/siwill22/agegrid-0.1) written by Simon Williams, Nicky Wright and John Cannon for gridding general z-values onto seafloor basin points using GPlately.\n", + "\n", + "This notebook demonstrates how to generate seafloor age and spreading-rate grids through time. The figure below, generated by this notebook, shows seafloor age at 65 Ma. You may adjust the time parameter to generate grids at different geological times.\n", + "\n", + "![Figure: seafloor_age_65Ma.png](https://gplates.github.io/gplately/latest/sphinx/html/_images/seafloor_age_65Ma.png)\n", + "\n", + "### Citation:\n", + "Simon Williams, Nicky M. Wright, John Cannon, Nicolas Flament, R. Dietmar Müller, Reconstructing seafloor age distributions in lost ocean basins, Geoscience Frontiers, Volume 12, Issue 2, 2021, Pages 769-780, ISSN 1674-9871,\n", + "https://doi.org/10.1016/j.gsf.2020.06.004." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db118eb4", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "#\n", + "# Setting GPLATELY_DEBUG to a value above 100 will create the following debug files\n", + "# during seafloor gridding (that can be loaded into GPlates):\n", + "#\n", + "# - A separate debug file for each time (from 'max_time' to 'min_time') containing the seed points\n", + "# reconstructed from that time to 'min_time'.\n", + "# * The debug file with \"initial_ocean_basin\" in the filename contains reconstructions of the initial ocean basin seed points, and\n", + "# * each debug file with \"mid_ocean_ridge\" in the filename contains reconstructions of the seed points created along mid-ocean ridges\n", + "# that formed at that time specified in the filename.\n", + "# - One big debug file containing the reconstructions of ALL seed points created at ALL times.\n", + "#\n", + "# These files are located in the 'debug' sub-directory of the save directory.\n", + "#\n", + "# os.environ[\"GPLATELY_DEBUG\"] = \"200\"\n", + "\n", + "import gplately\n", + "\n", + "import glob, warnings\n", + "from pathlib import Path\n", + "import matplotlib.pyplot as plt\n", + "import cartopy.crs as ccrs\n", + "from plate_model_manager import PlateModelManager\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", + "data_dir = Path(\"10-Seafloor-Grids-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "9a4bddee", + "metadata": {}, + "source": [ + "### Define a rotation model, topology features and continents for the `PlateReconstruction` model\n", + "There are two ways to do this. To use local files, set `use_local_files = True`. To use `PlateModelManager`, set `use_local_files = False`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b571bf94", + "metadata": {}, + "outputs": [], + "source": [ + "use_local_files = False" + ] + }, + { + "cell_type": "markdown", + "id": "4936baad", + "metadata": {}, + "source": [ + "#### 1) Manually pointing to files" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e5319664", + "metadata": {}, + "outputs": [], + "source": [ + "if use_local_files:\n", + " # Method 1: manually point to files\n", + " # you can download the model files at\n", + " # https://www.earthbyte.org/webdav/ftp/Data_Collections/Merdith_etal_2021_ESR/SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4.zip\n", + " input_directory = \"./SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4\"\n", + "\n", + " rotation_model = glob.glob(os.path.join(input_directory, \"*.rot\"))\n", + " static_polygons = input_directory + \"/shapes_static_polygons_Merdith_et_al.gpml\"\n", + " topology_features = [\n", + " input_directory + \"/250-0_plate_boundaries_Merdith_et_al.gpml\",\n", + " input_directory + \"/410-250_plate_boundaries_Merdith_et_al.gpml\",\n", + " input_directory + \"/1000-410-Convergence_Merdith_et_al.gpml\",\n", + " input_directory + \"/1000-410-Divergence_Merdith_et_al.gpml\",\n", + " input_directory + \"/1000-410-Topologies_Merdith_et_al.gpml\",\n", + " input_directory + \"/1000-410-Transforms_Merdith_et_al.gpml\",\n", + " input_directory + \"/TopologyBuildingBlocks_Merdith_et_al.gpml\",\n", + " ]\n", + "\n", + " continents = input_directory + \"/shapes_continents.gpml\"\n", + " coastlines = input_directory + \"/shapes_coastlines_Merdith_et_al_v2.gpmlz\"\n", + " COBs = None\n", + "\n", + " from pathlib import Path\n", + "\n", + " if not Path(input_directory).is_dir():\n", + " raise FileNotFoundError(\n", + " f\"The input directory does not exist: {input_directory}. Ensure your files are in the correct locations or download the example input files at https://www.earthbyte.org/webdav/ftp/Data_Collections/Merdith_etal_2021_ESR/SM2-Merdith_et_al_1_Ga_reconstruction_v1.2.4.zip\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "id": "1519da7c", + "metadata": {}, + "source": [ + "#### 2) Using `PlateModelManager`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ebf9727", + "metadata": {}, + "outputs": [], + "source": [ + "if not use_local_files:\n", + " # Method 2: use PlateModelManager\n", + " pmm_model = PlateModelManager().get_model(\n", + " \"Merdith2021\", data_dir=\"plate-model-repo\"\n", + " )\n", + " assert pmm_model is not None, \"Failed to load the Merdith2021 plate model.\"\n", + " rotation_model = pmm_model.get_rotation_model()\n", + " topology_features = pmm_model.get_topologies()\n", + " static_polygons = pmm_model.get_static_polygons()\n", + "\n", + " continents = pmm_model.get_layer(\"ContinentalPolygons\")" + ] + }, + { + "cell_type": "markdown", + "id": "3560443e", + "metadata": {}, + "source": [ + "## The SeafloorGrid object\n", + "...is a collection of methods to generate seafloor grids.\n", + "\n", + "The critical input parameters are:\n", + "\n", + "### Plate model parameters\n", + "* **`plate_reconstruction`**: The gplately `PlateReconstruction` object defined in the cell below. This object is a collection of methods for calculating plate tectonic stats through geological time.\n", + "* **`plot_topologies`**: The gplately `PlotTopologies` object defined in the cell below. This object is a collection of methods for resolving geological features needed for gridding to a certain geological time.\n", + "\n", + "#### Define the `PlateReconstruction` and `PlotTopologies` objects" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12bec53e", + "metadata": {}, + "outputs": [], + "source": [ + "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", + "\n", + "gplot = gplately.PlotTopologies(model, continents=continents)" + ] + }, + { + "cell_type": "markdown", + "id": "57d6e8b2", + "metadata": {}, + "source": [ + "### Time parameters\n", + "* **`max_time`**: The first time step to start generating age grids. This is the time step where we define certain **gridding initial conditions**, which will be explained below.\n", + "* **`min_time`**: The final time step to generate age grids. This is the time when recursive reconstructions starting from `max_time` stop.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17add2d7", + "metadata": {}, + "outputs": [], + "source": [ + "max_time = 410.0\n", + "min_time = 65.0" + ] + }, + { + "cell_type": "markdown", + "id": "703d2315", + "metadata": {}, + "source": [ + "### Ridge resolution parameters\n", + "With each reconstruction time step, mid-ocean ridge segments emerge and spread across the ocean floor. In `gplately`, these ridges are lines, or a tessellation of infinitesimal points. The spatio-temporal resolution of these ridge points can be controlled by two parameters:\n", + "* **`ridge_time_step`**: The \"delta time\" or time increment between successive resolutions of ridges, and hence successive grids. By default this is 1Myr, so grids are produced every millionth year.\n", + "* **`ridge_sampling`**: This controls the geographical resolution (in degrees) with which ridge lines are partitioned into points. The larger this is, the larger the spacing between successive ridge points, and hence the smaller the number of ridge points at each timestep. By default this is 0.5 degrees, so points are spaced 0.5 degrees apart. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b959a7b4", + "metadata": {}, + "outputs": [], + "source": [ + "ridge_time_step = 5.0 # We set the time step to 5 Myr to reduce the notebook’s runtime.\n", + "ridge_sampling = 0.5" + ] + }, + { + "cell_type": "markdown", + "id": "c1c20f7d", + "metadata": {}, + "source": [ + "***\n", + "### Grid resolution parameters\n", + "The ridge resolution parameters mentioned above take care of the spatio-temporal positioning of ridge features/points. However, these ridge points need to be interpolated onto regular grids - separate parameters are needed for these grids. \n", + "\n", + "#### For the `max_time` initial condition\n", + "At `max_time`, i.e. 410Ma, the `PlateReconstruction` model has not been initialised at 411Ma and any time(s) before that to sculpt the geological history before `max_time`. In terms of the workflow, all geological history before `max_time` is unknown. Thus, the global seafloor point distribution, and each point's spreading rate and age must be manually defined. \n", + "\n", + "The initial point distribution is an icosahedral point mesh.\n", + "\n", + "* **`refinement_levels`**: A unitless integer that controls the number of points in the `max_time` icosahedral point mesh.\n", + " \n", + " 6 is the default. Any higher will result in a finer mesh.\n", + " \n", + " \n", + "* **`initial_ocean_mean_spreading_rate`** (in units of mm/yr or km/myr): Since the geological history before and at `max_time` is unknown, we will need to manually define the spreading rates and ages of all ocean points. This is manually set to a uniform spreading rate of 50 (mm/yr or km/myr). Each point's age is equal to its proximity to the nearest mid ocean ridge (assuming that ridge is the source of the point) divided by half this uniform spreading rate (half to account for spreading to the left and right of a ridge). \n", + "\n", + "#### For the interpolated regular grids\n", + "The regular grid on which all data is interpolated has a resolution that can be controlled by:\n", + "\n", + "* **`grid_spacing`**: The degree spacing between successive nodes in the grid. By default, this is 0.1 degrees. **Acceptable degree spacings are 0.1, 0.25, 0.5, 0.75, or 1 degree(s)** because these allow cleanly divisible grid nodes across global latitude and longitude extents. Anything greater than 1 will be rounded to 1, and anything between these acceptable spacings will be rounded to the nearest acceptable spacing. \n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ec00f36", + "metadata": {}, + "outputs": [], + "source": [ + "refinement_levels = 6\n", + "initial_ocean_mean_spreading_rate = 50.0\n", + "\n", + "# Gridding parameter\n", + "grid_spacing = 0.25\n", + "\n", + "extent = (-180, 180, -90, 90)" + ] + }, + { + "cell_type": "markdown", + "id": "8464bfb4", + "metadata": {}, + "source": [ + "***\n", + "### File saving and naming parameters\n", + "When grids are produced, they are saved to:\n", + "* **`save_directory`**: A string to a directory that must exist already.\n", + "\n", + "These files are named according to:\n", + "* **`file_collection`**: A string used to help with naming all output files. \n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "106b61f5", + "metadata": {}, + "outputs": [], + "source": [ + "# continent masks, initial ocean seed points, and gridding input files are kept here\n", + "output_parent_directory = output_dir\n", + "save_directory = output_parent_directory / \"seafloor_grids\"\n", + "print(\n", + " f\"The output files created by this notebook will be saved in folder {save_directory}.\"\n", + ")\n", + "\n", + "# A string to help name files according to a plate model \"Merdith2021\"\n", + "file_collection = \"Merdith2021\"" + ] + }, + { + "cell_type": "markdown", + "id": "a328fa6d", + "metadata": {}, + "source": [ + "***\n", + "### Continent collision\n", + "\n", + "One way an oceanic point can be deleted from the ocean basin at a certain timestep is if it is collides into continental crust. At each timestep continental crust is represented as a **continental mask** which is a binary grid that partitions continental regions from oceanic regions. By default, continental masks are generated from the reconstructed continental polygons (where the continent polygons features are specified with ``continent_polygon_features``). But they can also be generated using [continent contouring](https://github.com/EarthByte/continent-contouring) (by specifying ``use_continent_contouring=True``) or you can explicitly provide them as your own continent masks (by specifying them with ``continent_mask_filename``).\n", + "\n", + "### Subduction parameters\n", + "Another way an oceanic point can be deleted from the ocean basin at a certain timestep is if it is approaching a plate boundary such that a **velocity and displacement test** is passed:\n", + "\n", + "#### Velocity test\n", + "First, given the trajectory of a point at the next time step, a point will cross a subducting plate boundary (and thus will be deleted) if the difference between its velocity on its current plate and the velocity it would have on the other plate is higher than a specified **threshold delta velocity in km/Myr**. \n", + "\n", + "#### Displacement test\n", + "If the proximity of the point's previous time position to the plate boundary it is approaching is higher than a set distance threshold (**threshold distance to boundary per Myr in km/Myr**), then the point is far enough away from the boundary that it cannot be subducted or consumed by it, and \n", + "hence the point is still active.\n", + "\n", + "The parameter needed to encapsulate these thresholds is:\n", + "* **`subduction_collision_parameters`**: This is a tuple with two elements, by default (5.0, 10.0), for the (threshold velocity delta in km/Myr, threshold distance to boundary per Myr in km/Myr)\n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68450bf6", + "metadata": {}, + "outputs": [], + "source": [ + "continent_polygon_features = continents\n", + "subduction_collision_parameters = (5.0, 10.0)" + ] + }, + { + "cell_type": "markdown", + "id": "bd978b09", + "metadata": {}, + "source": [ + "***\n", + "### Methodology parameters\n", + "* **`resume_from_checkpoints`**: Gridding can take a while (depending on the number of CPUs used). If this is changed to `True`, after gridding was interrupted, then rerunning the gridding cell will resume from where it left off.\n", + "* **`nprocs`**: The number of CPUs to use for parts of the code that are parallelized. The default is `-2` which uses all CPUs except one (to keep the system responsive).\n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1a6fbee", + "metadata": {}, + "outputs": [], + "source": [ + "# Methodology parameters\n", + "resume_from_checkpoints = False\n", + "nprocs = -2" + ] + }, + { + "cell_type": "markdown", + "id": "5038c7e6", + "metadata": {}, + "source": [ + "Use all parameters to define `SeafloorGrid`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ed5fc9e9", + "metadata": {}, + "outputs": [], + "source": [ + "# The SeafloorGrid object with all aforementioned parameters\n", + "seafloorgrid = gplately.SeafloorGrid(\n", + " plate_reconstruction=model,\n", + " # Time parameters\n", + " max_time=max_time,\n", + " min_time=min_time,\n", + " # Ridge tessellation parameters\n", + " ridge_time_step=ridge_time_step,\n", + " ridge_sampling=ridge_sampling,\n", + " # Gridding parameters\n", + " grid_spacing=grid_spacing,\n", + " extent=extent,\n", + " # Naming parameters\n", + " save_directory=save_directory,\n", + " file_collection=file_collection,\n", + " # Initial condition parameters\n", + " refinement_levels=refinement_levels,\n", + " initial_ocean_mean_spreading_rate=initial_ocean_mean_spreading_rate,\n", + " # Subduction parameters\n", + " subduction_collision_parameters=subduction_collision_parameters,\n", + " # Methodology parameters\n", + " resume_from_checkpoints=resume_from_checkpoints,\n", + " # Continental polygons.\n", + " continent_polygon_features=continent_polygon_features,\n", + " nprocs=nprocs,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ab3cbf87", + "metadata": {}, + "source": [ + "## How to use `SeafloorGrid`\n", + "\n", + "### 1) Run `seafloorgrid.reconstruct_by_topologies()`\n", + "Running `seafloorgrid.reconstruct_by_topologies()` prepares all ocean basin points and their seafloor ages and spreading rates for gridding, and reconstructs all active points per timestep to form the grids per timestep.\n", + "\n", + "#### Continental masks\n", + "A netCDF4 continent mask is produced for each _time_ from `max_time` to `min_time`.\n", + "\n", + "#### Initial ocean basin\n", + "At `max_time`, an initial ocean seed point icosahedral mesh fills the ocean basin. Each initial seed point is allocated an age based on its distance from the nearest mid-ocean ridge. And these are reconstructed to `min_time` to produce an `.npz` file (containing reconstructed seed point data for seed points created at `max_time`).\n", + "\n", + "#### Mid-ocean ridges\n", + "At each successive timestep (`ridge_time_step`), new seed points emerge from spreading ridges, and they are allocated their own spreading rates. The seed points emerging at a particular _time_ are reconstructed to `min_time` to produce an `.npz` file (containing reconstructed seed point data for seed points created at that _time_). This results in one `.npz` file for each _time_ from `max_time - ridge_time_step` to `min_time`.\n", + "\n", + "#### Seafloor age and spreading rate\n", + "After all seed points (initial and mid-ocean ridge) have been reconstructed to `min_time`, then one `.npz` file is generated for each _time_ from `max_time` to `min_time`. These files contain the gridding input that will later be used to generate the seafloor age and spreading rate grids.\n", + "\n", + "#### Resuming an interrupted gridding run\n", + "*If the `resume_from_checkpoints` parameter is passed as `True`, after `seafloorgrid.reconstruct_by_topologies()` was interrupted, then you can rerun the cell. The workflow will pick up from where it left off (provided the continent mask files and reconstructed seed point data files have not been erased).*\n", + "\n", + "To overwrite all files in `save_directory`, pass `resume_from_checkpoints` as `False` (this is the default behaviour).\n", + "\n", + "#### Reconstruct by topologies\n", + "The `ReconstructByTopologies` object (written by Simon Williams, Nicky Wright, and John Cannon) handles the reconstruction of seed points. `ReconstructByTopologies` (RBT) identifies active points on the ocean basin per timestep. It works as follows:\n", + "\n", + "If an ocean point on one plate ID transitions into another rigid plate ID at the next timestep, RBT calculates the point's velocity difference between both plates. The point **may** have subducted/collided with a continent at this boundary if this velocity difference is higher than a set velocity threshold. To ascertain whether the point should indeed be deactivated, a second test is conducted: RBT checks the previous time position of the point and calculates this point’s proximity to the boundary of the plate ID polygon it is approaching. If this distance is higher than a set distance threshold, then the point is far enough away from the boundary that it cannot be subducted or consumed by it and hence the point is still active. Else, it is deactivated/deleted.\n", + "\n", + "Once all active points and their z-values are identified, they are written to the gridding input file (.npz) for that timestep.\n", + "\n", + "Below is an example of the initial condition: the ocean basin is populated with an `initial_ocean_mean_spreading_rate` of 50 mm/yr at a `max_time` of 410Ma. Reconstruction over 10 Myr to 400 Ma sees new seed points emerging from ridges with their own plate-model-ascribed spreading rates.\n", + "\n", + "![init_condition](https://raw.githubusercontent.com/GPlates/gplately/master/Notebooks/NotebookFiles/Notebook10/Merdith2021_initial_sr_conditions.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143ea077", + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import clear_output\n", + "import datetime\n", + "import time\n", + "\n", + "start = time.time()\n", + "seafloorgrid.reconstruct_by_topologies()\n", + "end = time.time()\n", + "duration = datetime.timedelta(seconds=end - start)\n", + "clear_output()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27105a29-79f9-4cee-a935-c6bba8f5cb56", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Finished creating seafloor grids. Duration: {duration}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a0a0ca60", + "metadata": {}, + "source": [ + "### 2) Run `seafloorgrid.lat_lon_z_to_netCDF` to write grids to netCDF\n", + "Calling `seafloorgrid.lat_lon_z_to_netCDF` grids one set of z-data per latitude-longitude pair from each timestep's gridding input file (produced in `seafloorgrid.reconstruct_by_topologies()`). Grids are in netCDF format.\n", + "\n", + "The desired z-data to grid is identified using a `zval_name`. \n", + "For example, seafloor age grids can be produced using `SEAFLOOR_AGE`, and spreading rate grids are `SPREADING_RATE`. \n", + "\n", + "Use `unmasked = True` to output both the masked and unmasked versions of the grids." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "891183f3", + "metadata": {}, + "outputs": [], + "source": [ + "seafloorgrid.lat_lon_z_to_netCDF(\"SEAFLOOR_AGE\", unmasked=True)\n", + "clear_output()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d51a41a5", + "metadata": {}, + "outputs": [], + "source": [ + "seafloorgrid.lat_lon_z_to_netCDF(\"SPREADING_RATE\", unmasked=True)\n", + "clear_output()" + ] + }, + { + "cell_type": "markdown", + "id": "8213b46a", + "metadata": {}, + "source": [ + "### Plotting a sample age grid and spreading rate grid\n", + "Read one netCDF grid using GPlately's `Raster` object from `grids`, and plot it using the `PlotTopologies` object.\n", + "\n", + "Notice the evolution of seafloor spreading rate from the initial value set with `initial_ocean_mean_spreading_rate`. Eventually, this initial uniform spreading rate will be phased out with sufficient recursive reconstruction." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06a92ce9-ecea-49b9-9c99-c33438e75379", + "metadata": {}, + "outputs": [], + "source": [ + "time = min_time\n", + "# set the plot_unmasked_grid variable to True if you would like to see the unmasked age grid.\n", + "plot_unmasked_grid = False\n", + "\n", + "# age grids\n", + "if not plot_unmasked_grid:\n", + " agegrid_filename = f\"{seafloorgrid.save_directory}/SEAFLOOR_AGE/{seafloorgrid.file_collection}_SEAFLOOR_AGE_grid_{time:0.2f}Ma.nc\"\n", + "else:\n", + " agegrid_filename = f\"{seafloorgrid.save_directory}/SEAFLOOR_AGE/{seafloorgrid.file_collection}_SEAFLOOR_AGE_grid_unmasked_{time:0.2f}Ma.nc\"\n", + "\n", + "age_grid = gplately.grids.Raster(agegrid_filename)\n", + "\n", + "# Prepare plots\n", + "fig = plt.figure(figsize=(8, 4), dpi=240, linewidth=1)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", + "\n", + "gplot.time = time\n", + "plt.title(f\"Seafloor Age at {int(time)} Ma (Model: {seafloorgrid.file_collection})\")\n", + "\n", + "agegrid_cmap = \"YlGnBu\"\n", + "agegrid_cpt_file = f\"{output_parent_directory}/agegrid.cpt\"\n", + "# Download the age-grid CPT file if it is not present. This CPT is used for map plotting below.\n", + "if not os.path.isfile(agegrid_cpt_file):\n", + " import urllib.request\n", + "\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/agegrid.cpt\",\n", + " agegrid_cpt_file,\n", + " )\n", + "try:\n", + " from gplately.plot.gmt_cpt import get_cmap_from_gmt_cpt\n", + "\n", + " agegrid_cmap = get_cmap_from_gmt_cpt(agegrid_cpt_file)\n", + "except ImportError as e:\n", + " print(\"To use GMT cpt file, ensure you are using GPlately version 2.1 or higher.\")\n", + " print(f\"The '{agegrid_cmap}' will be used to plot the age grid.\")\n", + " print(e)\n", + "\n", + "im = gplot.plot_grid(\n", + " ax,\n", + " age_grid.data,\n", + " cmap=agegrid_cmap,\n", + " vmin=0,\n", + " vmax=410,\n", + ")\n", + "gplot.plot_ridges(ax, linewidth=1)\n", + "if not plot_unmasked_grid:\n", + " gplot.plot_continents(ax, edgecolor=\"grey\", facecolor=\"lightgrey\", linewidth=0.5)\n", + "plt.colorbar(im, label=\"Seafloor Age (Myr)\", shrink=0.5, pad=0.05)\n", + "\n", + "# Save figure\n", + "plt.savefig(\n", + " f\"{output_parent_directory}/{seafloorgrid.file_collection}_seafloor_age_{int(time)}Ma.png\",\n", + " bbox_inches=\"tight\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56e03e39", + "metadata": {}, + "outputs": [], + "source": [ + "time = min_time\n", + "# set the plot_unmasked_grid variable to True if you would like to see the unmasked spreading rate grid.\n", + "plot_unmasked_grid = False\n", + "\n", + "# spreading rate grids\n", + "if not plot_unmasked_grid:\n", + " srgrid_filename = f\"{seafloorgrid.save_directory}/SPREADING_RATE/{seafloorgrid.file_collection}_SPREADING_RATE_grid_{time:0.2f}Ma.nc\"\n", + "else:\n", + " srgrid_filename = f\"{seafloorgrid.save_directory}/SPREADING_RATE/{seafloorgrid.file_collection}_SPREADING_RATE_grid_unmasked_{time:0.2f}Ma.nc\"\n", + "\n", + "sr_grid = gplately.Raster(srgrid_filename)\n", + "\n", + "# Prepare plots\n", + "fig = plt.figure(figsize=(8, 4), dpi=240, linewidth=1)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=20))\n", + "\n", + "gplot.time = time\n", + "plt.title(\n", + " f\"Seafloor Spreading Rate at {int(time)} Ma (Model: {seafloorgrid.file_collection})\"\n", + ")\n", + "\n", + "spreading_rate_cmap = \"magma\"\n", + "spreading_rate_cpt_file = f\"{output_parent_directory}/spreading_full_rate.cpt\"\n", + "# Download the spreading rate CPT file if it is not present. This CPT is used for map plotting below.\n", + "if not os.path.isfile(spreading_rate_cpt_file):\n", + " import urllib.request\n", + "\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/spreading_full_rate.cpt\",\n", + " spreading_rate_cpt_file,\n", + " )\n", + "\n", + "try:\n", + " from gplately.plot.gmt_cpt import get_cmap_from_gmt_cpt\n", + "\n", + " spreading_rate_cmap = get_cmap_from_gmt_cpt(spreading_rate_cpt_file)\n", + "except ImportError as e:\n", + " print(\"To use GMT cpt file, ensure you are using GPlately version 2.1 or higher.\")\n", + " print(f\"The '{spreading_rate_cmap}' will be used to plot the spreading rate grid.\")\n", + " print(e)\n", + "\n", + "im = gplot.plot_grid(\n", + " ax,\n", + " sr_grid.data,\n", + " cmap=spreading_rate_cmap,\n", + " vmin=0,\n", + " vmax=200,\n", + ")\n", + "gplot.plot_ridges(ax, linewidth=1)\n", + "if not plot_unmasked_grid:\n", + " gplot.plot_continents(ax, edgecolor=\"grey\", facecolor=\"lightgrey\", linewidth=0.5)\n", + "plt.colorbar(\n", + " im,\n", + " label=\"Spreading rate (mm/yr)\",\n", + " shrink=0.5,\n", + " pad=0.05,\n", + " ticks=[0, 50, 100, 150, 200],\n", + ")\n", + "\n", + "# Save figure\n", + "plt.savefig(\n", + " f\"{output_parent_directory}/{seafloorgrid.file_collection}_seafloor_spreading_rate_{int(time)}Ma.png\",\n", + " bbox_inches=\"tight\",\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Notebooks/11-AndesFluxes.ipynb b/Notebooks/11-AndesFluxes.ipynb index 4c0fad80..94e73786 100644 --- a/Notebooks/11-AndesFluxes.ipynb +++ b/Notebooks/11-AndesFluxes.ipynb @@ -6,10 +6,10 @@ "metadata": {}, "source": [ "## 11 - AndesFluxes\n", - "This notebook demonstrates how the reconstructed subduction history along the Andean margin can be potentially used in the plate kinematics anylysis and data mining. For example, there is the potential link between the overall subduction flux and zircon age frequency. Another example is that the along-strike variations on inputs to the subduction zone could relate to porphyry copper formation.\n", + "This notebook demonstrates how the reconstructed subduction history along the Andean margin can potentially be used in plate kinematics analysis and data mining. For example, there is a potential link between the overall subduction flux and zircon age frequency. Another example is that along-strike variations in inputs to the subduction zone could be related to porphyry copper formation.\n", "\n", "### Citation:\n", - "Seton, M., Williams, S.E., Domeier, M. et al. Deconstructing plate tectonic reconstructions. Nat Rev Earth Environ 4, 185–204 (2023). https://doi.org/10.1038/s43017-022-00384-8" + "Seton, M., Williams, S.E., Domeier, M. et al. Deconstructing plate tectonic reconstructions. Nat Rev Earth Environ 4, 185\u2013204 (2023). https://doi.org/10.1038/s43017-022-00384-8" ] }, { @@ -19,11 +19,8 @@ "metadata": {}, "outputs": [], "source": [ - "import sys\n", "import gplately\n", "import numpy as np\n", - "import pygplates\n", - "import glob, os\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", @@ -31,7 +28,7 @@ "\n", "anchor_plate_id = 0\n", "\n", - "#south_america_plate_id = 201" + "# south_america_plate_id = 201" ] }, { @@ -41,7 +38,7 @@ "source": [ "### PlateModelManager\n", "\n", - "The **PlateModelManager** class can be used to download the plate reconstruction model files via Internet. The files will be fetched and saved into local folders. Users can use the member functions of PlateModel class to retrieve the local absolute path of the files. For example, the get_rotation_model() will return the local path to the rotation file(s).\n", + "The **PlateModelManager** class can be used to download the plate reconstruction model files via the internet. The files are fetched and saved into local folders. Users can use the member functions of the `PlateModel` class to retrieve the local absolute path of the files \u2014 for example, `get_rotation_model()` returns the local path to the rotation file(s).\n", "\n", "The **PlateReconstruction** class can be used to reconstruct tectonic plates and calculate subduction convergence stats.\n", "\n", @@ -55,19 +52,22 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture cap\n", "# Use PlateModelManager to download and manage files of the plate reconstruction models\n", - "pm_manager = PlateModelManager()\n", - "plate_model = pm_manager.get_model(\"Muller2019\", data_dir=\"plate-model-repo\")\n", - "\n", - "model = gplately.PlateReconstruction(plate_model.get_rotation_model(), \n", - " plate_model.get_topologies(), \n", - " plate_model.get_static_polygons(),\n", - " anchor_plate_id=anchor_plate_id)\n", - "gplot = gplately.plot.PlotTopologies(model, \n", - " plate_model.get_layer('Coastlines'), \n", - " plate_model.get_layer('ContinentalPolygons'), \n", - " plate_model.get_layer('COBs'))" + "model_name = \"Muller2019\" # only works for Muller2019 for now becasue SedimentThickness grids are only available for this model.\n", + "pmm_model = PlateModelManager().get_model(model_name, data_dir=\"plate-model-repo\")\n", + "assert pmm_model is not None, \"Failed to load plate model\"\n", + "model = gplately.PlateReconstruction(\n", + " pmm_model.get_rotation_model(),\n", + " pmm_model.get_topologies(),\n", + " pmm_model.get_static_polygons(),\n", + " anchor_plate_id=anchor_plate_id,\n", + ")\n", + "gplot = gplately.plot.PlotTopologies(\n", + " model,\n", + " pmm_model.get_layer(\"Coastlines\"),\n", + " pmm_model.get_layer(\"ContinentalPolygons\"),\n", + " pmm_model.get_layer(\"COBs\"),\n", + ")" ] }, { @@ -85,38 +85,56 @@ "metadata": {}, "outputs": [], "source": [ - "# We are only interested in the latitude -55 and 5 in this notebook. \n", - "lat_samples = np.arange(-55,5,0.1)\n", + "# We are only interested in the latitude range -55 to 5 in this notebook.\n", + "lat_samples = np.arange(-55, 5, 0.1)\n", + "\n", "\n", "def get_south_america_subduction_zone_data(r12n_time):\n", - " \"\"\"we are only interesed in the South America subduction zone in this notebook\n", - " filter and keep the South America subduction zone data only\n", - " \n", - " # Calculate subduction convergence stats with GPlately\n", - " # Col. 0 - longitude of sampled trench point\n", - " # Col. 1 - latitude of sampled trench point\n", - " # Col. 2 - subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", - " # Col. 3 - subducting convergence velocity obliquity angle (angle between trench normal vector and convergence velocity vector)\n", - " # Col. 4 - trench absolute (relative to anchor plate) velocity magnitude (in cm/yr)\n", - " # Col. 5 - trench absolute velocity obliquity angle (angle between trench normal vector and trench absolute velocity vector)\n", - " # Col. 6 - length of arc segment (in degrees) that current point is on\n", - " # Col. 7 - trench normal azimuth angle (clockwise starting at North, ie, 0 to 360 degrees) at current point\n", - " # Col. 8 - subducting plate ID\n", - " # Col. 9 - trench plate ID\n", + " \"\"\"Return subduction zone stats for the South America subduction zone at the given reconstruction time.\n", + "\n", + " We are only interested in the South America subduction zone in this notebook, so this\n", + " filters and keeps the South America subduction zone data only.\n", + "\n", + " Columns returned by `PlateReconstruction.tessellate_subduction_zones()`:\n", + " 0 - longitude of sampled trench point\n", + " 1 - latitude of sampled trench point\n", + " 2 - subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", + " 3 - subducting convergence velocity obliquity angle (angle between trench normal vector and convergence velocity vector)\n", + " 4 - trench absolute (relative to anchor plate) velocity magnitude (in cm/yr)\n", + " 5 - trench absolute velocity obliquity angle (angle between trench normal vector and trench absolute velocity vector)\n", + " 6 - length of arc segment (in degrees) that the current point is on\n", + " 7 - trench normal azimuth angle (clockwise from North, i.e. 0 to 360 degrees) at the current point\n", + " 8 - subducting plate ID\n", + " 9 - trench plate ID\n", " \"\"\"\n", - " subduction_data = model.tessellate_subduction_zones(r12n_time, \n", - " tessellation_threshold_radians=0.01, \n", - " anchor_plate_id=anchor_plate_id)\n", - " \n", - " ret=[]\n", + " subduction_data = model.tessellate_subduction_zones(\n", + " r12n_time, tessellation_threshold_radians=0.01, anchor_plate_id=anchor_plate_id\n", + " )\n", + "\n", + " ret = []\n", " for row in subduction_data:\n", - " # only keep the data for South America subduction zone\n", - " if row[8] in [902,908,919,904,911,985,224,802] and (row[9]==201 or row[9]>200000): # code copied from Simon's original notebook\n", + " # only keep the data for the South America subduction zone\n", + " if row[8] in [902, 908, 919, 904, 911, 985, 224, 802] and (\n", + " row[9] == 201 or row[9] > 200000\n", + " ): # code copied from Simon's original notebook\n", " ret.append(row)\n", " ret.sort(key=lambda row: row[1])\n", - " ret_df = pd.DataFrame(ret, columns=['lon', 'lat', 'conv_rate', 'conv_angle', 'trench_velocity', 'trench_velocity_angle', 'arc_length',\n", - " 'trench_azimuth_angle', 'subducting_pid', 'trench_pid'])\n", - " return ret_df[(ret_df.lat>lat_samples.min()) & (ret_df.lat lat_samples.min()) & (ret_df.lat < lat_samples.max())]" ] }, { @@ -125,7 +143,7 @@ "metadata": {}, "source": [ "### Plot the South America subduction zone sample points\n", - "In the cell below, we are going to plot the South America subduction zone sample points in a map. The sample points are coloured by the values of \"subducting convergence (relative to trench) velocity magnitude (in cm/yr)\"" + "In the cell below, we plot the South America subduction zone sample points on a map. The sample points are coloured by the \"subducting convergence (relative to trench) velocity magnitude (in cm/yr)\" value." ] }, { @@ -135,35 +153,51 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "reconstruction_time = 100\n", "# firstly, we get the data by calling the function defined in the above cell\n", - "south_america_subduction_data = get_south_america_subduction_zone_data(reconstruction_time)\n", - "\n", - "fig = plt.figure(figsize=(10,6), dpi=100)\n", - "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude = 0))\n", - "\n", - "gl=ax.gridlines(color='0.7',linestyle='--', xlocs=np.arange(-180,180,15), ylocs=np.arange(-90,90,15))\n", + "south_america_subduction_data = get_south_america_subduction_zone_data(\n", + " reconstruction_time\n", + ")\n", + "\n", + "fig = plt.figure(figsize=(10, 6), dpi=100)\n", + "ax = fig.add_subplot(111, projection=ccrs.Mollweide(central_longitude=0))\n", + "\n", + "gl = ax.gridlines(\n", + " color=\"0.7\",\n", + " linestyle=\"--\",\n", + " xlocs=np.arange(-180, 180, 15),\n", + " ylocs=np.arange(-90, 90, 15),\n", + ")\n", "gl.left_labels = True\n", "\n", - "plt.title(f'{reconstruction_time} Ma')\n", + "plt.title(f\"{reconstruction_time} Ma\")\n", "# you may change the extent to global to see the sample points in a world map.\n", - "#ax.set_global()\n", - "ax.set_extent([-80,0,-70,0])\n", + "# ax.set_global()\n", + "ax.set_extent((-80, 0, -70, 0))\n", "\n", "gplot.time = reconstruction_time\n", - "gplot.plot_coastlines(ax, color='grey')\n", + "gplot.plot_coastlines(ax, color=\"grey\")\n", "\n", "# Latitudes and longitudes of points along trench segments\n", "subduction_lon = south_america_subduction_data.lon\n", "subduction_lat = south_america_subduction_data.lat\n", "\n", - "conv_rate = south_america_subduction_data.conv_rate # subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", - "cb=ax.scatter(subduction_lon,subduction_lat, marker=\".\", s=5, c=conv_rate, transform=ccrs.PlateCarree(), cmap=\"inferno_r\")\n", + "conv_rate = (\n", + " south_america_subduction_data.conv_rate\n", + ") # subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", + "cb = ax.scatter(\n", + " subduction_lon,\n", + " subduction_lat,\n", + " marker=\".\",\n", + " s=5,\n", + " c=conv_rate,\n", + " transform=ccrs.PlateCarree(),\n", + " cmap=\"inferno_r\",\n", + ")\n", "\n", "cbar = plt.colorbar(cb)\n", "cbar.ax.get_yaxis().labelpad = 15\n", - "cbar.ax.set_ylabel('Convergence Velocity Magnitude (in cm/yr)', rotation=90)\n", + "cbar.ax.set_ylabel(\"Convergence Velocity Magnitude (in cm/yr)\", rotation=90)\n", "plt.show()" ] }, @@ -172,7 +206,7 @@ "id": "385f7418-95bb-45e1-93bd-11ab236fcf2f", "metadata": {}, "source": [ - "The get_extent_from_data() function is just a helper function to get the region of interest from the input data. It will return a much smaller area of the map which matters in this notebook. Then, we use the smaller area to do the \"raster query\". It will improve the performance dramatically." + "The `get_extent_from_data()` function is just a helper function to get the region of interest from the input data. It will return a much smaller area of the map than the full-globe extent, which is all we need for this notebook. We then use this smaller area to do the \"raster query,\" which improves performance dramatically." ] }, { @@ -183,15 +217,19 @@ "outputs": [], "source": [ "def get_extent_from_data(data, extent_buffer=2):\n", - " x0 = data.lon.min()-extent_buffer\n", - " if x0<-180: x0=360+x0\n", - " x1 = data.lon.max()+extent_buffer\n", - " if x1>180: x1=x1-360\n", - " y0 = data.lat.min()-extent_buffer\n", - " if y0<-90: y0=180+y0\n", - " y1 = data.lat.max()+extent_buffer\n", - " if y1>90: y1=y1-360\n", - " return [x0,x1,y0,y1]" + " x0 = data.lon.min() - extent_buffer\n", + " if x0 < -180:\n", + " x0 = 360 + x0\n", + " x1 = data.lon.max() + extent_buffer\n", + " if x1 > 180:\n", + " x1 = x1 - 360\n", + " y0 = data.lat.min() - extent_buffer\n", + " if y0 < -90:\n", + " y0 = -90 # clamp -- latitude does not wrap the way longitude does\n", + " y1 = data.lat.max() + extent_buffer\n", + " if y1 > 90:\n", + " y1 = 90 # clamp -- latitude does not wrap the way longitude does\n", + " return [x0, x1, y0, y1]" ] }, { @@ -200,14 +238,14 @@ "metadata": {}, "source": [ "### Calculate the South America subduction zone stats\n", - "We are going to calculate a few things in the code cell below.\n", + "We are going to calculate a few things in the code cell below:\n", "* Subduction area flux\n", "* Subduction volume flux\n", - "* Trench-Orthogonal convergence rate\n", + "* Trench-orthogonal convergence rate\n", "* Seafloor age\n", "* Thickness of subducting carbonate sediment\n", "\n", - "**Note: this code cell will take a while to run and will download a number of files from Internet.**" + "**Note: this code cell will take a while to run and will download a number of files from the internet.**" ] }, { @@ -218,66 +256,90 @@ "outputs": [], "source": [ "# We are only interested in the reconstruction time from 120Ma to 0Ma\n", - "time_series = np.arange(0,121,1)\n", + "time_series = np.arange(0, 121, 1)\n", "\n", - "area_flux_series=[]\n", - "volume_flux_series=[]\n", + "area_flux_series = []\n", + "volume_flux_series = []\n", "age_array = []\n", "conv_array = []\n", "carbonate_array = []\n", "for time in time_series:\n", " sa_sub_data = get_south_america_subduction_zone_data(time)\n", - " conv_rate = sa_sub_data.conv_rate # subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", - " conv_obliq = sa_sub_data.conv_angle # subducting convergence velocity obliquity angle (angle between trench normal vector and convergence velocity vector)\n", - " arc_length = sa_sub_data.arc_length # length of arc segment (in degrees) that current point is on\n", + " conv_rate = (\n", + " sa_sub_data.conv_rate\n", + " ) # subducting convergence (relative to trench) velocity magnitude (in cm/yr)\n", + " conv_obliq = (\n", + " sa_sub_data.conv_angle\n", + " ) # subducting convergence velocity obliquity angle (angle between trench normal vector and convergence velocity vector)\n", + " arc_length = (\n", + " sa_sub_data.arc_length\n", + " ) # length of arc segment (in degrees) that current point is on\n", " # Latitudes and longitudes of points along trench segments\n", " subduction_lon = sa_sub_data.lon\n", " subduction_lat = sa_sub_data.lat\n", "\n", - " ortho_conv_rate = conv_rate*np.abs(np.cos(np.radians(conv_obliq)))\n", - " area_flux = ortho_conv_rate * 100. * arc_length * 2 * np.pi * 6371000. / 360. # formula copied from Simon's Notebook\n", + " ortho_conv_rate = conv_rate * np.abs(np.cos(np.radians(conv_obliq)))\n", + " area_flux = (\n", + " ortho_conv_rate * 100.0 * arc_length * 2 * np.pi * 6371000.0 / 360.0\n", + " ) # formula copied from Simon's Notebook\n", "\n", " #\n", " # Query age grid rasters\n", " #\n", " age_grid_raster = gplately.Raster(\n", - " data=plate_model.get_raster(\"AgeGrids\",time),\n", + " data=pmm_model.get_raster(\"AgeGrids\", time),\n", " plate_reconstruction=model,\n", - " extent=[-180, 180, -90, 90],\n", + " extent=(-180, 180, -90, 90),\n", + " )\n", + " age_grid_raster = age_grid_raster.clip_by_extent(get_extent_from_data(sa_sub_data))\n", + " ages = age_grid_raster.query(\n", + " lons=subduction_lon, lats=subduction_lat, region_of_interest=200\n", " )\n", - " age_grid_raster=age_grid_raster.clip_by_extent(get_extent_from_data(sa_sub_data))\n", - " ages = age_grid_raster.query(subduction_lon, subduction_lat,region_of_interest=200)\n", "\n", " #\n", " # Query carbonate sediment thickness rasters\n", " #\n", " carbonate_thickness_raster = gplately.Raster(\n", - " data=plate_model.get_raster(\"SedimentThickness\",time),\n", + " data=pmm_model.get_raster(\"SedimentThickness\", time),\n", " plate_reconstruction=model,\n", - " extent=[-180, 180, -90, 90],\n", + " extent=(-180, 180, -90, 90),\n", " )\n", - " carbonate_thickness_raster=carbonate_thickness_raster.clip_by_extent(get_extent_from_data(sa_sub_data))\n", - " carbonate_thickness = carbonate_thickness_raster.query(subduction_lon, subduction_lat,region_of_interest=200)\n", - " \n", + " carbonate_thickness_raster = carbonate_thickness_raster.clip_by_extent(\n", + " get_extent_from_data(sa_sub_data)\n", + " )\n", + " carbonate_thickness = carbonate_thickness_raster.query(\n", + " lons=subduction_lon, lats=subduction_lat, region_of_interest=200\n", + " )\n", + "\n", " #\n", " # we have two methods to get the volume flux\n", " #\n", " # method 1: use gplately.tools.plate_isotherm_depth\n", " # plate_depth = [gplately.tools.plate_isotherm_depth(seafloor_age) for seafloor_age in ages]\n", - " # volume_flux = area_flux * plate_depth \n", - " \n", + " # volume_flux = area_flux * plate_depth\n", + "\n", " # method 2: use Simon's formula\n", - " volume_flux = area_flux * np.sqrt(ages) * 10. # UNITS????\n", + " volume_flux = area_flux * np.sqrt(ages) * 10.0 # UNITS????\n", "\n", " area_flux_series.append(area_flux.sum())\n", " volume_flux_series.append(np.nansum(volume_flux))\n", "\n", - " conv_array.append(np.interp(lat_samples, subduction_lat, ortho_conv_rate, left=np.nan, right=np.nan))\n", - " age_array.append(np.interp(lat_samples, subduction_lat, ages, left=np.nan, right=np.nan))\n", - " carbonate_array.append(np.interp(lat_samples, subduction_lat, carbonate_thickness, left=np.nan, right=np.nan))\n", - " \n", - " gplately.tools.update_progress(time/time_series.size) \n", - "gplately.tools.update_progress(1) " + " conv_array.append(\n", + " np.interp(\n", + " lat_samples, subduction_lat, ortho_conv_rate, left=np.nan, right=np.nan\n", + " )\n", + " )\n", + " age_array.append(\n", + " np.interp(lat_samples, subduction_lat, ages, left=np.nan, right=np.nan)\n", + " )\n", + " carbonate_array.append(\n", + " np.interp(\n", + " lat_samples, subduction_lat, carbonate_thickness, left=np.nan, right=np.nan\n", + " )\n", + " )\n", + "\n", + " gplately.tools.update_progress(time / time_series.size)\n", + "gplately.tools.update_progress(1)" ] }, { @@ -297,26 +359,31 @@ "source": [ "from scipy.ndimage import uniform_filter1d\n", "\n", - "fig = plt.figure(figsize=(8,4), dpi=100)\n", + "fig = plt.figure(figsize=(8, 4), dpi=100)\n", "ax = fig.add_subplot(111)\n", "\n", - "N=3\n", - "ax.plot(time_series, uniform_filter1d(area_flux_series, size=N), 'b', label='area flux')\n", - "ax.set_xlim(100,0)\n", - "ax.set_ylim(1e9,11e9)\n", - "ax.grid(axis='x', linestyle=':')\n", - "#ax.set_xticklabels([])\n", - "ax.set_ylabel('Area Flux', fontsize=14, color='b')\n", - "ax.tick_params(axis='y', labelcolor='b')\n", - "\n", - "ax1 = ax.twinx() \n", - "ax1.plot(time_series, uniform_filter1d(np.array(volume_flux_series), size=N), 'r', label='volume flux')\n", - "ax.set_xlabel('Reconstruction Time [Ma]', fontsize=14)\n", - "ax1.set_ylabel('Volume Flux', fontsize=14, color='r')\n", - "ax1.set_ylim(0e11,7e11)\n", - "ax1.tick_params(axis='y', labelcolor='r')\n", - "#ax.legend()\n", - "ax.set_title('Reconstructed Subduction Flux', fontsize=16)\n", + "N = 3\n", + "ax.plot(time_series, uniform_filter1d(area_flux_series, size=N), \"b\", label=\"area flux\")\n", + "ax.set_xlim(100, 0)\n", + "ax.set_ylim(1e9, 11e9)\n", + "ax.grid(axis=\"x\", linestyle=\":\")\n", + "# ax.set_xticklabels([])\n", + "ax.set_ylabel(\"Area Flux\", fontsize=14, color=\"b\")\n", + "ax.tick_params(axis=\"y\", labelcolor=\"b\")\n", + "\n", + "ax1 = ax.twinx()\n", + "ax1.plot(\n", + " time_series,\n", + " uniform_filter1d(np.array(volume_flux_series), size=N),\n", + " \"r\",\n", + " label=\"volume flux\",\n", + ")\n", + "ax.set_xlabel(\"Reconstruction Time [Ma]\", fontsize=14)\n", + "ax1.set_ylabel(\"Volume Flux\", fontsize=14, color=\"r\")\n", + "ax1.set_ylim(0e11, 7e11)\n", + "ax1.tick_params(axis=\"y\", labelcolor=\"r\")\n", + "# ax.legend()\n", + "ax.set_title(\"Reconstructed Subduction Flux\", fontsize=16)\n", "plt.show()" ] }, @@ -335,22 +402,28 @@ "metadata": {}, "outputs": [], "source": [ - "fig = plt.figure(figsize=(8,4), dpi=100)\n", + "fig = plt.figure(figsize=(8, 4), dpi=100)\n", "ax = fig.add_subplot(111)\n", "\n", - "m = ax.pcolormesh(time_series, \n", - " lat_samples, \n", - " np.array(conv_array).T, \n", - " vmin=0, vmax=15, cmap='Oranges')\n", - "\n", - "fig.colorbar(m, orientation=\"vertical\", extend='max', shrink=0.9, pad=0.03, label='Convergence Rate [cm/yr]')\n", - "\n", - "ax.set_xlim(100,0)\n", - "ax.set_ylim(-50,5)\n", - "ax.set_xlabel('Reconstruction Time [Ma]', fontsize=14)\n", - "ax.set_ylabel('Latitude', fontsize=14)\n", - "ax.set_title('Trench-Orthogonal Convergence Rate', fontsize=16)\n", - "plt.show()\n" + "m = ax.pcolormesh(\n", + " time_series, lat_samples, np.array(conv_array).T, vmin=0, vmax=15, cmap=\"Oranges\"\n", + ")\n", + "\n", + "fig.colorbar(\n", + " m,\n", + " orientation=\"vertical\",\n", + " extend=\"max\",\n", + " shrink=0.9,\n", + " pad=0.03,\n", + " label=\"Convergence Rate [cm/yr]\",\n", + ")\n", + "\n", + "ax.set_xlim(100, 0)\n", + "ax.set_ylim(-50, 5)\n", + "ax.set_xlabel(\"Reconstruction Time [Ma]\", fontsize=14)\n", + "ax.set_ylabel(\"Latitude\", fontsize=14)\n", + "ax.set_title(\"Trench-Orthogonal Convergence Rate\", fontsize=16)\n", + "plt.show()" ] }, { @@ -368,21 +441,32 @@ "metadata": {}, "outputs": [], "source": [ - "fig = plt.figure(figsize=(8,4), dpi=100)\n", + "fig = plt.figure(figsize=(8, 4), dpi=100)\n", "ax = fig.add_subplot(111)\n", "\n", - "m = ax.contourf(time_series, \n", - " lat_samples, \n", - " np.array(age_array).T, \n", - " levels=np.arange(0,101,5), cmap='plasma_r', extend='max')\n", - "\n", - "fig.colorbar(m, orientation=\"vertical\", extend='max', shrink=0.9, pad=0.03, label='Seafloor Age [Myr]')\n", - "\n", - "ax.set_xlim(100,0)\n", - "ax.set_ylim(-50,5)\n", - "ax.set_xlabel('Reconstruction Time [Ma]', fontsize=14)\n", - "ax.set_ylabel('Latitude', fontsize=14)\n", - "ax.set_title('Subducting Seafloor Age', fontsize=16)\n", + "m = ax.contourf(\n", + " time_series,\n", + " lat_samples,\n", + " np.array(age_array).T,\n", + " levels=np.arange(0, 101, 5),\n", + " cmap=\"plasma_r\",\n", + " extend=\"max\",\n", + ")\n", + "\n", + "fig.colorbar(\n", + " m,\n", + " orientation=\"vertical\",\n", + " extend=\"max\",\n", + " shrink=0.9,\n", + " pad=0.03,\n", + " label=\"Seafloor Age [Myr]\",\n", + ")\n", + "\n", + "ax.set_xlim(100, 0)\n", + "ax.set_ylim(-50, 5)\n", + "ax.set_xlabel(\"Reconstruction Time [Ma]\", fontsize=14)\n", + "ax.set_ylabel(\"Latitude\", fontsize=14)\n", + "ax.set_title(\"Subducting Seafloor Age\", fontsize=16)\n", "plt.show()" ] }, @@ -401,31 +485,35 @@ "metadata": {}, "outputs": [], "source": [ - "fig = plt.figure(figsize=(8,4), dpi=100)\n", + "fig = plt.figure(figsize=(8, 4), dpi=100)\n", "ax = fig.add_subplot(111)\n", "\n", - "m = ax.contourf(time_series, \n", - " lat_samples, \n", - " np.array(carbonate_array).T, \n", - " levels=[0,25,50,75,100,125,150], vmax=150, cmap='GnBu', extend='max')\n", - "\n", - "fig.colorbar(m, orientation=\"vertical\", extend='max', shrink=0.9, pad=0.03, label='Carbonate Sediment Thickness [meter]')\n", - "\n", - "ax.set_xlim(100,0)\n", - "ax.set_ylim(-50,5)\n", - "ax.set_xlabel('Reconstruction Time [Ma]', fontsize=14)\n", - "ax.set_ylabel('Latitude', fontsize=14)\n", - "ax.set_title('Thickness of Subducting Carbonate Sediment', fontsize=16)\n", + "m = ax.contourf(\n", + " time_series,\n", + " lat_samples,\n", + " np.array(carbonate_array).T,\n", + " levels=[0, 25, 50, 75, 100, 125, 150],\n", + " vmax=150,\n", + " cmap=\"GnBu\",\n", + " extend=\"max\",\n", + ")\n", + "\n", + "fig.colorbar(\n", + " m,\n", + " orientation=\"vertical\",\n", + " extend=\"max\",\n", + " shrink=0.9,\n", + " pad=0.03,\n", + " label=\"Carbonate Sediment Thickness [meter]\",\n", + ")\n", + "\n", + "ax.set_xlim(100, 0)\n", + "ax.set_ylim(-50, 5)\n", + "ax.set_xlabel(\"Reconstruction Time [Ma]\", fontsize=14)\n", + "ax.set_ylabel(\"Latitude\", fontsize=14)\n", + "ax.set_title(\"Thickness of Subducting Carbonate Sediment\", fontsize=16)\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ff675873-40fc-4b83-92b1-5cdb76a55b14", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/Notebooks/12-MutschlerWorldPorphyryCopperDepositsRegionalPlots.ipynb b/Notebooks/12-MutschlerWorldPorphyryCopperDepositsRegionalPlots.ipynb old mode 100755 new mode 100644 index 2422f26f..2d7b84a4 --- a/Notebooks/12-MutschlerWorldPorphyryCopperDepositsRegionalPlots.ipynb +++ b/Notebooks/12-MutschlerWorldPorphyryCopperDepositsRegionalPlots.ipynb @@ -5,26 +5,35 @@ "id": "ffff78b5", "metadata": {}, "source": [ - "# Gplately Regional Plots\n", + "# GPlately Regional Plots\n", "\n", - "Lauren Ilano, Hojat Shirmard, Dietmar Muller \n", + "Lauren Ilano, Hojat Shirmard, Dietmar Muller \n", "EarthByte Group, School of Geosciences, University of Sydney, NSW 2006, Australia\n", "\n", - "This notebook generates regional plots given a certain:\n", + "This notebook generates regional plots given the following inputs:\n", "\n", - "* `model_dir` - The absolute path to your plate reconstruction model directory\n", - "* `grid_filename` The absolute path to a grid, i.e. crustal, sediment thickness, seafloor age, spreading rate etc. If the grid(s) are unique per timestep, the time in the filename must be replaced by curly brackets `{:.0f}` with a `0` if the age is an integer, and `1` if the age is to 1 decimal place, and so on.\n", + "- `model_dir` - The absolute path to your plate reconstruction model directory\n", + "- `grid_filename` - The absolute path to a grid (e.g. crustal thickness, sediment thickness, seafloor age, spreading rate, etc.). If the grid(s) are unique per timestep, the time in the filename must be replaced with a curly-brace placeholder `{:.0f}`, using `0` if the age is an integer, `1` if the age has one decimal place, and so on.\n", "\n", + "### Instructions\n", "\n", - "### Instructions \n", - "\n", - "1. Ensure all dependencies are installed to your Python environment (the packages listed below)\n", - " - **Note: A dependency is `cmcrameri` from [GitHub](https://github.com/callumrollo/cmcrameri). Install by cloning the repository, changing the directory on your command line to the repository's top level, and typing `pip install .` into your terminal.**\n", - "2. Change `model_dir` to your plate model directory, and `grid_filename` to your required grids for plotting, ensuring to generalise the timesteps as mentioned above.\n", + "1. Ensure all dependencies are installed in your Python environment (the packages listed below).\n", + " - **Note: `cmcrameri` is a dependency available on [GitHub](https://github.com/callumrollo/cmcrameri). To install it, clone the repository, change your terminal's working directory to the repository's top level, and run `pip install .`.**\n", + "2. Set `model_dir` to your plate model directory, and `grid_filename` to your required grids for plotting, making sure to generalise the timesteps as described above.\n", "\n", "### Data\n", "\n", - "The datasets used in this ntoebook can be found at https://zenodo.org/records/13777155. Download the source_data.zip and unzip it in the same folder as this notebook file." + "**📌 IMPORTANT!!!**\n", + "\n", + "The notebook needs three datasets in the folder `./12-GPlately-Regional-Plots-Data/source_data/`:\n", + "\n", + "- Mutschler_WorldPorphyryCopperDeposits_GPlates.xlsx\n", + "- CarbonateThickness grids (0-170 Ma)\n", + "- SedimentThickness grids (0-170 Ma)\n", + "\n", + "You can download the datasets at https://repo.gplates.org/webdav/gplately/12-GPlately-Regional-Plots-data.zip. \n", + "\n", + "Alternatively, the datasets are also available at https://zenodo.org/records/13777155. \n" ] }, { @@ -35,37 +44,38 @@ "outputs": [], "source": [ "import gplately\n", - "\n", + "import warnings\n", + "from pathlib import Path\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", - "import cartopy.feature as cfeature\n", "import numpy as np\n", "import pandas as pd\n", - "import os\n", - "import gplately.tools as tools\n", - "import matplotlib.colors as mcolors\n", - "from cartopy.io import shapereader as shpreader\n", - "import netCDF4\n", - "import warnings\n", - "from scipy import ndimage\n", - "import glob\n", - "from rasterio.features import rasterize\n", - "from rasterio.transform import from_bounds\n", - "import matplotlib.gridspec as gridspec\n", - "import matplotlib\n", - "import pandas as pd\n", + "from IPython.display import clear_output\n", "\n", "# Need joblib to run the script on multiple cores, and moviepy to make the movies\n", "from joblib import Parallel, delayed\n", - "import joblib\n", "import moviepy.editor as mpy\n", "\n", "# Plotting dependencies\n", "from cmcrameri import cm\n", "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "warnings.filterwarnings(\"ignore\", category=FutureWarning)\n", + "\n", + "data_dir = Path(\"12-GPlately-Regional-Plots-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)\n", + "source_data_dir = data_dir / \"source_data\"\n", "\n", - "import cartopy.mpl.ticker as cticker\n", - "import matplotlib.ticker as mticker" + "# Set quick_run to False for a full run, or True for a quick run\n", + "quick_run = False\n", + "#quick_run = True\n", + "if not quick_run:\n", + " reconstruction_times = np.arange(170, -1, -1)\n", + "else:\n", + " # This notebook takes a long time to run. For a quick run, we can plot every 34 million years (170, 136, 102, 68, 34, 0)\n", + " reconstruction_times = np.arange(170, -1, -34)" ] }, { @@ -75,31 +85,27 @@ "metadata": {}, "outputs": [], "source": [ - "recon_model = gplately.PlateModelManager().get_model(\n", + "pmm_model = gplately.PlateModelManager().get_model(\n", " \"Alfonso2024\", # model name\n", " data_dir=\"plate-model-repo\", # the folder to save the model files\n", ")\n", - "\n", + "assert pmm_model is not None, \"Failed to load the plate model\"\n", "model = gplately.PlateReconstruction(\n", - " recon_model.get_rotation_model(),\n", - " topology_features=recon_model.get_layer(\"Topologies\"),\n", - " static_polygons=recon_model.get_layer(\"StaticPolygons\"),\n", + " pmm_model.get_rotation_model(),\n", + " topology_features=pmm_model.get_layer(\"Topologies\"),\n", + " static_polygons=pmm_model.get_layer(\"StaticPolygons\"),\n", ")\n", "\n", "gplot = gplately.PlotTopologies(\n", " model,\n", - " coastlines=recon_model.get_layer(\"Coastlines\"),\n", + " coastlines=pmm_model.get_layer(\"Coastlines\"),\n", " time=170,\n", ")\n", "\n", - "# Path to source data zip containing sed thickness grids\n", - "#source_data_dir = os.path.join('/source_data', 'source_data')\n", - "total_sed_grid_filename =\"./source_data/SedimentThickness/sed_thick_0.1d_{}.nc\"\n", - "carbonate_sed_grid_filename = \"./source_data/CarbonateThickness/uncompacted_carbonate_thickness_{}Ma.nc\"\n", - "\n", - "# Path to save all output plots to\n", - "output_dir = \"./outputs\"\n", - "os.makedirs(output_dir+\"/Plots_v2\", exist_ok=True)" + "# Template paths to the sediment thickness and carbonate sediment thickness grid\n", + "# files, with the reconstruction time filled in later via .format()\n", + "total_sed_grid_filename = f\"{source_data_dir}/SedimentThickness/sed_thick_0.1d_{{}}.nc\"\n", + "carbonate_sed_grid_filename = f\"{source_data_dir}/CarbonateThickness/uncompacted_carbonate_thickness_{{}}Ma.nc\"\n" ] }, { @@ -109,7 +115,7 @@ "source": [ "# Load deposits\n", "\n", - "Point to where your deposits (xlsx) file is, and this will generate a `pandas` dataframe with the deposit coordinates." + "Point to the location of your deposits (.xlsx) file — running this cell will generate a `pandas` DataFrame containing the deposit coordinates.\n" ] }, { @@ -120,58 +126,73 @@ "outputs": [], "source": [ "# Load the Excel file\n", - "file_path = \"./inputs/Mutschler_WorldPorphyryCopperDeposits_GPlates.xlsx\"\n", - "df = pd.read_excel(file_path, sheet_name=\"Deposits\")\n", + "df = pd.read_excel(f\"{source_data_dir}/Mutschler_WorldPorphyryCopperDeposits_GPlates.xlsx\", sheet_name=\"Deposits\")\n", "\n", "# Clean column names\n", - "df.columns = df.columns.str.strip().str.replace(' ', '_').str.replace('–', '-')\n", + "df.columns = df.columns.str.strip().str.replace(\" \", \"_\").str.replace(\"–\", \"-\")\n", "\n", "# Rename odd characters if present\n", - "df.rename(columns={\n", - " 'Au_grade_gPERt': 'Au_grade',\n", - " 'Ag_grade_gPERt': 'Ag_grade',\n", - " 'w’W_mt': 'W_mt'\n", - "}, inplace=True)\n", + "df.rename(\n", + " columns={\n", + " \"Au_grade_gPERt\": \"Au_grade\",\n", + " \"Ag_grade_gPERt\": \"Ag_grade\",\n", + " \"w’W_mt\": \"W_mt\",\n", + " },\n", + " inplace=True,\n", + ")\n", "\n", "# Keep only relevant columns\n", "columns_to_keep = [\n", - " 'Camp', 'Lat_Dec', 'Long_Dec', 'AGE', 'Type',\n", - " 'Ore_mt', 'Cu_mt', 'Mo_mt', 'Au_kg', 'Ag_kg',\n", - " 'Cu_grade', 'Mo_grade', 'Au_grade', 'Ag_grade'\n", + " \"Camp\",\n", + " \"Lat_Dec\",\n", + " \"Long_Dec\",\n", + " \"AGE\",\n", + " \"Type\",\n", + " \"Ore_mt\",\n", + " \"Cu_mt\",\n", + " \"Mo_mt\",\n", + " \"Au_kg\",\n", + " \"Ag_kg\",\n", + " \"Cu_grade\",\n", + " \"Mo_grade\",\n", + " \"Au_grade\",\n", + " \"Ag_grade\",\n", "]\n", "df = df[columns_to_keep]\n", "\n", "# Filter to Porphyry deposits (case-insensitive)\n", - "df = df[df['Type'].str.contains(\"Porphyry\", case=False, na=False)]\n", + "df = df[df[\"Type\"].str.contains(\"Porphyry\", case=False, na=False)]\n", "\n", "# Rename coordinate columns\n", - "df.rename(columns={'Lat_Dec': 'LAT', 'Long_Dec': 'LON'}, inplace=True)\n", + "df.rename(columns={\"Lat_Dec\": \"LAT\", \"Long_Dec\": \"LON\"}, inplace=True)\n", "\n", "# Convert AGE and Cu_mt to numeric\n", - "df['Cu_mt'] = pd.to_numeric(df['Cu_mt'], errors='coerce')\n", - "df['AGE'] = pd.to_numeric(df['AGE'], errors='coerce')\n", + "df[\"Cu_mt\"] = pd.to_numeric(df[\"Cu_mt\"], errors=\"coerce\")\n", + "df[\"AGE\"] = pd.to_numeric(df[\"AGE\"], errors=\"coerce\")\n", "\n", "# Filter out invalid or missing data\n", - "df = df.dropna(subset=['Cu_mt', 'AGE'])\n", - "df = df[df['Cu_mt'] > 0]\n", + "df = df.dropna(subset=[\"Cu_mt\", \"AGE\"])\n", + "df = df[df[\"Cu_mt\"] > 0]\n", "\n", - "# Filter deposits with AGE <= 170\n", - "df = df[df['AGE'] <= 171]\n", + "# Filter deposits with AGE <= 171 Ma (keeps ages up to and just beyond 170 Ma)\n", + "df = df[df[\"AGE\"] <= 171]\n", "\n", "# Define custom bins and labels\n", "custom_bins = [0, 2e6, 10e6, 30e6, np.inf]\n", "custom_labels = [\n", " \"Minor (<2 Mt)\",\n", - " \"Moderate (2–10 Mt)\",\n", - " \"Major (10–30 Mt)\",\n", - " \"Giant (>30 Mt)\"\n", + " \"Moderate (2-10 Mt)\",\n", + " \"Major (10-30 Mt)\",\n", + " \"Giant (>30 Mt)\",\n", "]\n", "\n", "# Categorize SIZE\n", - "df['SIZE'] = pd.cut(df['Cu_mt'], bins=custom_bins, labels=custom_labels, include_lowest=True)\n", + "df[\"SIZE\"] = pd.cut(\n", + " df[\"Cu_mt\"], bins=custom_bins, labels=custom_labels, include_lowest=True\n", + ")\n", "\n", "# Show distribution\n", - "print(df['SIZE'].value_counts())\n" + "print(df[\"SIZE\"].value_counts())" ] }, { @@ -182,10 +203,10 @@ "outputs": [], "source": [ "def plot_deposits(time, extents, proj, grid_type, save_fig=None):\n", - " \"\"\"Plots categorized copper deposits on a reconstructed map (Cu grade by color, size by symbol).\"\"\"\n", + " \"\"\"Plot categorized copper deposits on a reconstructed map, with Cu grade shown by colour and deposit size shown by symbol size.\"\"\"\n", "\n", " gplot.time = time\n", - " \n", + "\n", " fig = plt.figure(figsize=(14, 10))\n", " ax1 = fig.add_subplot(111, projection=proj)\n", " cmap = cm.batlow_r\n", @@ -200,63 +221,83 @@ " vmin, vmax = 0, 500\n", " grid_label = \"Carbonate sediment thickness (m)\"\n", " else:\n", - " raise ValueError(\"This grid type is not supported.\")\n", - " \n", + " raise ValueError(f\"Grid type '{grid_type}' is not supported.\")\n", + "\n", " im = gplot.plot_grid_from_netCDF(\n", - " ax1, grid_filename.format(int(gplot.time)), \n", - " cmap=cmap, vmin=vmin, vmax=vmax\n", + " ax1, grid_filename.format(int(gplot.time)), cmap=cmap, vmin=vmin, vmax=vmax\n", " )\n", "\n", " # Plot tectonic features\n", - " gplot.plot_coastlines(ax1, facecolor='silver', edgecolor='None')\n", + " gplot.plot_coastlines(ax1, facecolor=\"silver\", edgecolor=\"None\")\n", " gplot.plot_all_topological_sections(ax1, color='grey', tessellate_degrees=1)\n", " gplot.plot_ridges(ax1, color='darkred', linewidth=2, tessellate_degrees=1)\n", " gplot.plot_trenches(ax1, color='white', linewidth=5, tessellate_degrees=1)\n", " gplot.plot_trenches(ax1, color='k', tessellate_degrees=1)\n", " gplot.plot_subduction_teeth(ax1, color='k', spacing=0.02, zorder=10)\n", - " gplot.plot_plate_motion_vectors(ax1, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5)\n", + " gplot.plot_plate_motion_vectors(\n", + " ax1, spacingX=10, spacingY=10, normalise=True, zorder=10, alpha=0.5\n", + " )\n", "\n", " ax1.set_extent(extents, ccrs.PlateCarree())\n", "\n", " # Cu grade bins (in %), reversed color order\n", " cu_bins = [0, 0.3, 0.6, 1.0, float(\"inf\")]\n", - " cu_labels = [\"Low (<0.4%)\", \"Moderate (0.4–0.6%)\", \"High (0.6–0.8%)\", \"Very High (>0.8%)\"]\n", + " cu_labels = [\n", + " \"Low (<0.3%)\",\n", + " \"Moderate (0.3-0.6%)\",\n", + " \"High (0.6-1.0%)\",\n", + " \"Very High (>1.0%)\",\n", + " ]\n", " cu_colors = [\"blue\", \"green\", \"orange\", \"red\"] # reversed order\n", "\n", " # New Size categories (renamed labels)\n", " size_styles = {\n", " \"Small (<2 Mt)\": {\"size\": 70},\n", - " \"Medium (2–10 Mt)\": {\"size\": 100},\n", - " \"Large (10–30 Mt)\": {\"size\": 130},\n", + " \"Medium (2-10 Mt)\": {\"size\": 100},\n", + " \"Large (10-30 Mt)\": {\"size\": 130},\n", " \"Giant (>30 Mt)\": {\"size\": 170},\n", " }\n", "\n", " # Filter and classify deposits\n", - " cu_df = df[(df['AGE'] != 'Unknown') & (df['AGE'] >= time) & df['Cu_grade'].notna()].copy()\n", - " cu_df['Cu_grade_%'] = cu_df['Cu_grade'] * 100 # Convert to percent\n", - " cu_df['Cu_bin'] = pd.cut(cu_df['Cu_grade_%'], bins=cu_bins, labels=cu_labels, include_lowest=True)\n", + " cu_df = df[\n", + " (df[\"AGE\"] != \"Unknown\") & (df[\"AGE\"] >= time) & df[\"Cu_grade\"].notna()\n", + " ].copy()\n", + " cu_df[\"Cu_grade_%\"] = cu_df[\"Cu_grade\"] * 100 # Convert to percent\n", + " cu_df[\"Cu_bin\"] = pd.cut(\n", + " cu_df[\"Cu_grade_%\"], bins=cu_bins, labels=cu_labels, include_lowest=True\n", + " )\n", "\n", " # Replace size label column if needed (optional, based on your data)\n", " size_map = {\n", " \"Minor (<2 Mt)\": \"Small (<2 Mt)\",\n", - " \"Moderate (2–10 Mt)\": \"Medium (2–10 Mt)\",\n", - " \"Major (10–30 Mt)\": \"Large (10–30 Mt)\",\n", - " \"Giant (>30 Mt)\": \"Giant (>30 Mt)\"\n", + " \"Moderate (2-10 Mt)\": \"Medium (2-10 Mt)\",\n", + " \"Major (10-30 Mt)\": \"Large (10-30 Mt)\",\n", + " \"Giant (>30 Mt)\": \"Giant (>30 Mt)\",\n", " }\n", " cu_df[\"SIZE\"] = cu_df[\"SIZE\"].replace(size_map)\n", "\n", " # Plot deposits: by Cu bin (color) and SIZE (size)\n", " for cu_label, cu_color in zip(cu_labels, cu_colors):\n", " for size_label, style in size_styles.items():\n", - " subset = cu_df[(cu_df['Cu_bin'] == cu_label) & (cu_df['SIZE'] == size_label)]\n", + " subset = cu_df[\n", + " (cu_df[\"Cu_bin\"] == cu_label) & (cu_df[\"SIZE\"] == size_label)\n", + " ]\n", " if subset.empty:\n", " continue\n", - " points = gplately.Points(model, subset['LON'].to_numpy(), subset['LAT'].to_numpy())\n", + " points = gplately.Points(\n", + " model, subset[\"LON\"].to_numpy(), subset[\"LAT\"].to_numpy()\n", + " )\n", " lons, lats = points.reconstruct(time, return_array=True)\n", " ax1.scatter(\n", - " lons, lats, marker=\"o\", color=cu_color, edgecolors='black',\n", - " linewidths=0.2, alpha=0.7, s=style['size'],\n", - " transform=ccrs.PlateCarree()\n", + " lons,\n", + " lats,\n", + " marker=\"o\",\n", + " color=cu_color,\n", + " edgecolors=\"black\",\n", + " linewidths=0.2,\n", + " alpha=0.7,\n", + " s=style[\"size\"],\n", + " transform=ccrs.PlateCarree(),\n", " )\n", "\n", " # Legends\n", @@ -264,8 +305,17 @@ "\n", " # Cu Grade Legend (color)\n", " cu_legend_elements = [\n", - " Line2D([0], [0], marker='o', color=color, label=label,\n", - " markersize=10, linestyle='', markeredgecolor='black', alpha=0.7)\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " marker=\"o\",\n", + " color=color,\n", + " label=label,\n", + " markersize=10,\n", + " linestyle=\"\",\n", + " markeredgecolor=\"black\",\n", + " alpha=0.7,\n", + " )\n", " for label, color in zip(cu_labels, cu_colors)\n", " ]\n", " # legend1 = ax1.legend(handles=cu_legend_elements, title=\"Cu Grade (%)\",\n", @@ -273,39 +323,46 @@ "\n", " # Deposit Size Legend (size)\n", " size_legend_elements = [\n", - " Line2D([0], [0], marker='o', color='gray', label=label,\n", - " markersize=style['size'] * 0.05, linestyle='',\n", - " alpha=0.7, markeredgecolor='black')\n", + " Line2D(\n", + " [0],\n", + " [0],\n", + " marker=\"o\",\n", + " color=\"gray\",\n", + " label=label,\n", + " markersize=style[\"size\"] * 0.05,\n", + " linestyle=\"\",\n", + " alpha=0.7,\n", + " markeredgecolor=\"black\",\n", + " )\n", " for label, style in size_styles.items()\n", " ]\n", "\n", " # legend2 = ax1.legend(handles=size_legend_elements, title=\"Deposit Size (Mt)\",\n", " # loc='lower left', bbox_to_anchor=(0, 0), fontsize=10, title_fontsize=11)\n", "\n", - "\n", " legend1 = ax1.legend(\n", " handles=cu_legend_elements,\n", " title=\"Cu Grade (%)\",\n", - " loc='upper left',\n", + " loc=\"upper left\",\n", " bbox_to_anchor=(0, 1),\n", " fontsize=10,\n", " title_fontsize=11,\n", " framealpha=1, # fully opaque\n", - " facecolor='white', # background color\n", - " edgecolor='black' # optional: border color\n", - " )\n", + " facecolor=\"white\", # background color\n", + " edgecolor=\"black\", # optional: border color\n", + " )\n", "\n", " legend2 = ax1.legend(\n", " handles=size_legend_elements,\n", " title=\"Deposit Size (Mt)\",\n", - " loc='lower left',\n", + " loc=\"lower left\",\n", " bbox_to_anchor=(0, 0),\n", " fontsize=10,\n", " title_fontsize=11,\n", - " framealpha=1, # fully opaque\n", - " facecolor='white', # background color\n", - " edgecolor='black' # optional: border color\n", - " )\n", + " framealpha=1, # fully opaque\n", + " facecolor=\"white\", # background color\n", + " edgecolor=\"black\", # optional: border color\n", + " )\n", " legend1.set_zorder(100)\n", " legend2.set_zorder(100)\n", "\n", @@ -314,12 +371,14 @@ " ax1.add_artist(legend2)\n", "\n", " # Final touches\n", - " #latlonticks(ax1)\n", - " gl = ax1.gridlines(draw_labels=True, linewidth=0.5, color='gray', alpha=0.7, linestyle='--')\n", - " gl.top_labels = False # don't show top (longitude)\n", - " gl.right_labels = False # don't show right (latitude)\n", - " gl.bottom_labels = True # ✅ show bottom (longitude)\n", - " gl.left_labels = True # ✅ show left (latitude)\n", + " # latlonticks(ax1)\n", + " gl = ax1.gridlines(\n", + " draw_labels=True, linewidth=0.5, color=\"gray\", alpha=0.7, linestyle=\"--\"\n", + " )\n", + " gl.top_labels = False # don't show top labels (longitude)\n", + " gl.right_labels = False # don't show right labels (latitude)\n", + " gl.bottom_labels = True # show bottom labels (longitude)\n", + " gl.left_labels = True # show left labels (latitude)\n", " gl.xlabel_style = {\"size\": 10}\n", " gl.ylabel_style = {\"size\": 10}\n", "\n", @@ -328,10 +387,10 @@ " cbar.set_label(grid_label, fontsize=14)\n", "\n", " if save_fig:\n", - " fig.savefig(save_fig, dpi=300, bbox_inches='tight')\n", + " fig.savefig(save_fig, dpi=300, bbox_inches=\"tight\")\n", " else:\n", " plt.show()\n", - " plt.close()\n" + " plt.close()" ] }, { @@ -339,7 +398,7 @@ "id": "f3d91881", "metadata": {}, "source": [ - "# North America" + "# North America\n" ] }, { @@ -349,7 +408,12 @@ "metadata": {}, "outputs": [], "source": [ - "plot_deposits(0, extents=(-190, -30, -10, 65), proj=ccrs.PlateCarree(np.mean((-190, -30))), grid_type=\"carbonate\")" + "plot_deposits(\n", + " 0,\n", + " extents=(-190, -30, -10, 65),\n", + " proj=ccrs.PlateCarree(float(np.mean((-190, -30)))),\n", + " grid_type=\"carbonate\",\n", + ")" ] }, { @@ -359,7 +423,12 @@ "metadata": {}, "outputs": [], "source": [ - "plot_deposits(0, extents=(-190, -30, -10, 65), proj=ccrs.PlateCarree(np.mean((-190, -30))), grid_type=\"total\")" + "plot_deposits(\n", + " 0,\n", + " extents=(-190, -30, -10, 65),\n", + " proj=ccrs.PlateCarree(float(np.mean((-190, -30)))),\n", + " grid_type=\"total\",\n", + ")" ] }, { @@ -369,7 +438,7 @@ "source": [ "## Generating Plots (png)\n", "\n", - "This code generates and saves geological deposit plots for different times and grid types, either sequentially or in parallel depending on the use_parallel flag." + "This code generates and saves geological deposit plots for different times and grid types, either sequentially or in parallel depending on the `use_parallel` flag.\n" ] }, { @@ -381,33 +450,35 @@ }, "outputs": [], "source": [ - "reconstruction_times = np.arange(170,-1,-1)\n", - "\n", - "use_parallel=False\n", + "use_parallel = False\n", "\n", "grid_types = [\"carbonate\", \"total\"]\n", "if use_parallel:\n", " for grid_type in grid_types:\n", " # Use LokyBackend to protect the netCDF routine\n", - " slabdip_img = Parallel(n_jobs=-1, backend='loky', verbose=1) \\\n", - " (delayed(plot_deposits) \\\n", - " (reconstruction_time, \n", - " extents=(-190, -30, -10, 65), \n", - " proj=ccrs.PlateCarree(np.mean((-190, -30))),\n", - " grid_type=grid_type,\n", - " save_fig=output_dir+\"/Plots_v2/NorthAmerica_{}_{}.{}\".format(grid_type, reconstruction_time, \"png\"),\n", - " ) for reconstruction_time in reconstruction_times)\n", + " slabdip_img = Parallel(n_jobs=-1, backend=\"loky\", verbose=1)(\n", + " delayed(plot_deposits)(\n", + " reconstruction_time,\n", + " extents=(-190, -30, -10, 65),\n", + " proj=ccrs.PlateCarree(float(np.mean((-190, -30)))),\n", + " grid_type=grid_type,\n", + " save_fig=f\"{output_dir}/NorthAmerica_{grid_type}_{reconstruction_time}.png\"\n", + " )\n", + " for reconstruction_time in reconstruction_times\n", + " )\n", "else:\n", " for grid_type in grid_types:\n", " for reconstruction_time in reconstruction_times:\n", " plot_deposits(\n", - " reconstruction_time, \n", - " extents=(-190, -30, -10, 65), \n", - " proj=ccrs.PlateCarree(np.mean((-120, -30))),\n", + " reconstruction_time,\n", + " extents=(-190, -30, -10, 65),\n", + " proj=ccrs.PlateCarree(float(np.mean((-190, -30)))),\n", " grid_type=grid_type,\n", - " save_fig=output_dir+\"/Plots_v2/NorthAmerica_{}_{}.{}\".format(grid_type, reconstruction_time, \"png\"),\n", - " )\n", - " print(\"Time {} done\".format(reconstruction_time))\n" + " save_fig=f\"{output_dir}/NorthAmerica_{grid_type}_{reconstruction_time}.png\"\n", + " )\n", + "\n", + " print(f\"Finished plotting {reconstruction_time} Ma\")\n", + "clear_output()" ] }, { @@ -415,7 +486,7 @@ "id": "4a0adeb1-b284-4ac8-be3c-1fc40671bc76", "metadata": {}, "source": [ - "## Generating Movies (mp4) from Plots (png)" + "## Generating Movies (mp4) from Plots (png)\n" ] }, { @@ -427,28 +498,28 @@ "source": [ "for grid_type in grid_types:\n", " frame_list = []\n", - " for time in np.arange(170,-1,-1):\n", + " for time in reconstruction_times:\n", " frame_list.append(\n", - " output_dir+\"/Plots_v2/NorthAmerica_{}_{}.png\".format(grid_type, time), \n", - "\n", + " f\"{output_dir}/NorthAmerica_{grid_type}_{time}.png\"\n", " )\n", "\n", " clip = mpy.ImageSequenceClip(frame_list, fps=25)\n", "\n", " clip.write_videofile(\n", - " output_dir+\"/Plots_v2/NorthAmerica_{}.mp4\".format(grid_type),\n", - " fps=100,\n", - " codec=\"libx264\",\n", - " bitrate=\"5000k\",\n", - " audio=False,\n", - " logger=None,\n", - " ffmpeg_params=[\n", - " \"-vf\",\n", - " \"pad=ceil(iw/2)*2:ceil(ih/2)*2\",\n", - " \"-pix_fmt\",\n", - " \"yuv420p\",\n", - " ],\n", - ")" + " f\"{output_dir}/NorthAmerica_{grid_type}.mp4\",\n", + " fps=25,\n", + " codec=\"libx264\",\n", + " bitrate=\"5000k\",\n", + " audio=False,\n", + " logger=None,\n", + " ffmpeg_params=[\n", + " \"-vf\",\n", + " \"pad=ceil(iw/2)*2:ceil(ih/2)*2\",\n", + " \"-pix_fmt\",\n", + " \"yuv420p\",\n", + " ],\n", + " )\n", + " print(f\"The video file has been saved to {output_dir}/NorthAmerica_{grid_type}.mp4\")" ] }, { @@ -456,7 +527,7 @@ "id": "276fa22c-e5c8-4062-9119-37aeb533c915", "metadata": {}, "source": [ - "# East Asia" + "# East Asia\n" ] }, { @@ -466,7 +537,12 @@ "metadata": {}, "outputs": [], "source": [ - "plot_deposits(0, extents=(110, 170, -20, 40), proj=ccrs.PlateCarree(np.mean((120, 40))), grid_type=\"carbonate\")" + "plot_deposits(\n", + " 0,\n", + " extents=(110, 170, -20, 40),\n", + " proj=ccrs.PlateCarree(float(np.mean((120, 40)))),\n", + " grid_type=\"carbonate\",\n", + ")" ] }, { @@ -476,7 +552,12 @@ "metadata": {}, "outputs": [], "source": [ - "plot_deposits(0, extents=(110, 170, -20, 40), proj=ccrs.PlateCarree(np.mean((120, 40))), grid_type=\"total\")" + "plot_deposits(\n", + " 0,\n", + " extents=(110, 170, -20, 40),\n", + " proj=ccrs.PlateCarree(float(np.mean((120, 40)))),\n", + " grid_type=\"total\",\n", + ")" ] }, { @@ -486,7 +567,7 @@ "source": [ "## Generating Plots (png)\n", "\n", - "This code generates and saves geological deposit plots for different times and grid types, either sequentially or in parallel depending on the use_parallel flag." + "This code generates and saves geological deposit plots for different times and grid types, either sequentially or in parallel depending on the `use_parallel` flag.\n" ] }, { @@ -498,33 +579,34 @@ }, "outputs": [], "source": [ - "reconstruction_times = np.arange(170,-1,-1)\n", - "\n", - "use_parallel=False\n", + "use_parallel = False\n", "\n", "grid_types = [\"carbonate\", \"total\"]\n", "if use_parallel:\n", " for grid_type in grid_types:\n", " # Use LokyBackend to protect the netCDF routine\n", - " slabdip_img = Parallel(n_jobs=-1, backend='loky', verbose=1) \\\n", - " (delayed(plot_deposits) \\\n", - " (reconstruction_time, \n", - " extents=(110, 170, -20, 40), \n", - " proj=ccrs.PlateCarree(np.mean((120, 40))),\n", - " grid_type=grid_type,\n", - " save_fig=output_dir+\"/Plots_v2/EastAsia_{}_{}.{}\".format(grid_type, reconstruction_time, \"png\"),\n", - " ) for reconstruction_time in reconstruction_times)\n", + " slabdip_img = Parallel(n_jobs=-1, backend=\"loky\", verbose=1)(\n", + " delayed(plot_deposits)(\n", + " reconstruction_time,\n", + " extents=(110, 170, -20, 40),\n", + " proj=ccrs.PlateCarree(float(np.mean((120, 40)))),\n", + " grid_type=grid_type,\n", + " save_fig=f\"{output_dir}/EastAsia_{grid_type}_{reconstruction_time}.png\",\n", + " )\n", + " for reconstruction_time in reconstruction_times\n", + " )\n", "else:\n", " for grid_type in grid_types:\n", " for reconstruction_time in reconstruction_times:\n", " plot_deposits(\n", - " reconstruction_time, \n", - " extents=(110, 170, -20, 40), \n", - " proj=ccrs.PlateCarree(np.mean((120, 40))),\n", + " reconstruction_time,\n", + " extents=(110, 170, -20, 40),\n", + " proj=ccrs.PlateCarree(float(np.mean((120, 40)))),\n", " grid_type=grid_type,\n", - " save_fig=output_dir+\"/Plots_v2/EastAsia_{}_{}.{}\".format(grid_type, reconstruction_time, \"png\"),\n", + " save_fig=f\"{output_dir}/EastAsia_{grid_type}_{reconstruction_time}.png\",\n", " )\n", - " print(\"Time {} done\".format(reconstruction_time))\n" + " print(f\"Finished plotting {reconstruction_time} Ma\")\n", + "clear_output()" ] }, { @@ -532,7 +614,7 @@ "id": "af420bb6-7eb9-4c12-b9fc-8e4b9712a0ae", "metadata": {}, "source": [ - "## Generating Movies (mp4) from Plots (png)" + "## Generating Movies (mp4) from Plots (png)\n" ] }, { @@ -544,28 +626,28 @@ "source": [ "for grid_type in grid_types:\n", " frame_list = []\n", - " for time in np.arange(170,-1,-1):\n", + " for time in reconstruction_times:\n", " frame_list.append(\n", - " output_dir+\"/Plots_v2/EastAsia_{}_{}.png\".format(grid_type, time), \n", - "\n", + " f\"{output_dir}/EastAsia_{grid_type}_{time}.png\"\n", " )\n", "\n", " clip = mpy.ImageSequenceClip(frame_list, fps=25)\n", "\n", " clip.write_videofile(\n", - " output_dir+\"/Plots_v2/EastAsia_{}.mp4\".format(grid_type),\n", - " fps=100,\n", - " codec=\"libx264\",\n", - " bitrate=\"5000k\",\n", - " audio=False,\n", - " logger=None,\n", - " ffmpeg_params=[\n", - " \"-vf\",\n", - " \"pad=ceil(iw/2)*2:ceil(ih/2)*2\",\n", - " \"-pix_fmt\",\n", - " \"yuv420p\",\n", - " ],\n", - ")" + " f\"{output_dir}/EastAsia_{grid_type}.mp4\",\n", + " fps=25,\n", + " codec=\"libx264\",\n", + " bitrate=\"5000k\",\n", + " audio=False,\n", + " logger=None,\n", + " ffmpeg_params=[\n", + " \"-vf\",\n", + " \"pad=ceil(iw/2)*2:ceil(ih/2)*2\",\n", + " \"-pix_fmt\",\n", + " \"yuv420p\",\n", + " ],\n", + " )\n", + " print(f\"The video file has been saved to {output_dir}/EastAsia_{grid_type}.mp4\")" ] }, { @@ -573,9 +655,10 @@ "id": "3cfc17b4-241e-4004-9693-b86ff22b962b", "metadata": {}, "source": [ - "Generate plots and movies for other regions by updating the `extents` values in the **Generating Plots (png)** and **Generating Movies (mp4) from Plots (png)** sections—for example: \n", - "- **South America:** `extents = (-120, -30, -60, 5)` \n", - "- **Mediterranean Sea:** `extents = (0, 70, 10, 60)`" + "Generate plots and movies for other regions by updating the `extents` values in the **Generating Plots (png)** and **Generating Movies (mp4) from Plots (png)** sections—for example:\n", + "\n", + "- **South America:** `extents = (-120, -30, -60, 5)`\n", + "- **Mediterranean Sea:** `extents = (0, 70, 10, 60)`\n" ] } ], diff --git a/Notebooks/13-ReconstructingZirconData.ipynb b/Notebooks/13-ReconstructingZirconData.ipynb index 14993a19..b9dd380a 100644 --- a/Notebooks/13-ReconstructingZirconData.ipynb +++ b/Notebooks/13-ReconstructingZirconData.ipynb @@ -7,18 +7,21 @@ "source": [ "# 13 - Reconstructing Zircon Data\n", "\n", - "This notebook demonstrates how to reconstruct and plot point feature data stored in a compressed GPlates Markup Language (GPML) file on a global map through geological time. \n", + "This notebook demonstrates how to reconstruct and plot point feature data stored in a compressed GPlates Markup Language (GPML) file on a global map through geological time.\n", "\n", - "This notebook also demonstrates how to define a plate reconstruction model with `gplately`'s `PlateModelManager`, plot topological plate boundaries, continents, coastlines with an option to plot seafloor age grids if they are available to the user. \n", + "This notebook also demonstrates how to define a plate reconstruction model and plot topological plate boundaries, continents, and coastlines, with an option to plot seafloor age grids if they are available to the user.\n", "\n", "### Exercises completed in this notebook:\n", - "1. Plotting point data from a `.gpmlz` file using `gplately`\n", - "2. Omitting point data whose positions do not fall on continents (using continent masking);\n", - "3. Omitting point data which do not get reconstructed through geological time (for instance they are not properly ascribed a tectonic plate with an associated rotation).\n", + "\n", + "1. Plotting point data from a `.gpmlz` file using `GPlately`.\n", + "2. Omitting point data whose positions do not fall on continents (using continent masking).\n", + "3. Omitting point data that does not get reconstructed through geological time (for instance, because it is not properly assigned a tectonic plate with an associated rotation).\n", "\n", "The zircon point data is sourced from:\n", - "> __Citation__:\n", - "> 1. Wu, Y., Fang, X. and Ji, J., 2023. A global zircon U–Th–Pb geochronological database. Earth System Science Data, 15(11), pp.5171-5181." + "\n", + "> **Citation**:\n", + ">\n", + "> 1. Wu, Y., Fang, X. and Ji, J., 2023. A global zircon U–Th–Pb geochronological database. Earth System Science Data, 15(11), pp.5171-5181.\n" ] }, { @@ -28,12 +31,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Import all necessary packages\n", - "import logging\n", "import numpy as np\n", - "import glob, os\n", + "import os, warnings, logging\n", + "from pathlib import Path\n", "\n", - "os.environ[\"DISABLE_GPLATELY_DEV_WARNING\"] = \"true\"\n", "import gplately\n", "import pygplates\n", "from plate_model_manager import PlateModelManager\n", @@ -44,15 +45,30 @@ "import matplotlib.gridspec as gridspec\n", "\n", "from joblib import Parallel, delayed\n", - "from matplotlib.lines import Line2D\n", "import matplotlib\n", "\n", "plt.rcParams[\"font.family\"] = \"Helvetica\"\n", "\n", - "from scipy.spatial import cKDTree\n", + "from scipy.spatial import cKDTree # type: ignore\n", "from rasterio.features import rasterize\n", "from rasterio.transform import from_bounds\n", - "import moviepy.editor as mpy" + "import moviepy.editor as mpy\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + "\n", + "data_dir = Path(\"13-Reconstructing-Zircon-Data\")\n", + "output_dir = data_dir / \"output\"\n", + "output_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + "# Set quick_run to False for a full run, or True for a quick run\n", + "quick_run = True\n", + "#quick_run = False\n", + "if not quick_run:\n", + " time_steps = np.arange(1799, -1, -1)\n", + "else:\n", + " # This notebook takes a long time to run. For debug purposes, we can reduce the number of time steps.\n", + " time_steps = np.arange(1799, -1, -200)" ] }, { @@ -63,20 +79,22 @@ "outputs": [], "source": [ "# Obtain all rotation files, topology features and static polygons from Cao2024 via the plate model manager\n", - "pm_manager = PlateModelManager()\n", - "cao2024_model = pm_manager.get_model(\"Cao2024\", data_dir=\"plate-model-repo\")\n", - "rotation_model = cao2024_model.get_rotation_model()\n", - "topology_features = cao2024_model.get_topologies()\n", - "static_polygons = cao2024_model.get_static_polygons()\n", + "pmm_model = PlateModelManager().get_model(\"Cao2024\", data_dir=\"plate-model-repo\")\n", + "assert pmm_model is not None, \"Cao2024 model is not available.\"\n", + "rotation_model = pmm_model.get_rotation_model()\n", + "topology_features = pmm_model.get_topologies()\n", + "static_polygons = pmm_model.get_static_polygons()\n", "\n", "# Obtain geometries like continents, coastlines and continent-ocean boundaries (COBs)\n", - "coastlines = cao2024_model.get_coastlines()\n", - "continents = cao2024_model.get_continental_polygons()\n", - "COBs = cao2024_model.get_COBs()\n", + "coastlines = pmm_model.get_coastlines()\n", + "continents = pmm_model.get_continental_polygons()\n", + "COBs = pmm_model.get_COBs()\n", "model = gplately.PlateReconstruction(rotation_model, topology_features, static_polygons)\n", "\n", - "time = 1799 #Ma\n", - "gplot = gplately.PlotTopologies(model, coastlines=coastlines, continents=continents, COBs=COBs, time=time)" + "time = 1799 # Ma\n", + "gplot = gplately.PlotTopologies(\n", + " model, coastlines=coastlines, continents=continents, COBs=COBs, time=time\n", + ")" ] }, { @@ -84,7 +102,7 @@ "id": "50a4f923-f993-4f36-8471-12a16b67e81f", "metadata": {}, "source": [ - "### Define auxiliary functions for plotting the reconstructed data" + "### Define auxiliary functions for plotting the reconstructed data\n" ] }, { @@ -95,17 +113,22 @@ "outputs": [], "source": [ "def latlonticks(ax):\n", - " \"\"\"A function to plot lon and lat tick labels on the edges of the map.\n", - " \"\"\"\n", - " gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, x_inline=False,\n", - " linewidth=1, color='gray', alpha=0.3,)\n", - "\n", - " ax.text(0.49,-0.03, '270°E', transform=ax.transAxes)\n", - " #ax.text(0.46,-0.03, '0°', transform=ax.transAxes)\n", - " #ax.text(0.40,-0.025, '60°W', transform=ax.transAxes)\n", - " \n", - " gl.top_labels=False\n", - " gl.bottom_labels=False\n", + " \"\"\"A function to plot lon and lat tick labels on the edges of the map.\"\"\"\n", + " gl = ax.gridlines(\n", + " crs=ccrs.PlateCarree(),\n", + " draw_labels=True,\n", + " x_inline=False,\n", + " linewidth=1,\n", + " color=\"gray\",\n", + " alpha=0.3,\n", + " )\n", + "\n", + " ax.text(0.49, -0.03, \"270°E\", transform=ax.transAxes)\n", + " # ax.text(0.46,-0.03, '0°', transform=ax.transAxes)\n", + " # ax.text(0.40,-0.025, '60°W', transform=ax.transAxes)\n", + "\n", + " gl.top_labels = False\n", + " gl.bottom_labels = False\n", " return\n", "\n", "\n", @@ -113,23 +136,23 @@ " colors = []\n", " positions = []\n", "\n", - " with open(filename, 'r') as f:\n", + " with open(filename, \"r\") as f:\n", " for line in f:\n", " line = line.strip()\n", "\n", " # Skip comments or special lines\n", - " if not line or line.startswith('#') or line[0] in ('B', 'F', 'N'):\n", + " if not line or line.startswith(\"#\") or line[0] in (\"B\", \"F\", \"N\"):\n", " continue\n", "\n", " parts = line.split()\n", - " \n", + "\n", " try:\n", " # FORMAT A: Slash-separated RGB like 255/255/0\n", - " if len(parts) == 4 and '/' in parts[1] and '/' in parts[3]:\n", + " if len(parts) == 4 and \"/\" in parts[1] and \"/\" in parts[3]:\n", " pos1 = float(parts[0])\n", - " r1, g1, b1 = map(float, parts[1].split('/'))\n", + " r1, g1, b1 = map(float, parts[1].split(\"/\"))\n", " pos2 = float(parts[2])\n", - " r2, g2, b2 = map(float, parts[3].split('/'))\n", + " r2, g2, b2 = map(float, parts[3].split(\"/\"))\n", "\n", " # FORMAT B: Space-separated RGB\n", " elif len(parts) >= 8:\n", @@ -137,31 +160,29 @@ " r1, g1, b1 = map(float, parts[1:4])\n", " pos2 = float(parts[4])\n", " r2, g2, b2 = map(float, parts[5:8])\n", - " \n", + "\n", " else:\n", " # Line format not recognized — skip it\n", " continue\n", "\n", " # Add both ends of the segment\n", " positions.extend([pos1, pos2])\n", - " colors.extend([\n", - " (r1/255, g1/255, b1/255),\n", - " (r2/255, g2/255, b2/255)\n", - " ])\n", + " colors.extend(\n", + " [(r1 / 255, g1 / 255, b1 / 255), (r2 / 255, g2 / 255, b2 / 255)]\n", + " )\n", "\n", " except Exception as e:\n", - " print(f\"Skipping malformed line: {line}\")\n", + " print(f\"Skipping malformed line: {line!r} ({e})\")\n", " continue\n", "\n", " if len(positions) == 0:\n", - " raise ValueError(\"No color stops found in CPT file.\")\n", + " raise ValueError(f\"No color stops found in CPT file: {filename}\")\n", "\n", " # Normalize to [0, 1]\n", " positions = np.array(positions)\n", " positions = (positions - positions.min()) / (positions.max() - positions.min())\n", "\n", - " return LinearSegmentedColormap.from_list(cmap_name, list(zip(positions, colors)))\n", - "\n" + " return LinearSegmentedColormap.from_list(cmap_name, list(zip(positions, colors)))" ] }, { @@ -170,9 +191,10 @@ "metadata": {}, "source": [ "## Define and plot 3 colourmaps\n", + "\n", "1. For zircons\n", "2. For subduction convergence\n", - "3. For seafloor age (if provided)" + "3. For seafloor age (if provided)\n" ] }, { @@ -182,29 +204,51 @@ "metadata": {}, "outputs": [], "source": [ - "zircon_age_cmap = import_cpt('./NotebookFiles/GMT_wysiwyg.cpt', 'wysiwyg')\n", - "custom_cmap = import_cpt('./NotebookFiles/age_1000-0.cpt')\n", + "zircon_age_cmap_file = \"./NotebookFiles/GMT_wysiwyg.cpt\"\n", + "custom_cmap_file = \"./NotebookFiles/age_1000-0.cpt\"\n", + "import urllib.request\n", + "\n", + "if not os.path.exists(zircon_age_cmap_file):\n", + " zircon_age_cmap_file = data_dir / \"GMT_wysiwyg.cpt\"\n", + " if not os.path.exists(zircon_age_cmap_file):\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/GMT_wysiwyg.cpt\",\n", + " zircon_age_cmap_file,\n", + " )\n", + "zircon_age_cmap = import_cpt(zircon_age_cmap_file, \"wysiwyg\")\n", + "\n", + "if not os.path.exists(custom_cmap_file):\n", + " custom_cmap_file = data_dir / \"age_1000-0.cpt\"\n", + " if not os.path.exists(custom_cmap_file):\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/age_1000-0.cpt\",\n", + " custom_cmap_file,\n", + " )\n", + "custom_cmap = import_cpt(custom_cmap_file)\n", "\n", "# Also define a convergence colourmap for subduction zone convergence magnitudes later\n", - "conv_cmap = matplotlib.colors.LinearSegmentedColormap.from_list(\n", - " 'conv_cmap', ['white', 'mediumturquoise', 'teal', 'darkslategray', '#011C1A']\n", + "conv_cmap = LinearSegmentedColormap.from_list(\n", + " \"conv_cmap\", [\"white\", \"mediumturquoise\", \"teal\", \"darkslategray\", \"#011C1A\"]\n", ")\n", "\n", "plt.figure(figsize=(10, 1))\n", - "plt.imshow([np.linspace(0, 1, 256)], aspect='auto', cmap=zircon_age_cmap)\n", - "plt.xticks([]); plt.yticks([])\n", + "plt.imshow([np.linspace(0, 1, 256)], aspect=\"auto\", cmap=zircon_age_cmap)\n", + "plt.xticks([])\n", + "plt.yticks([])\n", "plt.title(\"GMT wysiwyg\")\n", "plt.show()\n", "plt.figure(figsize=(10, 1))\n", - "plt.imshow([np.linspace(0, 1, 256)], aspect='auto', cmap=conv_cmap)\n", - "plt.xticks([]); plt.yticks([])\n", + "plt.imshow([np.linspace(0, 1, 256)], aspect=\"auto\", cmap=conv_cmap)\n", + "plt.xticks([])\n", + "plt.yticks([])\n", "plt.title(\"Subduction convergence colormap\")\n", "plt.show()\n", "\n", "plt.figure(figsize=(10, 1))\n", - "plt.imshow([np.linspace(0, 1, 256)], aspect='auto', cmap=custom_cmap)\n", - "plt.xticks([]); plt.yticks([])\n", - "plt.colorbar(orientation='horizontal', label='Range')\n", + "plt.imshow([np.linspace(0, 1, 256)], aspect=\"auto\", cmap=custom_cmap)\n", + "plt.xticks([])\n", + "plt.yticks([])\n", + "plt.colorbar(orientation=\"horizontal\", label=\"Range\")\n", "plt.title(\"Age grid cmap\")\n", "plt.show()" ] @@ -215,7 +259,8 @@ "metadata": {}, "source": [ "### Define zircon data\n", - "The zircon data is stored in a `.gpmlz` file. The positions of the zircons can be reconstructed through time using a function on`gplately`'s `PlateReconstruction` object called `reconstruct()` which we call in the function `reconstruct_deposits` defined below." + "\n", + "The zircon data is stored in a `.gpmlz` file. The positions of the zircons can be reconstructed through time using a function on `gplately`'s `PlateReconstruction` object called `reconstruct()`, which we call in the function `reconstruct_deposits` defined below.\n" ] }, { @@ -229,30 +274,57 @@ "zircon_igneous_filename = \"./NotebookFiles/zircons_igneous_Wu2023.gpmlz\"\n", "zircon_metamorphic_filename = \"./NotebookFiles/zircons_metamorphic_Wu2023.gpmlz\"\n", "zircon_sedimentary_filename = \"./NotebookFiles/zircons_sedimentary_Wu2023.gpmlz\"\n", + "if not os.path.exists(zircon_igneous_filename):\n", + " zircon_igneous_filename = data_dir / \"zircons_igneous_Wu2023.gpmlz\"\n", + " if not os.path.exists(zircon_igneous_filename):\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/zircons_igneous_Wu2023.gpmlz\",\n", + " zircon_igneous_filename,\n", + " )\n", + "if not os.path.exists(zircon_metamorphic_filename):\n", + " zircon_metamorphic_filename = data_dir / \"zircons_metamorphic_Wu2023.gpmlz\"\n", + " if not os.path.exists(zircon_metamorphic_filename):\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/zircons_metamorphic_Wu2023.gpmlz\",\n", + " zircon_metamorphic_filename,\n", + " )\n", + "if not os.path.exists(zircon_sedimentary_filename):\n", + " zircon_sedimentary_filename = data_dir / \"zircons_sedimentary_Wu2023.gpmlz\"\n", + " if not os.path.exists(zircon_sedimentary_filename):\n", + " urllib.request.urlretrieve(\n", + " \"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/zircons_sedimentary_Wu2023.gpmlz\",\n", + " zircon_sedimentary_filename,\n", + " )\n", + "\n", "\n", "def reconstruct_deposits(zircon_fname, time):\n", - " \"\"\" Reconstruct the coordinates of deposits through time while also keeping track\n", + " \"\"\"Reconstruct the coordinates of deposits through time while also keeping track\n", " of their valid times\n", " \"\"\"\n", " zircon_fc = pygplates.FeatureCollection(zircon_fname)\n", - " \n", + "\n", " # RECONSTRUCT ZIRCONS\n", " reconstructed_features = model.reconstruct(\n", - " zircon_fc, time, from_time=0, anchor_plate_id=0)\n", - " \n", + " zircon_fc, time, from_time=0, anchor_plate_id=0\n", + " )\n", + "\n", " curr_lon, curr_lat = gplately.tools.extract_feature_lonlat(reconstructed_features)\n", - " \n", + "\n", " fromage = [f.get_feature().get_valid_time()[0] for f in reconstructed_features]\n", " return curr_lon, curr_lat, fromage\n", "\n", "\n", - "\n", "# Example\n", "time = 250\n", - "igneous_lon, igneous_lat, igneous_fromage = reconstruct_deposits(zircon_igneous_filename, time)\n", + "igneous_lon, igneous_lat, igneous_fromage = reconstruct_deposits(\n", + " zircon_igneous_filename, time\n", + ")\n", "print(\n", - " \"Igneous zircon longitudes at time {}Ma:\".format(time), igneous_lon, \n", - " \"\\n\", \"Igneous zircon latitudes at time {}Ma: \".format(time), igneous_lat\n", + " f\"Igneous zircon longitudes at time {time}Ma:\",\n", + " igneous_lon,\n", + " \"\\n\",\n", + " f\"Igneous zircon latitudes at time {time}Ma: \",\n", + " igneous_lat,\n", ")" ] }, @@ -262,7 +334,8 @@ "metadata": {}, "source": [ "### Define functions to help mask zircon data that are not located over a continent\n", - "We use a k-d tree to search for a continent along a radius (km) around each zircon point. If there is no lat-lon point corresponding to a continent we omit the zircon point. " + "\n", + "We use a k-d tree to search for a continent along a radius (km) around each zircon point. If there is no lat-lon point corresponding to a continent, we omit the zircon point.\n" ] }, { @@ -277,16 +350,18 @@ " Obtains the latitude and longitude coordinates of continents at the current reconstruction time.\n", " Assumes 0.2 degree resolution by default\n", " \"\"\"\n", - " \n", - " nx, ny = int(360/resX), int(180/resY)\n", "\n", - " platforms_grid = rasterize(gplately.geometry.pygplates_to_shapely(continent_polygons),\n", - " out_shape=(ny,nx),\n", - " transform=from_bounds(-180, 90, 180, -90, nx, ny))\n", + " nx, ny = int(360 / resX), int(180 / resY)\n", "\n", - " xq, yq = np.meshgrid(np.linspace(-180,180,nx),\n", - " np.linspace(-90, 90, ny))\n", + " platforms_grid = rasterize(\n", + " gplately.pygplates_to_shapely(continent_polygons),\n", + " out_shape=(ny, nx),\n", + " transform=from_bounds(-180, 90, 180, -90, nx, ny),\n", + " )\n", + "\n", + " xq, yq = np.meshgrid(np.linspace(-180, 180, nx), np.linspace(-90, 90, ny))\n", "\n", + " assert platforms_grid is not None, \"platforms_grid cannot be None.\"\n", " mask_grid = platforms_grid > 0\n", " xcoords = xq[mask_grid]\n", " ycoords = yq[mask_grid]\n", @@ -300,12 +375,12 @@ " \"\"\"\n", " xyz0 = gplately.tools.lonlat2xyz(lons0, lats0, degrees=True)\n", " xyz1 = gplately.tools.lonlat2xyz(lons1, lats1, degrees=True)\n", - " \n", + "\n", " tree = cKDTree(np.c_[xyz0])\n", " dist, index = tree.query(np.c_[xyz1])\n", " dist *= 6371\n", " mask_dist = dist < d_tol\n", - " \n", + "\n", " return mask_dist" ] }, @@ -316,7 +391,7 @@ "source": [ "### Define function to plot zircons over a global map\n", "\n", - "Seafloor age grids are optional inputs - we do not include them here but the user can pass a filename to the argument `agegrid_fname`." + "Seafloor age grids are optional inputs — we do not include them here, but the user can pass a filename via the `agegrid_fname` argument.\n" ] }, { @@ -326,40 +401,46 @@ "metadata": {}, "outputs": [], "source": [ - "def plot_zircons(time, agegrid_fname = None, save_fig=False):\n", - " \"\"\"Plots zircons with plate topological boundaries, plate velocity arrows, continents, coastlines and\n", - " optionally a seafloor age grid.\n", + "from matplotlib.ticker import FormatStrFormatter\n", "\n", - " If providing an age grid with the timestep in its name, e.g. \"./SeafloorAge_2.00Ma.nc\" this should be\n", - " formatted like \"./SeafloorAge_{:.2f}Ma.nc\", where the \":.2f\" allows the time to be substituted back into\n", - " the string to 2 decimal places.\n", - " \n", - " \"\"\"\n", - " logging.getLogger('matplotlib').setLevel(logging.ERROR) \n", + "\n", + "def plot_zircons(time, agegrid_fname=None, save_fig=False):\n", + " \"\"\"Plots zircons with plate topological boundaries, plate velocity arrows, continents, coastlines and\n", + " optionally a seafloor age grid.\"\"\"\n", + " warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + " logging.getLogger(\"matplotlib\").setLevel(logging.ERROR)\n", " # Ensure font is Helvetica\n", - " matplotlib.rcParams['font.family'] = 'Helvetica'\n", + " matplotlib.rcParams[\"font.family\"] = \"Helvetica\"\n", "\n", " # Define a matplotlib figure using a cartopy CRS Mollweide projection.\n", " proj = ccrs.Mollweide(central_longitude=-90)\n", - " fig, ax = plt.subplots(1,1, subplot_kw={'projection': proj}, figsize=(12, 7), dpi=150)\n", + " fig, ax = plt.subplots(\n", + " 1, 1, subplot_kw={\"projection\": proj}, figsize=(12, 7), dpi=150\n", + " )\n", "\n", - " # Set min and max longitude, min and max latitude respectively.\n", - " extent_globe = [-180,180,-90,90]\n", + " # Define the global map extent as (min lon, max lon, min lat, max lat).\n", + " extent_globe = (-180, 180, -90, 90)\n", "\n", " # Set the plot title.\n", - " ax.set_title(\"{} Ma\".format(time))\n", + " ax.set_title(f\"{time} Ma\")\n", "\n", " # ---------- If provided, plot the age grid. -----------------------------------------------------------\n", " if agegrid_fname:\n", - " agegrid = gplately.Raster(\n", - " agegrid_fname #as an example, grid_directory+\"PublishedAgeGrids/Cao2024_SEAFLOOR_AGE_grid_{:.2f}Ma.nc\".format(time)\n", - " )\n", + " agegrid = gplately.Raster(agegrid_fname)\n", " # Plot all NaNs in the age grid as a grey colour\n", " cmap = custom_cmap\n", - " cmap.set_bad('0.2',1.)\n", + " cmap.set_bad(\"0.2\", 1.0)\n", " # Let the age grid span the globe, with 20% fill opacity, setting min age to 0, max age to 160Ma.\n", - " im = ax.imshow(agegrid.data, extent=extent_globe, cmap=cmap, origin='lower', alpha=0.2,\n", - " vmin=0, vmax=160, transform=ccrs.PlateCarree(), zorder=1\n", + " im = ax.imshow(\n", + " agegrid.data,\n", + " extent=extent_globe,\n", + " cmap=cmap,\n", + " origin=\"lower\",\n", + " alpha=0.2,\n", + " vmin=0,\n", + " vmax=160,\n", + " transform=ccrs.PlateCarree(),\n", + " zorder=1,\n", " )\n", "\n", " # ---------- Plot topologies and coastlines -----------------------------------------------------------\n", @@ -368,64 +449,106 @@ " gplot.time = time\n", "\n", " # Plot plate motion vectors, coastlines, trenches and subduction teeth.\n", - " gplot.plot_plate_motion_vectors(ax, color='0.4', alpha=0.3, zorder=10, regrid_shape=20)\n", - " gplot.plot_coastlines(ax, facecolor='silver', edgecolor='none',)\n", + " gplot.plot_plate_motion_vectors(\n", + " ax, color=\"0.4\", alpha=0.3, zorder=10, regrid_shape=20\n", + " )\n", + " gplot.plot_coastlines(\n", + " ax,\n", + " facecolor=\"silver\",\n", + " edgecolor=\"none\",\n", + " )\n", " gplot.plot_trenches(ax, zorder=9)\n", " gplot.plot_subduction_teeth(ax, zorder=9)\n", "\n", - " \n", " # ---------- Calculate and plot subduction convergence rates ----------------------------------------\n", " # We are plotting the plate velocity arrows to show the direction and magnitude of plate motion, as well\n", - " # as subduction teeth to show the direction of subduction and orient which tectonic plate is the subducting and \n", + " # as subduction teeth to show the direction of subduction and orient which tectonic plate is the subducting and\n", " # overriding plate.\n", " # Split each subduction zone into tessellated points and find the magnitude of subduction convergence.\n", " trench_data = model.tessellate_subduction_zones(\n", - " time,\n", - " tessellation_threshold_radians=np.radians(0.01),\n", - " anchor_plate_id=0)\n", + " time, tessellation_threshold_radians=np.radians(0.01), anchor_plate_id=0\n", + " )\n", "\n", " # The 1st, 2nd and 3rd outputs are longitude, latitude and convergence magnitude of the tessellated trench points.\n", - " curr_subd_lon = trench_data[:,0]\n", - " curr_subd_lat = trench_data[:,1]\n", - " curr_subd_convergence = np.clip(trench_data[:,2], 0, 1e99) # Start in cm\n", + " curr_subd_lon = trench_data[:, 0]\n", + " curr_subd_lat = trench_data[:, 1]\n", + " curr_subd_convergence = np.clip(trench_data[:, 2], 0, 1e99) # convergence rate (cm/yr), clipped to be non-negative\n", "\n", " # Plot these lons and lats, colourmapped by subduction convergence and using the colourmap we defined a couple\n", " # of cells above.\n", - " sc = ax.scatter(curr_subd_lon, curr_subd_lat, c=curr_subd_convergence, cmap=conv_cmap, vmin=0, vmax=14,\n", - " transform=ccrs.PlateCarree(), rasterized=True, \n", + " sc = ax.scatter(\n", + " curr_subd_lon,\n", + " curr_subd_lat,\n", + " c=curr_subd_convergence,\n", + " cmap=conv_cmap,\n", + " vmin=0,\n", + " vmax=14,\n", + " transform=ccrs.PlateCarree(),\n", + " rasterized=True,\n", " )\n", " # Plot all other topological sections other than trenches or ridges.\n", - " gplot.plot_all_topological_sections(ax, color='grey', tessellate_degrees=1)\n", - "\n", - " \n", + " gplot.plot_all_topological_sections(ax, color=\"grey\", tessellate_degrees=1)\n", "\n", " # ---------- Reconstruct each zircon type - igneous, sedimentary and metamorphic ----------------------\n", - " igneous_lon, igneous_lat, igneous_fromage = reconstruct_deposits(zircon_igneous_filename, time)\n", - " metamorphic_lon, metamorphic_lat, metamorphic_fromage = reconstruct_deposits(zircon_metamorphic_filename, time)\n", - " sedimentary_lon, sedimentary_lat, sedimentary_fromage = reconstruct_deposits(zircon_sedimentary_filename, time)\n", - " # Turn these into numpy arrays \n", - " igneous_lon, igneous_lat, igneous_fromage = np.array(igneous_lon), np.array(igneous_lat), np.array(igneous_fromage )\n", - " metamorphic_lon, metamorphic_lat, metamorphic_fromage = np.array(metamorphic_lon), np.array(metamorphic_lat), np.array(metamorphic_fromage)\n", - " sedimentary_lon, sedimentary_lat, sedimentary_fromage = np.array(sedimentary_lon), np.array(sedimentary_lat), np.array(sedimentary_fromage)\n", - "\n", - "\n", + " igneous_lon, igneous_lat, igneous_fromage = reconstruct_deposits(\n", + " zircon_igneous_filename, time\n", + " )\n", + " metamorphic_lon, metamorphic_lat, metamorphic_fromage = reconstruct_deposits(\n", + " zircon_metamorphic_filename, time\n", + " )\n", + " sedimentary_lon, sedimentary_lat, sedimentary_fromage = reconstruct_deposits(\n", + " zircon_sedimentary_filename, time\n", + " )\n", + " # Turn these into numpy arrays\n", + " igneous_lon, igneous_lat, igneous_fromage = (\n", + " np.array(igneous_lon),\n", + " np.array(igneous_lat),\n", + " np.array(igneous_fromage),\n", + " )\n", + " metamorphic_lon, metamorphic_lat, metamorphic_fromage = (\n", + " np.array(metamorphic_lon),\n", + " np.array(metamorphic_lat),\n", + " np.array(metamorphic_fromage),\n", + " )\n", + " sedimentary_lon, sedimentary_lat, sedimentary_fromage = (\n", + " np.array(sedimentary_lon),\n", + " np.array(sedimentary_lat),\n", + " np.array(sedimentary_fromage),\n", + " )\n", "\n", " # OMITTING NON-MOVING ZIRCONS, AS WELL AS ZIRCONS THAT DO NOT FALL WITHIN CONTINENTAL BOUNDARIES.\n", "\n", - " \n", - " # ---------- Exercise: Omit zircons with no rotation by reconstructing their position at the next recent timestep \n", + " # ---------- Exercise: Omit zircons with no rotation by reconstructing their position at the next recent timestep\n", " # (assume we are rotating from a max time to present day, 1799Ma to 0Ma) and checking\n", " # whether the position now equals the position in the next timestep in the future.\n", " # However, only do this before 0Ma as there is no future timestep to project towards.\n", " if time > 0:\n", " # Reconstruct the zircons' positions in the next future timestep\n", - " future_igneous_lon, future_igneous_lat, future_igneous_fromage = reconstruct_deposits(zircon_igneous_filename, time-1)\n", - " future_metamorphic_lon, future_metamorphic_lat, future_metamorphic_fromage = reconstruct_deposits(zircon_metamorphic_filename, time-1)\n", - " future_sedimentary_lon, future_sedimentary_lat, future_sedimentary_fromage = reconstruct_deposits(zircon_sedimentary_filename, time-1) \n", + " future_igneous_lon, future_igneous_lat, future_igneous_fromage = (\n", + " reconstruct_deposits(zircon_igneous_filename, time - 1)\n", + " )\n", + " future_metamorphic_lon, future_metamorphic_lat, future_metamorphic_fromage = (\n", + " reconstruct_deposits(zircon_metamorphic_filename, time - 1)\n", + " )\n", + " future_sedimentary_lon, future_sedimentary_lat, future_sedimentary_fromage = (\n", + " reconstruct_deposits(zircon_sedimentary_filename, time - 1)\n", + " )\n", " # Turn these position arrays into numpy arrays so we can perform array masking.\n", - " future_igneous_lon, future_igneous_lat, future_igneous_fromage = np.array(future_igneous_lon), np.array(future_igneous_lat), np.array(future_igneous_fromage )\n", - " future_metamorphic_lon, future_metamorphic_lat, future_metamorphic_fromage = np.array(future_metamorphic_lon), np.array(future_metamorphic_lat), np.array(future_metamorphic_fromage)\n", - " future_sedimentary_lon, future_sedimentary_lat, future_sedimentary_fromage = np.array(future_sedimentary_lon), np.array(future_sedimentary_lat), np.array(future_sedimentary_fromage)\n", + " future_igneous_lon, future_igneous_lat, future_igneous_fromage = (\n", + " np.array(future_igneous_lon),\n", + " np.array(future_igneous_lat),\n", + " np.array(future_igneous_fromage),\n", + " )\n", + " future_metamorphic_lon, future_metamorphic_lat, future_metamorphic_fromage = (\n", + " np.array(future_metamorphic_lon),\n", + " np.array(future_metamorphic_lat),\n", + " np.array(future_metamorphic_fromage),\n", + " )\n", + " future_sedimentary_lon, future_sedimentary_lat, future_sedimentary_fromage = (\n", + " np.array(future_sedimentary_lon),\n", + " np.array(future_sedimentary_lat),\n", + " np.array(future_sedimentary_fromage),\n", + " )\n", "\n", " # All reconstructable zircons are considered to be values in the present-day arrays that are NOT in the future arrays.\n", " # If they did not move, they'd have identical lats and lons at the current and future timestep.\n", @@ -436,92 +559,159 @@ " moving_metamorphic = ~np.isin(metamorphic_lon, future_metamorphic_lon)\n", "\n", " # Collect the subset of original zircon lon, lat and age that are moving using the mask we just made\n", - " igneous_lon, igneous_lat, igneous_fromage = igneous_lon[moving_igneous], igneous_lat[moving_igneous], igneous_fromage[moving_igneous]\n", - " metamorphic_lon, metamorphic_lat, metamorphic_fromage = metamorphic_lon[moving_metamorphic], metamorphic_lat[moving_metamorphic], metamorphic_fromage[moving_metamorphic]\n", - " sedimentary_lon, sedimentary_lat, sedimentary_fromage = sedimentary_lon[moving_sedimentary], sedimentary_lat[moving_sedimentary], sedimentary_fromage[moving_sedimentary]\n", - "\n", + " igneous_lon, igneous_lat, igneous_fromage = (\n", + " igneous_lon[moving_igneous],\n", + " igneous_lat[moving_igneous],\n", + " igneous_fromage[moving_igneous],\n", + " )\n", + " metamorphic_lon, metamorphic_lat, metamorphic_fromage = (\n", + " metamorphic_lon[moving_metamorphic],\n", + " metamorphic_lat[moving_metamorphic],\n", + " metamorphic_fromage[moving_metamorphic],\n", + " )\n", + " sedimentary_lon, sedimentary_lat, sedimentary_fromage = (\n", + " sedimentary_lon[moving_sedimentary],\n", + " sedimentary_lat[moving_sedimentary],\n", + " sedimentary_fromage[moving_sedimentary],\n", + " )\n", "\n", - " \n", " # --------- Exercise: Delete zircons that are not deposited on continental boundaries\n", " # Here we use continents from the plate model manager and pass this to a function we defined above called\n", " # `get_continent_polygon_coordinates`.\n", + " assert gplot.continents is not None\n", " clons, clats = get_continent_polygon_coordinates(\n", - " [f.get_reconstructed_geometry() for f in gplot.continents])\n", + " [f.get_reconstructed_geometry() for f in gplot.continents]\n", + " )\n", "\n", - " # Using a tolerance of 100km, use a k-d tree to search which zircons are and are not within 100km radius of any \n", + " # Using a tolerance of 100km, use a k-d tree to search which zircons are and are not within 100km radius of any\n", " # continental point. Develop a mask - True if within 100km of a continent, false otherwise.\n", " d_tol = 100\n", - " igneous_in_continent = np.array(KD_dist(clons, clats, igneous_lon, igneous_lat, d_tol))\n", - " sedimentary_in_continent = np.array(KD_dist(clons, clats, sedimentary_lon, sedimentary_lat, d_tol))\n", - " metamorphic_in_continent = np.array(KD_dist(clons, clats, metamorphic_lon, metamorphic_lat, d_tol))\n", - " \n", + " igneous_in_continent = np.array(\n", + " KD_dist(clons, clats, igneous_lon, igneous_lat, d_tol)\n", + " )\n", + " sedimentary_in_continent = np.array(\n", + " KD_dist(clons, clats, sedimentary_lon, sedimentary_lat, d_tol)\n", + " )\n", + " metamorphic_in_continent = np.array(\n", + " KD_dist(clons, clats, metamorphic_lon, metamorphic_lat, d_tol)\n", + " )\n", + "\n", " # ------------ PLOT ZIRCONS --------------------------------------------------\n", " zircon_cmap = zircon_age_cmap\n", "\n", " # Plot the subset of igneous zircons that fall within continents using the mask we just made with the k-d tree\n", " sc_igneous = ax.scatter(\n", - " igneous_lon[igneous_in_continent], igneous_lat[igneous_in_continent],\n", - " marker=\"s\", c=igneous_fromage[igneous_in_continent], cmap=zircon_cmap, edgecolor='k', \n", - " s=10, label=\"Igneous\",\n", + " igneous_lon[igneous_in_continent],\n", + " igneous_lat[igneous_in_continent],\n", + " marker=\"s\",\n", + " c=igneous_fromage[igneous_in_continent],\n", + " cmap=zircon_cmap,\n", + " edgecolor=\"k\",\n", + " s=10,\n", + " label=\"Igneous\",\n", " vmin=0,\n", " vmax=1800,\n", - " transform=ccrs.PlateCarree(), linewidth=0.3\n", + " transform=ccrs.PlateCarree(),\n", + " linewidth=0.3,\n", " )\n", " # Plot the subset of metamorphic zircons that fall within continents using the mask we just made with the k-d tree\n", " sc_metamorphic = ax.scatter(\n", - " metamorphic_lon[metamorphic_in_continent], metamorphic_lat[metamorphic_in_continent],\n", - " marker=\"^\", c=metamorphic_fromage[metamorphic_in_continent], cmap=zircon_cmap, edgecolor='k', \n", - " s=10, label=\"Metamorphic\",\n", + " metamorphic_lon[metamorphic_in_continent],\n", + " metamorphic_lat[metamorphic_in_continent],\n", + " marker=\"^\",\n", + " c=metamorphic_fromage[metamorphic_in_continent],\n", + " cmap=zircon_cmap,\n", + " edgecolor=\"k\",\n", + " s=10,\n", + " label=\"Metamorphic\",\n", " vmin=0,\n", " vmax=1800,\n", - " transform=ccrs.PlateCarree(), linewidth=0.3\n", + " transform=ccrs.PlateCarree(),\n", + " linewidth=0.3,\n", " )\n", " # Plot the subset of sedimentary zircons that fall within continents using the mask we just made with the k-d tree\n", " sc_sedimentary = ax.scatter(\n", - " sedimentary_lon[sedimentary_in_continent], sedimentary_lat[sedimentary_in_continent],\n", - " marker=\"o\", c=sedimentary_fromage[sedimentary_in_continent], cmap=zircon_cmap, edgecolor='k', \n", - " s=10, label=\"Sedimentary\",\n", + " sedimentary_lon[sedimentary_in_continent],\n", + " sedimentary_lat[sedimentary_in_continent],\n", + " marker=\"o\",\n", + " c=sedimentary_fromage[sedimentary_in_continent],\n", + " cmap=zircon_cmap,\n", + " edgecolor=\"k\",\n", + " s=10,\n", + " label=\"Sedimentary\",\n", " vmin=0,\n", " vmax=1800,\n", - " transform=ccrs.PlateCarree(), linewidth=0.3\n", + " transform=ccrs.PlateCarree(),\n", + " linewidth=0.3,\n", " )\n", - " \n", + "\n", " # Show lat lon tick marks using the function we defined a few cells above.\n", " latlonticks(ax)\n", "\n", " # ---------- Plot colorbars for subduction convergence, seafloor age (if provided) and zircon age --------------\n", - " gs = gridspec.GridSpec(2,2, hspace=0.05, wspace=0.6, height_ratios=[0.96,0.04])\n", - "\n", - " cax1 = fig.add_axes([0.0, 0.21, 0.25, 0.02])\n", - " cb1 = fig.colorbar(sc, cax=cax1, ticks=np.arange(0,12.51,2.5), orientation='horizontal', label='Subduction convergence rate (cm/yr)', extend='max')\n", - " cax3 = fig.add_axes([0.6, 0.21, 0.25, 0.02])\n", + " gs = gridspec.GridSpec(2, 2, hspace=0.05, wspace=0.6, height_ratios=[0.96, 0.04])\n", + "\n", + " cax1 = fig.add_axes((0.0, 0.21, 0.25, 0.02))\n", + " cb1 = fig.colorbar(\n", + " sc,\n", + " cax=cax1,\n", + " ticks=np.arange(0, 12.51, 2.5),\n", + " orientation=\"horizontal\",\n", + " label=\"Subduction convergence rate (cm/yr)\",\n", + " extend=\"max\",\n", + " )\n", + " cax3 = fig.add_axes((0.6, 0.21, 0.25, 0.02))\n", "\n", " if agegrid_fname:\n", - " cax2 = fig.add_axes([0.3, 0.21, 0.25, 0.02])\n", - " cb2 = fig.colorbar(im, cax=cax3, orientation='horizontal', label='Seafloor age (Myr)', extend='max')\n", + " cax2 = fig.add_axes((0.3, 0.21, 0.25, 0.02))\n", + " fig.colorbar(\n", + " im,\n", + " cax=cax3,\n", + " orientation=\"horizontal\",\n", + " label=\"Seafloor age (Myr)\",\n", + " extend=\"max\",\n", + " )\n", "\n", - " #cax4 = fig.add_axes([0.0, 0.10, 0.25, 0.02])\n", - " zircon_cb = fig.colorbar(sc_igneous, cax=cax2, orientation='horizontal', label='Zircon age (Myr)', extend='max')\n", + " # cax4 = fig.add_axes((0.0, 0.10, 0.25, 0.02))\n", + " fig.colorbar(\n", + " sc_igneous,\n", + " cax=cax2,\n", + " orientation=\"horizontal\",\n", + " label=\"Zircon age (Myr)\",\n", + " extend=\"max\",\n", + " )\n", " else:\n", - " zircon_cb = fig.colorbar(sc_igneous, cax=cax3, orientation='horizontal', label='Zircon age (Myr)', extend='max')\n", + " fig.colorbar(\n", + " sc_igneous,\n", + " cax=cax3,\n", + " orientation=\"horizontal\",\n", + " label=\"Zircon age (Myr)\",\n", + " extend=\"max\",\n", + " )\n", "\n", - " cb1.set_ticklabels(np.arange(0,12.51,2.5)) \n", - " fig.subplots_adjust(bottom=0.25, top=0.95, left=0.05, right=0.80,\n", - " wspace=0.001, hspace=0.3)\n", + " ticks = np.arange(0, 12.51, 2.5)\n", + " cb1.set_ticks(ticks.tolist())\n", + " cb1.ax.xaxis.set_major_formatter(FormatStrFormatter(\"%.1f\"))\n", + " fig.subplots_adjust(\n", + " bottom=0.25, top=0.95, left=0.05, right=0.80, wspace=0.001, hspace=0.3\n", + " )\n", "\n", + " # --------------- LEGEND\n", "\n", - " # --------------- LEGEND \n", - " \n", " ax.legend(ncols=3, labelspacing=1.3, bbox_to_anchor=(0.72, -0.25))\n", - " ax.set_global()\n", - " \n", + " ax.set_global() # type: ignore\n", + "\n", " if save_fig:\n", - " os.makedirs(output_directory+\"/zircons\", exist_ok=True)\n", + " os.makedirs(f\"{output_dir}/zircons\", exist_ok=True)\n", " for out_format in [\"png\"]:\n", - " fig.savefig(output_directory+\"/zircons/zircons_{}Ma.{}\".format(time, out_format), dpi=300, bbox_inches='tight')\n", + " fig.savefig(\n", + " f\"{output_dir}/zircons/zircons_{time}Ma.{out_format}\",\n", + " dpi=300,\n", + " bbox_inches=\"tight\",\n", + " )\n", " else:\n", " plt.show()\n", - " \n", + "\n", " plt.close()\n", " return" ] @@ -531,7 +721,7 @@ "id": "413a15e6-c58b-4a2d-bfeb-dcb502a6842f", "metadata": {}, "source": [ - "## Test `time=0`" + "## Test `time=0`\n" ] }, { @@ -549,7 +739,7 @@ "id": "84498e2c-830c-4996-8a04-2f3bef8e52bb", "metadata": {}, "source": [ - "## Test with an agegrid" + "## Test with an age grid\n" ] }, { @@ -560,9 +750,17 @@ "outputs": [], "source": [ "time = 0\n", + "agegrid_0 = f\"./NotebookFiles/Cao2024_SEAFLOOR_AGE_grid_{time:.2f}Ma.nc\"\n", + "if not os.path.exists(agegrid_0):\n", + " agegrid_0 = data_dir / f\"Cao2024_SEAFLOOR_AGE_grid_{time:.2f}Ma.nc\"\n", + " if not os.path.exists(agegrid_0):\n", + " urllib.request.urlretrieve(\n", + " f\"https://github.com/GPlates/gplately/raw/refs/heads/master/Notebooks/NotebookFiles/Cao2024_SEAFLOOR_AGE_grid_{time:.2f}Ma.nc\",\n", + " agegrid_0,\n", + " )\n", "plot_zircons(\n", " time,\n", - " agegrid_fname=\"./NotebookFiles/Cao2024_SEAFLOOR_AGE_grid_{:.2f}Ma.nc\".format(time)\n", + " agegrid_fname=agegrid_0,\n", ")" ] }, @@ -572,14 +770,16 @@ "metadata": {}, "source": [ "# Repeat for all timesteps\n", - "In this notebook we demonstrate plotting zircons from 1799-0Ma, the full reconstruction extent of the `Cao2024` model. This is typically done in a `for` loop using an array of times, e.g. `np.arange(1799,-1,-1)`, i.e. starting at 1799Ma up to 0Ma, noting `arange` stops before -1, hence 0.\n", "\n", - "We also want to save each plot as a `.png` photo so we define an `output_directory` to save the maps to (see below). The plotting function already takes care of the filenames, so only an `output_directory` folder is needed.\n", + "This notebook demonstrates plotting zircons from 1799 Ma to 0 Ma, the full reconstruction extent of the `Cao2024` model. This is typically done using a `for` loop over an array of times, e.g. `np.arange(1799, -1, -1)` — starting at 1799 Ma and counting down to 0 Ma (note that `arange` stops before -1, so 0 Ma is included).\n", + "\n", + "We also want to save each plot as a `.png` image, so we define an `output_dir` to save the maps to (see below). The plotting function already takes care of the filenames, so only an `output_dir` folder path is needed.\n", "\n", "### Normal for loops vs. parallelisation\n", - "Running this plotting function in 1 for loop for 1799 timesteps can be slow. Instead of running each iteration one after the other, we can distribute them across multiple CPU cores so they can run simultaneously using `joblib`, a Python library. This is possible if the user's system has adequate memory.\n", "\n", - "Both normal for loops and parallelisation are demonstrated here and can be toggled using the boolean below:" + "Running this plotting function in a single for loop for 1799 timesteps can be slow. Instead of running each iteration one after another, we can distribute them across multiple CPU cores so they run simultaneously using `joblib`, a Python parallelisation library. This is possible provided the user's system has adequate memory.\n", + "\n", + "Both approaches — a normal for loop and parallelisation — are demonstrated below, and can be toggled using the boolean flag defined in the next cell:\n" ] }, { @@ -589,10 +789,7 @@ "metadata": {}, "outputs": [], "source": [ - "use_parallel = True\n", - "\n", - "output_directory = \"./NotebookFiles\" # Replace with your save directory here\n", - "os.makedirs(output_directory, exist_ok=True)" + "use_parallel = True" ] }, { @@ -604,17 +801,14 @@ "source": [ "if use_parallel:\n", " # Produce plots in a parallel routine - a progress bar and time taken will be shown.\n", - " parallel_plots = Parallel(n_jobs=-1, verbose=1) \\\n", - " (delayed(plot_zircons) \\\n", - " (time, save_fig=True) for time in np.arange(1799,-1,-1))\n", + " parallel_plots = Parallel(n_jobs=-1, verbose=1)(\n", + " delayed(plot_zircons)(time, save_fig=True) for time in time_steps\n", + " )\n", "\n", "else:\n", - " for time in np.arange(1799,-1,-1):\n", + " for time in time_steps:\n", " plot_zircons(time, save_fig=True)\n", - "\n", - " # This option won't show a progress bar, so make one here\n", - " if time in np.arange(1700,-1,-100):\n", - " print(\"{}Ma map plotted and saved.\".format(time))" + " print(f\"{time} Ma map plotted and saved.\")" ] }, { @@ -623,7 +817,8 @@ "metadata": {}, "source": [ "# Turn frames into a video\n", - "We now have 1799 frames to collate into a flipbook-style video. We use `moviepy` for this." + "\n", + "We now have a set of frames (one per timestep) to collate into a flipbook-style video. We use `moviepy` for this.\n" ] }, { @@ -634,16 +829,14 @@ "outputs": [], "source": [ "frame_list = []\n", - "for time in np.arange(1799,-1,-1):\n", - " frame_list.append(\n", - " output_directory+\"/zircons/zircons_{}Ma.{}\".format(time, 'png')\n", - " )\n", + "for time in time_steps:\n", + " frame_list.append(f\"{output_dir}/zircons/zircons_{time}Ma.png\")\n", "\n", "clip = mpy.ImageSequenceClip(frame_list, fps=25)\n", "\n", "clip.write_videofile(\n", - " output_directory+\"/zircons.mp4\",\n", - " fps=20,\n", + " f\"{output_dir}/zircons.mp4\",\n", + " fps=25,\n", " codec=\"libx264\",\n", " bitrate=\"5000k\",\n", " audio=False,\n", @@ -654,7 +847,8 @@ " \"-pix_fmt\",\n", " \"yuv420p\",\n", " ],\n", - ")" + ")\n", + "print(f\"Video saved to {output_dir}/zircons.mp4\")" ] } ], diff --git a/Notebooks/14-RuleBasedGPMLProcessingPipeline.py b/Notebooks/14-RuleBasedGPMLProcessingPipeline.py deleted file mode 100644 index dabafcb6..00000000 --- a/Notebooks/14-RuleBasedGPMLProcessingPipeline.py +++ /dev/null @@ -1,614 +0,0 @@ -# %% [markdown] - -## Rule Based GPML Processing Pipeline - -# This notebook demonstrates how to use GPlately to query and manipulate GPML files based on user-defined rules, -# which can be based on various criteria such as feature name, plate ID, birth age, disappearance age, region of interest, and more. -# This allows users to create customized GPML processing pipelines that can be applied to a wide range of use cases in geoscience research. - -# Built-in feature filters and transformers are available in GPlately to facilitate the creation of custom processing pipelines. -# For more information, see [**the documentation**](https://gplates.github.io/gplately/latest/sphinx/html/filters_and_transformers.html#) -# for each filter and transformer class. -# - -# %% [markdown] - -# **⚠️🚫 Don't commit changes to this notebook directly into GitHub repository. 🚫⚠️** - -# This notebook is generated from 14-RuleBasedGPMLProcessingPipeline.py using the command -# `jupytext --to notebook Notebooks/14-RuleBasedGPMLProcessingPipeline.py -o Notebooks/14-RuleBasedGPMLProcessingPipeline.ipynb`. -# If you need to commit changes to this notebook to the GPlately repository, make your edits in 14-RuleBasedGPMLProcessingPipeline.py and a GitHub workflow will regenerate this Jupyter Notebook file automatically. -# The reason that a .py file is used is to allow for easier version control and collaboration. And it is also more Copilot and code auto-formatting friendly. - -# %% [markdown] -# #### Download test GPML files and load them as pygplates FeatureCollection objects. -# %% -from os import makedirs -from os.path import exists -from urllib.request import urlretrieve -import matplotlib.pyplot as plt # type: ignore -import cartopy.crs as ccrs # type: ignore - -import pygplates # type: ignore - -from gplately.utils.feature_filter import ( - FeatureFilter, - EndTimeFilter, - FeatureIDFilter, - FeatureNameFilter, - PlateIDFilter, - BirthAgeFilter, - FeatureTypeFilter, - PolygonAreaFilter, - PropertyExistsFilter, - PropertyValueFilter, - RegionOfInterestFilter, - TopologicalFeaturesWithDuplicateSectionsFilter, - TopologicalReferenceFilter, - filter_feature_collection, -) -from gplately.plot import cartopy_plot - -from gplately.gpml import ( - FeatureCollectionProcessor, - gpml_to_pandas_dataframe, - merge_feature_collections, -) -from gplately.utils.feature_transformer import ( - SetReconstructionPlateIDTransformer, - SetValidTimeTransformer, -) - -DATA_DIR = "./rule-based-GPML-processing-pipeline-data" -makedirs(DATA_DIR, exist_ok=True) - -# Download the test feature collection files if they do not exist. -test_files = [ - ( - "https://repo.gplates.org/webdav/mchin/data/Global_EarthByte_GPlates_PresentDay_Coastlines.gpmlz", - f"{DATA_DIR}/Global_EarthByte_GPlates_PresentDay_Coastlines.gpmlz", - ), - ( - "https://repo.gplates.org/webdav/mchin/data/Feature_Geometries.gpmlz", - f"{DATA_DIR}/Feature_Geometries.gpmlz", - ), - ( - "https://repo.gplates.org/webdav/mchin/data/Plate_Boundaries.gpmlz", - f"{DATA_DIR}/Plate_Boundaries.gpmlz", - ), -] - -filepaths = [] -for test_file in test_files: - url, file_path = test_file - if not exists(file_path): - print(f"Downloading test file to {file_path}...") - urlretrieve(url, file_path) - filepaths.append(file_path) - -coastlines_feature_collection, topology_feature_collection, boundary_feature_collection = pygplates.FeatureCollection().read(filepaths) # type: ignore - -subduction_polarity_left = pygplates.Enumeration( # type: ignore - pygplates.EnumerationType.create_gpml("SubductionPolarityEnumeration"), "Left" # type: ignore -) -print(f"Finished preparation of running this notebook.") - -# %% [markdown] -#### Search and filter feature collection with pre-defined filters - -# **Pre-defined filters**: - -# - FeatureNameFilter: filter features based on their names, with options for exact match, case sensitivity, and exclusion. -# - PlateIDFilter: filter features based on their plate IDs, with an option for exclusion. -# - BirthAgeFilter: filter features based on their birth ages, with an option to keep features that are older or younger than a specified age. -# - FeatureTypeFilter: filter features based on their feature types. -# - PropertyExistsFilter: filter features based on the existence of a specified property, with an option for excluding features that have the property. -# - PropertyValueFilter: filter features based on the value of a specified property, with an option for excluding features that match the specified value. -# - EndTimeFilter: filter features based on their disappearance ages, with options to keep features that disappeared before or after a specified age. - -# **Examples**: - -# - case 1: Search features whose name contains "Australia", "New Zealand" or "Tasmania". -# - case 2: Filter out features whose name contains "Australia", "New Zealand" or "Tasmania" from the global coastlines. -# - case 3: Search features whose plate ID is from 701 to 715, which correspond to the Africa and its neighboring plates. -# - case 4: Filter out features whose plate ID is from 701 to 715 from the global coastlines. -# - case 5: Search features whose birth ages are older than 500 million years. -# - case 6: Search features whose birth ages are younger than 500 million years. -# - case 7: Search features whose feature type is gpml:Basin or gpml:IslandArc. -# - case 8: Search features with gpml:subductionPolarity property. -# - case 9: Search features whose gpml:subductionPolarity is "Left". -# - case 10: Search features that had disappeared 100 million years ago. -# - case 11: Search features that still exist at present day. - -# 📒 The results of case 10 and 11 are not plotted because they are time-dependent. You can open the output files in GPlates desktop software to check the results. -# You will see there is no feature after 100 Myr in the output file of case 10 and -# there are only features that still exist at present day in the output file of case 11. - -# %% -fig = plt.figure(figsize=(16, 8), dpi=72) -ax1 = fig.add_subplot(331, projection=ccrs.Robinson(central_longitude=180)) -ax2 = fig.add_subplot(332, projection=ccrs.Robinson(central_longitude=180)) -ax3 = fig.add_subplot(333, projection=ccrs.Robinson(central_longitude=0)) -ax4 = fig.add_subplot(334, projection=ccrs.Robinson(central_longitude=0)) -ax5 = fig.add_subplot(335, projection=ccrs.Robinson(central_longitude=0)) -ax6 = fig.add_subplot(336, projection=ccrs.Robinson(central_longitude=0)) -ax7 = fig.add_subplot(337, projection=ccrs.Robinson(central_longitude=0)) -ax8 = fig.add_subplot(338, projection=ccrs.Robinson(central_longitude=0)) -ax9 = fig.add_subplot(339, projection=ccrs.Robinson(central_longitude=0)) - -cases = [] -# case 1: features whose name contains "Australia", "New Zealand" or "Tasmania" will be saved in the output file. -cases.append( - { - "title": "Coastlines of Australia", - "feature_collection": coastlines_feature_collection, - "filters": [ - FeatureNameFilter( - ["Australia", "New Zealand", "Tasmania"], - exact_match=False, - case_sensitive=True, - ) - ], - "output_file": f"{DATA_DIR}/Australia_and_New_Zealand_coastlines.gpmlz", - "ax": ax1, - } -) -# case 2: features whose name contains "Australia", "New Zealand" or "Tasmania" will be taken out of the global coastlines. -cases.append( - { - "title": "Coastlines excluding Australia", - "feature_collection": coastlines_feature_collection, - "filters": [ - FeatureNameFilter( - ["Australia", "New Zealand", "Tasmania"], - reverse=True, - exact_match=False, - case_sensitive=True, - ) - ], - "output_file": f"{DATA_DIR}/coastlines_exclude_Australia_and_New_Zealand.gpmlz", - "ax": ax2, - } -) -# case 3: features whose plate ID is from 701 to 715, which correspond to the Africa and its neighboring plates, will be saved in the output file. -cases.append( - { - "title": "Coastlines of Africa", - "feature_collection": coastlines_feature_collection, - "filters": [ - PlateIDFilter( - list( - range(701, 716, 1) - ), # This is the list of plate IDs for the Africa and its neighboring plates - reverse=False, - ) - ], - "output_file": f"{DATA_DIR}/coastlines_with_plate_id_from_701_to_715.gpmlz", - "ax": ax3, - } -) -# case 4: features whose plate ID is from 701 to 715, which correspond to the Africa and its neighboring plates, will be taken out of the global coastlines. -cases.append( - { - "title": "Coastlines excluding Africa", - "feature_collection": coastlines_feature_collection, - "filters": [ - PlateIDFilter( - list( - range(701, 716, 1) - ), # This is the list of plate IDs for the Africa and its neighboring plates - reverse=True, # exclude features with the specified plate IDs - ) - ], - "output_file": f"{DATA_DIR}/coastlines_exclude_plate_id_from_701_to_715.gpmlz", - "ax": ax4, - } -) - -# case 5: features whose birth ages are older than 500 million years will be saved in the output file. -cases.append( - { - "title": "Coastlines older than 500 Myr", - "feature_collection": coastlines_feature_collection, - "filters": [ - BirthAgeFilter( - 500, reverse=False - ) # This filter will keep features that were born more than 500 million years ago. - ], - "output_file": f"{DATA_DIR}/coastlines_older_than_500_million_years.gpmlz", - "ax": ax5, - } -) - -# case 6: features whose birth ages are younger than 500 million years will be saved in the output file. -cases.append( - { - "title": "Coastlines younger than 500 Myr", - "feature_collection": coastlines_feature_collection, - "filters": [ - BirthAgeFilter( - 500, reverse=True - ) # This filter will keep features that were born less than 500 million years ago. - ], - "output_file": f"{DATA_DIR}/coastlines_younger_than_500_million_years.gpmlz", - "ax": ax6, - } -) - -# case 7: features whose feature type is gpml:Basin or gpml:IslandArc will be saved in the output file. -cases.append( - { - "title": "Coastlines with feature type Basin or IslandArc", - "feature_collection": coastlines_feature_collection, - "filters": [ - FeatureTypeFilter( - "gpml:Basin|gpml:IslandArc", - ) - ], - "output_file": f"{DATA_DIR}/coastlines_basins_and_island_arcs.gpmlz", - "ax": ax7, - } -) -# case 8: features with gpml:subductionPolarity property will be saved in the output file. -cases.append( - { - "title": "Topology with subduction polarity", - "feature_collection": topology_feature_collection, - "filters": [PropertyExistsFilter("gpml:subductionPolarity", reverse=False)], - "output_file": f"{DATA_DIR}/topology_with_subduction_polarity.gpmlz", - "ax": ax8, - } -) - -# case 9: features whose gpml:subductionPolarity is "Left" will be saved in the output file. -cases.append( - { - "title": "Topology with subduction polarity of Left", - "feature_collection": topology_feature_collection, - "filters": [ - PropertyValueFilter( - "gpml:subductionPolarity", subduction_polarity_left, reverse=False - ) - ], - "output_file": f"{DATA_DIR}/topology_with_subduction_polarity_left.gpmlz", - "ax": ax9, - } -) - -# case 10: features that had disappeared before 100 million years ago will be saved in the output file. -cases.append( - { - "title": "Topology features that had disappeared before 100 Ma", - "feature_collection": topology_feature_collection, - "filters": [EndTimeFilter(100, reverse=False)], - "output_file": f"{DATA_DIR}/topology_features_disappeared_before_100_million_years.gpmlz", - "ax": None, # no plot for this case - } -) - -# case 11: features that still exist at present day will be saved in the output file. -cases.append( - { - "title": "Topology features that still exist at present day", - "feature_collection": topology_feature_collection, - "filters": [EndTimeFilter(0, reverse=True)], - "output_file": f"{DATA_DIR}/topology_features_still_existing_present_day.gpmlz", - "ax": None, # no plot for this case - } -) - -idx = 0 -for case in cases: - idx += 1 - features = filter_feature_collection( - case["feature_collection"], - case["filters"], - ) - - features.write(case["output_file"]) # type: ignore - print(f"\"{case['title']}\" have been written to {case['output_file']}") - - # plot the output feature collection to check if the search worked as expected. - if case["ax"] is not None: - # we only plot the features that still exist at present day for better visualization. - present_day_features = filter_feature_collection( - pygplates.FeatureCollection( # type: ignore - case["output_file"], - ), - [EndTimeFilter(0, reverse=True)], - ) - cartopy_plot._plot_feature_collection( - present_day_features, - title=f"Fig {idx}: {case['title']}", - ax=case["ax"], - ) - -plt.tight_layout() -plt.show() - -# %% [markdown] -# #### Use Pandas DataFrame to filter features - - -# The code cell below demonstrates how to use the `gpml_to_pandas_dataframe` function to convert -# a GPML feature collection to a Pandas DataFrame, and then use the DataFrame to -# filter features based on their properties. -# In this example, we filter features whose name contains "Africa" and then check if -# the filtered features are correct by comparing the feature IDs of the filtered features with -# the feature IDs obtained from the DataFrame filtering. -# %% -gdf = gpml_to_pandas_dataframe(coastlines_feature_collection) -# use pandas dataframe to filter features whose name contains the specified name, and get the feature IDs of the filtered features. -africa_feature_ids = gdf[ - gdf["name"].str.contains("Africa", case=False, na=False) -].index.tolist() - -feature_id_filter = FeatureIDFilter(africa_feature_ids) - -features_with_name_africa = filter_feature_collection( - coastlines_feature_collection, - [feature_id_filter], -) - -# assert the list of filtrate features is in the same order as the list of given feature IDs. -# use filtrate_features_as_list to get the list of filtered features in the same order as the given feature IDs. -for fid, feature in zip( - africa_feature_ids, feature_id_filter.filtrate_features_as_list -): - assert ( - feature is not None - ), f"Feature with ID {fid} is not found in the feature collection." - assert ( - fid == feature.get_feature_id().get_string() - ), f"Feature ID mismatch: {fid} != {feature.get_feature_id().get_string()}" - -print( - f"There are {len(features_with_name_africa)} out of {len(coastlines_feature_collection)} features whose name contains 'Africa' in the global coastlines feature collection." -) - -# %% [markdown] -# #### Search feature collection with a user defined filter and update the filtrate features with a transformer - -# There are some features in the topology feature collection whose "end time" is 0, -# which should be "distant future" instead, unless some major geological events will happen soon to destroy them. - -# The code cell below demonstrates how to create a filter by subclassing the `FeatureFilter` class, -# and then use this filter to search a feature collection and update the filtered features' "end time" to "distant future". - - -# %% -# define a filter that finds all features with "end time" equal to 0 -class ZeroEndTimeFilter(FeatureFilter): - def should_keep(self, feature: pygplates.Feature) -> bool: # type: ignore - valid_time = feature.get_valid_time(None) - if valid_time: - if valid_time[1] == 0: - return True - return False - - -# update the "end time" to "distant future" for features with "end time" equal to 0 -updated_feature_collection = FeatureCollectionProcessor( - filters=[ZeroEndTimeFilter()], - transformers=[ - SetValidTimeTransformer( - end_time=pygplates.GeoTimeInstant.create_distant_future() # pyright: ignore[reportAttributeAccessIssue] - ) - ], -).process(topology_feature_collection) - -print( - f"{len(updated_feature_collection)} features out of {len(topology_feature_collection)} were updated. Changed end time to distant future for features whose end time was 0." -) -# %% [markdown] -# #### Find Laurussia topological plate and extract the reference features for investigation - -# The Laurussia topological plate has duplicate section features. -# Use its feature ID "GPlates-cfe96235-5906-4974-a654-2b14a260a3fe" to find it. -# We will find the section features for this topological plate and -# save them together in a new GPML file for investigation. -# Open the output file in GPlates desktop software, you will see the Laurussia topological plate at 371 Ma and -# the section features that are used to construct the topological geometry of this plate. - -# %% -# get the feature by feature ID -laurussia_371 = filter_feature_collection( - boundary_feature_collection, - [FeatureIDFilter(["GPlates-cfe96235-5906-4974-a654-2b14a260a3fe"])], -) - -# print some information about the laurussia_371 feature to check if we get the correct feature. -for feature in laurussia_371: - print(feature.get_feature_id().get_string()) - print(feature.get_valid_time(None)) - -# get the section features for the Laurussia topological plate -laurussia_371_section_features = filter_feature_collection( - topology_feature_collection, - [TopologicalReferenceFilter(laurussia_371)], -) - -merge_feature_collections([laurussia_371, laurussia_371_section_features]).write( - f"{DATA_DIR}/laurussia_371_with_section_features.gpmlz" -) -print( - f"Laurussia topological boundary and the section features have been written to {DATA_DIR}/laurussia_371_with_section_features.gpmlz" -) - -# %% [markdown] -# #### Find all topological boundaries with duplicate section features for investigation - -# Find all topological boundaries with duplicate section features from a feature collection, -# and then extract the reference section features for these topological boundaries and -# save them together in a new GPML file for investigation. -# %% -topology_with_duplicated_sections = filter_feature_collection( - boundary_feature_collection, - [TopologicalFeaturesWithDuplicateSectionsFilter()], -) - -print( - f"There are {len(topology_with_duplicated_sections)} out of {len(boundary_feature_collection)} features whose topological geometries have duplicate section features." -) - -topo_ref_filter = TopologicalReferenceFilter(topology_with_duplicated_sections) -topological_section_features = filter_feature_collection( - topology_feature_collection, - [topo_ref_filter], -) - -merge_feature_collections( - [topology_with_duplicated_sections, topological_section_features] -).write(f"{DATA_DIR}/topology_with_duplicate_sections_and_the_section_features.gpmlz") -print( - f"Topology features with duplicate sections and the section features have been written to {DATA_DIR}/topology_with_duplicate_sections_and_the_section_features.gpmlz" -) -# %% [markdown] -# #### Topological reference map - -# The map of topological feature ID to the list of section feature IDs used in the topological -# geometries of that topological feature can be accessed by the property "topological_reference_map" of -# the TopologicalReferenceFilter. -# The keys of this map are the string representation of the feature IDs of the topological boundary features, -# and the values are lists of string representation of the feature IDs of the section features used -# to construct the topological boundaries. - -# %% -# you will see the length of the topological reference map is the same -# as the number of topological features with duplicate sections -print(len(topo_ref_filter.topological_reference_map)) - -# now print all the reference section feature IDs for the Laurussia topological plate -print( - topo_ref_filter.topological_reference_map.get( - "GPlates-cfe96235-5906-4974-a654-2b14a260a3fe" - ) -) - -# %% [markdown] -# #### Find points inside a region of interest - -# Firstly, we create a feature collection for the vertices of an icosahedron mesh. -# Then we search for the vertices that are located within a region of interest defined by a bounding box (left, right, bottom, top). -# Finally, we create a feature for the region of interest and add it to the output feature collection for visualization. - - -# %% -from gplately.lib.icosahedron import get_mesh, xyz2lonlat - -# Create a feature collection for the vertices of an icosahedron mesh. -# We will use this feature collection as input for searching features within a region of interest in the next step. -mesh_resolution = 5 -vertices_0, faces_0 = get_mesh(mesh_resolution) -seen = set() -mesh_feature_collection = pygplates.FeatureCollection() # type: ignore -for v in vertices_0: - lon, lat = xyz2lonlat(v[0], v[1], v[2]) - point = f"{lon:0.2f} {lat:0.2f}" - if point in seen: - continue - else: - seen.add(point) - feature = pygplates.Feature() # type: ignore - feature.set_geometry(pygplates.PointOnSphere(lat, lon)) # type: ignore - mesh_feature_collection.add(feature) # type: ignore - -mesh_feature_collection.write(f"{DATA_DIR}/icosahedron_mesh_{mesh_resolution}.gpmlz") -print( - f"Icosahedron mesh have been written to {DATA_DIR}/icosahedron_mesh_{mesh_resolution}.gpmlz" -) - -# %% [markdown] -# if you open the icosahedron_mesh_5.gpmlz file in GPlates, you will see the vertices of the icosahedron mesh, -# which are represented as point features. - -#
-# -#
Icosahedron Mesh
-#
- -# %% [markdown] -# Search for the vertices that are located within a region of interest defined by a bounding box (left, right, bottom, top) in longitude and latitude. - - -# %% -# define the bounding box for the region of interest -left, right, bottom, top = (120, 140, -10, 10) -filters = [] -filters.append(RegionOfInterestFilter((left, right, bottom, top), reverse=False)) - -features = filter_feature_collection( - mesh_feature_collection, - filters, -) - -# Create a feature for the region of interest and add it to the output feature collection for visualization. -region_of_interest_feature = pygplates.Feature() # type: ignore -region_of_interest = pygplates.PolygonOnSphere((lat, lon) for lon, lat in [(left, bottom), (left, top), (right, top), (right, bottom)]) # type: ignore -region_of_interest_feature.set_geometry(region_of_interest) # type: ignore -features.add(region_of_interest_feature) # type: ignore - -features.write(f"{DATA_DIR}/icosahedron_vertices_in_region.gpmlz") -print( - f"Icosahedron vertices in the region of interest have been written to {DATA_DIR}/icosahedron_vertices_in_region.gpmlz" -) - -# %% [markdown] -# If you open icosahedron_vertices_in_region.gpmlz in GPlates, you will see the vertices of the icosahedron mesh that -# are located within the bounding box defined by (120, 140, -10, 10), -# as well as a polygon feature that represents the region of interest defined by the bounding box. - -#
-# -#
Icosahedron Vertices within (120, 140, -10, 10)
-#
- -# %% [markdown] -##### Find the points within the mainland of Australia and set their plate ID to 801. - - -# %% -# Firstly, we search for the feature that corresponds to the mainland of Australia in the global coastline feature collection. -# Then we use the geometry of this feature to define the region of interest for filtering the vertices of the icosahedron mesh. - -# find the polygon feature with area greater than 7 million among the features whose name contains "Australia", -# which should correspond to the mainland of Australia, and use its geometry as the region of interest -# for filtering the vertices of the icosahedron mesh. -features = filter_feature_collection( - coastlines_feature_collection, - [ - FeatureNameFilter( - ["Australia"], - exact_match=True, - case_sensitive=True, - ), - PolygonAreaFilter(7e6, reverse=False), - ], -) -assert ( - len(features) == 1 -), f"Expected to find exactly one feature for the mainland of Australia, but found {len(features)} features." - -australia_mainland_feature = features[0] -australia_mainland_geometry = australia_mainland_feature.get_geometry() # type: ignore - -##### find the points within the mainland of Australia and set their plate ID to 801. -features = FeatureCollectionProcessor( - filters=[RegionOfInterestFilter(australia_mainland_geometry, reverse=False)], - transformers=[SetReconstructionPlateIDTransformer(plate_id=801)], -).process(mesh_feature_collection) - -features.add(australia_mainland_feature) # type: ignore -output_file = f"{DATA_DIR}/icosahedron_vertices_within_australia.gpmlz" -features.write(output_file) -print( - f"Icosahedron vertices in the region of interest have been written to {output_file}" -) -# %% [markdown] -# If you open icosahedron_vertices_within_australia.gpmlz in GPlates, you will see the vertices of the icosahedron mesh that -# are located within the mainland of Australia, as well as a polygon feature that represents the mainland of Australia - -#
-# -#
Icosahedron Vertices within Australia
-#
diff --git a/Notebooks/15-ConvertGridReferenceFrame.ipynb b/Notebooks/15-ConvertGridReferenceFrame.ipynb index 324a3e71..7ccc62cd 100644 --- a/Notebooks/15-ConvertGridReferenceFrame.ipynb +++ b/Notebooks/15-ConvertGridReferenceFrame.ipynb @@ -7,7 +7,7 @@ "include-cell-in-app": true }, "source": [ - "# Convert grid reference frame\n", + "# Convert Grid Reference Frame\n", "\n", "Plate-tectonic models reconstruct the positions of continents and ocean\n", "basins through deep time. They always store those positions relative to\n", @@ -140,16 +140,18 @@ }, "outputs": [], "source": [ - "import os\n", - "import io\n", + "import os, warnings, io\n", "import contextlib\n", - "import warnings\n", "import numpy as np\n", "import gplately\n", "import pygplates\n", "import matplotlib.pyplot as plt\n", "import cartopy.crs as ccrs\n", - "from plate_model_manager import PlateModelManager" + "from plate_model_manager import PlateModelManager\n", + "from pathlib import Path\n", + "\n", + "warnings.filterwarnings(\"ignore\", category=UserWarning)\n", + "warnings.filterwarnings(\"ignore\", category=RuntimeWarning)" ] }, { @@ -209,16 +211,23 @@ "\n", "# --- Input / output paths (add actual file names below) ----------------------\n", "# Templates accept the reconstruction time via str.format(time), e.g. {:.2f}.\n", - "notebook_data_dir = \"15-ConvertGridReferenceFrame-notebook-data\"\n", - "input_grid_template = f\"{notebook_data_dir}/alfonso2024/Rasters/AgeGrids/Alfonso2024_SEAFLOOR_AGE_grid_\"+\"{:.2f}Ma.nc\"\n", - "output_grid_template = f\"{notebook_data_dir}/alfonso2024/Rasters/AgeGrids-PMAG/Alfonso2024_SEAFLOOR_AGE_grid_PMAG_\"+\"{:.2f}Ma.nc\"\n", - "\n", - "# Download the AgeGrids for this notebook\n", - "pm = PlateModelManager().get_model(\"Alfonso2024\", data_dir=notebook_data_dir)\n", - "with warnings.catch_warnings():\n", - " warnings.simplefilter(\"ignore\", category=RuntimeWarning)\n", - " pm.get_rasters(\"AgeGrids\", times=list(range(min_time,max_time+1,timestep_size)))\n", - " \n", + "data_dir = Path(\"15-Convert-Grid-Reference-Frame-Data\")\n", + "# Where gplately/PlateModelManager caches downloaded rotation models (shared across notebooks).\n", + "plate_model_repo_dir = \"plate-model-repo\"\n", + "input_grid_template = f\"{data_dir}/alfonso2024/Rasters/AgeGrids/Alfonso2024_SEAFLOOR_AGE_grid_\"+\"{:.2f}Ma.nc\"\n", + "output_grid_template = f\"{data_dir}/alfonso2024/Rasters/AgeGrids-PMAG/Alfonso2024_SEAFLOOR_AGE_grid_PMAG_\"+\"{:.2f}Ma.nc\"\n", + "\n", + "# Download the AgeGrids for this notebook (only applies when the SOURCE side\n", + "# uses a named registry model — if you're using your own rotation_files and\n", + "# grids, this block is skipped and input_grid_template should already point\n", + "# to grids you have on disk).\n", + "if from_model_name:\n", + " pm = PlateModelManager().get_model(from_model_name, data_dir=str(data_dir))\n", + " assert pm is not None, f\"Plate model '{from_model_name}' is not available.\"\n", + " pm.get_rasters(\"AgeGrids\", times=list(range(min_time, max_time + 1, timestep_size)))\n", + "else:\n", + " print(\"from_model_name is None (using local rotation_files) — skipping AgeGrids \"\n", + " \"download; make sure input_grid_template points to grids you already have.\")\n", "# Flag controlling execution mode: True uses Parallel (multi-process), False uses a single-process loop.\n", "use_parallel = True\n", "# ============================================================================\n" @@ -272,10 +281,12 @@ " raise ValueError(\"Provide either model_name or rotation_files (one must be set).\")\n", "\n", " if has_name:\n", - " pm = PlateModelManager().get_model(model_name, data_dir=notebook_data_dir)\n", + " pm = PlateModelManager().get_model(model_name, data_dir=plate_model_repo_dir)\n", + " assert pm is not None, f\"Plate model '{model_name}' is not available.\"\n", " return pygplates.RotationModel(pm.get_rotation_model())\n", "\n", " # rotation_files path — accept a single string or any iterable of paths.\n", + " assert rotation_files is not None\n", " if isinstance(rotation_files, (str, os.PathLike)):\n", " rotation_files = [rotation_files]\n", " rotation_files = [str(p) for p in rotation_files]\n", @@ -294,6 +305,29 @@ "# Make sure the output directory exists.\n", "os.makedirs(os.path.dirname(os.path.abspath(output_grid_template.format(0.0))), exist_ok=True)\n", "\n", + "# grid_spacing_degrees is normally 1.0 (the project standard, see the comment\n", + "# above it) — flag it loudly if someone has changed it, since a finer grid\n", + "# means much longer runtimes and a coarser one may not match downstream data.\n", + "if grid_spacing_degrees != 1.0:\n", + " warnings.warn(\n", + " f\"grid_spacing_degrees is {grid_spacing_degrees}°, not the project-standard \"\n", + " \"1.0° — make sure that's intentional.\",\n", + " stacklevel=2,\n", + " )\n", + "\n", + "# Confirm every input grid this run will need actually exists, so a typo in\n", + "# input_grid_template or an incomplete download fails here with a clear\n", + "# message rather than deep inside a parallel worker.\n", + "missing_inputs = [\n", + " t for t in reconstruction_times if not os.path.exists(input_grid_template.format(t))\n", + "]\n", + "if missing_inputs:\n", + " raise FileNotFoundError(\n", + " f\"{len(missing_inputs)} of {len(reconstruction_times)} input grid(s) are missing, \"\n", + " f\"e.g. {input_grid_template.format(missing_inputs[0])}. Check input_grid_template, \"\n", + " \"or that the AgeGrids download step above completed.\"\n", + " )\n", + "\n", "# Print a one-screen summary of what will run.\n", "def _summary(label, name, files, anchor):\n", " src = f\"model='{name}'\" if name else f\"files={files}\"\n", @@ -471,10 +505,10 @@ }, "outputs": [], "source": [ - "from gplately.mapping.gmt_cpt import get_cmap_from_gmt_cpt\n", + "from gplately.plot.gmt_cpt import get_cmap_from_gmt_cpt\n", "from gplately.auxiliary import get_gplot\n", "\n", - "cpt_file='agegrid.cpt'\n", + "cpt_file=data_dir / 'agegrid.cpt'\n", "if not os.path.isfile(cpt_file):\n", " import urllib.request\n", " urllib.request.urlretrieve(\n", @@ -484,8 +518,20 @@ "\n", "compare_time = max_time\n", "\n", - "from_gplot = get_gplot(from_model_name,time =compare_time, default_anchor_plate_id=from_anchor, model_repo_dir=notebook_data_dir)\n", - "to_gplot = get_gplot(to_model_name,time =compare_time, default_anchor_plate_id=to_anchor, model_repo_dir=notebook_data_dir)\n", + "# get_gplot needs a registry model name to fetch continent outlines, so it only\n", + "# works for sides configured with `..._model_name`. A side using local\n", + "# `..._rotation_files` still gets its raster panel below, just without the\n", + "# blue continent outline overlay.\n", + "from_gplot = (\n", + " get_gplot(from_model_name, time=compare_time, default_anchor_plate_id=from_anchor,\n", + " model_repo_dir=plate_model_repo_dir)\n", + " if from_model_name else None\n", + ")\n", + "to_gplot = (\n", + " get_gplot(to_model_name, time=compare_time, default_anchor_plate_id=to_anchor,\n", + " model_repo_dir=plate_model_repo_dir)\n", + " if to_model_name else None\n", + ")\n", "\n", "proj = ccrs.Mollweide(central_longitude=60)\n", "fig, (ax1, ax2) = plt.subplots(\n", @@ -499,7 +545,8 @@ " origin=input_raster.origin,\n", " cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=250\n", ")\n", - "from_gplot.plot_continents(ax1,color='blue',lw=0.5)\n", + "if from_gplot is not None:\n", + " from_gplot.plot_continents(ax1, color='blue', lw=0.5)\n", "ax1.gridlines()\n", "ax1.set_title(f\"From (anchor={from_anchor}) @ {compare_time} Ma\")\n", "\n", @@ -510,16 +557,47 @@ " origin=output_raster.origin,\n", " cmap=get_cmap_from_gmt_cpt(cpt_file), vmin=0, vmax=250\n", ")\n", - "to_gplot.plot_continents(ax2, color='blue',lw=0.5)\n", + "if to_gplot is not None:\n", + " to_gplot.plot_continents(ax2, color='blue', lw=0.5)\n", "ax2.gridlines()\n", "ax2.set_title(f\"To (anchor={to_anchor}) @ {compare_time} Ma\")\n", "plt.show()" ] + }, + { + "cell_type": "markdown", + "id": "8f2a1d10", + "metadata": {}, + "source": [ + "## Run summary\n", + "\n", + "Confirms how many of the expected output grids were actually written —\n", + "useful to check nothing silently failed, especially after the parallel run.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f2a1d11", + "metadata": {}, + "outputs": [], + "source": [ + "expected_outputs = [output_grid_template.format(t) for t in reconstruction_times]\n", + "written = [p for p in expected_outputs if os.path.exists(p)]\n", + "missing = [p for p in expected_outputs if p not in written]\n", + "\n", + "print(f\"{len(written)}/{len(expected_outputs)} output grids written to \"\n", + " f\"{os.path.dirname(os.path.abspath(expected_outputs[0]))}\")\n", + "if missing:\n", + " print(\"Missing:\")\n", + " for p in missing:\n", + " print(f\" {p}\")" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, diff --git a/gplately/__init__.py b/gplately/__init__.py index 0f0ebaa2..8598d928 100644 --- a/gplately/__init__.py +++ b/gplately/__init__.py @@ -76,6 +76,7 @@ from .ptt.subduction_convergence import subduction_convergence from .reconstruction import PlateReconstruction from .tools import EARTH_RADIUS +from .geometry import pygplates_to_shapely # To make the `gplately.mapping` module available for backward compatibility, we import the `plot` module # and assign it to `sys.modules["gplately.mapping"]`. This allows users to access the plotting functionalities through diff --git a/gplately/plot/plot_topologies.py b/gplately/plot/plot_topologies.py index 87a6b8c5..a628eb74 100644 --- a/gplately/plot/plot_topologies.py +++ b/gplately/plot/plot_topologies.py @@ -469,6 +469,20 @@ def _update_time(self): Moreover, coastlines, continents and COBs are reconstructed to the new :attr:`gplately.PlotTopologies.time`. """ + self._transforms = [] + self.continental_rifts = [] + self.faults = [] + self.fracture_zones = [] + self.inferred_paleo_boundaries = [] + self.terrane_boundaries = [] + self.transitional_crusts = [] + self.orogenic_belts = [] + self.sutures = [] + self.continental_crusts = [] + self.extended_continental_crusts = [] + self.passive_continental_boundaries = [] + self.slab_edges = [] + self.unclassified_features = [] # Get the topological snapshot (of resolved topologies) for the current time (and our anchor plate ID). topological_snapshot = self.plate_reconstruction.topological_snapshot( @@ -503,59 +517,59 @@ def _update_time(self): ) for topol in self.other: - if topol.get_feature_type() == pygplates.FeatureType.gpml_continental_rift: # type: ignore + if topol.get_feature_type() == pygplates.FeatureType.gpml_continental_rift: self.continental_rifts.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_fault: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_fault: self.faults.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_fracture_zone: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_fracture_zone: self.fracture_zones.append(topol) elif ( topol.get_feature_type() - == pygplates.FeatureType.gpml_inferred_paleo_boundary # type: ignore + == pygplates.FeatureType.gpml_inferred_paleo_boundary ): self.inferred_paleo_boundaries.append(topol) elif ( - topol.get_feature_type() == pygplates.FeatureType.gpml_terrane_boundary # type: ignore + topol.get_feature_type() == pygplates.FeatureType.gpml_terrane_boundary ): self.terrane_boundaries.append(topol) elif ( topol.get_feature_type() - == pygplates.FeatureType.gpml_transitional_crust # type: ignore + == pygplates.FeatureType.gpml_transitional_crust ): self.transitional_crusts.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_orogenic_belt: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_orogenic_belt: self.orogenic_belts.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_suture: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_suture: self.sutures.append(topol) elif ( - topol.get_feature_type() == pygplates.FeatureType.gpml_continental_crust # type: ignore + topol.get_feature_type() == pygplates.FeatureType.gpml_continental_crust ): self.continental_crusts.append(topol) elif ( topol.get_feature_type() - == pygplates.FeatureType.gpml_extended_continental_crust # type: ignore + == pygplates.FeatureType.gpml_extended_continental_crust ): self.extended_continental_crusts.append(topol) elif ( topol.get_feature_type() - == pygplates.FeatureType.gpml_passive_continental_boundary # type: ignore + == pygplates.FeatureType.gpml_passive_continental_boundary ): self.passive_continental_boundaries.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_slab_edge: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_slab_edge: self.slab_edges.append(topol) - elif topol.get_feature_type() == pygplates.FeatureType.gpml_transform: # type: ignore + elif topol.get_feature_type() == pygplates.FeatureType.gpml_transform: self._transforms.append(topol) # remove transform features from 'other' since 'other' is supposed to contain features that # are not subduction zones or mid-ocean ridges(ridge/transform) @@ -563,7 +577,7 @@ def _update_time(self): elif ( topol.get_feature_type() - == pygplates.FeatureType.gpml_unclassified_feature # type: ignore + == pygplates.FeatureType.gpml_unclassified_feature ): self.unclassified_features.append(topol) diff --git a/gplately/points.py b/gplately/points.py index 03125f58..fafaf9c6 100644 --- a/gplately/points.py +++ b/gplately/points.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2024-2025 The University of Sydney, Australia +# Copyright (C) 2024-2026 The University of Sydney, Australia # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License, version 2, as published by @@ -20,15 +20,14 @@ import numpy as np import pygplates from pygplates import ( - RotationModel as _RotationModel, # pyright: ignore[reportAttributeAccessIssue] - Feature as _Feature, # pyright: ignore[reportAttributeAccessIssue] - FeaturesFunctionArgument as _FeaturesFunctionArgument, # pyright: ignore[reportAttributeAccessIssue] - FeatureCollection as _FeatureCollection, # pyright: ignore[reportAttributeAccessIssue] - PointOnSphere as _PointOnSphere, # pyright: ignore[reportAttributeAccessIssue] - MultiPointOnSphere as _MultiPointOnSphere, # pyright: ignore[reportAttributeAccessIssue] - VelocityDeltaTimeType as _VelocityDeltaTimeType, # pyright: ignore[reportAttributeAccessIssue] - VelocityUnits as _VelocityUnits, # pyright: ignore[reportAttributeAccessIssue] - Earth as _Earth, # pyright: ignore[reportAttributeAccessIssue] + RotationModel as _RotationModel, + Feature as _Feature, + FeatureCollection as _FeatureCollection, + PointOnSphere as _PointOnSphere, + MultiPointOnSphere as _MultiPointOnSphere, + VelocityDeltaTimeType as _VelocityDeltaTimeType, + VelocityUnits as _VelocityUnits, + Earth as _Earth, ) from . import tools as _tools diff --git a/gplately/raster.py b/gplately/raster.py index 44974ed0..aa1a75b6 100644 --- a/gplately/raster.py +++ b/gplately/raster.py @@ -1715,9 +1715,9 @@ def sample_values(self, *, lons, lats, method="linear"): def query( self, - *, lons: np.ndarray, lats: np.ndarray, + *, interpolation_method: str = "nearest", region_of_interest: Union[None, float] = None, pointwise: bool = True, diff --git a/tests-dir/unittest/test_reconstruct_points.py b/tests-dir/unittest/test_reconstruct_points.py index e52a31ed..08f304af 100755 --- a/tests-dir/unittest/test_reconstruct_points.py +++ b/tests-dir/unittest/test_reconstruct_points.py @@ -7,57 +7,39 @@ import matplotlib.pyplot as plt import numpy as np from common import MODEL_REPO_DIR, save_fig -from plate_model_manager import PlateModelManager import gplately +from gplately.auxiliary import get_gplot print(gplately.__file__) def main(show=True): - pm_manager = PlateModelManager() - muller2019_model = pm_manager.get_model("Muller2019", data_dir=MODEL_REPO_DIR) - assert muller2019_model - rotation_model = muller2019_model.get_rotation_model() - topology_features = muller2019_model.get_topologies() - static_polygons = muller2019_model.get_static_polygons() - - model = gplately.PlateReconstruction( - rotation_model, topology_features, static_polygons - ) - - # Obtain features for the PlotTopologies object with PlateModelManager - coastlines = muller2019_model.get_layer("Coastlines") - continents = muller2019_model.get_layer("ContinentalPolygons") - COBs = muller2019_model.get_layer("COBs") - - # Call the PlotTopologies object - gplot = gplately.plot.PlotTopologies( - model, coastlines=coastlines, continents=continents, COBs=COBs - ) - - pt_lons = np.array([140.0, 150.0, 160.0]) - pt_lats = np.array([-30.0, -40.0, -50.0]) + gplot = get_gplot("Muller2019", time=0) + model = gplot.plate_reconstruction + pt_lons = np.array([140.0, 47, 13, 78]) + pt_lats = np.array([-30.0, 22, 42, 23]) gpts = gplately.Points(model, pt_lons, pt_lats) - rlons = np.empty((21, pt_lons.size)) - rlats = np.empty((21, pt_lons.size)) - - for time in range(0, 21): - rlons[time], rlats[time] = gpts.reconstruct(time, return_array=True) # type: ignore - - gplot.time = 0 # present day - - fig = plt.figure(figsize=(6, 8)) - ax1 = fig.add_subplot(111, projection=ccrs.Mercator(190)) - ax1.set_extent([130, 180, -60, -10]) # type: ignore + fig = plt.figure(figsize=(16, 8)) - gplot.plot_coastlines(ax1, color="0.8") + ax_1 = fig.add_subplot(121, projection=ccrs.Mollweide(0)) + ax_1.set_global() # type: ignore + gplot.plot_coastlines(ax_1, color="0.8") + ax_1.plot(pt_lons, pt_lats, "o", transform=ccrs.PlateCarree()) + ax_1.set_title(f"{int(gplot.time)} Ma") # type: ignore - for i in range(0, len(pt_lons)): - ax1.plot(rlons[:, i], rlats[:, i], "o", transform=ccrs.PlateCarree()) + ax_2 = fig.add_subplot(122, projection=ccrs.Mollweide(0)) + ax_2.set_global() # type: ignore + r_time = 100 + gplot.time = r_time + gplot.plot_coastlines(ax_2, color="0.8") + rlons, rlats = gpts.reconstruct(r_time, return_array=True) # type: ignore + ax_2.plot(rlons, rlats, "o", transform=ccrs.PlateCarree()) + ax_2.set_title(f"{r_time} Ma") + fig.tight_layout() if show: plt.show() else: