-
Notifications
You must be signed in to change notification settings - Fork 2
Couple riCouple river junction node to nearest river locations. This tool can reduce a lot of manual configuration time if client has hundred and thousand river junction needed to be configured. #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f158135
d172dc7
c859708
781017d
59ed7c6
3da4d94
6439277
9d28338
578f35c
8e074b2
3718b38
a476f72
4876e0f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -204,6 +204,45 @@ def close(self): | |
| f"Failed to close model database: {self._db_path}.\n{str(e)}" | ||
| ) | ||
|
|
||
| def begin_transaction(self): | ||
| """Begin the data transaction. | ||
|
|
||
| Using a BEGIN/END transaction significantly improves batch commit performance. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> from mikeplus import Database | ||
| >>> db = Database("path/to/model.sqlite") | ||
| >>> db.begin_transaction() | ||
| >>> commit = True | ||
| >>> try: | ||
| >>> db._tables.msm_Node.update({"Diameter": 0.35}).by_muid("Node_1").execute() | ||
| >>> db._tables.msm_Node.update({"Diameter": 0.40}).by_muid("Node_2").execute() | ||
| >>> ... [Update more data] | ||
| >>> except RuntimeError as e: | ||
| >>> print(f"An error occurred: {e}") | ||
| >>> commit = False | ||
| >>> finally: | ||
| >>> db.end_transaction(commit) | ||
| """ | ||
| if not self._is_open: | ||
| raise ValueError("Database is not open") | ||
|
|
||
| self._data_table_container.BeginTransaction() | ||
|
|
||
| def end_transaction(self, commit: bool = True): | ||
| """End the data transaction. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| commit : bool | ||
| true is to commit the data into database, false is to rollback the commit. | ||
| """ | ||
| if not self._is_open: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens in the case where a user opens the database, starts the database, and then calls close() without ending the transaction? I think a sensible default is to auto-commit (i.e. assume transactions will always be applied when calling end_transaction or close, unless the user explicitly wants to discard the changes). It is probably most common that a user will want to commit changes.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ryan-kipawa The database will be locked when the "begin transaction" happens. So "begin transaction" should always go with "end transaction". |
||
| raise ValueError("Database is not open") | ||
|
|
||
| self._data_table_container.EndTransaction(commit) | ||
|
|
||
| def __enter__(self): | ||
| """Context manager entry.""" | ||
| self.open() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| """Package containing spatial analysis for MIKE+ geometries.""" | ||
|
|
||
| import clr | ||
|
|
||
| clr.AddReference("DHI.Amelia.Infrastructure.Interface") | ||
| clr.AddReference("ThinkGeo.Core") | ||
|
|
||
|
|
||
| from .spatial_analysis_util import ( # noqa: E402 | ||
| get_nearest_river_at, | ||
| get_nearest_river_chainage_at, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "get_nearest_river_at", | ||
| "get_nearest_river_chainage_at", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| """Util to do spatial analysis for MIKE+ geometry data.""" | ||
|
|
||
| from ThinkGeo.Core import BaseShape # noqa: E402 | ||
| from ThinkGeo.Core import PointShape # noqa: E402 | ||
| from ThinkGeo.Core import GeographyUnit # noqa: E402 | ||
| from DHI.Amelia.Infrastructure.Interface.UtilityHelper import GeoAPIHelper # noqa: E402 | ||
|
|
||
|
|
||
| def get_nearest_river_chainage_at(database, x: float, y: float, tolorance: float): | ||
| """Get the nearest river chainage location by a give (x,y) location. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| database : Database or DataTables | ||
| x : float | ||
| x coordinate of target search location | ||
| y : float | ||
| y coordinate of target search location | ||
| tolorance : float | ||
| search radiu distance | ||
|
|
||
| Returns | ||
| ------- | ||
| [str, float] | ||
| first value is the river name, second value is the chainage value | ||
|
|
||
| """ | ||
| river_id = database.tables.mrm_Branch._net_table.GetNearestMuid( | ||
| x, y, tolorance, None, None | ||
| ) | ||
| if river_id is not None: | ||
| riverGeom = database.tables.mrm_Branch._net_table.GetGeometry(river_id) | ||
| lineGeom = GeoAPIHelper.GetWKBIGeometry(riverGeom) | ||
| lineShape = BaseShape.CreateShapeFromWellKnownData(lineGeom) | ||
| pointshp = PointShape(x, y) | ||
| locationPnt = lineShape.GetClosestPointTo(pointshp, GeographyUnit.Meter) | ||
| river_name = database.tables.mrm_Branch._net_table.GetName(river_id) | ||
| chainageMngr = database._data_table_container.GetChainageManager( | ||
| "mrm_Branch", river_name | ||
| ) | ||
| chainage = chainageMngr.GetChainageAt(locationPnt, tolorance) | ||
| return [river_name, chainage] | ||
| else: | ||
| return None | ||
|
|
||
|
|
||
| def get_nearest_river_at(database, x: float, y: float, tolorance: float): | ||
| """Get the nearest river name by a give (x,y) location. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| database : Database or DataTables | ||
| x : float | ||
| x coordinate of target search location | ||
| y : float | ||
| y coordinate of target search location | ||
| tolorance : float | ||
| search radiu distance | ||
|
|
||
| Returns | ||
| ------- | ||
| str: | ||
| river name | ||
|
|
||
| """ | ||
| river_id = database.tables.mrm_Branch._net_table.GetNearestMuid( | ||
| x, y, tolorance, None, None | ||
| ) | ||
| river_name = database.tables.mrm_Branch._net_table.GetName(river_id) | ||
| return river_name |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an error in this notebook - it should run cleanly |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| { | ||
| "cells": [ | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "d61696a6", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "# Couple river junction node to nearest river chainage lication\n", | ||
| "\n", | ||
| "This notebook demonstrates how to batch‑configure river chainage locations for river junction nodes that are missing chainage information.\n", | ||
| "The tool helps reduce manual configuration work and is especially useful when you need to configure hundreds or even thousands of river junctions." | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 1, | ||
| "id": "3ea70e7e", | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "data": { | ||
| "text/plain": [ | ||
| "Database<'River_CS.sqlite'>" | ||
| ] | ||
| }, | ||
| "execution_count": 1, | ||
| "metadata": {}, | ||
| "output_type": "execute_result" | ||
| } | ||
| ], | ||
| "source": [ | ||
| "import mikeplus as mp\n", | ||
| "from mikeplus.utilities import get_nearest_river_chainage_at\n", | ||
| "from mikeplus.dotnet import DotNetConverter\n", | ||
| "\n", | ||
| "db = mp.open(\"../tests/testdata/RiverCS/River_CS.sqlite\")\n", | ||
| "db" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "997c968e", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "## Analysis process\n", | ||
| "\n", | ||
| "The process consists of the following steps:\n", | ||
| "\n", | ||
| "1) Identify all river‑junction nodes for which the BranchID field is NULL.\n", | ||
| "2) For each of these nodes, determine the nearest river and calculate the corresponding river chainage.\n", | ||
| "3) Update the node table by assigning the derived river name and chainage values." | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 2, | ||
| "id": "881839e2", | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "name": "stdout", | ||
| "output_type": "stream", | ||
| "text": [ | ||
| "River junction of 'Node_33' has been coupled to 'River' at 755.0359876508826\n", | ||
| "River junction of 'Pump_3_Outlet' has been coupled to 'River' at 735.7046165978604\n" | ||
| ] | ||
| } | ||
| ], | ||
| "source": [ | ||
| "df = (\n", | ||
| " db._tables.msm_Node.select([\"GeomX\", \"GeomY\"])\n", | ||
| " .where(\"TypeNo=6 AND BranchID is NULL\")\n", | ||
| " .execute()\n", | ||
| " )\n", | ||
| "db.begin_transaction()\n", | ||
| "commit = True\n", | ||
| "try:\n", | ||
| " for key, value in df.items():\n", | ||
| " x = value[0]\n", | ||
| " y = value[1]\n", | ||
| " river_chainage = get_nearest_river_chainage_at(\n", | ||
| " db, x, y, 100.0\n", | ||
| " )\n", | ||
| " if river_chainage is not None:\n", | ||
| " river = river_chainage[0]\n", | ||
| " chainage = river_chainage[1]\n", | ||
| " db._tables.msm_Node.update({\"BranchID\": river, \"BranchChainage\": chainage}).by_muid(key).execute()\n", | ||
| " print(\n", | ||
| " f\"River junction of '{key}' has been coupled to '{river}' at {chainage}\"\n", | ||
| " )\n", | ||
| "\n", | ||
| "except RuntimeError as e:\n", | ||
| " print(f\"An error occurred: {e}\")\n", | ||
| " commit = False\n", | ||
| "\n", | ||
| "finally:\n", | ||
| " db.end_transaction(commit)" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "markdown", | ||
| "id": "66a9422c", | ||
| "metadata": {}, | ||
| "source": [ | ||
| "# Close database" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": 3, | ||
| "id": "f31060ad", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "# Close the database when you're done using it.\n", | ||
| "db.close()" | ||
| ] | ||
| } | ||
| ], | ||
| "metadata": { | ||
| "kernelspec": { | ||
| "display_name": "Python 3", | ||
| "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.7" | ||
| } | ||
| }, | ||
| "nbformat": 4, | ||
| "nbformat_minor": 5 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import pytest | ||
| from mikeplus import Database | ||
| from mikeplus.utilities import get_nearest_river_chainage_at | ||
|
|
||
|
|
||
| def test_get_nearest_river_chainage_at(river_junction_couple_db): | ||
| muid = "Node_33" | ||
| db = Database(river_junction_couple_db) | ||
| field_val_get = ( | ||
| db._tables.msm_Node.select(["GeomX", "GeomY"]).by_muid(muid).execute() | ||
| ) | ||
| x = field_val_get[muid][0] | ||
| y = field_val_get[muid][1] | ||
|
|
||
| river_chainage = get_nearest_river_chainage_at(db, x, y, 100.0) | ||
| river = river_chainage[0] | ||
| chainage = river_chainage[1] | ||
| assert river == "River" | ||
| assert chainage == pytest.approx(755.035988, abs=1e-6) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should raise an error instead of silently returning