refactor(db)!: finalize pooled WNDB v2 migration
This commit is contained in:
@@ -1,10 +1,5 @@
|
||||
from psycopg.rows import dict_row
|
||||
from ..core.database import read_all, sql_literal, try_read
|
||||
|
||||
from ..core.connection import project_connection
|
||||
from ..core.database import read_all, sql_literal, try_read, write
|
||||
from ..core.connection import project_connection
|
||||
from ..model.elements import get_link_nodes
|
||||
from psycopg.rows import dict_row
|
||||
|
||||
def sql_update_coord(node: str, x: float, y: float) -> str:
|
||||
geom = f"st_setsrid(st_makepoint({sql_literal(x)}, {sql_literal(y)}), 900914)"
|
||||
@@ -21,8 +16,8 @@ def sql_delete_coord(node: str) -> str:
|
||||
|
||||
|
||||
def from_postgis_point(coord: str) -> dict[str, float]:
|
||||
xy = coord.lower().removeprefix('point(').removesuffix(')').split(' ')
|
||||
return { 'x': float(xy[0]), 'y': float(xy[1]) }
|
||||
xy = coord.lower().removeprefix("point(").removesuffix(")").split(" ")
|
||||
return {"x": float(xy[0]), "y": float(xy[1])}
|
||||
|
||||
|
||||
def get_node_coord(name: str, node: str) -> dict[str, float]:
|
||||
@@ -31,51 +26,15 @@ def get_node_coord(name: str, node: str) -> dict[str, float]:
|
||||
"select st_astext(geom) as coord_geom from gis.node_geometries where node_id = %s",
|
||||
(node,),
|
||||
)
|
||||
if row == None:
|
||||
write(name, sql_insert_coord(node, 0.0, 0.0))
|
||||
return {'x': 0.0, 'y': 0.0}
|
||||
return from_postgis_point(row['coord_geom'])
|
||||
|
||||
# DingZQ 2025-01-03, get nodes in extent
|
||||
# return node id list
|
||||
# node_id:junction:x:y
|
||||
def get_nodes_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -> list[str]:
|
||||
nodes = []
|
||||
objs = read_all(name, 'select node_id, st_astext(geom) as coord_geom from gis.node_geometries')
|
||||
for obj in objs:
|
||||
node_id = obj['node_id']
|
||||
coord = from_postgis_point(obj['coord_geom'])
|
||||
x = coord['x']
|
||||
y = coord['y']
|
||||
if x1 <= x <= x2 and y1 <= y <= y2:
|
||||
nodes.append(f"{node_id}:junction:{x}:{y}")
|
||||
return nodes
|
||||
|
||||
# DingZQ 2025-01-03, get links in extent
|
||||
# return link id list
|
||||
# link_id:pipe:node_id1:node_id2
|
||||
def get_links_in_extent(name: str, x1: float, y1: float, x2: float, y2: float) -> list[str]:
|
||||
node_ids = set([s.split(':')[0] for s in get_nodes_in_extent(name, x1, y1, x2, y2)])
|
||||
|
||||
all_link_ids = []
|
||||
with project_connection(name) as conn:
|
||||
with conn.cursor(row_factory=dict_row) as cur:
|
||||
cur.execute("select link_id from network.pipes")
|
||||
for record in cur:
|
||||
all_link_ids.append(record['link_id'])
|
||||
|
||||
links = []
|
||||
for link_id in all_link_ids:
|
||||
nodes = get_link_nodes(name, link_id)
|
||||
if nodes[0] in node_ids and nodes[1] in node_ids:
|
||||
links.append(f"{link_id}:pipe:{nodes[0]}:{nodes[1]}")
|
||||
return links
|
||||
if row is None:
|
||||
return {"x": 0.0, "y": 0.0}
|
||||
return from_postgis_point(row["coord_geom"])
|
||||
|
||||
|
||||
def node_has_coord(name: str, node: str) -> bool:
|
||||
return try_read(
|
||||
name, "select node_id from gis.node_geometries where node_id = %s", (node,)
|
||||
) != None
|
||||
) is not None
|
||||
|
||||
|
||||
#--------------------------------------------------------------
|
||||
@@ -94,11 +53,14 @@ def inp_in_coord(line: str) -> str:
|
||||
|
||||
def inp_out_coord(name: str) -> list[str]:
|
||||
lines = []
|
||||
objs = read_all(name, 'select node_id, st_astext(geom) as coord_geom from gis.node_geometries')
|
||||
objs = read_all(
|
||||
name,
|
||||
"select node_id, st_astext(geom) as coord_geom from gis.node_geometries",
|
||||
)
|
||||
for obj in objs:
|
||||
node = obj['node_id']
|
||||
coord = from_postgis_point(obj['coord_geom'])
|
||||
x = coord['x']
|
||||
y = coord['y']
|
||||
lines.append(f'{node} {x} {y}')
|
||||
node = obj["node_id"]
|
||||
coord = from_postgis_point(obj["coord_geom"])
|
||||
x = coord["x"]
|
||||
y = coord["y"]
|
||||
lines.append(f"{node} {x} {y}")
|
||||
return lines
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
"""Read-only projections backed by the GIS materialized-view query layer."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ..core.database import read, read_all
|
||||
|
||||
|
||||
def _node_coord_rows(
|
||||
rows: list[Any],
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
return {
|
||||
str(row["id"]): {
|
||||
"x": float(row["x"]),
|
||||
"y": float(row["y"]),
|
||||
"type": str(row["node_type"]),
|
||||
}
|
||||
for row in rows
|
||||
}
|
||||
|
||||
|
||||
def get_network_node_coords(name: str) -> dict[str, dict[str, Any]]:
|
||||
"""Return every publishable node with one view-backed query."""
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT id, x, y, node_type
|
||||
FROM gis.network_nodes
|
||||
ORDER BY id
|
||||
""",
|
||||
)
|
||||
return _node_coord_rows(rows)
|
||||
|
||||
|
||||
def get_major_node_coords(
|
||||
name: str, diameter: int
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Return endpoints of pipes above the requested diameter."""
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT n.id, n.x, n.y, n.node_type
|
||||
FROM gis.pipes AS p
|
||||
CROSS JOIN LATERAL (
|
||||
VALUES (p.start_node_id), (p.end_node_id)
|
||||
) AS endpoint(id)
|
||||
JOIN gis.network_nodes AS n ON n.id = endpoint.id
|
||||
WHERE p.diameter > %s
|
||||
GROUP BY n.id, n.x, n.y, n.node_type
|
||||
ORDER BY n.id
|
||||
""",
|
||||
(diameter,),
|
||||
)
|
||||
return _node_coord_rows(rows)
|
||||
|
||||
|
||||
def get_network_link_nodes(name: str) -> list[str]:
|
||||
"""Return every publishable link in the established API wire format."""
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT id, link_type, start_node_id, end_node_id
|
||||
FROM gis.network_links
|
||||
ORDER BY id
|
||||
""",
|
||||
)
|
||||
return [
|
||||
f"{row['id']}:{row['link_type']}:{row['start_node_id']}:{row['end_node_id']}"
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
def get_major_pipe_nodes(name: str, diameter: int) -> list[str]:
|
||||
"""Return large pipes in the established API wire format."""
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT id, start_node_id, end_node_id
|
||||
FROM gis.pipes
|
||||
WHERE diameter > %s
|
||||
ORDER BY id
|
||||
""",
|
||||
(diameter,),
|
||||
)
|
||||
return [
|
||||
f"{row['id']}:pipe:{row['start_node_id']}:{row['end_node_id']}"
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
def get_topology_rows(
|
||||
name: str, node_ids: list[str]
|
||||
) -> tuple[list[Any], list[Any]]:
|
||||
"""Load selected nodes and their internal links with two batch queries."""
|
||||
if not node_ids:
|
||||
return [], []
|
||||
|
||||
nodes = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT n.id,
|
||||
ST_X(g.geom) AS x,
|
||||
ST_Y(g.geom) AS y,
|
||||
n.node_type::text AS node_type
|
||||
FROM network.nodes AS n
|
||||
JOIN gis.node_geometries AS g ON g.node_id = n.id
|
||||
WHERE n.id = ANY(%s)
|
||||
ORDER BY n.id
|
||||
""",
|
||||
(node_ids,),
|
||||
)
|
||||
links = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT l.id,
|
||||
l.start_node_id,
|
||||
l.end_node_id,
|
||||
COALESCE(p.length, 0.0) AS length
|
||||
FROM network.links AS l
|
||||
LEFT JOIN network.pipes AS p ON p.link_id = l.id
|
||||
WHERE l.start_node_id = ANY(%s)
|
||||
AND l.end_node_id = ANY(%s)
|
||||
ORDER BY l.id
|
||||
""",
|
||||
(node_ids, node_ids),
|
||||
)
|
||||
return nodes, links
|
||||
|
||||
|
||||
def get_boundary_link_ids(name: str, node_ids: list[str]) -> list[str]:
|
||||
"""Return links with exactly one endpoint inside the supplied node set."""
|
||||
if not node_ids:
|
||||
return []
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT id
|
||||
FROM network.links
|
||||
WHERE (start_node_id = ANY(%s)) <> (end_node_id = ANY(%s))
|
||||
ORDER BY id
|
||||
""",
|
||||
(node_ids, node_ids),
|
||||
)
|
||||
return [str(row["id"]) for row in rows]
|
||||
|
||||
|
||||
def get_junction_demands(
|
||||
name: str, node_ids: list[str]
|
||||
) -> dict[str, list[dict[str, Any]]]:
|
||||
"""Read authoritative demands for selected junctions from model tables."""
|
||||
if not node_ids:
|
||||
return {}
|
||||
rows = read_all(
|
||||
name,
|
||||
"""
|
||||
SELECT junction_id,
|
||||
sequence_no,
|
||||
base_demand,
|
||||
pattern_id,
|
||||
category
|
||||
FROM network.demands
|
||||
WHERE junction_id = ANY(%s)
|
||||
ORDER BY junction_id, sequence_no
|
||||
""",
|
||||
(node_ids,),
|
||||
)
|
||||
result: dict[str, list[dict[str, Any]]] = {}
|
||||
for row in rows:
|
||||
result.setdefault(str(row["junction_id"]), []).append(
|
||||
{
|
||||
"demand": float(row["base_demand"]),
|
||||
"pattern": row["pattern_id"],
|
||||
"category": row["category"],
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def sum_junction_base_demand(name: str, node_ids: list[str]) -> float:
|
||||
"""Sum selected junction demand from the authoritative model table."""
|
||||
if not node_ids:
|
||||
return 0.0
|
||||
row = read(
|
||||
name,
|
||||
"""
|
||||
SELECT COALESCE(SUM(base_demand), 0.0) AS total_base_demand
|
||||
FROM network.demands
|
||||
WHERE junction_id = ANY(%s)
|
||||
""",
|
||||
(node_ids,),
|
||||
)
|
||||
return float(row["total_base_demand"])
|
||||
@@ -2,10 +2,8 @@ import platform
|
||||
import math
|
||||
from typing import Any
|
||||
import pyclipper
|
||||
from ..model.elements import get_node_links, get_link_nodes, is_pipe
|
||||
from ..model.pipes import get_pipe
|
||||
from ..core.database import read, try_read, read_all
|
||||
from .coordinates import node_has_coord, get_node_coord
|
||||
from .network_views import get_boundary_link_ids, get_topology_rows
|
||||
|
||||
|
||||
def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]:
|
||||
@@ -42,21 +40,7 @@ def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> lis
|
||||
|
||||
|
||||
def _get_links_on_boundary(name: str, nodes: list[str]) -> list[str]:
|
||||
links: list[str] = []
|
||||
|
||||
for node in nodes:
|
||||
node_links = get_node_links(name, node)
|
||||
for link in node_links:
|
||||
if link in links:
|
||||
continue
|
||||
|
||||
link_nodes = get_link_nodes(name, link)
|
||||
if link_nodes[0] in nodes and link_nodes[1] not in nodes:
|
||||
links.append(link)
|
||||
elif link_nodes[0] not in nodes and link_nodes[1] in nodes:
|
||||
links.append(link)
|
||||
|
||||
return links
|
||||
return get_boundary_link_ids(name, nodes)
|
||||
|
||||
|
||||
def get_nodes_in_region(name: str, region_id: str) -> list[str]:
|
||||
@@ -128,37 +112,39 @@ def _angle_of_node_link(node: str, link: str, nodes, links) -> float:
|
||||
|
||||
class Topology:
|
||||
def __init__(self, db: str, nodes: list[str]) -> None:
|
||||
self._nodes: dict[str, Any] = {}
|
||||
self._max_x_node = ''
|
||||
self._node_list: list[str] = []
|
||||
for node in nodes:
|
||||
if not node_has_coord(db, node):
|
||||
continue
|
||||
if get_node_links(db, node) == 0:
|
||||
continue
|
||||
self._nodes[node] = get_node_coord(db, node) | { 'links': [] }
|
||||
self._node_list.append(node)
|
||||
if self._max_x_node == '' or self._nodes[node]['x'] > self._nodes[self._max_x_node]['x']:
|
||||
self._max_x_node = node
|
||||
node_rows, link_rows = get_topology_rows(db, nodes)
|
||||
|
||||
self._nodes: dict[str, Any] = {
|
||||
str(row["id"]): {
|
||||
"x": float(row["x"]),
|
||||
"y": float(row["y"]),
|
||||
"type": str(row["node_type"]),
|
||||
"links": [],
|
||||
}
|
||||
for row in node_rows
|
||||
}
|
||||
self._node_list = list(self._nodes)
|
||||
self._max_x_node = max(
|
||||
self._nodes,
|
||||
key=lambda node_id: self._nodes[node_id]["x"],
|
||||
default="",
|
||||
)
|
||||
|
||||
self._links: dict[str, Any] = {}
|
||||
self._link_list: list[str] = []
|
||||
for node in self._nodes:
|
||||
for link in get_node_links(db, node):
|
||||
candidate = True
|
||||
link_nodes = get_link_nodes(db, link)
|
||||
for link_node in link_nodes:
|
||||
if link_node not in self._nodes:
|
||||
candidate = False
|
||||
break
|
||||
if candidate:
|
||||
length = get_pipe(db, link)['length'] if is_pipe(db, link) else 0.0
|
||||
self._links[link] = { 'node1' : link_nodes[0], 'node2' : link_nodes[1], 'length' : length }
|
||||
self._link_list.append(link)
|
||||
if link not in self._nodes[link_nodes[0]]['links']:
|
||||
self._nodes[link_nodes[0]]['links'].append(link)
|
||||
if link not in self._nodes[link_nodes[1]]['links']:
|
||||
self._nodes[link_nodes[1]]['links'].append(link)
|
||||
for row in link_rows:
|
||||
link_id = str(row["id"])
|
||||
node1 = str(row["start_node_id"])
|
||||
node2 = str(row["end_node_id"])
|
||||
if node1 not in self._nodes or node2 not in self._nodes:
|
||||
continue
|
||||
self._links[link_id] = {
|
||||
"node1": node1,
|
||||
"node2": node2,
|
||||
"length": float(row["length"]),
|
||||
}
|
||||
self._nodes[node1]["links"].append(link_id)
|
||||
self._nodes[node2]["links"].append(link_id)
|
||||
self._link_list = list(self._links)
|
||||
|
||||
def nodes(self):
|
||||
return self._nodes
|
||||
|
||||
Reference in New Issue
Block a user