{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8aa84168-aa52-4e63-b801-8c9b105c2ef0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import matplotlib.dates as mdates\n",
    "import datetime\n",
    "from IPython.display import display, clear_output\n",
    "import os.path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "be741f3b-5de5-4330-a591-f3b9606214ad",
   "metadata": {},
   "outputs": [],
   "source": [
    "products = [\n",
    "    \"G18-ABI-CONUS-BAND07\", # GOES-16 East\n",
    "    \"G16-ABI-FD-BAND07\", # GOES-18 West\n",
    "]\n",
    "km2_per_pixel = 1.0\n",
    "mi2_per_km2 = 0.386102\n",
    "day_start = datetime.datetime(2025, 1, 7, 6, 0, 0, 0)\n",
    "time_start = datetime.datetime(2025, 1, 7, 18, 16, 0, 0) # UTC time\n",
    "time_end = datetime.datetime(2025, 1, 11, 5, 1, 0, 0)\n",
    "time_step = datetime.timedelta(minutes=5) # GOES update rate\n",
    "PST_offset = datetime.timedelta(hours=-8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "eb94a560-8600-46bc-b14e-a769126dc24a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# pixel and physical coordinates\n",
    "dx = 0.2\n",
    "r = 64 # km\n",
    "\n",
    "x_raw = np.arange(-r, r, 1)\n",
    "X_raw, Y_raw = np.meshgrid(x_raw, x_raw)\n",
    "\n",
    "x = np.arange(-r, r, dx)\n",
    "X, Y = np.meshgrid(x, x)\n",
    "extent = -r, r, -r, r"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c3c39560-c2f3-4f25-bd87-ad17e65c5e16",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7e5878835810>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 172888 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# overlay from Google Maps\n",
    "reference = plt.imread(\"reference.png\")\n",
    "plt.imshow(reference, extent=extent)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "85fbdbf2-5a99-4c65-bd8b-ead4e301ffb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# templates\n",
    "shape = (128, 128)\n",
    "ones = np.ones(shape)\n",
    "zeros = np.zeros(shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "43b3243c-8442-4951-9c70-ec552be72b0e",
   "metadata": {},
   "outputs": [],
   "source": [
    "fires = {\n",
    "    \"Palisades\": {\n",
    "        \"offset\": (-17, -8),\n",
    "        \"r\": 16,\n",
    "        \"color\": \"black\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "    \"Eaton\": {\n",
    "        \"offset\": (28, 13),\n",
    "        \"r\": 15,\n",
    "        \"color\": \"purple\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "    \"Hurst / Archer\": {\n",
    "        \"offset\": (-8, 26),\n",
    "        \"r\": 7,\n",
    "        \"color\": \"red\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "    \"Lidia\": {\n",
    "        \"offset\": (15, 40),\n",
    "        \"r\": 10,\n",
    "        \"color\": \"blue\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "    \"Sunset\": {\n",
    "        \"offset\": (3, 2),\n",
    "        \"r\": 7,\n",
    "        \"color\": \"orange\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "    \"Kenneth\": {\n",
    "        \"offset\": (-27, 10),\n",
    "        \"r\": 7,\n",
    "        \"color\": \"maroon\",\n",
    "        \"area\": [],\n",
    "    },\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "dfc4a62d-4b7d-497a-8e90-d6f05c1100ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.\n",
      "No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.\n",
      "No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.\n",
      "No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.\n"
     ]
    }
   ],
   "source": [
    "time = time_start\n",
    "times = []\n",
    "data_history = [ zeros ]*12*12 # take the max of 12 hours\n",
    "while time < time_end:\n",
    "    fig = plt.figure(figsize=(8,8), dpi=100)\n",
    "\n",
    "    time_string = time.isoformat().replace(\"T\",\"+\")\n",
    "    \n",
    "    images = []\n",
    "    for product in products:\n",
    "        path = f\"raw/{product}/{time}.png\"\n",
    "        if os.path.isfile(path) and os.path.getsize(path) > 1000:\n",
    "            img = plt.imread(path)[:,:,0]\n",
    "            img = np.where(img > 0.9, zeros, img) # fix overflowing issue\n",
    "        else: #ignore corrupt / empty files\n",
    "            img = ones\n",
    "        images.append(img)\n",
    "        \n",
    "    new_data = np.clip(1 - 2 * (images[0] * images[1]) ** 0.5, 0, 1) # remap to highlight fires\n",
    "    \n",
    "    data_history.pop(0)\n",
    "    data_history.append(new_data)\n",
    "    max_data = np.max(np.array(data_history), axis=0)\n",
    "    \n",
    "    mix_data = (max_data + new_data)/2\n",
    "\n",
    "    alpha = max_data ** 0.2 # mask to draw over street map\n",
    "    alpha = np.where(max_data > 0.1, alpha, zeros)\n",
    "\n",
    "    # display(fig)\n",
    "    plt.title(f\"{(time + PST_offset).strftime('%d %b %Y, %l:%M %p')} PST\")\n",
    "    plt.imshow(mix_data, extent=extent, cmap=\"inferno\", vmin=0, vmax=1)\n",
    "    plt.imshow(reference, extent=extent, alpha=0.5)\n",
    "    plt.imshow(mix_data, extent=extent, alpha=alpha, cmap=\"inferno\", vmin=0, vmax=1)\n",
    " \n",
    "    for key in fires:\n",
    "        fire = fires[key]\n",
    "\n",
    "        cx, cy = fire[\"offset\"]\n",
    "        r = fire[\"r\"]\n",
    "        # the y coordinate is flipped\n",
    "        mask = (X_raw - cx)**2 + (Y_raw + cy)**2 < r**2\n",
    "        \n",
    "        area = np.sum(max_data[mask] > 0.1) * km2_per_pixel\n",
    "        fire[\"area\"].append(area)\n",
    "        if area < 1: continue\n",
    "            \n",
    "        patch = plt.Circle(\n",
    "            (cx, cy), r, color=fire[\"color\"], \n",
    "            fill=False, label=f\"{key}: {area*mi2_per_km2:.0f} mi$^2$ ({area:.0f} km$^2$)\"\n",
    "        )\n",
    "        plt.gca().add_patch(patch)\n",
    "        \n",
    "    plt.legend(loc='upper left')\n",
    "    \n",
    "    plt.xlabel(\"\"\"kilometers\n",
    "Satellite analysis can be inaccurate. Refer to fire.ca.gov for official information.\n",
    "Data: Google Maps, NOAA GOES, UW SSEC  | Code: github.com/xingyzt/fire\n",
    "\"\"\")\n",
    "    # clear_output(wait=True)\n",
    "    fig.tight_layout()\n",
    "    plt.savefig(f\"render/{time_string}.png\")\n",
    "    # print(time_string, end=\",\")\n",
    "    # plt.pause(.05)\n",
    "    plt.close()\n",
    "\n",
    "    time += time_step\n",
    "    times.append(time)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "f26476cb-403a-4783-a5ac-60cbb76d628f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 187056 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(10, 5), dpi=200)\n",
    "ax = plt.subplot()\n",
    "\n",
    "time_axis = [t + PST_offset for t in times]\n",
    "for key in fires:\n",
    "    fire = fires[key]\n",
    "    plt.plot(time_axis, np.array(fire[\"area\"]), label=key, c=fire[\"color\"])\n",
    "\n",
    "plt.title(\"Hotspot areas, max over 12 hours\")\n",
    "plt.legend(ncol=len(fires)//2)\n",
    "\n",
    "xaxis = ax.xaxis\n",
    "xaxis.set_major_formatter(mdates.DateFormatter('%b%e\\n%a'))\n",
    "xaxis.set_minor_formatter(mdates.DateFormatter('%l %p'))\n",
    "xaxis.set_major_locator(mdates.DayLocator(interval=1))\n",
    "xaxis.set_minor_locator(mdates.HourLocator(interval=6))\n",
    "plt.xlim(left=day_start)\n",
    "\n",
    "plt.ylim(0, 350)\n",
    "plt.ylabel(\"area (km$^2$)\")\n",
    "plt.xlabel(\"datetime (PST)\")\n",
    "plt.grid(which='major')\n",
    "plt.grid(which='minor')\n",
    "\n",
    "ax2 = ax.twinx()\n",
    "plt.ylim(0, 350 * mi2_per_km2)\n",
    "plt.ylabel(\"area (mi$^2$)\")\n",
    "\n",
    "fig.tight_layout()\n",
    "plt.savefig(\"area.png\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "befa28a1-3e5b-4d22-b630-d6f65283c0b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 204456 characters]",
      "text/plain": [
       "<Figure size 2000x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(10, 5), dpi=200)\n",
    "ax = plt.subplot()\n",
    "\n",
    "time_axis = [t + PST_offset for t in times]\n",
    "for key in fires:\n",
    "    fire = fires[key]\n",
    "    da_dt = np.diff(np.array(fire[\"area\"])) / 5 # 5 minute intervals\n",
    "    window_size = 3\n",
    "    da_dt_average = np.convolve(da_dt, np.ones(window_size)/window_size, mode='valid')\n",
    "    \n",
    "    plt.plot(time_axis[:-window_size], da_dt_average, label=key, c=fire[\"color\"])\n",
    "\n",
    "plt.title(\"Growth rate, 15 minute average\")\n",
    "plt.legend(ncol=len(fires)//2)\n",
    "\n",
    "xaxis = ax.xaxis\n",
    "xaxis.set_major_formatter(mdates.DateFormatter('%b%e\\n%a'))\n",
    "xaxis.set_minor_formatter(mdates.DateFormatter('%l %p'))\n",
    "xaxis.set_major_locator(mdates.DayLocator(interval=1))\n",
    "xaxis.set_minor_locator(mdates.HourLocator(interval=6))\n",
    "plt.xlim(left=day_start)\n",
    "\n",
    "plt.ylim(0, 4)\n",
    "plt.ylabel(\"growth rate (km$^2$/minute)\")\n",
    "plt.xlabel(\"datetime (PST)\")\n",
    "plt.grid(which='major')\n",
    "plt.grid(which='minor')\n",
    "\n",
    "ax2 = ax.twinx()\n",
    "plt.ylim(0, 4 * mi2_per_km2)\n",
    "plt.ylabel(\"growth rate (mi$^2$/minute)\")\n",
    "\n",
    "fig.tight_layout()\n",
    "plt.savefig(\"speed.png\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19e0678e-356b-457e-b9ca-5f141d4b8b57",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
