{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fbba7ec1-5e4c-4d04-a21a-5b5d3fb67bf9",
   "metadata": {},
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "import pandas as pd\n",
    "import param\n",
    "\n",
    "pn.extension('deckgl', design='bootstrap', theme='dark', template='bootstrap')\n",
    "\n",
    "pn.state.template.config.raw_css.append(\"\"\"\n",
    "#main {\n",
    "  padding: 0;\n",
    "}\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9925cce4-516f-4f44-8c5d-cd7cb72d3f16",
   "metadata": {},
   "source": [
    "## Define App"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0574d7b1-ba4c-4c17-b35f-5819415a9aef",
   "metadata": {},
   "outputs": [],
   "source": [
    "class App(pn.viewable.Viewer):\n",
    "\n",
    "    data = param.DataFrame(precedence=-1)\n",
    "\n",
    "    view = param.DataFrame(precedence=-1)\n",
    "\n",
    "    arc_view = param.DataFrame(precedence=-1)\n",
    "\n",
    "    radius = param.Integer(default=50, bounds=(20, 1000))\n",
    "\n",
    "    elevation = param.Integer(default=10, bounds=(0, 50))\n",
    "\n",
    "    hour = param.Integer(default=0, bounds=(0, 23))\n",
    "\n",
    "    speed = param.Integer(default=1, bounds=(0, 10), precedence=-1)\n",
    "\n",
    "    play = param.Event(label='▷')\n",
    "\n",
    "    def __init__(self, **params):\n",
    "        self.deck_gl = None\n",
    "        super().__init__(**params)\n",
    "        self._update_arc_view()\n",
    "        self.deck_gl = pn.pane.DeckGL(\n",
    "            self.spec,\n",
    "            throttle={'click': 10},\n",
    "            sizing_mode='stretch_both',\n",
    "            margin=0\n",
    "        )\n",
    "        self.deck_gl.param.watch(self._update_arc_view, 'click_state')\n",
    "        self._playing = False\n",
    "        self._cb = pn.state.add_periodic_callback(\n",
    "            self._update_hour, 1000//self.speed, start=False\n",
    "        )\n",
    "\n",
    "    @param.depends('view', 'radius', 'elevation', 'arc_view')\n",
    "    def spec(self):\n",
    "        return {\n",
    "            \"initialViewState\": {\n",
    "                \"bearing\": 0,\n",
    "                \"latitude\": 40.7,\n",
    "                \"longitude\": -73.9,\n",
    "                \"maxZoom\": 15,\n",
    "                \"minZoom\": 5,\n",
    "                \"pitch\": 40.5,\n",
    "                \"zoom\": 11\n",
    "            },\n",
    "            \"mapStyle\": \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\",\n",
    "            \"layers\": [self.hex_layer, self.arc_layer],\n",
    "            \"views\": [\n",
    "                {\"@@type\": \"MapView\", \"controller\": True}\n",
    "            ]\n",
    "        }\n",
    "\n",
    "    @property\n",
    "    def hex_layer(self):\n",
    "        return {\n",
    "            \"@@type\": \"HexagonLayer\",\n",
    "            \"autoHighlight\": True,\n",
    "            \"coverage\": 1,\n",
    "            \"data\": self.data if self.view is None else self.view,\n",
    "            \"elevationRange\": [0, 100],\n",
    "            \"elevationScale\": self.elevation,\n",
    "            \"radius\": self.radius,\n",
    "            \"extruded\": True,\n",
    "            \"getPosition\": \"@@=[pickup_x, pickup_y]\",\n",
    "            \"id\": \"8a553b25-ef3a-489c-bbe2-e102d18a3211\"\n",
    "        }\n",
    "\n",
    "    @property\n",
    "    def arc_layer(self):\n",
    "        return {\n",
    "            \"@@type\": \"ArcLayer\",\n",
    "            \"id\": 'arc-layer',\n",
    "            \"data\": self.arc_view,\n",
    "            \"pickable\": True,\n",
    "            \"getWidth\": 2,\n",
    "            \"getSourcePosition\": \"@@=[pickup_x, pickup_y]\",\n",
    "            \"getTargetPosition\": \"@@=[dropoff_x, dropoff_y]\",\n",
    "            \"getSourceColor\": [0, 255, 0, 180],\n",
    "            \"getTargetColor\": [240, 100, 0, 180]\n",
    "        }\n",
    "\n",
    "    def _update_hour(self):\n",
    "        self.hour = (self.hour+1) % 24\n",
    "\n",
    "    @param.depends('hour', watch=True, on_init=True)\n",
    "    def _update_hourly_view(self):\n",
    "        self.view = self.data[self.data.hour==self.hour]\n",
    "\n",
    "    @param.depends('view', 'radius', watch=True)\n",
    "    def _update_arc_view(self, event=None):\n",
    "        data = self.data if self.view is None else self.view\n",
    "        lon, lat, = (-73.9857, 40.7484)\n",
    "        if self.deck_gl:\n",
    "            lon, lat = self.deck_gl.click_state.get('coordinate', (lon, lat))\n",
    "        tol = self.radius / 100000\n",
    "        self.arc_view = data[\n",
    "            (data.pickup_x>=float(lon-tol)) &\n",
    "            (data.pickup_x<=float(lon+tol)) &\n",
    "            (data.pickup_y>=float(lat-tol)) &\n",
    "            (data.pickup_y<=float(lat+tol))\n",
    "        ]\n",
    "\n",
    "    @param.depends('speed', watch=True)\n",
    "    def _update_speed(self):\n",
    "        self._cb.period = 1000//self.speed\n",
    "\n",
    "    @param.depends('play', watch=True)\n",
    "    def _play_pause(self):\n",
    "        if self._playing:\n",
    "            self._cb.stop()\n",
    "            self.param.play.label = '▷'\n",
    "            self.param.speed.precedence = -1\n",
    "        else:\n",
    "            self._cb.start()\n",
    "            self.param.play.label = '❚❚'\n",
    "            self.param.speed.precedence = 1\n",
    "        self._playing = not self._playing\n",
    "\n",
    "    @property\n",
    "    def controls(self):\n",
    "        return pn.Param(app.param, show_name=False)\n",
    "\n",
    "    def __panel__(self):\n",
    "        return pn.Row(\n",
    "            self.controls,\n",
    "            self.deck_gl,\n",
    "            min_height=800,\n",
    "            sizing_mode='stretch_both',\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbda0317-8c0c-47b1-8247-2cf223d8a6ad",
   "metadata": {},
   "source": [
    "## Display app"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "189e7992-6988-49bf-96d5-46d6ddbaa26e",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_parquet('https://datasets.holoviz.org/nyc_taxi_small/v1/nyc_taxi_small.parq')\n",
    "\n",
    "app = App(data=df)\n",
    "\n",
    "app.controls.servable(area='sidebar')\n",
    "app.deck_gl.servable(title='NYC Taxi Deck.GL Explorer')\n",
    "\n",
    "app"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
