{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Failure modes\n",
    "\n",
    "Our algorithm doesn't always work.\n",
    "It breaks down when a geometry's edge comes very close to a pole, which usually means the GeoJSON geometry is especially strange.\n",
    "Here's one real-world example from a [Sentinel 3](https://sentinels.copernicus.eu/web/sentinel/missions/sentinel-3) [STAC](https://stacspec.org/) Item:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 158256 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import json\n",
    "from pathlib import Path\n",
    "from typing import Any\n",
    "\n",
    "from cartopy.crs import PlateCarree, RotatedPole\n",
    "from matplotlib import pyplot\n",
    "\n",
    "\n",
    "def read_example(name: str) -> dict[str, Any]:\n",
    "    with open(Path(\"examples\") / f\"{name}.json\") as f:\n",
    "        return json.load(f)\n",
    "\n",
    "\n",
    "data = read_example(\"S3A_SL_2_LST_20160521T015552_20160521T033651_6059_004_217_____\")\n",
    "x = list()\n",
    "y = list()\n",
    "for point in data[\"geometry\"][\"coordinates\"][0]:\n",
    "    x.append(point[0])\n",
    "    y.append(point[1])\n",
    "\n",
    "rotated_pole = RotatedPole(pole_latitude=37.5, pole_longitude=177.5)\n",
    "plate_carree = PlateCarree()\n",
    "axes = pyplot.axes(projection=rotated_pole)\n",
    "axes.set_title(\"S3A_SL_2_LST_20160521T015552_20160521T033651_6059_004_217_____\")\n",
    "axes.coastlines()\n",
    "axes.gridlines()\n",
    "axes.plot(x, y, marker=\"o\", transform=plate_carree)\n",
    "axes.set_xlim(-90, 90)\n",
    "pyplot.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What's an algorithm to do?\n",
    "\n",
    "Our normal algorithm can't handle this strange geometry:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 210148 characters]",
      "text/plain": [
       "<Figure size 640x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import shapely.geometry\n",
    "from cartopy.crs import Mollweide, PlateCarree\n",
    "from shapely.geometry import MultiPolygon, Polygon\n",
    "\n",
    "import antimeridian\n",
    "\n",
    "\n",
    "def plot(input: Polygon, output: Polygon | MultiPolygon, name: str) -> None:\n",
    "    figure = pyplot.figure()\n",
    "    figure.suptitle(name)\n",
    "\n",
    "    axes = figure.add_subplot(2, 2, 1, projection=PlateCarree())\n",
    "    axes.set_title(\"Original in PlateCarree\")\n",
    "    axes.stock_img()\n",
    "    axes.coastlines()\n",
    "    axes.add_geometries(input, crs=PlateCarree(), color=\"coral\", alpha=0.7)\n",
    "\n",
    "    axes = figure.add_subplot(2, 2, 2, projection=PlateCarree())\n",
    "    axes.set_title(\"Fixed in PlateCarree\")\n",
    "    axes.stock_img()\n",
    "    axes.coastlines()\n",
    "    axes.add_geometries(output, crs=PlateCarree(), color=\"coral\", alpha=0.7)\n",
    "\n",
    "    axes = figure.add_subplot(2, 2, 3, projection=Mollweide(central_longitude=180))\n",
    "    axes.set_title(\"Original in Mollweide\")\n",
    "    axes.stock_img()\n",
    "    axes.coastlines()\n",
    "    axes.add_geometries(input, crs=PlateCarree(), color=\"coral\", alpha=0.7)\n",
    "\n",
    "    axes = figure.add_subplot(2, 2, 4, projection=Mollweide(central_longitude=180))\n",
    "    axes.set_title(\"Fixed in Mollweide\")\n",
    "    axes.stock_img()\n",
    "    axes.coastlines()\n",
    "    axes.add_geometries(output, crs=PlateCarree(), color=\"coral\", alpha=0.7)\n",
    "\n",
    "    pyplot.show()\n",
    "\n",
    "\n",
    "input = shapely.geometry.shape(data[\"geometry\"])\n",
    "output = shapely.geometry.shape(antimeridian.fix_geojson(data[\"geometry\"]))\n",
    "plot(input, output, \"S3A_SL_2_LST_20160521T015552_20160521T033651_6059_004_217_____\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Force the geometry over the north pole\n",
    "\n",
    "In some cases, we can use our priors to help the algorithm out.\n",
    "Here, we can force our fixer to extend the geometry over the north pole, which provides a better result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 240516 characters]",
      "text/plain": [
       "<Figure size 640x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "output = shapely.geometry.shape(\n",
    "    antimeridian.fix_geojson(data[\"geometry\"], force_north_pole=True)\n",
    ")\n",
    "plot(\n",
    "    input,\n",
    "    output,\n",
    "    \"S3A_SL_2_LST_20160521T015552_20160521T033651_6059_004_217_____\\n\"\n",
    "    \"(forced north pole)\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## It doesn't always work\n",
    "\n",
    "Some geometries are so bad that even our north pole hack doesn't produce a valid result:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 233664 characters]",
      "text/plain": [
       "<Figure size 640x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "data = read_example(\"S3A_SL_2_LST_20160521T033651_20160521T051750_6059_004_218_____\")\n",
    "input = shapely.geometry.shape(data[\"geometry\"])\n",
    "output = shapely.geometry.shape(\n",
    "    antimeridian.fix_geojson(data[\"geometry\"], force_north_pole=True)\n",
    ").buffer(0)\n",
    "plot(\n",
    "    input,\n",
    "    output,\n",
    "    \"S3A_SL_2_LST_20160521T033651_20160521T051750_6059_004_218_____\\n\"\n",
    "    \"(forced north pole)\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this case, you're better off not using the **antimeridian** package at all, but instead using [shapely's buffer(0) fixer](https://shapely.readthedocs.io/en/stable/manual.html#constructive-methods).\n",
    "You lose the pole coverage, but at least your geometry is close to what you want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 243836 characters]",
      "text/plain": [
       "<Figure size 640x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "input = shapely.geometry.shape(data[\"geometry\"])\n",
    "output = input.buffer(0)\n",
    "plot(\n",
    "    input,\n",
    "    output,\n",
    "    \"S3A_SL_2_LST_20160521T033651_20160521T051750_6059_004_218_____\\n(buffer(0))\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusions\n",
    "\n",
    "It's a messy business, dealing with these improperly constructed GeoJSON geometries.\n",
    "There's probably not going to be one solution that works for every case."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
