from typing import Any from psycopg import sql from ..core.database import ( ChangeSet, DatabaseCommand, execute_command, execute_locked_command, g_add_prefix, g_delete_prefix, g_update_prefix, read_all, sql_literal, try_read, ) from ..gis.coordinates import sql_delete_coord, sql_insert_coord, sql_update_coord from .elements import get_all_node_links def get_junction_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, 'x' : {'type': 'float' , 'optional': False , 'readonly': False}, 'y' : {'type': 'float' , 'optional': False , 'readonly': False}, 'elevation' : {'type': 'float' , 'optional': False , 'readonly': False}, 'links' : {'type': 'str_list' , 'optional': False , 'readonly': True } } def get_junction(name: str, id: str) -> dict[str, Any]: j = try_read( name, """ SELECT n.id, j.elevation, ST_X(g.geom) AS x, ST_Y(g.geom) AS y, COALESCE(array_agg(l.id ORDER BY l.id) FILTER (WHERE l.id IS NOT NULL), '{}') AS links FROM network.nodes AS n JOIN network.junctions AS j ON j.node_id = n.id LEFT JOIN gis.node_geometries AS g ON g.node_id = n.id LEFT JOIN network.links AS l ON l.start_node_id = n.id OR l.end_node_id = n.id WHERE n.id = %s GROUP BY n.id, j.elevation, g.geom """, (id,), ) if j == None: return {} d = {} d['id'] = str(j['id']) d['x'] = float(j['x'] or 0.0) d['y'] = float(j['y'] or 0.0) d['elevation'] = float(j['elevation']) d['links'] = list(j['links']) return d # DingZQ, 2025-03-29 def get_all_junctions(name: str) -> list[dict[str, Any]]: rows = read_all( name, """ SELECT id, elevation, x, y FROM gis.junctions ORDER BY id """, ) if rows == None: return [] links_by_node = get_all_node_links(name) result = [] for row in rows: d = {} id = str(row['id']) d['id'] = id d['x'] = float(row['x'] or 0.0) d['y'] = float(row['y'] or 0.0) d['elevation'] = float(row['elevation']) d['links'] = links_by_node.get(id, []) result.append(d) return result class Junction(object): def __init__(self, input: dict[str, Any]) -> None: self.type = 'junction' self.id = str(input['id']) self.x = float(input['x']) self.y = float(input['y']) self.elevation = float(input['elevation']) self.f_type = sql_literal(self.type) self.f_id = sql_literal(self.id) self.f_elevation = sql_literal(self.elevation) def as_dict(self) -> dict[str, Any]: return { 'type': self.type, 'id': self.id, 'x': self.x, 'y': self.y, 'elevation': self.elevation } def _set_junction( name: str, cs: ChangeSet, current: dict[str, Any] | None = None, ) -> DatabaseCommand: raw_new = current if current is not None else get_junction(name, cs.operations[0]['id']) new_dict = cs.operations[0] schema = get_junction_schema(name) for key, value in schema.items(): if key in new_dict and not value['readonly']: raw_new[key] = new_dict[key] new = Junction(raw_new) statement = f"update network.junctions set elevation = {new.f_elevation} where node_id = {new.f_id};" statement += f"\n{sql_update_coord(new.id, new.x, new.y)}" change = g_update_prefix | new.as_dict() return DatabaseCommand(statement, [change]) def set_junction(name: str, cs: ChangeSet) -> ChangeSet: operation = cs.operations[0] if 'id' not in operation: return ChangeSet() def build_command() -> DatabaseCommand | None: current = get_junction(name, operation['id']) return None if current == {} else _set_junction(name, cs, current) return execute_locked_command(name, build_command) def _add_junction(name: str, cs: ChangeSet) -> DatabaseCommand: new = Junction(cs.operations[0]) statement = f"insert into network.nodes (id, node_type) values ({new.f_id}, {new.f_type});" statement += f"\ninsert into network.junctions (node_id, elevation) values ({new.f_id}, {new.f_elevation});" statement += f"\n{sql_insert_coord(new.id, new.x, new.y)}" change = g_add_prefix | new.as_dict() return DatabaseCommand(statement, [change]) def add_junction(name: str, cs: ChangeSet) -> ChangeSet: if 'id' not in cs.operations[0]: return ChangeSet() if get_junction(name, cs.operations[0]['id']) != {}: return ChangeSet() return execute_command(name, _add_junction(name, cs)) def _delete_junction(name: str, cs: ChangeSet) -> DatabaseCommand: element_id = str(cs.operations[0]['id']) f_id = sql_literal(element_id) statement = sql_delete_coord(element_id) statement += f"\ndelete from network.nodes where id = {f_id};" change = g_delete_prefix | {'type': 'junction', 'id': element_id} return DatabaseCommand(statement, [change]) def delete_junction(name: str, cs: ChangeSet) -> ChangeSet: if 'id' not in cs.operations[0]: return ChangeSet() if get_junction(name, cs.operations[0]['id']) == {}: return ChangeSet() return execute_command(name, _delete_junction(name, cs)) #-------------------------------------------------------------- # [EPA2] # [IN] # id elev. (demand) (demand pattern) ;desc # [OUT] # id elev. ;desc #-------------------------------------------------------------- # [EPA3] # [IN] # id elev. (demand) (demand pattern) # [OUT] # id elev. * * minpressure fullpressure #-------------------------------------------------------------- def inp_in_junction(line: str, demand_outside: bool) -> str: tokens = line.split() num = len(tokens) has_desc = tokens[-1].startswith(';') num_without_desc = (num - 1) if has_desc else num id = str(tokens[0]) elevation = float(tokens[1]) demand = float(tokens[2]) if num_without_desc >= 3 and tokens[2] != '*' else None pattern = str(tokens[3]) if num_without_desc >= 4 and tokens[3] != '*' else None desc = str(tokens[-1]) if has_desc else None sql = f"insert into network.nodes (id, node_type) values ({sql_literal(id)}, 'junction');insert into network.junctions (node_id, elevation) values ({sql_literal(id)}, {sql_literal(elevation)});" if demand != None and demand_outside == False: sql += f"insert into network.demands (junction_id, sequence_no, base_demand, pattern_id) values ({sql_literal(id)}, 0, {sql_literal(demand)}, {sql_literal(pattern)});" return str(sql) def inp_out_junction(name: str) -> list[str]: lines = [] objs = read_all(name, 'select node_id as id, elevation from network.junctions order by node_id') for obj in objs: id = obj['id'] elev = obj['elevation'] desc = ';' lines.append(f'{id} {elev} {desc}') return lines