{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "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": 11,
   "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",
    "time_start = datetime.datetime(2025, 1, 7, 18, 16, 0, 0) # UTC time\n",
    "time_end = datetime.datetime(2025, 1, 13, 21, 6, 0, 0)\n",
    "\n",
    "time_step = datetime.timedelta(minutes=5) # GOES update rate\n",
    "PST_offset = datetime.timedelta(hours=-8)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "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": 6,
   "id": "c3c39560-c2f3-4f25-bd87-ad17e65c5e16",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x798f740fe920>"
      ]
     },
     "execution_count": 6,
     "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": 7,
   "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": 12,
   "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": 13,
   "id": "dfc4a62d-4b7d-497a-8e90-d6f05c1100ae",
   "metadata": {},
   "outputs": [],
   "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": 14,
   "id": "8953c65e-10d6-479f-928f-f5adb1a9ae35",
   "metadata": {},
   "outputs": [],
   "source": [
    "day_start = datetime.datetime(2025, 1, 7, 6, 0, 0, 0)\n",
    "day_end = datetime.datetime(2025, 1, 13, 18, 0, 0, 0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "f26476cb-403a-4783-a5ac-60cbb76d628f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 236048 characters]",
      "text/plain": [
       "<Figure size 3000x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(15, 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",
    "day_start = datetime.datetime(2025, 1, 7, 6, 0, 0, 0)\n",
    "plt.xlim(day_start, day_end)\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": 16,
   "id": "befa28a1-3e5b-4d22-b630-d6f65283c0b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 249420 characters]",
      "text/plain": [
       "<Figure size 3000x1000 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(15, 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(day_start, day_end)\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
}
