{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "From version 0.16 onwards Datashader supports rendering GeoPandas `GeoDataFrame`s directly rather than having to convert them to SpatialPandas first.\n",
    "\n",
    "Here is a demonstration using the \"geoda.natregimes\" dataset from `geodatasets`, which includes data on US counties."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import colorcet as cc\n",
    "import datashader as ds\n",
    "import datashader.transfer_functions as tf\n",
    "import geopandas\n",
    "from geodatasets import get_path"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First load the GeoPandas `GeoDataFrame`. The first time this is called will download and cache the dataset, and subsequent calls will be faster as they will use the cached dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = geopandas.read_file(get_path(\"geoda.natregimes\"))\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The geometry type of this `GeoDataFrame` is POLYGON, and there are many columns. Columns that we will use are \"DNL90\" (log of population density in 1990) and \"UE90\" (unemployment rate in 1990)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Population\n",
    "\n",
    "To view 1990 population data using Datashader, first create a canvas to render to of an appropriate size."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "canvas = ds.Canvas(plot_width=800, plot_height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The polygons are rasterized to the canvas using the `Canvas.polygons` method. This takes the source dataframe and name of the geometry column, plus an aggregator. Here we aggregate using the maximum of the population density column so that if there are multiple polygons touching a particular pixel it selects the maximum population density of those polygons."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agg = canvas.polygons(df, geometry=\"geometry\", agg=ds.max(\"DNL90\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we shade the aggregation using Colorcet's `fire` colormap using histogram equalization so that the colors are applied nonlinearly for most even use of the colormap."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "im = tf.shade(agg, cmap=cc.fire, how=\"eq_hist\")\n",
    "tf.set_background(im, \"black\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Counties with the lowest population density are rendered in black and dark red, and those with the highest population density are rendered in yellow and white."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Unemployment"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The \"UE90\" column contains unemployment percentage per county in 1990."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"UE90\"], df[\"UE90\"].min(), df[\"UE90\"].max()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see from the `min` and `max` values that unemployment goes from zero to just over 30%.\n",
    "\n",
    "To rasterize this using Datashader it is recommended to use a monochromatic colormap such as `colorcet.blues` and apply this linearly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agg = canvas.polygons(df, geometry=\"geometry\", agg=ds.max(\"UE90\"))\n",
    "im = tf.shade(agg, cmap=cc.blues, how=\"linear\")\n",
    "tf.set_background(im, \"white\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lines\n",
    "\n",
    "Datashader can also render GeoPandas `GeoDataFrame`s as lines rather than polygons. Use the same code as in the population example above but replace `Canvas.polygons()` with `Canvas.line()` instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agg = canvas.line(df, geometry=\"geometry\", agg=ds.max(\"DNL90\"))\n",
    "im = tf.shade(agg, cmap=cc.fire, how=\"eq_hist\")\n",
    "tf.set_background(im, \"black\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lines can be rendered with antialiasing. Here is the previous example with an antialiased line width of 2 pixels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "agg = canvas.line(df, geometry=\"geometry\", agg=ds.max(\"DNL90\"), line_width=2)\n",
    "im = tf.shade(agg, cmap=cc.fire, how=\"eq_hist\")\n",
    "tf.set_background(im, \"black\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Geometry type support"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following table shows which geometry types are supported by which Datashader `Canvas` functions.\n",
    "\n",
    "|Canvas function  |Supported geometry types                          |\n",
    "|-----------------|--------------------------------------------------|\n",
    "|`Canvas.line`    |LineString, MultiLineString, MultiPolygon, Polygon|\n",
    "|`Canvas.point`   |MultiPoint, Point                                 |\n",
    "|`Canvas.polygons`|MultiPolygon, Polygon                             |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## GeoPandas or SpatialPandas?\n",
    "\n",
    "Datashader supports the same line, point and polygon rendering using GeoPandas and SpatialPandas, and produces the same output using either. They work in different ways such that SpatialPandas is usually faster for viewing large datasets and GeoPandas faster when zooming into a small region of a large dataset. The GeoPandas approach is more convenient if you already have your data in GeoPandas format and do not want the overhead of converting to SpatialPandas."
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
