192 lines
4.9 KiB
Python
192 lines
4.9 KiB
Python
"""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"])
|