{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import pydeck as pdk\n",
    "pn.extension('deckgl', sizing_mode=\"stretch_width\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to use Deck.gl you need a MAP BOX Key which you can acquire for free for limited use at [mapbox.com](https://account.mapbox.com/access-tokens/).\n",
    "\n",
    "Now we can define a JSON spec and pass it to the DeckGL pane along with the Mapbox key:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Advanced Example Based on Global Power Plant Database\n",
    "\n",
    "We will base this example on data from the [Global Power Plant Database](http://datasets.wri.org/dataset/540dcf46-f287-47ac-985d-269b04bea4c6/resource/c240ed2e-1190-4d7e-b1da-c66b72e08858/download/globalpowerplantdatabasev120).\n",
    "\n",
    "We will create an interactive application to explore the location and types of power plants globally."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import param\n",
    "import pandas as pd\n",
    "\n",
    "POWER_PLANT_URL = (\n",
    "    \"https://raw.githubusercontent.com/MarcSkovMadsen/awesome-streamlit/master/\"\n",
    "    \"gallery/global_power_plant_database/global_power_plant_database.csv\"\n",
    ")\n",
    "\n",
    "MAPBOX_KEY = \"pk.eyJ1IjoicGFuZWxvcmciLCJhIjoiY2s1enA3ejhyMWhmZjNobjM1NXhtbWRrMyJ9.B_frQsAVepGIe-HiOJeqvQ\"\n",
    "\n",
    "pp_data = pd.read_csv(POWER_PLANT_URL)\n",
    "pp_data.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Clean the data as PyDeck does not handle NA data well"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pp_data.primary_fuel = pp_data.primary_fuel.fillna(\"NA\")\n",
    "pp_data.capacity_mw = pp_data.capacity_mw.fillna(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Transform the data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "FUEL_COLORS = {\n",
    "    \"Oil\": \"black\",\n",
    "    \"Solar\": \"green\",\n",
    "    \"Gas\": \"black\",\n",
    "    \"Other\": \"gray\",\n",
    "    \"Hydro\": \"blue\",\n",
    "    \"Coal\": \"black\",\n",
    "    \"Petcoke\": \"black\",\n",
    "    \"Biomass\": \"green\",\n",
    "    \"Waste\": \"green\",\n",
    "    \"Cogeneration\": \"gray\",\n",
    "    \"Storage\": \"orange\",\n",
    "    \"Wind\": \"green\",\n",
    "}\n",
    "\n",
    "COLORS_R = {\"black\": 0, \"green\": 0, \"blue\": 0, \"orange\": 255, \"gray\": 128}\n",
    "COLORS_G = {\"black\": 0, \"green\": 128, \"blue\": 0, \"orange\": 165, \"gray\": 128}\n",
    "COLORS_B = {\"black\": 0, \"green\": 0, \"blue\": 255, \"orange\": 0, \"gray\": 128}\n",
    "\n",
    "pp_data[\"primary_fuel_color\"] = pp_data.primary_fuel.map(FUEL_COLORS)\n",
    "pp_data[\"primary_fuel_color\"] = pp_data[\"primary_fuel_color\"].fillna(\"gray\")\n",
    "pp_data[\"color_r\"] = pp_data[\"primary_fuel_color\"].map(COLORS_R)\n",
    "pp_data[\"color_g\"] = pp_data[\"primary_fuel_color\"].map(COLORS_G)\n",
    "pp_data[\"color_b\"] = pp_data[\"primary_fuel_color\"].map(COLORS_B)\n",
    "pp_data[\"color_a\"] = 140\n",
    "\n",
    "# \"name\", \"primary_fuel\", \"capacity_mw\",\n",
    "pp_data = pp_data[[\n",
    "    \"latitude\",\"longitude\", \"name\", \"capacity_mw\",\n",
    "    \"color_r\",\"color_g\",\"color_b\",\"color_a\",]\n",
    "]\n",
    "pp_data.head(1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create the app\n",
    "\n",
    "Here we create a parameterized class which creates a PyDeck plot in the constructor and connects it to some parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class GlobalPowerPlantDatabaseApp(param.Parameterized):\n",
    "    \n",
    "    data = param.DataFrame(pp_data, precedence=-1)\n",
    "\n",
    "    opacity = param.Number(default=0.8, step=0.05, bounds=(0, 1))\n",
    "    \n",
    "    pitch = param.Number(default=0, bounds=(0, 90))\n",
    "    \n",
    "    zoom = param.Integer(default=1, bounds=(1, 10))\n",
    "    \n",
    "    def __init__(self, **params):\n",
    "        super(GlobalPowerPlantDatabaseApp, self).__init__(**params)\n",
    "        self._view_state = pdk.ViewState(\n",
    "            latitude=52.2323,\n",
    "            longitude=-1.415,\n",
    "            zoom=self.zoom,\n",
    "            min_zoom=self.param.zoom.bounds[0],\n",
    "            max_zoom=self.param.zoom.bounds[1],\n",
    "        )\n",
    "        self._scatter = pdk.Layer(\n",
    "            \"ScatterplotLayer\",\n",
    "            data=self.data,\n",
    "            get_position=[\"longitude\", \"latitude\"],\n",
    "            get_fill_color=\"[color_r, color_g, color_b, color_a]\",\n",
    "            get_radius=\"capacity_mw*10\",\n",
    "            pickable=True,\n",
    "            opacity=self.opacity,\n",
    "            filled=True,\n",
    "            wireframe=True,\n",
    "        )\n",
    "        self._deck = pdk.Deck(\n",
    "            map_style=\"mapbox://styles/mapbox/light-v9\",\n",
    "            initial_view_state=self._view_state,\n",
    "            layers=[self._scatter],\n",
    "            tooltip=True,\n",
    "            api_keys={'mapbox': MAPBOX_KEY},\n",
    "        )\n",
    "        self.pane = pn.pane.DeckGL(self._deck, sizing_mode=\"stretch_width\", height=700)\n",
    "        self.param.watch(self._update, ['data', 'opacity', 'pitch', 'zoom'])\n",
    "\n",
    "    @pn.depends('pane.hover_state', 'data')\n",
    "    def _info(self):\n",
    "        index = self.pane.hover_state.get('index', -1)\n",
    "        if index == -1:\n",
    "            index = slice(0, 0)\n",
    "        return self.data.iloc[index][['name', 'capacity_mw']]\n",
    "\n",
    "    @pn.depends('pane.view_State', watch=True)\n",
    "    def _update(self):\n",
    "        state = self.pane.view_state\n",
    "        self._view_state.longitude = state['longitude']\n",
    "        self._view_state.latitude = state['latitude']   \n",
    "\n",
    "    def _update(self, event):\n",
    "        if event.name == 'data':\n",
    "            self._scatter.data = self.data\n",
    "        if event.name == 'opacity':\n",
    "            self._scatter.opacity = self.opacity\n",
    "        if event.name == 'zoom':\n",
    "            self._view_state.zoom = self.zoom\n",
    "        if event.name == 'pitch':\n",
    "            self._view_state.pitch = self.pitch\n",
    "        self.pane.param.trigger('object')\n",
    "    \n",
    "    def view(self):\n",
    "        return pn.Row(pn.Column(self.param, pn.panel(self._info, height=200)), self.pane)\n",
    "\n",
    "component = GlobalPowerPlantDatabaseApp()\n",
    "component.view()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## App\n",
    "\n",
    "Lets wrap it into nice template that can be served via `panel serve deck_gl_global_power_plants.ipynb`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.template.FastListTemplate(site=\"Panel\", title=\"Global Power Plants\", \n",
    "                             sidebar=[pn.Param(component, show_name=False)], \n",
    "                             main=[\n",
    "                                 \"The data is from the [Global Power Plant Database](http://datasets.wri.org/dataset/540dcf46-f287-47ac-985d-269b04bea4c6/resource/c240ed2e-1190-4d7e-b1da-c66b72e08858/download/globalpowerplantdatabasev120).\",\n",
    "                                 pn.Column(component.pane, pn.panel(component._info, height=100))\n",
    "                             ]\n",
    "                            ).servable();"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
