{ "cells": [ { "cell_type": "markdown", "id": "fa6102d1", "metadata": {}, "source": [ "# Databases - Hydrology" ] }, { "cell_type": "markdown", "id": "0c2004d7", "metadata": {}, "source": [ "This tutorial covers `HydrologyDB`, the database for river reaches. Each reach is a site, a point at its gauge or outlet, and it may have a *catchment*, the polygon that drains to it. Sites and catchments are two layers of the same GeoPackage. Every catchment needs a site, and a site has at most one catchment, but it may have none. That matters in practice: gauges are usually known from the start, while catchments arrive over time, as someone draws or generates them.\n", "\n", "The example is a river with six gauges, of which a regional dataset covers only three.\n", "\n", "```{seealso}\n", "Check out the {doc}`Databases - Spatial ` tutorial first, which introduces the `columns_map` and setup files.\n", "```" ] }, { "cell_type": "markdown", "id": "34fdbac3", "metadata": {}, "source": [ "## Notebook setup\n", "\n", "For users running this tutorial as a Jupyter Notebook, this cell must be executed first:" ] }, { "cell_type": "code", "execution_count": 1, "id": "97ad6807", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:45.381817Z", "iopub.status.busy": "2026-11-09T13:35:45.381602Z", "iopub.status.idle": "2026-11-09T13:35:45.989958Z", "shell.execute_reply": "2026-11-09T13:35:45.989537Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Outputs will be saved to: ./outputs\\databases\n" ] } ], "source": [ "import sys\n", "import json\n", "import warnings\n", "from pathlib import Path\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import geopandas as gpd\n", "from shapely.geometry import Point, box\n", "\n", "# Install `plans` in `google.colab`.\n", "# Use `pip install plans` for other environments.\n", "\n", "if \"google.colab\" in sys.modules:\n", " import os\n", " os.system(f\"{sys.executable} -m pip install -q plans\")\n", "\n", "from plans.databases import HydrologyDB\n", "\n", "# This avoids warnings related to uninstalled fonts\n", "import logging\n", "# Set the matplotlib font manager logger to only show errors (hides warnings)\n", "logging.getLogger('matplotlib.font_manager').setLevel(logging.ERROR)\n", "\n", "# define output folder\n", "OUTPUT_DIR = Path(\"outputs/databases\")\n", "OUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n", "print(f\"Outputs will be saved to: ./{OUTPUT_DIR}\")" ] }, { "cell_type": "markdown", "id": "ff5f732c", "metadata": {}, "source": [ "## The sources\n", "\n", "The gauges run along the river from west to east. Each catchment is simplified to a box, from the headwaters down to its gauge, so catchments overlap as they nest downstream. Lets keep a helper that makes one, with its area, in a metric CRS. The gauges come in one file, and the regional dataset in another, in a different CRS. It holds three of our gauges and two from another river:" ] }, { "cell_type": "code", "execution_count": 2, "id": "d28b7342", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:45.992856Z", "iopub.status.busy": "2026-11-09T13:35:45.992555Z", "iopub.status.idle": "2026-11-09T13:35:46.089833Z", "shell.execute_reply": "2026-11-09T13:35:46.089419Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
GaugeCodeAreaKm2Survey
0R0014280.02019
1R0028558.02019
2R00312834.02019
3R9012568.02019
4R9022568.02019
\n", "
" ], "text/plain": [ " GaugeCode AreaKm2 Survey\n", "0 R001 4280.0 2019\n", "1 R002 8558.0 2019\n", "2 R003 12834.0 2019\n", "3 R901 2568.0 2019\n", "4 R902 2568.0 2019" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "CRS = \"EPSG:4674\" # SIRGAS 2000, geographic\n", "CRS_METRIC = \"EPSG:31982\" # SIRGAS 2000 / UTM zone 22S\n", "\n", "codes = [f\"R00{i}\" for i in range(1, 7)]\n", "x_gauge = dict(zip(codes, [-53.0, -52.5, -52.0, -51.5, -51.0, -50.5]))\n", "\n", "\n", "def make_catchment(code, column=\"Code\"):\n", " # a box from the headwaters to the gauge, with its area in km2\n", " gdf = gpd.GeoDataFrame({column: [code]}, geometry=[box(-53.5, -30.4, x_gauge.get(code, -53.2), -29.6)], crs=CRS)\n", " return gdf.assign(Area=gdf.to_crs(CRS_METRIC).area.round(-6) / 1e6)\n", "\n", "\n", "gauges = gpd.GeoDataFrame(\n", " data={\"GaugeCode\": codes, \"GaugeName\": [\"Upper\", \"Bend\", \"Mill\", \"Bridge\", \"Ferry\", \"Mouth\"]},\n", " geometry=[Point(x_gauge[c], -30.0) for c in codes],\n", " crs=CRS,\n", ")\n", "file_gauges = OUTPUT_DIR / \"gauges.gpkg\"\n", "gauges.to_file(filename=file_gauges, layer=\"gauges\", driver=\"GPKG\")\n", "\n", "regional = pd.concat([make_catchment(code=c, column=\"GaugeCode\") for c in [\"R001\", \"R002\", \"R003\", \"R901\", \"R902\"]], ignore_index=True)\n", "regional = regional.rename(columns={\"Area\": \"AreaKm2\"}).assign(Survey=2019).to_crs(CRS_METRIC)\n", "file_regional = OUTPUT_DIR / \"regional.gpkg\"\n", "regional.to_file(filename=file_regional, layer=\"catchments\", driver=\"GPKG\")\n", "regional.drop(columns=\"geometry\")" ] }, { "cell_type": "markdown", "id": "7bc3b33c", "metadata": {}, "source": [ "Two datasets will live here. Streamflow is measured at the gauge, while rainfall is averaged over the catchment. Both are records of the site, and the `extent` of the spec tells which is which:" ] }, { "cell_type": "code", "execution_count": 3, "id": "26af889e", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.093490Z", "iopub.status.busy": "2026-11-09T13:35:46.093313Z", "iopub.status.idle": "2026-11-09T13:35:46.097233Z", "shell.execute_reply": "2026-11-09T13:35:46.096733Z" } }, "outputs": [], "source": [ "def make_spec(name, abstract, variable, statistic, source, extent):\n", " # one monthly spec\n", " return {\n", " \"name\": name,\n", " \"abstract\": abstract,\n", " \"method\": \"synthetic\",\n", " \"timestep\": \"1M\",\n", " \"extent\": extent,\n", " \"scale\": 1,\n", " \"offset\": 0,\n", " \"start\": \"2016-01-01\",\n", " \"end\": None,\n", " \"variable_name\": variable,\n", " \"statistic_name\": statistic,\n", " \"source_name\": source,\n", " }\n", "\n", "\n", "specs = [\n", " make_spec(\"Q_MONTHLY\", \"Monthly mean streamflow\", \"standard streamflow\", \"mean\", \"ANA\", \"gauge\"),\n", " make_spec(\"PPT_MONTHLY_AREAL\", \"Monthly rainfall over the catchment\", \"precipitation\", \"sum\", \"CHIRPS v2.0\", \"catchment\"),\n", "]\n", "file_specs = OUTPUT_DIR / \"reaches_specs.json\"\n", "_ = file_specs.write_text(json.dumps(specs, indent=2), encoding=\"utf-8\")" ] }, { "cell_type": "markdown", "id": "fa11f247", "metadata": {}, "source": [ "## Creating the database\n", "\n", "The setup takes an optional `catchments` section, read like `spatial`. In its `columns_map`, the `name` entry is the source column holding the site names, here also called `GaugeCode`, and each catchment takes the name of its site. As anywhere in a setup, it can be a dict or a file:\n", "\n", "`````{tab-set}\n", ":sync-group: setup\n", "\n", "````{tab-item} TOML\n", ":sync: toml\n", "\n", "```toml\n", "[database]\n", "path = \"outputs/databases/reaches_db.gpkg\"\n", "\n", "[specs]\n", "path = \"outputs/databases/reaches_specs.json\"\n", "\n", "[spatial]\n", "file = \"outputs/databases/gauges.gpkg\"\n", "layer = \"gauges\"\n", "\n", "[spatial.columns_map]\n", "name = \"GaugeCode\"\n", "gauge_title = \"GaugeName\"\n", "\n", "[catchments]\n", "file = \"outputs/databases/regional.gpkg\"\n", "layer = \"catchments\"\n", "\n", "[catchments.columns_map]\n", "name = \"GaugeCode\"\n", "area_km2 = \"AreaKm2\"\n", "```\n", "````\n", "\n", "````{tab-item} JSON\n", ":sync: json\n", "\n", "```json\n", "{\n", " \"database\": {\"path\": \"outputs/databases/reaches_db.gpkg\"},\n", " \"specs\": {\"path\": \"outputs/databases/reaches_specs.json\"},\n", " \"spatial\": {\n", " \"file\": \"outputs/databases/gauges.gpkg\",\n", " \"layer\": \"gauges\",\n", " \"columns_map\": {\n", " \"name\": \"GaugeCode\",\n", " \"gauge_title\": \"GaugeName\"\n", " }\n", " },\n", " \"catchments\": {\n", " \"file\": \"outputs/databases/regional.gpkg\",\n", " \"layer\": \"catchments\",\n", " \"columns_map\": {\n", " \"name\": \"GaugeCode\",\n", " \"area_km2\": \"AreaKm2\"\n", " }\n", " }\n", "}\n", "```\n", "````\n", "`````\n", "\n", "When catchments are not available yet, the section is simply left out, and the layer starts empty. Lets write the TOML version and pass its path to `.new()`:" ] }, { "cell_type": "code", "execution_count": 4, "id": "da895d3b", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.100417Z", "iopub.status.busy": "2026-11-09T13:35:46.100208Z", "iopub.status.idle": "2026-11-09T13:35:46.219952Z", "shell.execute_reply": "2026-11-09T13:35:46.219474Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2 feature(s) naming no site were skipped\n" ] } ], "source": [ "file_setup = OUTPUT_DIR / \"reaches_setup.toml\"\n", "file_setup.write_text(\n", " f\"\"\"[database]\n", "path = \"{(OUTPUT_DIR / 'reaches_db.gpkg').as_posix()}\"\n", "\n", "[specs]\n", "path = \"{file_specs.as_posix()}\"\n", "\n", "[spatial]\n", "file = \"{file_gauges.as_posix()}\"\n", "layer = \"gauges\"\n", "\n", "[spatial.columns_map]\n", "name = \"GaugeCode\"\n", "gauge_title = \"GaugeName\"\n", "\n", "[catchments]\n", "file = \"{file_regional.as_posix()}\"\n", "layer = \"catchments\"\n", "\n", "[catchments.columns_map]\n", "name = \"GaugeCode\"\n", "area_km2 = \"AreaKm2\"\n", "\"\"\",\n", " encoding=\"utf-8\",\n", ")\n", "db = HydrologyDB(name=\"River\")\n", "with warnings.catch_warnings(record=True) as caught:\n", " warnings.simplefilter(\"always\")\n", " db.new(config=file_setup, overwrite=True)\n", "print(*[w.message for w in caught], sep=\"\\n\")" ] }, { "cell_type": "markdown", "id": "19b2ac1b", "metadata": {}, "source": [ "The two features from the other river name no site, so they were skipped with a warning. With `skip_unmatched = false` in the section, they would be an error instead. If nothing matched at all, it is an error either way, since that usually means the `name` entry points to the wrong column.\n", "\n", "The layer holds what matched, reprojected to the CRS of the sites. The `Survey` column stayed out, as it is not in the map. The `.missing_catchments()` method tells which sites still have none:" ] }, { "cell_type": "code", "execution_count": 5, "id": "418fa2a8", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.222899Z", "iopub.status.busy": "2026-11-09T13:35:46.222685Z", "iopub.status.idle": "2026-11-09T13:35:46.235425Z", "shell.execute_reply": "2026-11-09T13:35:46.234964Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Missing: ['R004', 'R005', 'R006']\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamearea_km2
01R0014280.0
12R0028558.0
23R00312834.0
\n", "
" ], "text/plain": [ " id name area_km2\n", "0 1 R001 4280.0\n", "1 2 R002 8558.0\n", "2 3 R003 12834.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "print(\"Missing:\", db.missing_catchments())\n", "db.get_catchments().drop(columns=\"geometry\")" ] }, { "cell_type": "code", "execution_count": 6, "id": "13cbedfb", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.238418Z", "iopub.status.busy": "2026-11-09T13:35:46.238174Z", "iopub.status.idle": "2026-11-09T13:35:46.387427Z", "shell.execute_reply": "2026-11-09T13:35:46.386974Z" } }, "outputs": [ { "data": { "image/png": "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cScpbrw9PSU0PEqiohj1xYdPKi/xEF57RJSMgj7kxSG3Dvl00WG5kd/nCASJ9jpMGIM+Rw/gEOChthFNyQULFtTew/Xr15fs2bNrT13UAmI7wP5v3ryp62z5/fffNZcverBaq1YtadKkiaxcufKZ16AzSZ8+fczPEQTnyZNHkkrGtD7iXyGPRLGKJUFwtXmnildndmL5JRgCPh+WHxElp6APAQ96w4aHh2snihw5cmiz6ooVK2T48OF67yjLZtj4QK0jGIEj8vfQXIvcPiOvb//+/TqANNbZgl7AloHn+fPntRMIllvmBlpKnTq13pITBH5ERERE9sTppyXm10UN24QJEyRv3rxaQ4b7SZMmyaxZs3R9YkCNG/4mOl0g4Dx58qTm9xUoUEBq1qxpzilEjR9q4S5duiSBgYH6GOuR/2dAjh+adY3AE50/jJsRiOLeqC0kIiIicgVxjmzef/99CQoK0gGZMTwK7vEcyxMLgkl0nkAOX9q0aaVRo0ZSuHBhbZ617HG7ZMkSKVSokDb/FitWTMfdi177iNpANAETERERuRMPk5uNRIxmYeQLxlSTh6AQtX6OQk4fho3BeIF+fn5OPFIiIiIi58UhccrpcwWx5RLGJeAjIiIiSimYuEZERETkBhj0EREREbkBBn1EREREboBBHxEREZEbYNBHRERE5AYY9BERERG5AQZ9RERERG6AQR8RERGRG2DQR0REROQGGPQRERERuQEGfURERERugEEfERERkRtg0EdERETkBhj0EREREbkBBn1EREREboBBHxEREZEbYNBHRERE5AYY9BERERG5AQZ9RERERG6AQR8RERGRG2DQR0REROQGGPQRERERuQEGfURERERugEEfERERkRtg0EdERETkBlJM0Hfs2DFp3769vPzyy1KhQgXp37+/3L5922qbkJAQGTRokJQsWVK3wzZPnjyJdd979uyRJk2aSNGiRaVx48Zy5MiRRDwTIiIioucvRQR9Dx48kO7du0ujRo1k9erVMnnyZNm+fbsGaJY6d+4sy5cvl++++05mzJgha9eulXbt2tnd97p166ROnTpSqVIl+fnnn6Vfv34yZMiQRD4jIiIioufLw2QymSSZMw7Rw8PDvGzVqlXSokULuXfvnqRPn17Onz8vhQsXlo0bN0rDhg11my1btkj9+vXl5MmTWvMXXVhYmOTL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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "def plot_network(db):\n", " # shaded catchments, with the gauges on top\n", " fig, ax = plt.subplots(figsize=(8, 3))\n", " db.get_catchments().plot(ax=ax, facecolor=\"tab:blue\", edgecolor=\"tab:blue\", alpha=0.15)\n", " sites = db.get_features()\n", " sites.plot(ax=ax, color=\"black\", markersize=20, zorder=3)\n", " for site in sites.itertuples():\n", " ax.annotate(site.name, (site.geometry.x, site.geometry.y), xytext=(0, 6), textcoords=\"offset points\", ha=\"center\", fontsize=8)\n", " ax.set_xlim(-53.7, -50.3)\n", " ax.set_ylim(-30.6, -29.4)\n", " plt.tight_layout()\n", " plt.show()\n", "\n", "\n", "plot_network(db=db)" ] }, { "cell_type": "markdown", "id": "2c49af64", "metadata": {}, "source": [ "## Adding catchments later\n", "\n", "The `.append_catchment()` method adds one catchment to one site, and keeps the geometry apart from the attributes. The geometry always comes from a spatial file. Attributes may come from that file or from a dict, and anything left out stays empty.\n", "\n", "### A drawn polygon\n", "\n", "Someone draws the catchment of `R004` in a GIS and saves it. The file holds one feature, with no code and no area, so the site is given by `name`, and the area by value. The polygon comes in another CRS here, and is reprojected to the layer:" ] }, { "cell_type": "code", "execution_count": 7, "id": "2eccdc4f", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.388917Z", "iopub.status.busy": "2026-11-09T13:35:46.388757Z", "iopub.status.idle": "2026-11-09T13:35:46.445348Z", "shell.execute_reply": "2026-11-09T13:35:46.444892Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamearea_km2
04R00417110.0
\n", "
" ], "text/plain": [ " id name area_km2\n", "0 4 R004 17110.0" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "drawn = make_catchment(code=\"R004\").to_crs(CRS_METRIC)\n", "area_drawn = float(drawn[\"Area\"].iloc[0])\n", "file_drawn = OUTPUT_DIR / \"drawn.gpkg\"\n", "drawn[[\"geometry\"]].to_file(filename=file_drawn, driver=\"GPKG\")\n", "\n", "db.append_catchment(name=\"R004\", file=file_drawn, attributes={\"area_km2\": area_drawn})\n", "db.get_catchments(names=\"R004\").drop(columns=\"geometry\")" ] }, { "cell_type": "markdown", "id": "18fa9bd2", "metadata": {}, "source": [ "### One feature of a larger file\n", "\n", "Later, a national dataset with many catchments comes along. With `name` in the `columns_map`, that source column picks the feature. The method first reads only that column to find the row, and then reads only that row, so the rest of the file is never loaded. No match, or more than one, is an error:" ] }, { "cell_type": "code", "execution_count": 8, "id": "885a655f", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.448722Z", "iopub.status.busy": "2026-11-09T13:35:46.448505Z", "iopub.status.idle": "2026-11-09T13:35:46.515274Z", "shell.execute_reply": "2026-11-09T13:35:46.514835Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamearea_km2
05R00521385.0
\n", "
" ], "text/plain": [ " id name area_km2\n", "0 5 R005 21385.0" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "national = pd.concat([make_catchment(code=c) for c in [\"R005\", \"R006\", \"R903\", \"R904\"]], ignore_index=True)\n", "file_national = OUTPUT_DIR / \"national.gpkg\"\n", "national.to_file(filename=file_national, layer=\"all\", driver=\"GPKG\")\n", "\n", "db.append_catchment(\n", " name=\"R005\",\n", " file=file_national,\n", " layer=\"all\",\n", " columns_map={\"name\": \"Code\", \"area_km2\": \"Area\"},\n", ")\n", "db.get_catchments(names=\"R005\").drop(columns=\"geometry\")" ] }, { "cell_type": "markdown", "id": "c006bdb6", "metadata": {}, "source": [ "### Generated in Python\n", "\n", "A catchment generated by a script is already a `GeoDataFrame`, so `.insert_catchments()` takes it directly. It also takes many at once, which suits bulk loads better than repeated appends, and skips features naming no site, as at creation:" ] }, { "cell_type": "code", "execution_count": 9, "id": "50234141", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.518100Z", "iopub.status.busy": "2026-11-09T13:35:46.517893Z", "iopub.status.idle": "2026-11-09T13:35:46.705312Z", "shell.execute_reply": "2026-11-09T13:35:46.704774Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Missing: []\n" ] }, { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "generated = make_catchment(code=\"R006\")\n", "db.insert_catchments(gdf=generated, columns_map={\"name\": \"Code\", \"area_km2\": \"Area\"})\n", "print(\"Missing:\", db.missing_catchments())\n", "plot_network(db=db)" ] }, { "cell_type": "markdown", "id": "813c5f86", "metadata": {}, "source": [ "### What is refused\n", "\n", "Catchments without a site and second catchments for a site are refused. These rules also live in the file itself, as a unique index and a trigger, so they hold for any tool writing through GDAL:" ] }, { "cell_type": "code", "execution_count": 10, "id": "0d294594", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.710615Z", "iopub.status.busy": "2026-11-09T13:35:46.710382Z", "iopub.status.idle": "2026-11-09T13:35:46.714922Z", "shell.execute_reply": "2026-11-09T13:35:46.713906Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Name 'R999' not in the sites layer\n", "Site 'R001' already has a catchment\n" ] } ], "source": [ "for name in [\"R999\", \"R001\"]:\n", " try:\n", " db.append_catchment(name=name, file=file_drawn)\n", " except ValueError as error:\n", " print(error)" ] }, { "cell_type": "markdown", "id": "b42fc1cc", "metadata": {}, "source": [ "### Replacing and new attributes\n", "\n", "A better delineation replaces the old one with `overwrite=True`. By default, the layer keeps the columns it was created with, so an unknown attribute is refused, and a typo does not become a column. With `add_columns=True`, the column is added, typed from its value, and the other rows stay empty:" ] }, { "cell_type": "code", "execution_count": 11, "id": "8564d76c", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.717475Z", "iopub.status.busy": "2026-11-09T13:35:46.717220Z", "iopub.status.idle": "2026-11-09T13:35:46.810698Z", "shell.execute_reply": "2026-11-09T13:35:46.810243Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Column(s) ['delineation'] not in the catchments layer\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamearea_km2delineation
01R0014280.0NaN
12R0028558.0NaN
23R00312834.0NaN
35R00521385.0NaN
46R00625660.0NaN
58R00417110.0manual
\n", "
" ], "text/plain": [ " id name area_km2 delineation\n", "0 1 R001 4280.0 NaN\n", "1 2 R002 8558.0 NaN\n", "2 3 R003 12834.0 NaN\n", "3 5 R005 21385.0 NaN\n", "4 6 R006 25660.0 NaN\n", "5 8 R004 17110.0 manual" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "values = {\"area_km2\": area_drawn, \"delineation\": \"manual\"}\n", "try:\n", " db.append_catchment(name=\"R004\", file=file_drawn, attributes=values, overwrite=True)\n", "except ValueError as error:\n", " print(error)\n", "\n", "db.append_catchment(name=\"R004\", file=file_drawn, attributes=values, overwrite=True, add_columns=True)\n", "db.get_catchments().drop(columns=\"geometry\")" ] }, { "cell_type": "markdown", "id": "f1330d64", "metadata": {}, "source": [ "A replacement is written as a new row, so the catchment takes a new `id`. Nothing depends on it: catchments are linked to sites by `name`, and the `id` of a catchment is not the `id` of its site.\n", "\n", "## Records\n", "\n", "Records always point to the site, by `site_id` or `site_name`, whichever spec they belong to. Rainfall falls over the catchment and streamflow is read at the gauge, but both are loaded the same way:" ] }, { "cell_type": "code", "execution_count": 12, "id": "e999327c", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.814644Z", "iopub.status.busy": "2026-11-09T13:35:46.814172Z", "iopub.status.idle": "2026-11-09T13:35:46.854967Z", "shell.execute_reply": "2026-11-09T13:35:46.854540Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
datetimespec_namevaluesite_id
02016-01-01 00:00:00+00:00Q_MONTHLY153.6248644
12016-02-01 00:00:00+00:00Q_MONTHLY223.2260314
22016-01-01 00:00:00+00:00PPT_MONTHLY_AREAL58.2000844
32016-02-01 00:00:00+00:00PPT_MONTHLY_AREAL84.6527974
\n", "
" ], "text/plain": [ " datetime spec_name value site_id\n", "0 2016-01-01 00:00:00+00:00 Q_MONTHLY 153.624864 4\n", "1 2016-02-01 00:00:00+00:00 Q_MONTHLY 223.226031 4\n", "2 2016-01-01 00:00:00+00:00 PPT_MONTHLY_AREAL 58.200084 4\n", "3 2016-02-01 00:00:00+00:00 PPT_MONTHLY_AREAL 84.652797 4" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "rng = np.random.default_rng(seed=5) # ensure reproducibility\n", "dates = pd.date_range(start=\"2016-01-01\", end=\"2020-12-01\", freq=\"MS\")\n", "season = np.cos(2 * np.pi * (dates.month - 1) / 12)\n", "seconds = dates.days_in_month * 86400\n", "\n", "areas = db.get_catchments().set_index(\"name\")[\"area_km2\"]\n", "rain = 130 * (1 - 0.3 * season) * rng.gamma(shape=6, scale=1 / 6, size=len(dates))\n", "frames_q, frames_p = [], []\n", "for code in codes:\n", " flow = areas[code] * 1e6 * 0.4 * rain / 1000 / seconds\n", " frames_q.append(pd.DataFrame({\"datetime\": dates, \"value\": flow, \"site_name\": code}))\n", " frames_p.append(pd.DataFrame({\"datetime\": dates, \"value\": rain * rng.normal(1, 0.03, len(dates)), \"site_name\": code}))\n", "with db.transaction():\n", " db.insert_records(dataframe=pd.concat(frames_q, ignore_index=True), spec_name=\"Q_MONTHLY\", flag=1)\n", " db.insert_records(dataframe=pd.concat(frames_p, ignore_index=True), spec_name=\"PPT_MONTHLY_AREAL\", flag=1)\n", "db.query_records(query_dict={\"site_name\": \"R004\", \"start\": \"2016-01-01\", \"end\": \"2016-02-01\"})[[\"datetime\", \"spec_name\", \"value\", \"site_id\"]]" ] }, { "cell_type": "markdown", "id": "f8c0701a", "metadata": {}, "source": [ "Joining the three pieces goes through the names. Sites map `site_id` to `name`, and catchments map `name` to their attributes. Dividing the mean flow by the area gives the specific discharge, which is about the same along the river, as this synthetic runoff should be:" ] }, { "cell_type": "code", "execution_count": 13, "id": "1a11b3ec", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.858723Z", "iopub.status.busy": "2026-11-09T13:35:46.858559Z", "iopub.status.idle": "2026-11-09T13:35:46.883470Z", "shell.execute_reply": "2026-11-09T13:35:46.883022Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
idnamegauge_titleq_m3sarea_km2q_ls_km2
01R001Upper80.94280.018.9
12R002Bend161.78558.018.9
23R003Mill242.512834.018.9
34R004Bridge323.317110.018.9
45R005Ferry404.121385.018.9
56R006Mouth484.925660.018.9
\n", "
" ], "text/plain": [ " id name gauge_title q_m3s area_km2 q_ls_km2\n", "0 1 R001 Upper 80.9 4280.0 18.9\n", "1 2 R002 Bend 161.7 8558.0 18.9\n", "2 3 R003 Mill 242.5 12834.0 18.9\n", "3 4 R004 Bridge 323.3 17110.0 18.9\n", "4 5 R005 Ferry 404.1 21385.0 18.9\n", "5 6 R006 Mouth 484.9 25660.0 18.9" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "flow = db.query_records(query_dict={\"spec_name\": \"Q_MONTHLY\"}).groupby(\"site_id\")[\"value\"].mean().rename(\"q_m3s\")\n", "table = (\n", " db.get_features()[[\"id\", \"name\", \"gauge_title\"]]\n", " .merge(flow, left_on=\"id\", right_index=True)\n", " .merge(db.get_catchments()[[\"name\", \"area_km2\"]], on=\"name\", how=\"left\")\n", ")\n", "table[\"q_ls_km2\"] = 1000 * table[\"q_m3s\"] / table[\"area_km2\"]\n", "table.round(1)" ] }, { "cell_type": "markdown", "id": "15f23602", "metadata": {}, "source": [ "The left join keeps every site, so a site still missing its catchment would show an empty area, instead of dropping out of the table.\n", "\n", "The file holds both layers, and both open in QGIS next to the tables of the database:" ] }, { "cell_type": "code", "execution_count": 14, "id": "341c7fa0", "metadata": { "execution": { "iopub.execute_input": "2026-11-09T13:35:46.886995Z", "iopub.status.busy": "2026-11-09T13:35:46.886765Z", "iopub.status.idle": "2026-11-09T13:35:46.896748Z", "shell.execute_reply": "2026-11-09T13:35:46.896311Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namegeometry_type
0sitesPoint
1catchmentsMultiPolygon
2variablesNaN
3flagsNaN
4sourcesNaN
5statisticsNaN
6specsNaN
7recordsNaN
\n", "
" ], "text/plain": [ " name geometry_type\n", "0 sites Point\n", "1 catchments MultiPolygon\n", "2 variables NaN\n", "3 flags NaN\n", "4 sources NaN\n", "5 statistics NaN\n", "6 specs NaN\n", "7 records NaN" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "db.close()\n", "gpd.list_layers(OUTPUT_DIR / \"reaches_db.gpkg\")" ] } ], "metadata": { "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.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }