{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating maps with Cartopy\n",
    "\n",
    "This notebook demonstrates how to display gridded and point data on a map.\n",
    "Examples are given for several of the most commonly used map projections.\n",
    "* `EqualEarth`: best global maps \n",
    "* `AlbersEqualArea` or `LambertConformal`: best for regional maps in the mid-latitudes\n",
    "* `TransverseMercator`: best for maps of regions that are tall and narrow\n",
    "* `NorthPolarStereographic` or `SouthPolarStereographic`: best for polar regions\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Import packages"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import cartopy.crs as ccrs\n",
    "import cartopy.feature as cfeature\n",
    "from acgc import figstyle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Read or create data for visualization\n",
    "\n",
    "In this example, we create two artificial datasets. One is a global gridded array of sinusoidal variations. The other is a random collection of points over the United States. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create artificial gridded data\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "# Number of points in x and y directions\n",
    "n = 20\n",
    "\n",
    "pi180 = np.pi/180\n",
    "\n",
    "# Edges of the latitude-longitude grid\n",
    "latedge  = np.linspace(-90,90,n+1)\n",
    "lonedge  = np.linspace(-180,180,2*n+1)\n",
    "lon2de, lat2de = np.meshgrid( lonedge, latedge )\n",
    "\n",
    "# Centers of the latitude-longitude grid\n",
    "lat      = np.convolve(latedge, [0.5,0.5], 'valid' )\n",
    "lon      = np.convolve(lonedge, [0.5,0.5], 'valid' )\n",
    "lon2d, lat2d = np.meshgrid( lon, lat )\n",
    "\n",
    "# Artificial data\n",
    "data = np.cos( 3*lat2d*pi180 ) * np.cos( 2*lon2d*pi180 )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create artificial point data\n",
    "\n",
    "# number of points\n",
    "npoint = 50\n",
    "\n",
    "# random latitude longitude and value\n",
    "plat  = np.random.rand(npoint) * 25 + 25\n",
    "plon  = np.random.rand(npoint) * 50 - 120\n",
    "pdata = np.random.rand(npoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Define coordinate system of the data\n",
    "\n",
    "The artificial data have latitude and longitude for coordinates. In Cartopy, this is the `PlateCarree` coordinate system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define data coordinate system\n",
    "# Use PlateCarree for latitude-longitude coordinates\n",
    "datacoord = ccrs.PlateCarree()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Global maps\n",
    "\n",
    "The `EqualEarth` projection is a good choice for global maps because the sizes of all features of the map are proportional to their true area on Earth. It also does a reasonable job at preserving shapes of features. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.colorbar.Colorbar at 0x12a121090>"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 164012 characters]",
      "text/plain": [
       "<Figure size 400x330 with 2 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 341,
       "width": 410
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Global map, Equal Earth Projection\n",
    "fig, ax = plt.subplots( subplot_kw={'projection':ccrs.EqualEarth()} )\n",
    "\n",
    "# Add gridded data\n",
    "m = ax.pcolormesh(lonedge, latedge, data, transform=datacoord)\n",
    "\n",
    "# Add point data\n",
    "p = ax.scatter(plon, plat, marker='o', c=pdata, s=50, transform=datacoord)\n",
    "\n",
    "# Add coasts; Use ‘110m’ or ‘50m’ resolution\n",
    "ax.coastlines(resolution='110m')\n",
    "\n",
    "# draw parallels and meridians, but don't label them.\n",
    "ax.gridlines(draw_labels=False)\n",
    "\n",
    "# Add a colorbar\n",
    "fig.colorbar(m)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4b Transforming the data coordinates\n",
    "\n",
    "The latitude and longitude coordinates can be projected into the map projection coordinates. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<cartopy.mpl.feature_artist.FeatureArtist at 0x129fdc950>"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 186176 characters]",
      "text/plain": [
       "<Figure size 400x330 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 256,
       "width": 411
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Map projection\n",
    "projcoord=ccrs.EqualEarth()\n",
    "ax = plt.axes( projection=projcoord )\n",
    "\n",
    "# Convert the points into the projection coordinate system\n",
    "xyz = projcoord.transform_points( datacoord, plon, plat )\n",
    "\n",
    "# Plot the points using *projection* coordinates\n",
    "# Note: Now we don't need \"transform\"\n",
    "x=xyz[:,0]; y=xyz[:,1]\n",
    "plt.scatter( x, y, c=pdata, s=50, marker='o', edgecolor='black' )\n",
    "\n",
    "# Add coasts; Use ‘110m’ or ‘50m’ resolution\n",
    "ax.coastlines(resolution='110m')\n",
    "\n",
    "# Add state borders\n",
    "ax.add_feature(cfeature.STATES.with_scale('50m'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Contiguous United States (CONUS) maps\n",
    "\n",
    "The `AlbersEqualArea` and `LambertConformal` projections are commonly used for CONUS and other large regional areas in the mid-latitudes. \n",
    "\n",
    "The values of `central_longitude`, `central_latitude`, and `standard_parallels` in the example below match the values commonly used by USGS and NOAA for CONUS maps. Their values can be changed for mapping other regions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 199604 characters]",
      "text/plain": [
       "<Figure size 400x330 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 265,
       "width": 411
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Map projection for CONUS\n",
    "ax = plt.axes( projection=ccrs.AlbersEqualArea(central_longitude=-96,\n",
    "                                               central_latitude=37.5, \n",
    "                                               standard_parallels=(29.5,45.5) ))\n",
    "# ax = plt.axes( projection=ccrs.LambertConformal(central_longitude=-96,\n",
    "#                                                 central_latitude=39,\n",
    "#                                                 standard_parallels=(33,45)))\n",
    "\n",
    "# Set lat-lon of map edges. These are reasonable for CONUS\n",
    "ax.set_extent([-120, -73, 25, 50], ccrs.PlateCarree())\n",
    "\n",
    "# Add point data\n",
    "p = ax.scatter(plon, plat, marker='o', c=pdata, s=50, transform=datacoord)\n",
    "\n",
    "# Add coasts; Use ‘50m’ resolution for maps of US or smaller\n",
    "ax.coastlines(resolution='50m')\n",
    "\n",
    "# Add state borders\n",
    "ax.add_feature(cfeature.STATES.with_scale('50m'))\n",
    "\n",
    "# Add a colorbar\n",
    "plt.colorbar(p)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Vertically oriented regions\n",
    "\n",
    "The `TransverseMercator` projection is a good choice for mapping narrow regions at any latitude that have a larger north-south extent than east-west extent. `UTM` is a special case of `TransverseMercator`.\n",
    "\n",
    "`central_longitude` should be set to the center of the region being mapped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<cartopy.mpl.feature_artist.FeatureArtist at 0x1271f0810>"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 57280 characters]",
      "text/plain": [
       "<Figure size 400x330 with 1 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 341,
       "width": 184
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Map projection for a vertically oriented region\n",
    "ax = plt.axes( projection=ccrs.TransverseMercator(central_longitude=-72) )\n",
    "\n",
    "# Set lat-lon of map edges.\n",
    "ax.set_extent([-74, -70, 40, 46], ccrs.PlateCarree())\n",
    "\n",
    "# Add point data\n",
    "p = ax.scatter(plon, plat, marker='o', c=pdata, s=50, transform=datacoord)\n",
    "\n",
    "# Add coasts; Use ‘50m’ resolution for maps of US or smaller\n",
    "ax.coastlines(resolution='50m')\n",
    "ax.add_feature(cfeature.STATES.with_scale('50m'))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Polar maps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.colorbar.Colorbar at 0x127f4b990>"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 186608 characters]",
      "text/plain": [
       "<Figure size 400x330 with 2 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 341,
       "width": 410
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Map projection for North Pole\n",
    "ax = plt.axes(projection=ccrs.NorthPolarStereo())\n",
    "ax.set_extent([-180, 180, 60, 90], ccrs.PlateCarree())\n",
    "\n",
    "# Add gridded data\n",
    "m = ax.pcolormesh(lonedge, latedge, data, transform=datacoord)\n",
    "\n",
    "# Add coasts; Use ‘110m’ or ‘50m’ resolution\n",
    "ax.coastlines(resolution='110m')\n",
    "\n",
    "# Add colorbar\n",
    "plt.colorbar(m)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.colorbar.Colorbar at 0x126f5ad90>"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 190748 characters]",
      "text/plain": [
       "<Figure size 400x330 with 2 Axes>"
      ]
     },
     "metadata": {
      "image/png": {
       "height": 341,
       "width": 410
      }
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Map projection for South Pole\n",
    "ax = plt.axes(projection=ccrs.SouthPolarStereo())\n",
    "ax.set_extent([-180, 180, -90, -60], ccrs.PlateCarree())\n",
    "\n",
    "# Add gridded data\n",
    "m = ax.pcolormesh(lonedge, latedge, data, transform=datacoord)\n",
    "\n",
    "# Add coasts; Use ‘110m’ or ‘50m’ resolution\n",
    "ax.coastlines(resolution='110m')\n",
    "\n",
    "# Add colorbar\n",
    "plt.colorbar(m)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "main",
   "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.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
