{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# 01. Floridan aquifer system - point retrieval\n",
        "Lester J. Williams | LCG Mapper In Chief\n",
        "\n",
        "Retrieve major and detailed hydrogeologic intervals using the same interpretation as the LCG website.\n",
        "No ArcPy or local rasters are required. Install `pandas matplotlib ipykernel` in your Python environment\n",
        "if needed, then choose that kernel in JupyterLab or VS Code. Run numbered steps in order.\n",
        "The public hostname requires the expanded release to be deployed; use localhost while testing.\n",
        "\n",
        "Regional estimates are not a site-specific boring. Elevations are U.S. survey feet NAVD88;\n",
        "depths are below local land. A missing numeric value is never filled with zero."
      ],
      "id": "cell-01"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. Connect and inspect the model"
      ],
      "id": "cell-02"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "import io\n",
        "import json\n",
        "import os\n",
        "import urllib.error\n",
        "import urllib.parse\n",
        "import urllib.request\n",
        "from pathlib import Path\n",
        "import pandas as pd\n",
        "import matplotlib.pyplot as plt\n",
        "from IPython.display import display\n",
        "\n",
        "API_BASE = os.environ.get(\"LCG_SUBSURFACE_API_BASE\", \"https://subsurface.logiccloudgeo.com\").rstrip(\"/\")\n",
        "# For a local development API, use: API_BASE = \"http://127.0.0.1:8000\"\n",
        "OUTPUT_DIR = Path(\"LCG_Retrieval_Results\")\n",
        "OUTPUT_DIR.mkdir(exist_ok=True)\n",
        "\n",
        "def request(path, payload=None):\n",
        "    body = None if payload is None else json.dumps(payload).encode(\"utf-8\")\n",
        "    req = urllib.request.Request(API_BASE + path, data=body,\n",
        "        headers={\"Accept\": \"application/json, text/csv\", \"Content-Type\": \"application/json\"})\n",
        "    try:\n",
        "        with urllib.request.urlopen(req, timeout=180) as response:\n",
        "            return response.read()\n",
        "    except urllib.error.HTTPError as error:\n",
        "        raise RuntimeError(f\"HTTP {error.code}: {error.read().decode('utf-8', errors='replace')}\") from error\n",
        "    except urllib.error.URLError as error:\n",
        "        raise RuntimeError(f\"Cannot reach {API_BASE}. Check endpoint readiness or select your local API.\") from error\n",
        "\n",
        "ready = json.loads(request(\"/v2/ready\"))\n",
        "model_response = json.loads(request(\"/v2/model\"))\n",
        "model = model_response[\"configuration\"]\n",
        "print(\"API:\", API_BASE, \"| Ready:\", ready[\"status\"], \"| Model:\", model[\"model_version\"])\n",
        "print(\"Configuration SHA-256:\", model_response[\"sha256\"])"
      ],
      "id": "cell-03"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. Enter a WGS84 point"
      ],
      "id": "cell-04"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "UWI = \"OCALA_REVIEW\"\n",
        "LATITUDE = 29.183302\n",
        "LONGITUDE = -82.211380\n",
        "if not (-90 <= LATITUDE <= 90 and -180 <= LONGITUDE <= 180):\n",
        "    raise ValueError(\"Latitude/longitude is outside the valid range.\")\n",
        "params = urllib.parse.urlencode({\"latitude\": LATITUDE, \"longitude\": LONGITUDE})\n",
        "point = json.loads(request(\"/v2/point?\" + params))\n",
        "print(\"Coverage:\", point[\"coverage\"][\"status\"])\n",
        "print(\"Shallow interpretation:\", point[\"shallow_resolution\"])\n",
        "print(\"Salinity reference:\", point[\"salinity_reference\"])"
      ],
      "id": "cell-05"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. Display major and detailed tables"
      ],
      "id": "cell-06"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "fields = [\"id\", \"label\", \"top\", \"bottom\", \"top_depth\", \"bottom_depth\", \"thickness\", \"status\"]\n",
        "major = pd.DataFrame(point[\"major_units\"])[fields]\n",
        "detailed = pd.DataFrame(point[\"detailed_units\"])[fields]\n",
        "for title, table in [(\"Major aquifers and confining units\", major), (\"Detailed units and zones\", detailed)]:\n",
        "    print(title)\n",
        "    with pd.option_context(\"display.max_rows\", None, \"display.max_columns\", None):\n",
        "        display(table.round(2).astype(object).where(table.notna(), \"\u2014\"))\n",
        "# outside_unit_extent/absent: outside mapped unit or modeled absence.\n",
        "# unavailable/partial: applicable contacts are unresolved. Review status before interpretation."
      ],
      "id": "cell-07"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 4. Draw an elevation column\n",
        "Parent aquifer rows are omitted from the detailed fill so their thicknesses are not counted twice.\n",
        "Overlapping interpretations use their configured lanes. Open lower boundaries are labeled;\n",
        "no artificial lower confining-unit bottom is drawn. TDS is a reference, not an extra rock layer."
      ],
      "id": "cell-08"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "def draw_column(units, title):\n",
        "    usable = [u for u in units if not u.get(\"parent\") and u[\"status\"] == \"available\"\n",
        "              and u[\"top\"] is not None and u[\"bottom\"] is not None and u[\"top\"] > u[\"bottom\"]]\n",
        "    if not usable:\n",
        "        print(title, \"- no resolved intervals to draw.\")\n",
        "        return\n",
        "    fig, ax = plt.subplots(figsize=(7, 8))\n",
        "    for u in usable:\n",
        "        lane = u.get(\"lane\", 0)\n",
        "        ax.bar(lane, u[\"top\"]-u[\"bottom\"], bottom=u[\"bottom\"], width=.7,\n",
        "               color=u[\"color\"], edgecolor=\"#52616a\", label=u[\"label\"])\n",
        "    salinity = point.get(\"salinity_reference\", {})\n",
        "    if salinity.get(\"value\") is not None:\n",
        "        ax.axhline(salinity[\"value\"], color=\"#9d4778\", linestyle=\"--\", label=salinity[\"label\"])\n",
        "    for unit in units:\n",
        "        if unit[\"status\"] == \"open_bottom\" and unit[\"top\"] is not None:\n",
        "            ax.annotate(unit[\"label\"] + \" - bottom not mapped\", (0, unit[\"top\"]),\n",
        "                        xytext=(5, -14), textcoords=\"offset points\", fontsize=8)\n",
        "    ax.set_ylabel(\"Elevation (U.S. survey ft NAVD88)\")\n",
        "    ax.set_xticks([])\n",
        "    ax.set_title(title + f\"\\n{UWI}: {LATITUDE}, {LONGITUDE}\")\n",
        "    ax.grid(axis=\"y\", alpha=.25)\n",
        "    ax.legend(loc=\"upper left\", bbox_to_anchor=(1.02, 1), fontsize=8)\n",
        "    fig.savefig(OUTPUT_DIR / (\"Point_\" + title.split()[0] + \".svg\"), bbox_inches=\"tight\")\n",
        "    plt.show()\n",
        "\n",
        "draw_column(point[\"major_units\"], \"Major hydrogeologic units\")\n",
        "draw_column(point[\"detailed_units\"], \"Detailed hydrogeologic units\")"
      ],
      "id": "cell-09"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 5. Save the response and tables"
      ],
      "id": "cell-10"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": null,
      "outputs": [],
      "source": [
        "(OUTPUT_DIR / \"Point_response.json\").write_text(json.dumps(point, indent=2) + \"\\n\", encoding=\"utf-8\")\n",
        "for label, table in [(\"Major\", major), (\"Detailed\", detailed)]:\n",
        "    table = table.copy()\n",
        "    table.insert(0, \"UWI\", UWI)\n",
        "    table.insert(1, \"latitude\", LATITUDE)\n",
        "    table.insert(2, \"longitude\", LONGITUDE)\n",
        "    table.to_csv(OUTPUT_DIR / f\"Point_{label}.csv\", index=False, encoding=\"utf-8-sig\")\n",
        "print(\"Saved files in\", OUTPUT_DIR.resolve())\n",
        "print(\"Lineage:\", point[\"lineage\"])"
      ],
      "id": "cell-11"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 6. Interpretation and next use\n",
        "The methods page explains masks, exposure, estimated boundaries and regional limits:\n",
        "https://logiccloudgeo.com/subsurface/how-it-works/\n",
        "\n",
        "The table's major separator and individual detailed composite units may overlap; do not sum them.\n",
        "To sample a transect, use notebook 02 with UWI/latitude/longitude rows ordered along the line.\n",
        "Outputs use fixed descriptive names and are replaced when these cells are rerun."
      ],
      "id": "cell-12"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.12"
    },
    "authors": [
      {
        "name": "Lester J. Williams"
      }
    ]
  },
  "nbformat": 4,
  "nbformat_minor": 5
}
