diff --git a/api/CClipper2.dll b/api/CClipper2.dll new file mode 100644 index 0000000..d21c606 Binary files /dev/null and b/api/CClipper2.dll differ diff --git a/api/CMetis.dll b/api/CMetis.dll new file mode 100644 index 0000000..3385f4c Binary files /dev/null and b/api/CMetis.dll differ diff --git a/api/__init__.py b/api/__init__.py index d0ff572..4fa3293 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -18,8 +18,9 @@ from .database import get_operation_by_snapshot, get_snapshot_by_operation from .database import pick_snapshot from .database import pick_operation, sync_with_server from .database import get_restore_operation, set_restore_operation, set_restore_operation_to_current, restore +from .database import read, try_read, read_all, write -from .batch_cmds import execute_batch_commands, execute_batch_command +from .batch_exe import execute_batch_commands, execute_batch_command from .s0_base import JUNCTION, RESERVOIR, TANK, PIPE, PUMP, VALVE, PATTERN, CURVE from .s0_base import is_node, is_junction, is_reservoir, is_tank @@ -32,25 +33,25 @@ from .s0_base import get_node_links from .s1_title import get_title_schema, get_title, set_title from .s2_junctions import get_junction_schema, add_junction, get_junction, set_junction -from .del_cmd import delete_junction_cascade +from .batch_api import delete_junction_cascade from .s3_reservoirs import get_reservoir_schema, add_reservoir, get_reservoir, set_reservoir -from .del_cmd import delete_reservoir_cascade +from .batch_api import delete_reservoir_cascade from .s4_tanks import OVERFLOW_YES, OVERFLOW_NO from .s4_tanks import get_tank_schema, add_tank, get_tank, set_tank -from .del_cmd import delete_tank_cascade +from .batch_api import delete_tank_cascade from .s5_pipes import PIPE_STATUS_OPEN, PIPE_STATUS_CLOSED, PIPE_STATUS_CV from .s5_pipes import get_pipe_schema, add_pipe, get_pipe, set_pipe -from .del_cmd import delete_pipe_cascade +from .batch_api import delete_pipe_cascade from .s6_pumps import get_pump_schema, add_pump, get_pump, set_pump -from .del_cmd import delete_pump_cascade +from .batch_api import delete_pump_cascade from .s7_valves import VALVES_TYPE_PRV, VALVES_TYPE_PSV, VALVES_TYPE_PBV, VALVES_TYPE_FCV, VALVES_TYPE_TCV, VALVES_TYPE_GPV from .s7_valves import get_valve_schema, add_valve, get_valve, set_valve -from .del_cmd import delete_valve_cascade +from .batch_api import delete_valve_cascade from .s8_tags import TAG_TYPE_NODE, TAG_TYPE_LINK from .s8_tags import get_tag_schema, get_tags, get_tag, set_tag @@ -61,11 +62,11 @@ from .s10_status import LINK_STATUS_OPEN, LINK_STATUS_CLOSED, LINK_STATUS_ACTIVE from .s10_status import get_status_schema, get_status, set_status from .s11_patterns import get_pattern_schema, get_pattern, set_pattern, add_pattern -from .del_cmd import delete_pattern_cascade +from .batch_api import delete_pattern_cascade from .s12_curves import CURVE_TYPE_PUMP, CURVE_TYPE_EFFICIENCY, CURVE_TYPE_VOLUME, CURVE_TYPE_HEADLOSS from .s12_curves import get_curve_schema, get_curve, set_curve, add_curve -from .del_cmd import delete_curve_cascade +from .batch_api import delete_curve_cascade from .s13_controls import get_control_schema, get_control, set_control @@ -98,7 +99,7 @@ from .s23_options_util import OPTION_UNBALANCED_STOP, OPTION_UNBALANCED_CONTINUE from .s23_options_util import OPTION_DEMAND_MODEL_DDA, OPTION_DEMAND_MODEL_PDA from .s23_options_util import OPTION_QUALITY_NONE, OPTION_QUALITY_CHEMICAL, OPTION_QUALITY_AGE, OPTION_QUALITY_TRACE from .s23_options_util import get_option_schema, get_option -from .s23_options import set_option +from .batch_api import set_option_ex from .s23_options_util import OPTION_V3_FLOW_UNITS_CFS, OPTION_V3_FLOW_UNITS_GPM, OPTION_V3_FLOW_UNITS_MGD, OPTION_V3_FLOW_UNITS_IMGD, OPTION_V3_FLOW_UNITS_AFD, OPTION_V3_FLOW_UNITS_LPS, OPTION_V3_FLOW_UNITS_LPM, OPTION_V3_FLOW_UNITS_MLD, OPTION_V3_FLOW_UNITS_CMH, OPTION_V3_FLOW_UNITS_CMD from .s23_options_util import OPTION_V3_PRESSURE_UNITS_PSI, OPTION_V3_PRESSURE_UNITS_KPA, OPTION_V3_PRESSURE_UNITS_M @@ -110,7 +111,7 @@ from .s23_options_util import OPTION_V3_LEAKAGE_MODEL_NONE, OPTION_V3_LEAKAGE_MO from .s23_options_util import OPTION_V3_QUALITY_MODEL_NONE, OPTION_V3_QUALITY_MODEL_CHEMICAL, OPTION_V3_QUALITY_MODEL_AGE, OPTION_V3_QUALITY_MODEL_TRACE from .s23_options_util import OPTION_V3_QUALITY_UNITS_HRS, OPTION_V3_QUALITY_UNITS_PCNT, OPTION_V3_QUALITY_UNITS_MGL, OPTION_V3_QUALITY_UNITS_UGL from .s23_options_util import get_option_v3_schema, get_option_v3 -from .s23_options_v3 import set_option_v3 +from .batch_api import set_option_v3_ex from .s24_coordinates import get_node_coord @@ -122,12 +123,26 @@ from .s27_backdrop import get_backdrop_schema, get_backdrop, set_backdrop from .s29_scada_device import SCADA_DEVICE_TYPE_PRESSURE, SCADA_DEVICE_TYPE_DEMAND, SCADA_DEVICE_TYPE_QUALITY, SCADA_DEVICE_TYPE_LEVEL, SCADA_DEVICE_TYPE_FLOW from .s29_scada_device import get_scada_device_schema, get_scada_devices, get_scada_device, set_scada_device, add_scada_device, delete_scada_device -from .del_cmd import clean_scada_device +from .clean_api import clean_scada_device from .s30_scada_device_data import get_scada_device_data_schema, get_scada_device_data, set_scada_device_data, add_scada_device_data, delete_scada_device_data -from .del_cmd import clean_scada_device_data +from .clean_api import clean_scada_device_data from .s31_scada_element import SCADA_MODEL_TYPE_JUNCTION, SCADA_MODEL_TYPE_RESERVOIR, SCADA_MODEL_TYPE_TANK, SCADA_MODEL_TYPE_PIPE, SCADA_MODEL_TYPE_PUMP, SCADA_MODEL_TYPE_VALVE from .s31_scada_element import SCADA_ELEMENT_STATUS_OFFLINE, SCADA_ELEMENT_STATUS_ONLINE from .s31_scada_element import get_scada_element_schema, get_scada_elements, get_scada_element, set_scada_element, add_scada_element, delete_scada_element -from .del_cmd import clean_scada_element +from .clean_api import clean_scada_element + +from .s32_region_util import get_nodes_in_boundary, get_nodes_in_region, calculate_convex_hull, calculate_boundary, inflate_boundary, inflate_region + +from .s33_region import get_region_schema, get_region, set_region, add_region, delete_region + +from .s34_water_distribution import DISTRIBUTION_TYPE_ADD, DISTRIBUTION_TYPE_OVERRIDE +from .s34_water_distribution import distribute_demand_to_nodes, distribute_demand_to_region + +from .s35_district_metering_area import PARTITION_TYPE_RB, PARTITION_TYPE_KWAY +from .s35_district_metering_area import calculate_district_metering_area + +from .s36_service_area import calculate_service_area + +from .s37_virtual_district import calculate_virtual_district diff --git a/api/batch_api.py b/api/batch_api.py new file mode 100644 index 0000000..1c47d54 --- /dev/null +++ b/api/batch_api.py @@ -0,0 +1,53 @@ +from .sections import * +from .database import ChangeSet, API_DELETE, API_UPDATE +from .batch_exe import execute_batch_command + + +def delete_junction_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s2_junction } + return execute_batch_command(name, cs) + + +def delete_reservoir_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s3_reservoir } + return execute_batch_command(name, cs) + + +def delete_tank_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s4_tank } + return execute_batch_command(name, cs) + + +def delete_pipe_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s5_pipe } + return execute_batch_command(name, cs) + + +def delete_pump_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s6_pump } + return execute_batch_command(name, cs) + + +def delete_valve_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s7_valve } + return execute_batch_command(name, cs) + + +def delete_pattern_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s11_pattern } + return execute_batch_command(name, cs) + + +def delete_curve_cascade(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_DELETE, 'type' : s12_curve } + return execute_batch_command(name, cs) + + +def set_option_ex(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option } + return execute_batch_command(name, cs) + + +def set_option_v3_ex(name: str, cs: ChangeSet) -> ChangeSet: + cs.operations[0] |= { 'operation' : API_UPDATE, 'type' : s23_option_v3 } + return execute_batch_command(name, cs) diff --git a/api/del_cmd_raw.py b/api/batch_api_cs.py similarity index 60% rename from api/del_cmd_raw.py rename to api/batch_api_cs.py index 1953fda..755a8e4 100644 --- a/api/del_cmd_raw.py +++ b/api/batch_api_cs.py @@ -18,8 +18,10 @@ from .s20_mixing import delete_mixing_by_tank from .s25_vertices import delete_vertex_by_link from .s26_labels import unset_label_by_node +from .s23_options_util import generate_v2, generate_v3 -def delete_junction_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: + +def delete_junction_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -31,11 +33,11 @@ def delete_junction_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: for link in links: if is_pipe(name, link): - result.merge(delete_pipe_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) + result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) if is_pump(name, link): - result.merge(delete_pump_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) + result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) if is_valve(name, link): - result.merge(delete_valve_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) + result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) result.merge(delete_tag_by_node(name, id)) result.merge(delete_demand_by_junction(name, id)) @@ -48,7 +50,7 @@ def delete_junction_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_reservoir_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_reservoir_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -60,11 +62,11 @@ def delete_reservoir_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: for link in links: if is_pipe(name, link): - result.merge(delete_pipe_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) + result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) if is_pump(name, link): - result.merge(delete_pump_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) + result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) if is_valve(name, link): - result.merge(delete_valve_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) + result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) result.merge(delete_tag_by_node(name, id)) result.merge(delete_quality_by_node(name, id)) @@ -75,7 +77,7 @@ def delete_reservoir_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_tank_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_tank_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -87,11 +89,11 @@ def delete_tank_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: for link in links: if is_pipe(name, link): - result.merge(delete_pipe_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) + result.merge(delete_pipe_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pipe', 'id': link}))) if is_pump(name, link): - result.merge(delete_pump_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) + result.merge(delete_pump_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'pump', 'id': link}))) if is_valve(name, link): - result.merge(delete_valve_cascade_batch_cmd(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) + result.merge(delete_valve_cascade_batch_cs(name, ChangeSet(g_delete_prefix | {'type': 'valve', 'id': link}))) result.merge(delete_tag_by_node(name, id)) result.merge(delete_quality_by_node(name, id)) @@ -104,7 +106,7 @@ def delete_tank_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_pipe_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_pipe_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -121,7 +123,7 @@ def delete_pipe_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_pump_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_pump_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -138,7 +140,7 @@ def delete_pump_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_valve_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_valve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -155,7 +157,7 @@ def delete_valve_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_pattern_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_pattern_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -173,7 +175,7 @@ def delete_pattern_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def delete_curve_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: +def delete_curve_cascade_batch_cs(name: str, cs: ChangeSet) -> ChangeSet: result = ChangeSet() id = cs.operations[0]['id'] @@ -189,24 +191,48 @@ def delete_curve_cascade_batch_cmd(name: str, cs: ChangeSet) -> ChangeSet: return result -def del_cascade_cmd(name: str, cs: ChangeSet) -> ChangeSet: - type = cs.operations[0]['type'] +def set_option_cs(cs: ChangeSet) -> ChangeSet: + cs.operations[0]['operation'] = API_UPDATE + cs.operations[0]['type'] = 'option' + new_cs = cs + new_cs.merge(generate_v3(cs)) + return new_cs - if type == s2_junction: - return delete_junction_cascade_batch_cmd(name, cs) - elif type == s3_reservoir: - return delete_reservoir_cascade_batch_cmd(name, cs) - elif type == s4_tank: - return delete_tank_cascade_batch_cmd(name, cs) - elif type == s5_pipe: - return delete_pipe_cascade_batch_cmd(name, cs) - elif type == s6_pump: - return delete_pump_cascade_batch_cmd(name, cs) - elif type == s7_valve: - return delete_valve_cascade_batch_cmd(name, cs) - elif type == s11_pattern: - return delete_pattern_cascade_batch_cmd(name, cs) - elif type == s12_curve: - return delete_curve_cascade_batch_cmd(name, cs) + +def set_option_v3_cs(cs: ChangeSet) -> ChangeSet: + cs.operations[0]['operation'] = API_UPDATE + cs.operations[0]['type'] = 'option_v3' + new_cs = cs + new_cs.merge(generate_v2(cs)) + return new_cs + + +def rewrite_batch_api(name: str, cs: ChangeSet) -> ChangeSet: + op = cs.operations[0] + api = op['operation'] + type = op['type'] + + if api == API_DELETE: + if type == s2_junction: + return delete_junction_cascade_batch_cs(name, cs) + elif type == s3_reservoir: + return delete_reservoir_cascade_batch_cs(name, cs) + elif type == s4_tank: + return delete_tank_cascade_batch_cs(name, cs) + elif type == s5_pipe: + return delete_pipe_cascade_batch_cs(name, cs) + elif type == s6_pump: + return delete_pump_cascade_batch_cs(name, cs) + elif type == s7_valve: + return delete_valve_cascade_batch_cs(name, cs) + elif type == s11_pattern: + return delete_pattern_cascade_batch_cs(name, cs) + elif type == s12_curve: + return delete_curve_cascade_batch_cs(name, cs) + elif api == API_UPDATE: + if type == s23_option: + return set_option_cs(cs) + elif type == s23_option_v3: + return set_option_v3_cs(cs) return cs diff --git a/api/batch_cmd.py b/api/batch_cmd.py deleted file mode 100644 index c6d352f..0000000 --- a/api/batch_cmd.py +++ /dev/null @@ -1,304 +0,0 @@ -from .sections import * -from .database import API_ADD, API_UPDATE, API_DELETE, ChangeSet, DbChangeSet, execute_command -from .s1_title import set_title_cmd -from .s2_junctions import set_junction_cmd, add_junction_cmd, delete_junction_cmd -from .s3_reservoirs import set_reservoir_cmd, add_reservoir_cmd, delete_reservoir_cmd -from .s4_tanks import set_tank_cmd, add_tank_cmd, delete_tank_cmd -from .s5_pipes import set_pipe_cmd, add_pipe_cmd, delete_pipe_cmd -from .s6_pumps import set_pump_cmd, add_pump_cmd, delete_pump_cmd -from .s7_valves import set_valve_cmd, add_valve_cmd, delete_valve_cmd -from .s8_tags import set_tag_cmd -from .s9_demands import set_demand_cmd -from .s10_status import set_status_cmd -from .s11_patterns import set_pattern_cmd, add_pattern_cmd, delete_pattern_cmd -from .s12_curves import set_curve_cmd, add_curve_cmd, delete_curve_cmd -from .s13_controls import set_control_cmd -from .s14_rules import set_rule_cmd -from .s15_energy import set_energy_cmd, set_pump_energy_cmd -from .s16_emitters import set_emitter_cmd -from .s17_quality import set_quality_cmd -from .s18_sources import set_source_cmd, add_source_cmd, delete_source_cmd -from .s19_reactions import set_reaction_cmd, set_pipe_reaction_cmd, set_tank_reaction_cmd -from .s20_mixing import set_mixing_cmd, add_mixing_cmd, delete_mixing_cmd -from .s21_times import set_time_cmd -#from .s22_report import * -from .s23_options_util import set_option_cmd, set_option_v3_cmd -#from .s24_coordinates import * -from .s25_vertices import set_vertex_cmd, add_vertex_cmd, delete_vertex_cmd -from .s26_labels import set_label_cmd, add_label_cmd, delete_label_cmd -from .s27_backdrop import set_backdrop_cmd -# from .s28_end import * -from .s29_scada_device import set_scada_device_cmd, add_scada_device_cmd, delete_scada_device_cmd -from .s30_scada_device_data import set_scada_device_data_cmd, add_scada_device_data_cmd, delete_scada_device_data_cmd -from .s31_scada_element import set_scada_element_cmd, add_scada_element_cmd, delete_scada_element_cmd -from .del_cmd_raw import del_cascade_cmd - - -def add_cmd(name: str, cs: ChangeSet) -> DbChangeSet | None: - type = cs.operations[0]['type'] - - if type == s1_title: - return None - if type == s2_junction: - return add_junction_cmd(name, cs) - elif type == s3_reservoir: - return add_reservoir_cmd(name, cs) - elif type == s4_tank: - return add_tank_cmd(name, cs) - elif type == s5_pipe: - return add_pipe_cmd(name, cs) - elif type == s6_pump: - return add_pump_cmd(name, cs) - elif type == s7_valve: - return add_valve_cmd(name, cs) - elif type == s8_tag: - return None - elif type == s9_demand: - return None - elif type == s10_status: - return None - elif type == s11_pattern: - return add_pattern_cmd(name, cs) - elif type == s12_curve: - return add_curve_cmd(name, cs) - elif type == s13_control: - return None - elif type == s14_rule: - return None - elif type == s15_energy: - return None - elif type == s15_pump_energy: - return None - elif type == s16_emitter: - return None - elif type == s17_quality: - return None - elif type == s18_source: - return add_source_cmd(name, cs) - elif type == s19_reaction: - return None - elif type == s19_pipe_reaction: - return None - elif type == s19_tank_reaction: - return None - elif type == s20_mixing: - return add_mixing_cmd(name, cs) - elif type == s21_time: - return None - elif type == s22_report: - return None - elif type == s23_option: - return None - elif type == s23_option_v3: - return None - elif type == s24_coordinate: - return None - elif type == s25_vertex: - return add_vertex_cmd(name, cs) - elif type == s26_label: - return add_label_cmd(name, cs) - elif type == s27_backdrop: - return None - elif type == s28_end: - return None - elif type == s29_scada_device: - return add_scada_device_cmd(name, cs) - elif type == s30_scada_device_data: - return add_scada_device_data_cmd(name, cs) - elif type == s31_scada_element: - return add_scada_element_cmd(name, cs) - - return None - - -def set_cmd(name: str, cs: ChangeSet) -> DbChangeSet | None: - type = cs.operations[0]['type'] - - if type == s1_title: - return set_title_cmd(name, cs) - if type == s2_junction: - return set_junction_cmd(name, cs) - elif type == s3_reservoir: - return set_reservoir_cmd(name, cs) - elif type == s4_tank: - return set_tank_cmd(name, cs) - elif type == s5_pipe: - return set_pipe_cmd(name, cs) - elif type == s6_pump: - return set_pump_cmd(name, cs) - elif type == s7_valve: - return set_valve_cmd(name, cs) - elif type == s8_tag: - return set_tag_cmd(name, cs) - elif type == s9_demand: - return set_demand_cmd(name, cs) - elif type == s10_status: - return set_status_cmd(name, cs) - elif type == s11_pattern: - return set_pattern_cmd(name, cs) - elif type == s12_curve: - return set_curve_cmd(name, cs) - elif type == s13_control: - return set_control_cmd(name, cs) - elif type == s14_rule: - return set_rule_cmd(name, cs) - elif type == s15_energy: - return set_energy_cmd(name, cs) - elif type == s15_pump_energy: - return set_pump_energy_cmd(name, cs) - elif type == s16_emitter: - return set_emitter_cmd(name, cs) - elif type == s17_quality: - return set_quality_cmd(name, cs) - elif type == s18_source: - return set_source_cmd(name, cs) - elif type == s19_reaction: - return set_reaction_cmd(name, cs) - elif type == s19_pipe_reaction: - return set_pipe_reaction_cmd(name, cs) - elif type == s19_tank_reaction: - return set_tank_reaction_cmd(name, cs) - elif type == s20_mixing: - return set_mixing_cmd(name, cs) - elif type == s21_time: - return set_time_cmd(name, cs) - elif type == s22_report: # no api now - return None - elif type == s23_option: - return set_option_cmd(name, cs) - elif type == s23_option_v3: - return set_option_v3_cmd(name, cs) - elif type == s24_coordinate: # do not support update here - return None - elif type == s25_vertex: - return set_vertex_cmd(name, cs) - elif type == s26_label: - return set_label_cmd(name, cs) - elif type == s27_backdrop: - return set_backdrop_cmd(name, cs) - elif type == s28_end: # end - return None - elif type == s29_scada_device: - return set_scada_device_cmd(name, cs) - elif type == s30_scada_device_data: - return set_scada_device_data_cmd(name, cs) - elif type == s31_scada_element: - return set_scada_element_cmd(name, cs) - - return None - - -def del_cmd(name: str, cs: ChangeSet) -> DbChangeSet | None: - type = cs.operations[0]['type'] - - if type == s1_title: - return None - if type == s2_junction: - return delete_junction_cmd(name, cs) - elif type == s3_reservoir: - return delete_reservoir_cmd(name, cs) - elif type == s4_tank: - return delete_tank_cmd(name, cs) - elif type == s5_pipe: - return delete_pipe_cmd(name, cs) - elif type == s6_pump: - return delete_pump_cmd(name, cs) - elif type == s7_valve: - return delete_valve_cmd(name, cs) - elif type == s8_tag: - return None - elif type == s9_demand: - return None - elif type == s10_status: - return None - elif type == s11_pattern: - return delete_pattern_cmd(name, cs) - elif type == s12_curve: - return delete_curve_cmd(name, cs) - elif type == s13_control: - return None - elif type == s14_rule: - return None - elif type == s15_energy: - return None - elif type == s15_pump_energy: - return None - elif type == s16_emitter: - return None - elif type == s17_quality: - return None - elif type == s18_source: - return delete_source_cmd(name, cs) - elif type == s19_reaction: - return None - elif type == s19_pipe_reaction: - return None - elif type == s19_tank_reaction: - return None - elif type == s20_mixing: - return delete_mixing_cmd(name, cs) - elif type == s21_time: - return None - elif type == s22_report: - return None - elif type == s23_option: - return None - elif type == s23_option_v3: - return None - elif type == s24_coordinate: - return None - elif type == s25_vertex: - return delete_vertex_cmd(name, cs) - elif type == s26_label: - return delete_label_cmd(name, cs) - elif type == s27_backdrop: - return None - elif type == s28_end: - return None - elif type == s29_scada_device: - return delete_scada_device_cmd(name, cs) - elif type == s30_scada_device_data: - return delete_scada_device_data_cmd(name, cs) - elif type == s31_scada_element: - return delete_scada_element_cmd(name, cs) - - return None - - -def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet: - css: list[DbChangeSet] = [] - - # for delete, generate cascade command - new_cs = ChangeSet() - for op in cs.operations: - if op['operation'] == API_DELETE: - new_cs.merge(del_cascade_cmd(name, ChangeSet(op))) - else: - new_cs.merge(ChangeSet(op)) - - try: - for op in new_cs.operations: - operation = op['operation'] - - r = None - - if operation == API_ADD: - r = add_cmd(name, ChangeSet(op)) - elif operation == API_UPDATE: - r = set_cmd(name, ChangeSet(op)) - elif operation == API_DELETE: - r = del_cmd(name, ChangeSet(op)) - - if r == None: - print(f'ERROR: Build [{op}] returns None') - return ChangeSet() - - css.append(r) - - except: - return ChangeSet() - - try: - return execute_command(name, DbChangeSet.from_list(css)) - except: - return ChangeSet() diff --git a/api/batch_cmds.py b/api/batch_exe.py similarity index 90% rename from api/batch_cmds.py rename to api/batch_exe.py index 479d92b..7f28c4e 100644 --- a/api/batch_cmds.py +++ b/api/batch_exe.py @@ -22,21 +22,17 @@ from .s18_sources import set_source, add_source, delete_source from .s19_reactions import set_reaction, set_pipe_reaction, set_tank_reaction from .s20_mixing import set_mixing, add_mixing, delete_mixing from .s21_times import set_time -#from .s22_report import * -from .s23_options import set_option -from .s23_options_v3 import set_option_v3 -#from .s24_coordinates import * +from .s23_options_util import set_option, set_option_v3 from .s25_vertices import set_vertex, add_vertex, delete_vertex from .s26_labels import set_label, add_label, delete_label from .s27_backdrop import set_backdrop -# from .s28_end import * from .s29_scada_device import set_scada_device, add_scada_device, delete_scada_device from .s30_scada_device_data import set_scada_device_data, add_scada_device_data, delete_scada_device_data from .s31_scada_element import set_scada_element, add_scada_element, delete_scada_element -from .del_cmd_raw import del_cascade_cmd +from .batch_api_cs import rewrite_batch_api -def execute_add_command(name: str, cs: ChangeSet) -> ChangeSet: +def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet: type = cs.operations[0]['type'] if type == s1_title: @@ -113,7 +109,7 @@ def execute_add_command(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() -def execute_update_command(name: str, cs: ChangeSet) -> ChangeSet: +def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet: type = cs.operations[0]['type'] if type == s1_title: @@ -190,7 +186,7 @@ def execute_update_command(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() -def execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet: +def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet: type = cs.operations[0]['type'] if type == s1_title: @@ -268,13 +264,9 @@ def execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet: def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet: - # for delete, generate cascade command new_cs = ChangeSet() for op in cs.operations: - if op['operation'] == API_DELETE: - new_cs.merge(del_cascade_cmd(name, ChangeSet(op))) - else: - new_cs.merge(ChangeSet(op)) + new_cs.merge(rewrite_batch_api(name, ChangeSet(op))) result = ChangeSet() @@ -285,11 +277,11 @@ def execute_batch_commands(name: str, cs: ChangeSet) -> ChangeSet: todo = op operation = op['operation'] if operation == API_ADD: - result.merge(execute_add_command(name, ChangeSet(op))) + result.merge(_execute_add_command(name, ChangeSet(op))) elif operation == API_UPDATE: - result.merge(execute_update_command(name, ChangeSet(op))) + result.merge(_execute_update_command(name, ChangeSet(op))) elif operation == API_DELETE: - result.merge(execute_delete_command(name, ChangeSet(op))) + result.merge(_execute_delete_command(name, ChangeSet(op))) except: print(f'ERROR: Fail to execute {todo}') @@ -300,13 +292,9 @@ def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet: write(name, 'delete from batch_operation where id > 0') write(name, "update operation_table set option = 'batch_operation' where option = 'operation'") - # for delete, generate cascade command new_cs = ChangeSet() for op in cs.operations: - if op['operation'] == API_DELETE: - new_cs.merge(del_cascade_cmd(name, ChangeSet(op))) - else: - new_cs.merge(ChangeSet(op)) + new_cs.merge(rewrite_batch_api(name, ChangeSet(op))) result = ChangeSet() @@ -317,11 +305,11 @@ def execute_batch_command(name: str, cs: ChangeSet) -> ChangeSet: todo = op operation = op['operation'] if operation == API_ADD: - result.merge(execute_add_command(name, ChangeSet(op))) + result.merge(_execute_add_command(name, ChangeSet(op))) elif operation == API_UPDATE: - result.merge(execute_update_command(name, ChangeSet(op))) + result.merge(_execute_update_command(name, ChangeSet(op))) elif operation == API_DELETE: - result.merge(execute_delete_command(name, ChangeSet(op))) + result.merge(_execute_delete_command(name, ChangeSet(op))) except: print(f'ERROR: Fail to execute {todo}') diff --git a/api/clean_api.py b/api/clean_api.py new file mode 100644 index 0000000..ba3d7f8 --- /dev/null +++ b/api/clean_api.py @@ -0,0 +1,45 @@ +from .database import ChangeSet, read_all +from .batch_exe import execute_batch_command + +# TODO: merge to batch_api + +def clean_scada_device_cs(name: str) -> ChangeSet: + cs = ChangeSet() + + rows = read_all(name, 'select id from scada_device acs') + for row in rows: + cs.delete({ 'type': 'scada_device', 'id': row['id'] }) + + return cs + + +def clean_scada_device_data_cs(name: str) -> ChangeSet: + cs = ChangeSet() + + rows = read_all(name, 'select distinct device_id from scada_device_data acs') + for row in rows: + cs.update({ 'type': 'scada_device_data', 'device_id': row['device_id'], 'data': [] }) + + return cs + + +def clean_scada_element_cs(name: str) -> ChangeSet: + cs = ChangeSet() + + rows = read_all(name, 'select id from scada_element acs') + for row in rows: + cs.delete({ 'type': 'scada_element', 'id': row['id'] }) + + return cs + + +def clean_scada_device(name: str) -> ChangeSet: + return execute_batch_command(name, clean_scada_device_cs(name)) + + +def clean_scada_device_data(name: str) -> ChangeSet: + return execute_batch_command(name, clean_scada_device_data_cs(name)) + + +def clean_scada_element(name: str) -> ChangeSet: + return execute_batch_command(name, clean_scada_element_cs(name)) diff --git a/api/del_cmd.py b/api/del_cmd.py deleted file mode 100644 index 1d20140..0000000 --- a/api/del_cmd.py +++ /dev/null @@ -1,65 +0,0 @@ -from .del_cmd_raw import * -from .batch_cmd import execute_batch_command -from .s29_scada_device import clean_scada_device_cmd -from .s30_scada_device_data import clean_scada_device_data_cmd -from .s31_scada_element import clean_scada_element_cmd - - -def delete_junction_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'junction' } - #raw_cmd = delete_junction_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_reservoir_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'reservoir' } - #raw_cmd = delete_reservoir_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_tank_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'tank' } - #raw_cmd = delete_tank_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_pipe_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'pipe' } - #raw_cmd = delete_pipe_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_pump_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'pump' } - #raw_cmd = delete_pump_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_valve_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'valve' } - #raw_cmd = delete_valve_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_pattern_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'pattern' } - #raw_cmd = delete_pattern_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def delete_curve_cascade(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0] |= { 'operation' : API_DELETE, 'type' : 'curve' } - #raw_cmd = delete_curve_cascade_batch_cmd(name, cs) - return execute_batch_command(name, cs) - - -def clean_scada_device(name: str) -> ChangeSet: - return execute_batch_command(name, clean_scada_device_cmd(name)) - - -def clean_scada_device_data(name: str) -> ChangeSet: - return execute_batch_command(name, clean_scada_device_data_cmd(name)) - - -def clean_scada_element(name: str) -> ChangeSet: - return execute_batch_command(name, clean_scada_element_cmd(name)) diff --git a/api/inp_out.py b/api/inp_out.py index 25568ca..38cbff2 100644 --- a/api/inp_out.py +++ b/api/inp_out.py @@ -40,7 +40,9 @@ def dump_inp(project: str, inp: str, version: str = '3'): if not have_project(project): return - if not is_project_open(project): + project_open = is_project_open(project) + + if not project_open: open_project(project) dir = os.getcwd() @@ -154,7 +156,8 @@ def dump_inp(project: str, inp: str, version: str = '3'): file.close() - close_project(project) + if not project_open: + close_project(project) def export_inp(project: str, version: str = '3') -> ChangeSet: diff --git a/api/s0_base.py b/api/s0_base.py index 5609f8f..bace826 100644 --- a/api/s0_base.py +++ b/api/s0_base.py @@ -1,5 +1,6 @@ from psycopg.rows import dict_row, Row from .connection import g_conn_dict as conn +from .database import read _NODE = '_node' @@ -105,3 +106,14 @@ def get_node_links(name: str, id: str) -> list[str]: for p in cur.execute(f"select id from valves where node1 = '{id}' or node2 = '{id}'").fetchall(): links.append(p['id']) return links + + +def get_link_nodes(name: str, id: str) -> list[str]: + row = {} + if is_pipe(name, id): + row = read(name, f"select node1, node2 from pipes where id = '{id}'") + elif is_pump(name, id): + row = read(name, f"select node1, node2 from pumps where id = '{id}'") + elif is_valve(name, id): + row = read(name, f"select node1, node2 from valves where id = '{id}'") + return [str(row['node1']), str(row['node2'])] diff --git a/api/s10_status.py b/api/s10_status.py index 9aa61c4..cf07ccb 100644 --- a/api/s10_status.py +++ b/api/s10_status.py @@ -39,7 +39,7 @@ class Status(object): return { 'type': self.type, 'link': self.link, 'status': self.status, 'setting': self.setting } -def set_status_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_status(name: str, cs: ChangeSet) -> DbChangeSet: old = Status(get_status(name, cs.operations[0]['link'])) raw_new = get_status(name, cs.operations[0]['link']) @@ -65,7 +65,7 @@ def set_status_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_status(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_status_cmd(name, cs)) + return execute_command(name, _set_status(name, cs)) #-------------------------------------------------------------- diff --git a/api/s11_patterns.py b/api/s11_patterns.py index 236720c..382d554 100644 --- a/api/s11_patterns.py +++ b/api/s11_patterns.py @@ -21,7 +21,7 @@ def get_pattern(name: str, id: str) -> dict[str, Any]: return { 'id': id, 'factors': ps } -def set_pattern_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_pattern(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -53,10 +53,10 @@ def set_pattern(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pattern(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_pattern_cmd(name, cs)) + return execute_command(name, _set_pattern(name, cs)) -def add_pattern_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_pattern(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -81,10 +81,10 @@ def add_pattern(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pattern(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_pattern_cmd(name, cs)) + return execute_command(name, _add_pattern(name, cs)) -def delete_pattern_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_pattern(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -109,7 +109,7 @@ def delete_pattern(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pattern(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_pattern_cmd(name, cs)) + return execute_command(name, _delete_pattern(name, cs)) #-------------------------------------------------------------- diff --git a/api/s12_curves.py b/api/s12_curves.py index a615564..1ea6456 100644 --- a/api/s12_curves.py +++ b/api/s12_curves.py @@ -30,7 +30,7 @@ def get_curve(name: str, id: str) -> dict[str, Any]: return d -def set_curve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_curve(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -72,10 +72,10 @@ def set_curve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_curve(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_curve_cmd(name, cs)) + return execute_command(name, _set_curve(name, cs)) -def add_curve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_curve(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -104,10 +104,10 @@ def add_curve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_curve(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_curve_cmd(name, cs)) + return execute_command(name, _add_curve(name, cs)) -def delete_curve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_curve(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] f_id = f"'{id}'" @@ -134,7 +134,7 @@ def delete_curve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_curve(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_curve_cmd(name, cs)) + return execute_command(name, _delete_curve(name, cs)) #-------------------------------------------------------------- diff --git a/api/s13_controls.py b/api/s13_controls.py index 8f50de4..b3cf9b7 100644 --- a/api/s13_controls.py +++ b/api/s13_controls.py @@ -13,7 +13,7 @@ def get_control(name: str) -> dict[str, Any]: return { 'controls': ds } -def set_control_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_control(name: str, cs: ChangeSet) -> DbChangeSet: old = get_control(name) redo_sql = 'delete from controls;' @@ -31,7 +31,7 @@ def set_control_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_control(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_control_cmd(name, cs)) + return execute_command(name, _set_control(name, cs)) #-------------------------------------------------------------- diff --git a/api/s14_rules.py b/api/s14_rules.py index 49a47cf..b7f9e9a 100644 --- a/api/s14_rules.py +++ b/api/s14_rules.py @@ -13,7 +13,7 @@ def get_rule(name: str) -> dict[str, Any]: return { 'rules': ds } -def set_rule_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_rule(name: str, cs: ChangeSet) -> DbChangeSet: old = get_rule(name) redo_sql = 'delete from rules;' @@ -31,7 +31,7 @@ def set_rule_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_rule(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_rule_cmd(name, cs)) + return execute_command(name, _set_rule(name, cs)) #-------------------------------------------------------------- diff --git a/api/s15_energy.py b/api/s15_energy.py index 8bca11b..cc04c6c 100644 --- a/api/s15_energy.py +++ b/api/s15_energy.py @@ -19,7 +19,7 @@ def get_energy(name: str) -> dict[str, Any]: return d -def set_energy_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_energy(name: str, cs: ChangeSet) -> DbChangeSet: raw_old = get_energy(name) old = {} @@ -54,7 +54,7 @@ def set_energy_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_energy(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_energy_cmd(name, cs)) + return execute_command(name, _set_energy(name, cs)) def get_pump_energy_schema(name: str) -> dict[str, dict[str, Any]]: @@ -94,7 +94,7 @@ class PumpEnergy(object): return { 'type': self.type, 'pump': self.pump, 'price': self.price, 'pattern': self.pattern, 'effic': self.effic } -def set_pump_energy_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_pump_energy(name: str, cs: ChangeSet) -> DbChangeSet: old = PumpEnergy(get_pump_energy(name, cs.operations[0]['pump'])) raw_new = get_pump_energy(name, cs.operations[0]['pump']) @@ -128,7 +128,7 @@ def set_pump_energy_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_pump_energy(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_pump_energy_cmd(name, cs)) + return execute_command(name, _set_pump_energy(name, cs)) #-------------------------------------------------------------- diff --git a/api/s16_emitters.py b/api/s16_emitters.py index ee88df4..0bf06ad 100644 --- a/api/s16_emitters.py +++ b/api/s16_emitters.py @@ -30,7 +30,7 @@ class Emitter(object): return { 'type': self.type, 'junction': self.junction, 'coefficient': self.coefficient } -def set_emitter_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_emitter(name: str, cs: ChangeSet) -> DbChangeSet: old = Emitter(get_emitter(name, cs.operations[0]['junction'])) raw_new = get_emitter(name, cs.operations[0]['junction']) @@ -56,7 +56,7 @@ def set_emitter_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_emitter(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_emitter_cmd(name, cs)) + return execute_command(name, _set_emitter(name, cs)) #-------------------------------------------------------------- diff --git a/api/s17_quality.py b/api/s17_quality.py index a524e0c..1087863 100644 --- a/api/s17_quality.py +++ b/api/s17_quality.py @@ -30,7 +30,7 @@ class Quality(object): return { 'type': self.type, 'node': self.node, 'quality': self.quality } -def set_quality_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_quality(name: str, cs: ChangeSet) -> DbChangeSet: old = Quality(get_quality(name, cs.operations[0]['node'])) raw_new = get_quality(name, cs.operations[0]['node']) @@ -56,7 +56,7 @@ def set_quality_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_quality(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_quality_cmd(name, cs)) + return execute_command(name, _set_quality(name, cs)) #-------------------------------------------------------------- diff --git a/api/s18_sources.py b/api/s18_sources.py index dcc4db9..2ea3406 100644 --- a/api/s18_sources.py +++ b/api/s18_sources.py @@ -46,7 +46,7 @@ class Source(object): return { 'type': self.type, 'node': self.node } -def set_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_source(name: str, cs: ChangeSet) -> DbChangeSet: old = Source(get_source(name, cs.operations[0]['node'])) raw_new = get_source(name, cs.operations[0]['node']) @@ -67,10 +67,10 @@ def set_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_source(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_source_cmd(name, cs)) + return execute_command(name, _set_source(name, cs)) -def add_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_source(name: str, cs: ChangeSet) -> DbChangeSet: new = Source(cs.operations[0]) redo_sql = f"insert into sources (node, type, strength, pattern) values ({new.f_node}, {new.f_s_type}, {new.f_strength}, {new.f_pattern});" @@ -83,10 +83,10 @@ def add_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def add_source(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, add_source_cmd(name, cs)) + return execute_command(name, _add_source(name, cs)) -def delete_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_source(name: str, cs: ChangeSet) -> DbChangeSet: old = Source(get_source(name, cs.operations[0]['node'])) redo_sql = f"delete from sources where node = {old.f_node};" @@ -99,7 +99,7 @@ def delete_source_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def delete_source(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, delete_source_cmd(name, cs)) + return execute_command(name, _delete_source(name, cs)) #-------------------------------------------------------------- diff --git a/api/s19_reactions.py b/api/s19_reactions.py index 19b9c3f..8ebf7a7 100644 --- a/api/s19_reactions.py +++ b/api/s19_reactions.py @@ -22,7 +22,7 @@ def get_reaction(name: str) -> dict[str, Any]: return d -def set_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_reaction(name: str, cs: ChangeSet) -> DbChangeSet: raw_old = get_reaction(name) old = {} @@ -57,7 +57,7 @@ def set_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_reaction(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_reaction_cmd(name, cs)) + return execute_command(name, _set_reaction(name, cs)) def get_pipe_reaction_schema(name: str) -> dict[str, dict[str, Any]]: @@ -92,7 +92,7 @@ class PipeReaction(object): return { 'type': self.type, 'pipe': self.pipe, 'bulk': self.bulk, 'wall': self.wall } -def set_pipe_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_pipe_reaction(name: str, cs: ChangeSet) -> DbChangeSet: old = PipeReaction(get_pipe_reaction(name, cs.operations[0]['pipe'])) raw_new = get_pipe_reaction(name, cs.operations[0]['pipe']) @@ -122,7 +122,7 @@ def set_pipe_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_pipe_reaction(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_pipe_reaction_cmd(name, cs)) + return execute_command(name, _set_pipe_reaction(name, cs)) def get_tank_reaction_schema(name: str) -> dict[str, dict[str, Any]]: @@ -152,7 +152,7 @@ class TankReaction(object): return { 'type': self.type, 'tank': self.tank, 'value': self.value } -def set_tank_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_tank_reaction(name: str, cs: ChangeSet) -> DbChangeSet: old = TankReaction(get_tank_reaction(name, cs.operations[0]['tank'])) raw_new = get_tank_reaction(name, cs.operations[0]['tank']) @@ -178,7 +178,7 @@ def set_tank_reaction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_tank_reaction(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_tank_reaction_cmd(name, cs)) + return execute_command(name, _set_tank_reaction(name, cs)) #-------------------------------------------------------------- diff --git a/api/s1_title.py b/api/s1_title.py index c897c62..a038857 100644 --- a/api/s1_title.py +++ b/api/s1_title.py @@ -10,7 +10,7 @@ def get_title(name: str) -> dict[str, Any]: return { 'value': title['value'] } -def set_title_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_title(name: str, cs: ChangeSet) -> DbChangeSet: new = cs.operations[0]['value'] old = get_title(name)['value'] @@ -24,7 +24,7 @@ def set_title_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_title(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_title_cmd(name ,cs)) + return execute_command(name, _set_title(name ,cs)) def inp_in_title(section: list[str]) -> str: diff --git a/api/s20_mixing.py b/api/s20_mixing.py index 1b8917c..db4c8d3 100644 --- a/api/s20_mixing.py +++ b/api/s20_mixing.py @@ -42,7 +42,7 @@ class Mixing(object): return { 'type': self.type, 'tank': self.tank } -def set_mixing_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_mixing(name: str, cs: ChangeSet) -> DbChangeSet: old = Mixing(get_mixing(name, cs.operations[0]['tank'])) raw_new = get_mixing(name, cs.operations[0]['tank']) @@ -67,10 +67,10 @@ def set_mixing(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_mixing(name, cs.operations[0]['tank']) == {}: return ChangeSet() - return execute_command(name, set_mixing_cmd(name, cs)) + return execute_command(name, _set_mixing(name, cs)) -def add_mixing_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_mixing(name: str, cs: ChangeSet) -> DbChangeSet: new = Mixing(cs.operations[0]) redo_sql = f"insert into mixing (tank, model, value) values ({new.f_tank}, {new.f_model}, {new.f_value});" @@ -87,10 +87,10 @@ def add_mixing(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_mixing(name, cs.operations[0]['tank']) != {}: return ChangeSet() - return execute_command(name, add_mixing_cmd(name, cs)) + return execute_command(name, _add_mixing(name, cs)) -def delete_mixing_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_mixing(name: str, cs: ChangeSet) -> DbChangeSet: old = Mixing(get_mixing(name, cs.operations[0]['tank'])) redo_sql = f"delete from mixing where tank = {old.f_tank};" @@ -107,7 +107,7 @@ def delete_mixing(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_mixing(name, cs.operations[0]['tank']) == {}: return ChangeSet() - return execute_command(name, delete_mixing_cmd(name, cs)) + return execute_command(name, _delete_mixing(name, cs)) #-------------------------------------------------------------- diff --git a/api/s21_times.py b/api/s21_times.py index 590e91d..16910d9 100644 --- a/api/s21_times.py +++ b/api/s21_times.py @@ -29,7 +29,7 @@ def get_time(name: str) -> dict[str, Any]: return d -def set_time_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_time(name: str, cs: ChangeSet) -> DbChangeSet: raw_old = get_time(name) old = {} @@ -64,7 +64,7 @@ def set_time_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_time(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_time_cmd(name, cs)) + return execute_command(name, _set_time(name, cs)) #-------------------------------------------------------------- diff --git a/api/s23_options.py b/api/s23_options.py index 6748f76..08d674c 100644 --- a/api/s23_options.py +++ b/api/s23_options.py @@ -1,14 +1,5 @@ from .database import * from .s23_options_util import get_option_schema, generate_v3 -from .batch_cmd import execute_batch_command - - -def set_option(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0]['operation'] = API_UPDATE - cs.operations[0]['type'] = 'option' - new_cs = cs - new_cs.merge(generate_v3(cs)) - return execute_batch_command(name, new_cs) def _inp_in_option(section: list[str]) -> ChangeSet: diff --git a/api/s23_options_util.py b/api/s23_options_util.py index 5a7a4be..8995c3b 100644 --- a/api/s23_options_util.py +++ b/api/s23_options_util.py @@ -106,7 +106,7 @@ def get_option(name: str) -> dict[str, Any]: return d -def set_option_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_option(name: str, cs: ChangeSet) -> DbChangeSet: raw_old = get_option(name) old = {} @@ -140,9 +140,8 @@ def set_option_cmd(name: str, cs: ChangeSet) -> DbChangeSet: return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) -def set_option_only(name: str, cs: ChangeSet) -> ChangeSet: - v2_cmd = set_option_cmd(name, cs) - return execute_command(name, v2_cmd) +def set_option(name: str, cs: ChangeSet) -> ChangeSet: + return execute_command(name, _set_option(name, cs)) OPTION_V3_FLOW_UNITS_CFS = OPTION_UNITS_CFS @@ -231,7 +230,7 @@ def get_option_v3(name: str) -> dict[str, Any]: return d -def set_option_v3_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_option_v3(name: str, cs: ChangeSet) -> DbChangeSet: raw_old = get_option_v3(name) old = {} @@ -265,9 +264,8 @@ def set_option_v3_cmd(name: str, cs: ChangeSet) -> DbChangeSet: return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) -def set_option_v3_only(name: str, cs: ChangeSet) -> ChangeSet: - v3_cmd = set_option_v3_cmd(name, cs) - return execute_command(name, v3_cmd) +def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet: + return execute_command(name, _set_option_v3(name, cs)) _key_map_23 = { diff --git a/api/s23_options_v3.py b/api/s23_options_v3.py index 4e62c88..0a7738b 100644 --- a/api/s23_options_v3.py +++ b/api/s23_options_v3.py @@ -1,14 +1,5 @@ from .database import * from .s23_options_util import get_option_schema, get_option_v3_schema, generate_v2, generate_v3 -from .batch_cmd import execute_batch_command - - -def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet: - cs.operations[0]['operation'] = API_UPDATE - cs.operations[0]['type'] = 'option_v3' - new_cs = cs - new_cs.merge(generate_v2(cs)) - return execute_batch_command(name, new_cs) def _parse_v2(v2_lines: list[str]) -> dict[str, str]: diff --git a/api/s24_coordinates.py b/api/s24_coordinates.py index 6c54245..6c36216 100644 --- a/api/s24_coordinates.py +++ b/api/s24_coordinates.py @@ -1,17 +1,35 @@ from .database import * -def _to_client_point(coord: str) -> dict[str, float]: - xy = coord.removeprefix('(').removesuffix(')').split(',') +def sql_update_coord(node: str, x: float, y: float) -> str: + coord = f"st_geomfromtext('point({x} {y})')" + return f"update coordinates set coord = {coord} where node = '{node}';" + + +def sql_insert_coord(node: str, x: float, y: float) -> str: + coord = f"st_geomfromtext('point({x} {y})')" + return f"insert into coordinates (node, coord) values ('{node}', {coord});" + + +def sql_delete_coord(node: str) -> str: + return f"delete from coordinates where node = '{node}';" + + +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]) } -def get_node_coord(name: str, id: str) -> dict[str, float]: - row = try_read(name, f"select * from coordinates where node = '{id}'") +def get_node_coord(name: str, node: str) -> dict[str, float]: + row = try_read(name, f"select st_astext(coord) as coord_geom from coordinates where node = '{node}'") if row == None: - write(name, f"insert into coordinates (node, coord) values ('{id}', '(0.0,0.0)');") + write(name, sql_insert_coord(node, 0.0, 0.0)) return {'x': 0.0, 'y': 0.0} - return _to_client_point(row['coord']) + return from_postgis_point(row['coord_geom']) + + +def node_has_coord(name: str, node: str) -> bool: + return try_read(name, f"select node from coordinates where node = '{node}'") != None #-------------------------------------------------------------- @@ -24,16 +42,16 @@ def get_node_coord(name: str, id: str) -> dict[str, float]: def inp_in_coord(line: str) -> str: tokens = line.split() node = tokens[0] - coord = f"'({tokens[1]}, {tokens[2]})'" + coord = f"st_geomfromtext('point({tokens[1]} {tokens[2]})')" return f"insert into coordinates (node, coord) values ('{node}', {coord});" def inp_out_coord(name: str) -> list[str]: lines = [] - objs = read_all(name, 'select * from coordinates') + objs = read_all(name, 'select node, st_astext(coord) as coord_geom from coordinates') for obj in objs: node = obj['node'] - coord = _to_client_point(obj['coord']) + coord = from_postgis_point(obj['coord_geom']) x = coord['x'] y = coord['y'] lines.append(f'{node} {x} {y}') diff --git a/api/s25_vertices.py b/api/s25_vertices.py index 96c381c..20f8cf5 100644 --- a/api/s25_vertices.py +++ b/api/s25_vertices.py @@ -16,7 +16,7 @@ def get_vertex(name: str, link: str) -> dict[str, Any]: return { 'link': link, 'coords': cs } -def set_vertex_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_vertex(name: str, cs: ChangeSet) -> DbChangeSet: link = cs.operations[0]['link'] old = get_vertex(name, link) @@ -44,34 +44,34 @@ def set_vertex_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_vertex(name: str, cs: ChangeSet) -> ChangeSet: - result = set_vertex_cmd(name, cs) + result = _set_vertex(name, cs) result.redo_cs[0] |= g_update_prefix result.undo_cs[0] |= g_update_prefix return execute_command(name, result) -def add_vertex_cmd(name: str, cs: ChangeSet) -> DbChangeSet: - result = set_vertex_cmd(name, cs) +def _add_vertex(name: str, cs: ChangeSet) -> DbChangeSet: + result = _set_vertex(name, cs) result.redo_cs[0] |= g_add_prefix result.undo_cs[0] |= g_delete_prefix return result -def delete_vertex_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_vertex(name: str, cs: ChangeSet) -> DbChangeSet: cs.operations[0]['coords'] = [] - result = set_vertex_cmd(name, cs) + result = _set_vertex(name, cs) result.redo_cs[0] |= g_delete_prefix result.undo_cs[0] |= g_add_prefix return result def add_vertex(name: str, cs: ChangeSet) -> ChangeSet: - result = add_vertex_cmd(name, cs) + result = _add_vertex(name, cs) return execute_command(name, result) def delete_vertex(name: str, cs: ChangeSet) -> ChangeSet: - result = delete_vertex_cmd(name, cs) + result = _delete_vertex(name, cs) return execute_command(name, result) diff --git a/api/s26_labels.py b/api/s26_labels.py index 437bc0e..c7e7193 100644 --- a/api/s26_labels.py +++ b/api/s26_labels.py @@ -43,7 +43,7 @@ class Label(object): return { 'type': self.type, 'x': self.x, 'y': self.y } -def set_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_label(name: str, cs: ChangeSet) -> DbChangeSet: old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y'])) raw_new = get_label(name, cs.operations[0]['x'], cs.operations[0]['y']) @@ -64,10 +64,10 @@ def set_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_label(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_label_cmd(name, cs)) + return execute_command(name, _set_label(name, cs)) -def add_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_label(name: str, cs: ChangeSet) -> DbChangeSet: new = Label(cs.operations[0]) redo_sql = f"insert into labels (x, y, label, node) values ({new.f_x}, {new.f_y}, {new.f_label}, {new.f_node});" @@ -80,10 +80,10 @@ def add_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def add_label(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, add_label_cmd(name, cs)) + return execute_command(name, _add_label(name, cs)) -def delete_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_label(name: str, cs: ChangeSet) -> DbChangeSet: old = Label(get_label(name, cs.operations[0]['x'], cs.operations[0]['y'])) redo_sql = f"delete from labels where x = {old.f_x} and y = {old.f_y};" @@ -96,7 +96,7 @@ def delete_label_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def delete_label(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, delete_label_cmd(name, cs)) + return execute_command(name, _delete_label(name, cs)) def inp_in_label(line: str) -> str: diff --git a/api/s27_backdrop.py b/api/s27_backdrop.py index 9766948..18f938a 100644 --- a/api/s27_backdrop.py +++ b/api/s27_backdrop.py @@ -10,7 +10,7 @@ def get_backdrop(name: str) -> dict[str, Any]: return { 'content': e['content'] } -def set_backdrop_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_backdrop(name: str, cs: ChangeSet) -> DbChangeSet: old = get_backdrop(name) redo_sql = f"update backdrop set content = '{cs.operations[0]['content']}' where content = '{old['content']}';" @@ -23,7 +23,7 @@ def set_backdrop_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_backdrop(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_backdrop_cmd(name, cs)) + return execute_command(name, _set_backdrop(name, cs)) def inp_in_backdrop(section: list[str]) -> str: diff --git a/api/s29_scada_device.py b/api/s29_scada_device.py index 7d48978..337277c 100644 --- a/api/s29_scada_device.py +++ b/api/s29_scada_device.py @@ -56,7 +56,7 @@ class ScadaDevice(object): return { 'type': self.type, 'id': self.id } -def set_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_scada_device(name: str, cs: ChangeSet) -> DbChangeSet: old = ScadaDevice(get_scada_device(name, cs.operations[0]['id'])) raw_new = get_scada_device(name, cs.operations[0]['id']) @@ -79,10 +79,10 @@ def set_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_scada_device(name: str, cs: ChangeSet) -> ChangeSet: if get_scada_device(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_scada_device_cmd(name, cs)) + return execute_command(name, _set_scada_device(name, cs)) -def add_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_scada_device(name: str, cs: ChangeSet) -> DbChangeSet: new = ScadaDevice(cs.operations[0]) redo_sql = f"insert into scada_device (id, name, address, type) values ({new.f_id}, {new.f_name}, {new.f_address}, {new.f_sd_type});" @@ -97,10 +97,10 @@ def add_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def add_scada_device(name: str, cs: ChangeSet) -> ChangeSet: if get_scada_device(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_scada_device_cmd(name, cs)) + return execute_command(name, _add_scada_device(name, cs)) -def delete_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_scada_device(name: str, cs: ChangeSet) -> DbChangeSet: old = ScadaDevice(get_scada_device(name, cs.operations[0]['id'])) redo_sql = f"delete from scada_device where id = {old.f_id};" @@ -115,14 +115,4 @@ def delete_scada_device_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def delete_scada_device(name: str, cs: ChangeSet) -> ChangeSet: if get_scada_device(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_scada_device_cmd(name, cs)) - - -def clean_scada_device_cmd(name: str) -> ChangeSet: - cs = ChangeSet() - - rows = read_all(name, 'select id from scada_device acs') - for row in rows: - cs.delete({ 'type': 'scada_device', 'id': row['id'] }) - - return cs + return execute_command(name, _delete_scada_device(name, cs)) diff --git a/api/s2_junctions.py b/api/s2_junctions.py index cbe3c1b..84164ea 100644 --- a/api/s2_junctions.py +++ b/api/s2_junctions.py @@ -35,7 +35,6 @@ class Junction(object): self.f_type = f"'{self.type}'" self.f_id = f"'{self.id}'" - self.f_coord = f"'({self.x}, {self.y})'" self.f_elevation = self.elevation def as_dict(self) -> dict[str, Any]: @@ -45,7 +44,7 @@ class Junction(object): return { 'type': self.type, 'id': self.id } -def set_junction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_junction(name: str, cs: ChangeSet) -> DbChangeSet: old = Junction(get_junction(name, cs.operations[0]['id'])) raw_new = get_junction(name, cs.operations[0]['id']) @@ -57,9 +56,9 @@ def set_junction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: new = Junction(raw_new) redo_sql = f"update junctions set elevation = {new.f_elevation} where id = {new.f_id};" - redo_sql += f"\nupdate coordinates set coord = {new.f_coord} where node = {new.f_id};" + redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}" - undo_sql = f"update coordinates set coord = {old.f_coord} where node = {old.f_id};" + undo_sql = sql_update_coord(old.id, old.x, old.y) undo_sql += f"\nupdate junctions set elevation = {old.f_elevation} where id = {old.f_id};" redo_cs = g_update_prefix | new.as_dict() @@ -73,17 +72,17 @@ def set_junction(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_junction(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_junction_cmd(name, cs)) + return execute_command(name, _set_junction(name, cs)) -def add_junction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_junction(name: str, cs: ChangeSet) -> DbChangeSet: new = Junction(cs.operations[0]) redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});" redo_sql += f"\ninsert into junctions (id, elevation) values ({new.f_id}, {new.f_elevation});" - redo_sql += f"\ninsert into coordinates (node, coord) values ({new.f_id}, {new.f_coord});" + redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}" - undo_sql = f"delete from coordinates where node = {new.f_id};" + undo_sql = sql_delete_coord(new.id) undo_sql += f"\ndelete from junctions where id = {new.f_id};" undo_sql += f"\ndelete from _node where id = {new.f_id};" @@ -98,19 +97,19 @@ def add_junction(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_junction(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_junction_cmd(name, cs)) + return execute_command(name, _add_junction(name, cs)) -def delete_junction_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_junction(name: str, cs: ChangeSet) -> DbChangeSet: old = Junction(get_junction(name, cs.operations[0]['id'])) - redo_sql = f"delete from coordinates where node = {old.f_id};" + redo_sql = sql_delete_coord(old.id) redo_sql += f"\ndelete from junctions where id = {old.f_id};" redo_sql += f"\ndelete from _node where id = {old.f_id};" undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});" undo_sql += f"\ninsert into junctions (id, elevation) values ({old.f_id}, {old.f_elevation});" - undo_sql += f"\ninsert into coordinates (node, coord) values ({old.f_id}, {old.f_coord});" + undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}" redo_cs = g_delete_prefix | old.as_id_dict() undo_cs = g_add_prefix | old.as_dict() @@ -123,7 +122,7 @@ def delete_junction(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_junction(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_junction_cmd(name, cs)) + return execute_command(name, _delete_junction(name, cs)) #-------------------------------------------------------------- diff --git a/api/s30_scada_device_data.py b/api/s30_scada_device_data.py index e09b813..5f23800 100644 --- a/api/s30_scada_device_data.py +++ b/api/s30_scada_device_data.py @@ -16,7 +16,7 @@ def get_scada_device_data(name: str, device_id: str) -> dict[str, Any]: return { 'device_id': device_id, 'data': ds } -def set_scada_device_data_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet: device_id = cs.operations[0]['device_id'] old = get_scada_device_data(name, device_id) @@ -45,10 +45,10 @@ def set_scada_device_data_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_scada_device_data_cmd(name, cs)) + return execute_command(name, _set_scada_device_data(name, cs)) -def add_scada_device_data_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet: values = cs.operations[0] device_id = values['device_id'] time = values['time'] @@ -66,10 +66,10 @@ def add_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet: row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'") if row != None: return ChangeSet() - return execute_command(name, add_scada_device_data_cmd(name, cs)) + return execute_command(name, _add_scada_device_data(name, cs)) -def delete_scada_device_data_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_scada_device_data(name: str, cs: ChangeSet) -> DbChangeSet: values = cs.operations[0] device_id = values['device_id'] time = values['time'] @@ -87,14 +87,4 @@ def delete_scada_device_data(name: str, cs: ChangeSet) -> ChangeSet: row = try_read(name, f"select * from scada_device_data where device_id = '{cs.operations[0]['device_id']}' and time = '{cs.operations[0]['time']}'") if row == None: return ChangeSet() - return execute_command(name, delete_scada_device_data_cmd(name, cs)) - - -def clean_scada_device_data_cmd(name: str) -> ChangeSet: - cs = ChangeSet() - - rows = read_all(name, 'select distinct device_id from scada_device_data acs') - for row in rows: - cs.update({ 'type': 'scada_device_data', 'device_id': row['device_id'], 'data': [] }) - - return cs + return execute_command(name, _delete_scada_device_data(name, cs)) diff --git a/api/s31_scada_element.py b/api/s31_scada_element.py index ab47316..f4739ab 100644 --- a/api/s31_scada_element.py +++ b/api/s31_scada_element.py @@ -114,7 +114,7 @@ class ScadaModel(object): return { 'type': self.type, 'id': self.id } -def set_scada_element_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_scada_element(name: str, cs: ChangeSet) -> DbChangeSet: old = ScadaModel(get_scada_element(name, cs.operations[0]['id'])) raw_new = get_scada_element(name, cs.operations[0]['id']) @@ -139,10 +139,10 @@ def set_scada_element(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if _check_model(name, cs) == False: return ChangeSet() - return execute_command(name, set_scada_element_cmd(name, cs)) + return execute_command(name, _set_scada_element(name, cs)) -def add_scada_element_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_scada_element(name: str, cs: ChangeSet) -> DbChangeSet: new = ScadaModel(cs.operations[0]) redo_sql = f"insert into scada_element (id, x, y, device_id, model_id, model_type, status) values ({new.f_id}, {new.f_x}, {new.f_y}, {new.f_device_id}, {new.f_model_id}, {new.f_model_type}, {new.f_status});" @@ -159,10 +159,10 @@ def add_scada_element(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if _check_model(name, cs) == False: return ChangeSet() - return execute_command(name, add_scada_element_cmd(name, cs)) + return execute_command(name, _add_scada_element(name, cs)) -def delete_scada_element_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_scada_element(name: str, cs: ChangeSet) -> DbChangeSet: old = ScadaModel(get_scada_element(name, cs.operations[0]['id'])) redo_sql = f"delete from scada_element where id = {old.f_id};" @@ -177,14 +177,4 @@ def delete_scada_element_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def delete_scada_element(name: str, cs: ChangeSet) -> ChangeSet: if get_scada_element(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_scada_element_cmd(name, cs)) - - -def clean_scada_element_cmd(name: str) -> ChangeSet: - cs = ChangeSet() - - rows = read_all(name, 'select id from scada_element acs') - for row in rows: - cs.delete({ 'type': 'scada_element', 'id': row['id'] }) - - return cs + return execute_command(name, _delete_scada_element(name, cs)) diff --git a/api/s32_region_util.py b/api/s32_region_util.py new file mode 100644 index 0000000..eee3d85 --- /dev/null +++ b/api/s32_region_util.py @@ -0,0 +1,248 @@ +import ctypes +import platform +import os +import math +from typing import Any +from .s0_base import get_node_links, get_link_nodes, is_pipe +from .s5_pipes import get_pipe +from .database import read, try_read, read_all, write +from .s24_coordinates import node_has_coord, get_node_coord + + +def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]: + boundary = polygon.lower().removeprefix('polygon((').removesuffix('))').split(',') + xys = [] + for pt in boundary: + xy = pt.split(' ') + xys.append((float(xy[0]), float(xy[1]))) + return xys + + +def to_postgis_polygon(boundary: list[tuple[float, float]]) -> str: + polygon = '' + for pt in boundary: + polygon += f'{pt[0]} {pt[1]},' + return f'polygon(({polygon[:-1]}))' + + +def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]: + api = 'get_nodes_in_boundary' + write(name, f"delete from temp_region where id = '{api}'") + write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')") + + nodes: list[str] = [] + for row in read_all(name, f"select c.node from coordinates as c, temp_region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"): + nodes.append(row['node']) + + write(name, f"delete from temp_region where id = '{api}'") + + return nodes + + +def get_nodes_in_region(name: str, region_id: str) -> list[str]: + nodes: list[str] = [] + for row in read_all(name, f"select c.node from coordinates as c, region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{region_id}'"): + nodes.append(row['node']) + return nodes + + +def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]: + write(name, f'delete from temp_node') + for node in nodes: + write(name, f"insert into temp_node values ('{node}')") + + # TODO: check none + polygon = read(name, f'select st_astext(st_convexhull(st_collect(array(select coord from coordinates where node in (select * from temp_node))))) as boundary' )['boundary'] + write(name, f'delete from temp_node') + + return from_postgis_polygon(polygon) + + +def _verify_platform(): + _platform = platform.system() + if _platform != "Windows": + raise Exception(f'Platform {_platform} unsupported (not yet)') + + +def _normal(v: tuple[float, float]) -> tuple[float, float]: + l = math.sqrt(v[0] * v[0] + v[1] * v[1]) + return (v[0] / l, v[1] / l) + + +def _angle(v: tuple[float, float]) -> float: + if v[0] >= 0 and v[1] >= 0: + return math.asin(v[1]) + elif v[0] <= 0 and v[1] >= 0: + return math.pi - math.asin(v[1]) + elif v[0] <= 0 and v[1] <= 0: + return math.asin(-v[1]) + math.pi + elif v[0] >= 0 and v[1] <= 0: + return math.pi * 2 - math.asin(-v[1]) + return 0 + + +def _angle_of_node_link(node: str, link: str, nodes, links) -> float: + n1 = node + n2 = links[link]['node1'] if n1 == links[link]['node2'] else links[link]['node2'] + x1, y1 = nodes[n1]['x'], nodes[n1]['y'] + x2, y2 = nodes[n2]['x'], nodes[n2]['y'] + v = _normal((x2 - x1, y2 - y1)) + return _angle(v) + + +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 + + self._links = {} + 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) + + def nodes(self): + return self._nodes + + def node_list(self): + return self._node_list + + def max_x_node(self): + return self._max_x_node + + def links(self): + return self._links + + def link_list(self): + return self._link_list + + +def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]: + topology = Topology(name, nodes) + t_nodes = topology.nodes() + t_links = topology.links() + + cursor = topology.max_x_node() + in_angle = 0 + + paths: list[str] = [] + while True: + #print(cursor) + paths.append(cursor) + sorted_links = [] + overlapped_link = '' + for link in t_nodes[cursor]['links']: + angle = _angle_of_node_link(cursor, link, t_nodes, t_links) + if angle == in_angle: + overlapped_link = link + continue + sorted_links.append((angle, link)) + + # work into a branch, return + if len(sorted_links) == 0: + cursor = paths[-2] + in_angle = in_angle = _angle_of_node_link(cursor, overlapped_link, t_nodes, t_links) + continue + + sorted_links = sorted(sorted_links, key=lambda s:s[0]) + out_link = sorted_links[0][1] + for angle, link in sorted_links: + if angle > in_angle: + out_link = link + break + + cursor = t_links[out_link]['node1'] if cursor == t_links[out_link]['node2'] else t_links[out_link]['node2'] + # end up trip :) + if cursor == topology.max_x_node(): + paths.append(cursor) + break + + in_angle = _angle_of_node_link(cursor, out_link, t_nodes, t_links) + + boundary: list[tuple[float, float]] = [] + for node in paths: + boundary.append((t_nodes[node]['x'], t_nodes[node]['y'])) + + return boundary + + +''' +# CClipper2.dll +# int inflate_paths(double* path, size_t size, double delta, int jt, int et, double miter_limit, int precision, double arc_tolerance, double** out_path, size_t* out_size); +# int simplify_paths(double* path, size_t size, double epsilon, int is_closed_path, double** out_path, size_t* out_size); +# void free_paths(double** paths); +''' +def inflate_boundary(name: str, boundary: list[tuple[float, float]], delta: float = 0.5) -> list[tuple[float, float]]: + if boundary[0] == boundary[-1]: + del(boundary[-1]) + + lib = ctypes.CDLL(os.path.join(os.getcwd(), 'api', 'CClipper2.dll')) + + c_size = ctypes.c_size_t(len(boundary) * 2) + c_path = (ctypes.c_double * c_size.value)() + i = 0 + for xy in boundary: + c_path[i] = xy[0] + i += 1 + c_path[i] = xy[1] + i += 1 + c_delta = ctypes.c_double(delta) + JoinType_Square, JoinType_Round, JoinType_Miter = 0, 1, 2 + c_jt = ctypes.c_int(JoinType_Square) + EndType_Polygon, EndType_Joined, EndType_Butt, EndType_Square, EndType_Round = 0, 1, 2, 3, 4 + c_et = ctypes.c_int(EndType_Polygon) + c_miter_limit = ctypes.c_double(2.0) + c_precision = ctypes.c_int(2) + c_arc_tolerance = ctypes.c_double(0.0) + c_out_path = ctypes.POINTER(ctypes.c_double)() + c_out_size = ctypes.c_size_t(0) + + lib.inflate_paths(c_path, c_size, c_delta, c_jt, c_et, c_miter_limit, c_precision, c_arc_tolerance, ctypes.byref(c_out_path), ctypes.byref(c_out_size)) + if c_out_size.value == 0: + lib.free_paths(ctypes.byref(c_out_path)) + return [] + + # TODO: simplify_paths :) + + result: list[tuple[float, float]] = [] + for i in range(0, c_out_size.value, 2): + result.append((c_out_path[i], c_out_path[i + 1])) + result.append(result[0]) + + lib.free_paths(ctypes.byref(c_out_path)) + return result + + +def inflate_region(name: str, region_id: str, delta: float = 0.5) -> list[tuple[float, float]]: + r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{region_id}'") + if r == None: + return [] + boundary = from_postgis_polygon(str(r['boundary_geom'])) + return inflate_boundary(name, boundary, delta) + + +if __name__ == '__main__': + _verify_platform() diff --git a/api/s33_region.py b/api/s33_region.py new file mode 100644 index 0000000..9d90e20 --- /dev/null +++ b/api/s33_region.py @@ -0,0 +1,83 @@ +from .database import * +from .s32_region_util import from_postgis_polygon, to_postgis_polygon + +def get_region_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False} } + + +def get_region(name: str, id: str) -> dict[str, Any]: + r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{id}'") + if r == None: + return {} + d = {} + d['id'] = str(r['id']) + d['boundary'] = from_postgis_polygon(str(r['boundary_geom'])) + return d + + +def _set_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + new = cs.operations[0]['boundary'] + old = get_region(name, id)['boundary'] + + redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new)}') where id = '{id}';" + undo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old)}') where id = '{id}';" + redo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': new } + undo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': old } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def set_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]: + return ChangeSet() + b = cs.operations[0]['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) == {}: + return ChangeSet() + return execute_command(name, _set_region(name, cs)) + + +def _add_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + new = cs.operations[0]['boundary'] + + redo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(new)}');" + undo_sql = f"delete from region where id = '{id}';" + redo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': new } + undo_cs = g_delete_prefix | { 'type': 'region', 'id': id } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def add_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]: + return ChangeSet() + b = cs.operations[0]['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) != {}: + return ChangeSet() + return execute_command(name, _add_region(name, cs)) + + +def _delete_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + old = get_region(name, id)['boundary'] + + redo_sql = f"delete from region where id = '{id}';" + undo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(old)}');" + redo_cs = g_delete_prefix | { 'type': 'region', 'id': id } + undo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': old } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def delete_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0]: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) == {}: + return ChangeSet() + return execute_command(name, _delete_region(name, cs)) diff --git a/api/s34_water_distribution.py b/api/s34_water_distribution.py new file mode 100644 index 0000000..de444eb --- /dev/null +++ b/api/s34_water_distribution.py @@ -0,0 +1,52 @@ +from .database import ChangeSet +from .s0_base import is_junction +from .s9_demands import get_demand +from .s32_region_util import Topology, get_nodes_in_region +from .batch_exe import execute_batch_command + +DISTRIBUTION_TYPE_ADD = 'ADD' +DISTRIBUTION_TYPE_OVERRIDE = 'OVERRIDE' + +def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet: + if len(nodes) == 0 or demand == 0.0: + return ChangeSet() + if type != DISTRIBUTION_TYPE_ADD and type != DISTRIBUTION_TYPE_OVERRIDE: + return ChangeSet() + + topology = Topology(name, nodes) + t_nodes = topology.nodes() + t_links = topology.links() + + length_sum = 0.0 + for value in t_links.values(): + length_sum += abs(value['length']) + + if length_sum <= 0.0: + return ChangeSet() + + demand_per_length = demand / length_sum + + cs = ChangeSet() + + for node, value in t_nodes.items(): + if not is_junction(name, node): + continue + demand_per_node = 0.0 + for link in value['links']: + demand_per_node += abs(t_links[link]['length']) * demand_per_length * 0.5 + + ds = get_demand(name, node)['demands'] + if len(ds) == 0: + ds = [{'demand': demand_per_node, 'pattern': None, 'category': None}] + elif type == DISTRIBUTION_TYPE_ADD: + ds[0]['demand'] += demand_per_node + else: + ds[0]['demand'] = demand_per_node + cs.update({'type': 'demand', 'junction': node, 'demands': ds}) + + return execute_batch_command(name, cs) + + +def distribute_demand_to_region(name: str, demand: float, region: str, type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet: + nodes = get_nodes_in_region(name, region) + return distribute_demand_to_nodes(name, demand, nodes, type) diff --git a/api/s35_district_metering_area.py b/api/s35_district_metering_area.py new file mode 100644 index 0000000..d907374 --- /dev/null +++ b/api/s35_district_metering_area.py @@ -0,0 +1,85 @@ +import ctypes +import os +from .database import * +from .s32_region_util import Topology + + +PARTITION_TYPE_RB = 0 +PARTITION_TYPE_KWAY = 1 + + +def calculate_district_metering_area(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]: + if part_type != PARTITION_TYPE_RB and part_type != PARTITION_TYPE_KWAY: + return [] + if part_count <= 0: + return [] + elif part_count == 1: + return [nodes] + + lib = ctypes.CDLL(os.path.join(os.getcwd(), 'api', 'CMetis.dll')) + + METIS_NOPTIONS = 40 + c_options = (ctypes.c_int64 * METIS_NOPTIONS)() + + METIS_OK = 1 + result = lib.set_default_options(c_options) + if result != METIS_OK: + return [] + + METIS_OPTION_PTYPE , METIS_OPTION_CONTIG = 0, 13 + c_options[METIS_OPTION_PTYPE] = part_type + c_options[METIS_OPTION_CONTIG] = 1 + + topology = Topology(name, nodes) + t_nodes = topology.nodes() + t_links = topology.links() + t_node_list = topology.node_list() + t_link_list = topology.link_list() + + nedges = len(t_link_list) * 2 + + c_nvtxs = ctypes.c_int64(len(t_node_list)) + c_ncon = ctypes.c_int64(1) + c_xadj = (ctypes.c_int64 * (c_nvtxs.value + 1))() + c_adjncy = (ctypes.c_int64 * nedges)() + c_vwgt = (ctypes.c_int64 * (c_ncon.value * c_nvtxs.value))() + c_adjwgt = (ctypes.c_int64 * nedges)() + c_vsize = (ctypes.c_int64 * c_nvtxs.value)() + + c_xadj[0] = 0 + + l, n = 0, 0 + c_xadj_i = 1 + for node in t_node_list: + links = t_nodes[node]['links'] + for link in links: + node1 = t_links[link]['node1'] + node2 = t_links[link]['node2'] + c_adjncy[l] = t_node_list.index(node2) if node2 != node else t_node_list.index(node1) + c_adjwgt[l] = 1 + l += 1 + if len(links) > 0: + c_xadj[c_xadj_i] = l # adjncy.size() + c_xadj_i += 1 + c_vwgt[n] = 1 + c_vsize[n] = 1 + n += 1 + + part_func = lib.part_graph_recursive if part_type == PARTITION_TYPE_RB else lib.part_graph_kway + + c_nparts = ctypes.c_int64(part_count) + c_tpwgts = ctypes.POINTER(ctypes.c_double)() + c_ubvec = ctypes.POINTER(ctypes.c_double)() + c_out_edgecut = ctypes.c_int64(0) + c_out_part = (ctypes.c_int64 * c_nvtxs.value)() + result = part_func(ctypes.byref(c_nvtxs), ctypes.byref(c_ncon), c_xadj, c_adjncy, c_vwgt, c_vsize, c_adjwgt, ctypes.byref(c_nparts), c_tpwgts, c_ubvec, c_options, ctypes.byref(c_out_edgecut), c_out_part) + if result != METIS_OK: + return [] + + dmas : list[list[str]]= [] + for i in range(part_count): + dmas.append([]) + for i in range(c_nvtxs.value): + dmas[c_out_part[i]].append(t_node_list[i]) + + return dmas diff --git a/api/s36_service_area.py b/api/s36_service_area.py new file mode 100644 index 0000000..ec012e5 --- /dev/null +++ b/api/s36_service_area.py @@ -0,0 +1,119 @@ +import sys +import json +from queue import Queue +from .database import * +from .s0_base import get_node_links, get_link_nodes + +sys.path.append('..') +from epanet.epanet import run_project + +def calculate_service_area(name: str, time_index: int = 0) -> dict[str, Any]: + inp = json.loads(run_project(name)) + + time_count = len(inp['node_results'][0]['result']) + if time_index >= time_count: + return {} + + sources : dict[str, list[str]] = {} + for node_result in inp['node_results']: + result = node_result['result'][time_index] + if result['demand'] < 0: + sources[node_result['node']] = [] + + link_flows: dict[str, float] = {} + for link_result in inp['link_results']: + result = link_result['result'][time_index] + link_flows[link_result['link']] = float(result['flow']) + + # build source to nodes map + for source in sources: + queue = Queue() + queue.put(source) + + while not queue.empty(): + cursor = queue.get() + if cursor not in sources[source]: + sources[source].append(cursor) + + links = get_node_links(name, cursor) + for link in links: + node1, node2 = get_link_nodes(name, link) + if node1 == cursor and link_flows[link] > 0: + queue.put(node2) + elif node2 == cursor and link_flows[link] < 0: + queue.put(node1) + + # calculation concentration + concentration_map: dict[str, dict[str, float]] = {} + node_wip: list[str] = [] + for source, nodes in sources.items(): + for node in nodes: + if node not in concentration_map: + concentration_map[node] = {} + concentration_map[node][source] = 0.0 + if node not in node_wip: + node_wip.append(node) + + # if only one source, done + for node, concentrations in concentration_map.items(): + if len(concentrations) == 1: + node_wip.remove(node) + for key in concentrations.keys(): + concentration_map[node][key] = 1.0 + + node_upstream : dict[str, list[tuple[str, str]]] = {} + for node in node_wip: + if node not in node_upstream: + node_upstream[node] = [] + + links = get_node_links(name, node) + for link in links: + node1, node2 = get_link_nodes(name, link) + if node2 == node and link_flows[link] > 0: + node_upstream[node].append((link, node1)) + elif node1 == node and link_flows[link] < 0: + node_upstream[node].append((link, node2)) + + while len(node_wip) != 0: + done = [] + for node in node_wip: + up_link_nodes = node_upstream[node] + ready = True + for link_node in up_link_nodes: + if link_node in node_wip: + ready = False + break + if ready: + for link_node in up_link_nodes: + for source, concentration in concentration_map[link_node[1]].items(): + concentration_map[node][source] += concentration * abs(link_flows[link_node[0]]) + + # normalize + sum = 0.0 + for source, concentration in concentration_map[node].items(): + sum += concentration + for source in concentration_map[node].keys(): + concentration_map[node][source] /= sum + + done.append(node) + + for node in done: + node_wip.remove(node) + + source_to_main_node: dict[str, list[str]] = {} + for node, value in concentration_map.items(): + max_source = '' + max_concentration = 0.0 + for s, c in value.items(): + if c > max_concentration: + max_concentration = c + max_source = s + if max_source not in source_to_main_node: + source_to_main_node[max_source] = [] + source_to_main_node[max_source].append(node) + + sas: list[dict[str, Any]] = [] + for source, nodes in source_to_main_node.items(): + sas.append({ 'source': source, 'nodes': nodes }) + + return { 'service_areas' : sas, 'concentrations': concentration_map } diff --git a/api/s37_virtual_district.py b/api/s37_virtual_district.py new file mode 100644 index 0000000..b115814 --- /dev/null +++ b/api/s37_virtual_district.py @@ -0,0 +1,66 @@ +from .database import * +from .s0_base import get_node_links + + +def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[Any]]: + write(name, 'delete from temp_vd_topology') + + # map node name to index + i = 0 + isolated_nodes = [] + node_index: dict[str, int] = {} + for row in read_all(name, 'select id from _node'): + node = str(row['id']) + if get_node_links(name, node) == []: + isolated_nodes.append(node) + continue + i += 1 + node_index[node] = i + + # build topology graph + pipes = read_all(name, 'select node1, node2, length from pipes') + for pipe in pipes: + source = node_index[str(pipe['node1'])] + target = node_index[str(pipe['node2'])] + cost = float(pipe['length']) + write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, {cost})") + pumps = read_all(name, 'select node1, node2 from pumps') + for pump in pumps: + source = node_index[str(pump['node1'])] + target = node_index[str(pump['node2'])] + write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)") + valves = read_all(name, 'select node1, node2 from valves') + for valve in valves: + source = node_index[str(valve['node1'])] + target = node_index[str(valve['node2'])] + write(name, f"insert into temp_vd_topology (source, target, cost) values ({source}, {target}, 0.0)") + + # dijkstra distance + node_distance: dict[str, dict[str, Any]] = {} + for center in centers: + for node, index in node_index.items(): + if node == center: + node_distance[node] = { 'center': center, 'distance' : 0.0 } + continue + # TODO: check none + distance = float(read(name, f"select max(agg_cost) as distance from pgr_dijkstraCost('select id, source, target, cost from temp_vd_topology', {index}, {node_index[center]}, false)")['distance']) + if node not in node_distance: + node_distance[node] = { 'center': center, 'distance' : distance } + elif distance < node_distance[node]['distance']: + node_distance[node] = { 'center': center, 'distance' : distance } + + write(name, 'delete from temp_vd_topology') + + # reorganize the distance result + center_node: dict[str, list[str]] = {} + for node, value in node_distance.items(): + if value['center'] not in center_node: + center_node[value['center']] = [] + center_node[value['center']].append(node) + + vds: list[dict[str, Any]] = [] + + for center, value in center_node.items(): + vds.append({ 'center': center, 'nodes': value }) + + return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes } diff --git a/api/s3_reservoirs.py b/api/s3_reservoirs.py index d3a3d7f..992b50b 100644 --- a/api/s3_reservoirs.py +++ b/api/s3_reservoirs.py @@ -38,7 +38,6 @@ class Reservoir(object): self.f_type = f"'{self.type}'" self.f_id = f"'{self.id}'" - self.f_coord = f"'({self.x}, {self.y})'" self.f_head = self.head self.f_pattern = f"'{self.pattern}'" if self.pattern != None else 'null' @@ -49,7 +48,7 @@ class Reservoir(object): return { 'type': self.type, 'id': self.id } -def set_reservoir_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_reservoir(name: str, cs: ChangeSet) -> DbChangeSet: old = Reservoir(get_reservoir(name, cs.operations[0]['id'])) raw_new = get_reservoir(name, cs.operations[0]['id']) @@ -61,9 +60,9 @@ def set_reservoir_cmd(name: str, cs: ChangeSet) -> DbChangeSet: new = Reservoir(raw_new) redo_sql = f"update reservoirs set head = {new.f_head}, pattern = {new.f_pattern} where id = {new.f_id};" - redo_sql += f"\nupdate coordinates set coord = {new.f_coord} where node = {new.f_id};" + redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}" - undo_sql = f"update coordinates set coord = {old.f_coord} where node = {old.f_id};" + undo_sql = sql_update_coord(old.id, old.x, old.y) undo_sql += f"\nupdate reservoirs set head = {old.f_head}, pattern = {old.f_pattern} where id = {old.f_id};" redo_cs = g_update_prefix | new.as_dict() @@ -77,17 +76,17 @@ def set_reservoir(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_reservoir(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_reservoir_cmd(name, cs)) + return execute_command(name, _set_reservoir(name, cs)) -def add_reservoir_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_reservoir(name: str, cs: ChangeSet) -> DbChangeSet: new = Reservoir(cs.operations[0]) redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});" redo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({new.f_id}, {new.f_head}, {new.f_pattern});" - redo_sql += f"\ninsert into coordinates (node, coord) values ({new.f_id}, {new.f_coord});" + redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}" - undo_sql = f"delete from coordinates where node = {new.f_id};" + undo_sql = sql_delete_coord(new.id) undo_sql += f"\ndelete from reservoirs where id = {new.f_id};" undo_sql += f"\ndelete from _node where id = {new.f_id};" @@ -102,19 +101,19 @@ def add_reservoir(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_reservoir(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_reservoir_cmd(name, cs)) + return execute_command(name, _add_reservoir(name, cs)) -def delete_reservoir_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_reservoir(name: str, cs: ChangeSet) -> DbChangeSet: old = Reservoir(get_reservoir(name, cs.operations[0]['id'])) - redo_sql = f"delete from coordinates where node = {old.f_id};" + redo_sql = sql_delete_coord(old.id) redo_sql += f"\ndelete from reservoirs where id = {old.f_id};" redo_sql += f"\ndelete from _node where id = {old.f_id};" undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});" undo_sql += f"\ninsert into reservoirs (id, head, pattern) values ({old.f_id}, {old.f_head}, {old.f_pattern});" - undo_sql += f"\ninsert into coordinates (node, coord) values ({old.f_id}, {old.f_coord});" + undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}" redo_cs = g_delete_prefix | old.as_id_dict() undo_cs = g_add_prefix | old.as_dict() @@ -127,7 +126,7 @@ def delete_reservoir(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_reservoir(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_reservoir_cmd(name, cs)) + return execute_command(name, _delete_reservoir(name, cs)) #-------------------------------------------------------------- diff --git a/api/s4_tanks.py b/api/s4_tanks.py index 45341fe..837c9af 100644 --- a/api/s4_tanks.py +++ b/api/s4_tanks.py @@ -60,7 +60,6 @@ class Tank(object): self.f_type = f"'{self.type}'" self.f_id = f"'{self.id}'" - self.f_coord = f"'({self.x}, {self.y})'" self.f_elevation = self.elevation self.f_init_level = self.init_level self.f_min_level = self.min_level @@ -77,7 +76,7 @@ class Tank(object): return { 'type': self.type, 'id': self.id } -def set_tank_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_tank(name: str, cs: ChangeSet) -> DbChangeSet: old = Tank(get_tank(name, cs.operations[0]['id'])) raw_new = get_tank(name, cs.operations[0]['id']) @@ -89,9 +88,9 @@ def set_tank_cmd(name: str, cs: ChangeSet) -> DbChangeSet: new = Tank(raw_new) redo_sql = f"update tanks set elevation = {new.f_elevation}, init_level = {new.f_init_level}, min_level = {new.f_min_level}, max_level = {new.f_max_level}, diameter = {new.f_diameter}, min_vol = {new.f_min_vol}, vol_curve = {new.f_vol_curve}, overflow = {new.f_overflow} where id = {new.f_id};" - redo_sql += f"\nupdate coordinates set coord = {new.f_coord} where node = {new.f_id};" + redo_sql += f"\n{sql_update_coord(new.id, new.x, new.y)}" - undo_sql = f"update coordinates set coord = {old.f_coord} where node = {old.f_id};" + undo_sql = sql_update_coord(old.id, old.x, old.y) undo_sql += f"\nupdate tanks set elevation = {old.f_elevation}, init_level = {old.f_init_level}, min_level = {old.f_min_level}, max_level = {old.f_max_level}, diameter = {old.f_diameter}, min_vol = {old.f_min_vol}, vol_curve = {old.f_vol_curve}, overflow = {old.f_overflow} where id = {old.f_id};" redo_cs = g_update_prefix | new.as_dict() @@ -105,17 +104,17 @@ def set_tank(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_tank(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_tank_cmd(name, cs)) + return execute_command(name, _set_tank(name, cs)) -def add_tank_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_tank(name: str, cs: ChangeSet) -> DbChangeSet: new = Tank(cs.operations[0]) redo_sql = f"insert into _node (id, type) values ({new.f_id}, {new.f_type});" redo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({new.f_id}, {new.f_elevation}, {new.f_init_level}, {new.f_min_level}, {new.f_max_level}, {new.f_diameter}, {new.f_min_vol}, {new.f_vol_curve}, {new.f_overflow});" - redo_sql += f"\ninsert into coordinates (node, coord) values ({new.f_id}, {new.f_coord});" + redo_sql += f"\n{sql_insert_coord(new.id, new.x, new.y)}" - undo_sql = f"delete from coordinates where node = {new.f_id};" + undo_sql = sql_delete_coord(new.id) undo_sql += f"\ndelete from tanks where id = {new.f_id};" undo_sql += f"\ndelete from _node where id = {new.f_id};" @@ -130,19 +129,19 @@ def add_tank(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_tank(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_tank_cmd(name, cs)) + return execute_command(name, _add_tank(name, cs)) -def delete_tank_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_tank(name: str, cs: ChangeSet) -> DbChangeSet: old = Tank(get_tank(name, cs.operations[0]['id'])) - redo_sql = f"delete from coordinates where node = {old.f_id};" + redo_sql = sql_delete_coord(old.id) redo_sql += f"\ndelete from tanks where id = {old.f_id};" redo_sql += f"\ndelete from _node where id = {old.f_id};" undo_sql = f"insert into _node (id, type) values ({old.f_id}, {old.f_type});" undo_sql += f"\ninsert into tanks (id, elevation, init_level, min_level, max_level, diameter, min_vol, vol_curve, overflow) values ({old.f_id}, {old.f_elevation}, {old.f_init_level}, {old.f_min_level}, {old.f_max_level}, {old.f_diameter}, {old.f_min_vol}, {old.f_vol_curve}, {old.f_overflow});" - undo_sql += f"\ninsert into coordinates (node, coord) values ({old.f_id}, {old.f_coord});" + undo_sql += f"\n{sql_insert_coord(old.id, old.x, old.y)}" redo_cs = g_delete_prefix | old.as_id_dict() undo_cs = g_add_prefix | old.as_dict() @@ -155,7 +154,7 @@ def delete_tank(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_tank(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_tank_cmd(name, cs)) + return execute_command(name, _delete_tank(name, cs)) #-------------------------------------------------------------- diff --git a/api/s5_pipes.py b/api/s5_pipes.py index 4c6c089..248e607 100644 --- a/api/s5_pipes.py +++ b/api/s5_pipes.py @@ -63,7 +63,7 @@ class Pipe(object): return { 'type': self.type, 'id': self.id } -def set_pipe_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_pipe(name: str, cs: ChangeSet) -> DbChangeSet: old = Pipe(get_pipe(name, cs.operations[0]['id'])) raw_new = get_pipe(name, cs.operations[0]['id']) @@ -88,10 +88,10 @@ def set_pipe(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pipe(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_pipe_cmd(name, cs)) + return execute_command(name, _set_pipe(name, cs)) -def add_pipe_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_pipe(name: str, cs: ChangeSet) -> DbChangeSet: new = Pipe(cs.operations[0]) redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});" @@ -111,10 +111,10 @@ def add_pipe(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pipe(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_pipe_cmd(name, cs)) + return execute_command(name, _add_pipe(name, cs)) -def delete_pipe_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_pipe(name: str, cs: ChangeSet) -> DbChangeSet: old = Pipe(get_pipe(name, cs.operations[0]['id'])) redo_sql = f"delete from pipes where id = {old.f_id};" @@ -134,7 +134,7 @@ def delete_pipe(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pipe(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_pipe_cmd(name, cs)) + return execute_command(name, _delete_pipe(name, cs)) #-------------------------------------------------------------- diff --git a/api/s6_pumps.py b/api/s6_pumps.py index 1f73cfb..d57a2c4 100644 --- a/api/s6_pumps.py +++ b/api/s6_pumps.py @@ -54,7 +54,7 @@ class Pump(object): return { 'type': self.type, 'id': self.id } -def set_pump_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_pump(name: str, cs: ChangeSet) -> DbChangeSet: old = Pump(get_pump(name, cs.operations[0]['id'])) raw_new = get_pump(name, cs.operations[0]['id']) @@ -79,10 +79,10 @@ def set_pump(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pump(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_pump_cmd(name, cs)) + return execute_command(name, _set_pump(name, cs)) -def add_pump_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_pump(name: str, cs: ChangeSet) -> DbChangeSet: new = Pump(cs.operations[0]) redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});" @@ -102,10 +102,10 @@ def add_pump(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pump(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_pump_cmd(name, cs)) + return execute_command(name, _add_pump(name, cs)) -def delete_pump_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_pump(name: str, cs: ChangeSet) -> DbChangeSet: old = Pump(get_pump(name, cs.operations[0]['id'])) redo_sql = f"delete from pumps where id = {old.f_id};" @@ -125,7 +125,7 @@ def delete_pump(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_pump(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_pump_cmd(name, cs)) + return execute_command(name, _delete_pump(name, cs)) #-------------------------------------------------------------- diff --git a/api/s7_valves.py b/api/s7_valves.py index c828916..64f79bc 100644 --- a/api/s7_valves.py +++ b/api/s7_valves.py @@ -62,7 +62,7 @@ class Valve(object): return { 'type': self.type, 'id': self.id } -def set_valve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_valve(name: str, cs: ChangeSet) -> DbChangeSet: old = Valve(get_valve(name, cs.operations[0]['id'])) raw_new = get_valve(name, cs.operations[0]['id']) @@ -87,10 +87,10 @@ def set_valve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_valve(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, set_valve_cmd(name, cs)) + return execute_command(name, _set_valve(name, cs)) -def add_valve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _add_valve(name: str, cs: ChangeSet) -> DbChangeSet: new = Valve(cs.operations[0]) redo_sql = f"insert into _link (id, type) values ({new.f_id}, {new.f_type});" @@ -110,10 +110,10 @@ def add_valve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_valve(name, cs.operations[0]['id']) != {}: return ChangeSet() - return execute_command(name, add_valve_cmd(name, cs)) + return execute_command(name, _add_valve(name, cs)) -def delete_valve_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _delete_valve(name: str, cs: ChangeSet) -> DbChangeSet: old = Valve(get_valve(name, cs.operations[0]['id'])) redo_sql = f"delete from valves where id = {old.f_id};" @@ -133,7 +133,7 @@ def delete_valve(name: str, cs: ChangeSet) -> ChangeSet: return ChangeSet() if get_valve(name, cs.operations[0]['id']) == {}: return ChangeSet() - return execute_command(name, delete_valve_cmd(name, cs)) + return execute_command(name, _delete_valve(name, cs)) #-------------------------------------------------------------- diff --git a/api/s8_tags.py b/api/s8_tags.py index aa2ed7a..9f72610 100644 --- a/api/s8_tags.py +++ b/api/s8_tags.py @@ -53,7 +53,7 @@ class Tag(object): return { 'type': self.type, 't_type': self.t_type, 'id': self.id, 'tag': self.tag } -def set_tag_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_tag(name: str, cs: ChangeSet) -> DbChangeSet: old = Tag(get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id'])) raw_new = get_tag(name, cs.operations[0]['t_type'], cs.operations[0]['id']) @@ -89,7 +89,7 @@ def set_tag_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_tag(name: str, cs: ChangeSet) -> ChangeSet: if 't_type' not in cs.operations[0] or 'id' not in cs.operations[0] or 'tag' not in cs.operations[0]: return ChangeSet() - return execute_command(name, set_tag_cmd(name, cs)) + return execute_command(name, _set_tag(name, cs)) def inp_in_tag(line: str) -> str: diff --git a/api/s9_demands.py b/api/s9_demands.py index e1465e4..de3de41 100644 --- a/api/s9_demands.py +++ b/api/s9_demands.py @@ -20,7 +20,7 @@ def get_demand(name: str, junction: str) -> dict[str, Any]: return { 'junction': junction, 'demands': ds } -def set_demand_cmd(name: str, cs: ChangeSet) -> DbChangeSet: +def _set_demand(name: str, cs: ChangeSet) -> DbChangeSet: junction = cs.operations[0]['junction'] old = get_demand(name, junction) new = { 'junction': junction, 'demands': [] } @@ -56,7 +56,7 @@ def set_demand_cmd(name: str, cs: ChangeSet) -> DbChangeSet: def set_demand(name: str, cs: ChangeSet) -> ChangeSet: - return execute_command(name, set_demand_cmd(name, cs)) + return execute_command(name, _set_demand(name, cs)) #-------------------------------------------------------------- diff --git a/create_template.py b/create_template.py index 50ff02b..c0260c2 100644 --- a/create_template.py +++ b/create_template.py @@ -33,11 +33,13 @@ sql_create = [ "script/sql/create/29.scada_device.sql", "script/sql/create/30.scada_device_data.sql", "script/sql/create/31.scada_element.sql", + "script/sql/create/32.region.sql", "script/sql/create/operation.sql" ] sql_drop = [ "script/sql/drop/operation.sql", + "script/sql/drop/32.region.sql", "script/sql/drop/31.scada_element.sql", "script/sql/drop/30.scada_device_data.sql", "script/sql/drop/29.scada_device.sql", @@ -78,6 +80,8 @@ def create_template(): cur.execute("create database project") with pg.connect(conninfo="dbname=project host=127.0.0.1") as conn: with conn.cursor() as cur: + cur.execute('create extension postgis cascade') + cur.execute('create extension pgrouting cascade') for sql in sql_create: with open(sql, "r") as f: cur.execute(f.read()) diff --git a/script/sql/create/24.coordinates.sql b/script/sql/create/24.coordinates.sql index 692bca7..945acef 100644 --- a/script/sql/create/24.coordinates.sql +++ b/script/sql/create/24.coordinates.sql @@ -3,10 +3,9 @@ create table coordinates ( node varchar(32) primary key references _node(id) -, coord point not null +, coord geometry ); -- delete when delete node -create index coordinates_spgist on coordinates using spgist(coord); create index coordinates_gist on coordinates using gist(coord); diff --git a/script/sql/create/32.region.sql b/script/sql/create/32.region.sql new file mode 100644 index 0000000..a8dff0f --- /dev/null +++ b/script/sql/create/32.region.sql @@ -0,0 +1,37 @@ +create table region +( + id text primary key +, boundary geometry not null unique +); + +create index region_gist on region using gist(boundary); + + +create table temp_region +( + id text primary key +, boundary geometry not null unique +); + +create index temp_region_gist on temp_region using gist(boundary); + + +create table temp_node +( + node varchar(32) primary key references _node(id) +); + + +create table temp_link +( + link varchar(32) primary key references _link(id) +); + + +create table temp_vd_topology +( + id serial +, source integer +, target integer +, cost numeric +); diff --git a/script/sql/drop/24.coordinates.sql b/script/sql/drop/24.coordinates.sql index b7f86b0..00c63ee 100644 --- a/script/sql/drop/24.coordinates.sql +++ b/script/sql/drop/24.coordinates.sql @@ -2,6 +2,4 @@ drop index if exists coordinates_gist; -drop index if exists coordinates_spgist; - drop table if exists coordinates; diff --git a/script/sql/drop/32.region.sql b/script/sql/drop/32.region.sql new file mode 100644 index 0000000..693aa07 --- /dev/null +++ b/script/sql/drop/32.region.sql @@ -0,0 +1,13 @@ +drop table if exists temp_vd_topology; + +drop table if exists temp_link; + +drop table if exists temp_node; + +drop index if exists temp_region_gist; + +drop table if exists temp_region; + +drop index if exists region_gist; + +drop table if exists region; diff --git a/test_tjnetwork.py b/test_tjnetwork.py index 2d0ddaa..a287a7d 100644 --- a/test_tjnetwork.py +++ b/test_tjnetwork.py @@ -19,6 +19,21 @@ class TestApi: delete_project(p) + # encoding + + + def test_utf8(self): + p = 'test_utf8' + self.enter(p) + + write(p, f"create table {p} (a varchar(32))") + write(p, f"insert into {p} values ('你好')") + result = read_all(p, f"select * from { p}") + assert result == [{'a': '你好'}] + + self.leave(p) + + # project @@ -5564,5 +5579,723 @@ class TestApi: self.leave(p) + # 32 region_util + + + def test_get_nodes_in_boundary(self): + p = 'test_get_nodes_in_boundary' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + boundary = calculate_boundary(p, vds[0]['nodes']) + boundary = inflate_boundary(p, boundary) + nodes = get_nodes_in_boundary(p, boundary) + assert nodes == ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + + self.leave(p) + + + def test_get_nodes_in_region(self): + p = 'test_get_nodes_in_region' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + boundary = calculate_boundary(p, vds[0]['nodes']) + boundary = inflate_boundary(p, boundary) + + add_region(p, ChangeSet({'id': 'r', 'boundary': boundary})) + + nodes = get_nodes_in_region(p, 'r') + assert nodes == ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + + self.leave(p) + + + def test_calculate_convex_hull(self): + p = 'test_calculate_convex_hull' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + nodes = ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + ch = calculate_convex_hull(p, nodes) + assert ch == [(20.21, 17.53), (12.96, 21.31), (8.0, 27.53), (9.0, 27.85), (23.7, 22.76), (20.21, 17.53)] + + self.leave(p) + + + def test_calculate_boundary(self): + p = 'test_calculate_boundary' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + nodes = ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + b = calculate_boundary(p, nodes) + assert b == [(23.7, 22.76), (22.08, 23.1), (21.17, 23.32), (20.8, 23.4), (20.32, 21.57), (16.97, 21.28), (13.81, 22.94), (9.0, 27.85), (8.0, 27.53), (9.0, 27.85), (13.81, 22.94), (12.96, 21.31), (17.64, 18.92), (20.21, 17.53), (20.98, 19.18), (21.69, 21.28), (22.08, 23.1), (23.7, 22.76)] + + self.leave(p) + + + def test_inflate_boundary(self): + p = 'test_inflate_boundary' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + nodes = ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + b = calculate_boundary(p, nodes) + b = inflate_boundary(p, b) + assert b == [(20.57, 17.12), (21.44, 18.98), (21.45, 19.01), (22.17, 21.13), (22.18, 21.16), (22.46, 22.5), (24.09, 22.17), (24.29, 23.150000000000002), (22.19, 23.580000000000002), (22.2, 23.59), (21.28, 23.81), (20.71, 23.93), (20.37, 23.72), (19.92, 22.03), (17.06, 21.79), (14.120000000000001, 23.330000000000002), (9.26, 28.3), (8.98, 28.37), (7.37, 27.85), (7.68, 26.900000000000002), (8.85, 27.27), (13.19, 22.84), (12.42, 21.36), (12.55, 20.96), (17.41, 18.47), (20.16, 16.990000000000002), (20.57, 17.12)] + + self.leave(p) + + + def test_inflate_region(self): + p = 'test_inflate_region' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + boundary = calculate_boundary(p, vds[0]['nodes']) + add_region(p, ChangeSet({'id': 'r', 'boundary': boundary})) + b = inflate_region(p, 'r') + assert b == [(20.57, 17.12), (21.44, 18.98), (21.45, 19.01), (22.17, 21.13), (22.18, 21.16), (22.46, 22.5), (24.09, 22.17), (24.29, 23.150000000000002), (22.19, 23.580000000000002), (22.2, 23.59), (21.28, 23.81), (20.71, 23.93), (20.37, 23.72), (19.92, 22.03), (17.06, 21.79), (14.120000000000001, 23.330000000000002), (9.26, 28.3), (8.98, 28.37), (7.37, 27.85), (7.68, 26.900000000000002), (8.85, 27.27), (13.19, 22.84), (12.42, 21.36), (12.55, 20.96), (17.41, 18.47), (20.16, 16.990000000000002), (20.57, 17.12)] + + self.leave(p) + + + # 33 region + + + def test_region(self): + p = 'test_region' + self.enter(p) + + r = get_region(p, 'r') + assert r == {} + + add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})) + r = get_region(p, 'r') + assert r == { 'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] } + + set_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)]})) + r = get_region(p, 'r') + assert r == { 'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] } + + delete_region(p, ChangeSet({'id': 'r'})) + r = get_region(p, 'r') + assert r == {} + + self.leave(p) + + + def test_region_op(self): + p = 'test_region_op' + self.enter(p) + + cs = add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = set_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)]})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = delete_region(p, ChangeSet({'id': 'r'})).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + self.leave(p) + + + # 34 water_distribution + + + def test_distribute_demand_to_nodes(self): + p = 'test_distribute_demand_to_nodes' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + nodes = vds[0]['nodes'] + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + distribute_demand_to_nodes(p, 100.0, nodes, DISTRIBUTION_TYPE_ADD) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + distribute_demand_to_nodes(p, -100.0, nodes, DISTRIBUTION_TYPE_ADD) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + distribute_demand_to_nodes(p, 100.0, nodes, DISTRIBUTION_TYPE_OVERRIDE) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + distribute_demand_to_nodes(p, -100.0, nodes, DISTRIBUTION_TYPE_OVERRIDE) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == -17.357291284684024 + assert demands[1] == -22.112211221122113 + assert demands[2] == -6.466202175773133 + assert demands[3] == -8.232489915658233 + assert demands[4] == -4.180418041804181 + assert demands[5] == -7.260726072607261 + assert demands[6] == -3.862608483070529 + assert demands[7] == -6.466202175773133 + assert demands[8] == -5.738907224055739 + assert demands[9] == -0.892311453367559 + assert demands[10] == -3.9665077618873 + assert demands[11] == -2.9275149737195942 + assert demands[12] == -2.1391027991688056 + assert demands[13] == -2.9275149737195942 + assert demands[14] == -5.469991443588803 + + self.leave(p) + + + def test_distribute_demand_to_nodes_op(self): + p = 'test_distribute_demand_to_nodes_op' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + nodes = vds[0]['nodes'] + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + cs = distribute_demand_to_nodes(p, 100.0, nodes, DISTRIBUTION_TYPE_ADD).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_undo(p).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + demands.reverse() + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + cs = execute_redo(p).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = distribute_demand_to_nodes(p, 100.0, nodes, DISTRIBUTION_TYPE_OVERRIDE).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_undo(p).operations + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + demands.reverse() + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_redo(p).operations + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + self.leave(p) + + + def test_distribute_demand_to_region(self): + p = 'test_distribute_demand_to_region' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + nodes = vds[0]['nodes'] + boundary = calculate_boundary(p, nodes) + boundary = inflate_boundary(p, boundary, 0.1) + add_region(p, ChangeSet({'id': 'r', 'boundary': boundary})) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + distribute_demand_to_region(p, 100.0, 'r', DISTRIBUTION_TYPE_ADD) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + distribute_demand_to_region(p, -100.0, 'r', DISTRIBUTION_TYPE_ADD) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + distribute_demand_to_region(p, 100.0, 'r', DISTRIBUTION_TYPE_OVERRIDE) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + distribute_demand_to_region(p, -100.0, 'r', DISTRIBUTION_TYPE_OVERRIDE) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == -17.357291284684024 + assert demands[1] == -22.112211221122113 + assert demands[2] == -6.466202175773133 + assert demands[3] == -8.232489915658233 + assert demands[4] == -4.180418041804181 + assert demands[5] == -7.260726072607261 + assert demands[6] == -3.862608483070529 + assert demands[7] == -6.466202175773133 + assert demands[8] == -5.738907224055739 + assert demands[9] == -0.892311453367559 + assert demands[10] == -3.9665077618873 + assert demands[11] == -2.9275149737195942 + assert demands[12] == -2.1391027991688056 + assert demands[13] == -2.9275149737195942 + assert demands[14] == -5.469991443588803 + + self.leave(p) + + + def test_distribute_demand_to_region_op(self): + p = 'test_distribute_demand_to_region_op' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + nodes = vds[0]['nodes'] + boundary = calculate_boundary(p, nodes) + boundary = inflate_boundary(p, boundary, 0.1) + add_region(p, ChangeSet({'id': 'r', 'boundary': boundary})) + + demands = [] + for node in nodes: + if not is_junction(p, node): + continue + ds = get_demand(p, node)['demands'] + demands.append(ds[0]['demand']) + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + cs = distribute_demand_to_region(p, 100.0, 'r', DISTRIBUTION_TYPE_ADD).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_undo(p).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + demands.reverse() + assert demands == [0.0, 189.95, 133.2, 135.37, 54.64, 231.4, 141.94, 52.1, 117.71, 176.13, 0.0, 0.0, 0.0, 0.0, 0.0] + + cs = execute_redo(p).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = distribute_demand_to_region(p, 100.0, 'r', DISTRIBUTION_TYPE_OVERRIDE).operations + + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_undo(p).operations + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + demands.reverse() + assert demands[0] == 17.357291284684024 + assert demands[1] == 212.0622112211221 + assert demands[2] == 139.66620217577312 + assert demands[3] == 143.60248991565823 + assert demands[4] == 58.82041804180418 + assert demands[5] == 238.66072607260728 + assert demands[6] == 145.80260848307051 + assert demands[7] == 58.566202175773135 + assert demands[8] == 123.44890722405573 + assert demands[9] == 177.02231145336756 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + cs = execute_redo(p).operations + demands = [] + for value in cs: + ds = value['demands'] + demands.append(ds[0]['demand']) + assert demands[0] == 17.357291284684024 + assert demands[1] == 22.112211221122113 + assert demands[2] == 6.466202175773133 + assert demands[3] == 8.232489915658233 + assert demands[4] == 4.180418041804181 + assert demands[5] == 7.260726072607261 + assert demands[6] == 3.862608483070529 + assert demands[7] == 6.466202175773133 + assert demands[8] == 5.738907224055739 + assert demands[9] == 0.892311453367559 + assert demands[10] == 3.9665077618873 + assert demands[11] == 2.9275149737195942 + assert demands[12] == 2.1391027991688056 + assert demands[13] == 2.9275149737195942 + assert demands[14] == 5.469991443588803 + + self.leave(p) + + + # 35 district_metering_area + + + def test_calculate_district_metering_area(self): + p = 'test_calculate_district_metering_area' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + dmas = calculate_district_metering_area(p, get_nodes(p), 3) + assert len(dmas) == 3 + assert dmas[0] == ['173', '184', '185', '199', '2', '201', '203', '205', '206', '207', '208', '209', '211', '213', '215', '217', '219', '225', '229', '231', '237', '239', '241', '243', '247', '249', '251', '253', '255', '273', '275', '50'] + assert dmas[1] == ['1', '10', '101', '103', '109', '111', '113', '161', '163', '164', '166', '167', '169', '171', '179', '181', '183', '187', '189', '191', '193', '195', '197', '204', '265', '267', '269', '271', '35', '40', 'Lake'] + assert dmas[2] == ['105', '107', '115', '117', '119', '120', '121', '123', '125', '127', '129', '131', '139', '141', '143', '145', '147', '149', '15', '151', '153', '157', '159', '20', '257', '259', '261', '263', '3', '60', '601', '61', 'River'] + + self.leave(p) + + + # 36 service_area + + + def test_calculate_service_area(self): + p = 'test_calculate_service_area' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + result = calculate_service_area(p, 1) + sas = result['service_areas'] + assert len(sas) == 3 + assert sas[0]['source'] == 'River' + assert sas[0]['nodes'] == ['River', '60', '61', '123', '601', '121', '120', '119', '125', '151', '157', '127', '153', '149', '159', '20', '129', '147', '161', '3', '131', '139', '145', '163', '195', '141', '164', '265', '143', '166', '169', '15', '167', '171', '269', '173', '271', '199', '181', '201', '273', '35', '177', '203', '275'] + assert sas[1]['source'] == 'Lake' + assert sas[1]['nodes'] == ['117', '115', '111', '113', '197', '193', '191', '267', '187', '189', '204', '183', '185', '179', '184', '40', '205', '1', '207', '206', '208', '209', '211', '213', '237', '215', '229', '239', '217', '231', '241', '249', '219', '225', '243', 'Lake', '10', '101', '103', '105', '109', '107', '263', '259', '261', '257'] + assert sas[2]['source'] == '2' + assert sas[2]['nodes'] == ['247', '2', '50', '255', '253', '251'] + + nss = result['concentrations'] + assert nss == {'River': {'River': 1.0}, '60': {'River': 1.0}, '61': {'River': 1.0}, '123': {'River': 1.0}, '601': {'River': 1.0}, '121': {'River': 1.0}, '120': {'River': 0.8482133936739926, 'Lake': 0.15178660632600738}, '119': {'River': 0.9996314569097752, 'Lake': 0.0003685430902247304}, '125': {'River': 1.0}, '117': {'River': 0.3008743130788367, 'Lake': 0.6991256869211634}, '151': {'River': 0.9997011779067223, 'Lake': 0.00029882209327779035}, '157': {'River': 0.9996314569097753, 'Lake': 0.00036854309022473045}, '127': {'River': 1.0}, '153': {'River': 1.0}, '115': {'River': 0.1925947954811129, 'Lake': 0.8074052045188872}, '149': {'River': 0.9997011779067222, 'Lake': 0.0002988220932777903}, '159': {'River': 0.9996314569097753, 'Lake': 0.00036854309022473045}, '20': {'River': 1.0}, '129': {'River': 1.0}, '111': {'River': 0.05905486831242531, 'Lake': 0.9409451316875747}, '113': {'River': 0.0957651873908324, 'Lake': 0.9042348126091676}, '147': {'River': 0.9997011779067222, 'Lake': 0.0002988220932777903}, '161': {'River': 0.9996314569097752, 'Lake': 0.0003685430902247304}, '3': {'River': 1.0}, '131': {'River': 1.0}, '139': {'River': 1.0}, '197': {'River': 0.05905486831242531, 'Lake': 0.9409451316875747}, '193': {'River': 0.09576518739083238, 'Lake': 0.9042348126091676}, '145': {'River': 0.9997011779067223, 'Lake': 0.0002988220932777903}, '163': {'River': 0.9996314569097753, 'Lake': 0.00036854309022473045}, '195': {'River': 0.9996314569097754, 'Lake': 0.00036854309022473045}, '141': {'River': 0.9998401927344082, 'Lake': 0.00015980726559174143}, '191': {'River': 0.06331784947523231, 'Lake': 0.9366821505247677}, '267': {'River': 0.09576518739083238, 'Lake': 0.9042348126091676}, '164': {'River': 0.9996314569097753, 'Lake': 0.00036854309022473045}, '265': {'River': 0.9906312413668901, 'Lake': 0.00936875863310988}, '143': {'River': 0.9998401927344083, 'Lake': 0.00015980726559174143}, '187': {'River': 0.06331784947523231, 'Lake': 0.9366821505247677}, '189': {'River': 0.09256760501206042, 'Lake': 0.9074323949879396}, '166': {'River': 0.9996314569097753, 'Lake': 0.00036854309022473045}, '169': {'River': 0.9906312413668901, 'Lake': 0.00936875863310988}, '15': {'River': 0.9998401927344083, 'Lake': 0.00015980726559174143}, '204': {'River': 0.06331784947523231, 'Lake': 0.9366821505247677}, '183': {'River': 0.09256760501206042, 'Lake': 0.9074323949879396}, '167': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '171': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '269': {'River': 0.9906312413668901, 'Lake': 0.00936875863310988}, '185': {'River': 0.06961984624591949, 'Lake': 0.9303801537540806}, '179': {'River': 0.09256760501206042, 'Lake': 0.9074323949879396}, '173': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '271': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '184': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '40': {'River': 0.09256760501206042, 'Lake': 0.9074323949879396}, '199': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '181': {'River': 0.9906312413668901, 'Lake': 0.009368758633109882}, '205': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '1': {'River': 0.09256760501206042, 'Lake': 0.9074323949879396}, '201': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '273': {'River': 0.9906312413668902, 'Lake': 0.009368758633109882}, '35': {'River': 0.9906312413668901, 'Lake': 0.009368758633109882}, '177': {'River': 0.9906312413668901, 'Lake': 0.009368758633109882}, '207': {'River': 0.06961984624591949, 'Lake': 0.9303801537540806}, '203': {'River': 0.9906312413668901, 'Lake': 0.00936875863310988}, '275': {'River': 0.9906312413668901, 'Lake': 0.009368758633109882}, '206': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '208': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '209': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '211': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '213': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '237': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '215': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '229': {'River': 0.06961984624591946, 'Lake': 0.9303801537540805}, '239': {'River': 0.06961984624591946, 'Lake': 0.9303801537540805}, '217': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '231': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '241': {'River': 0.06961984624591946, 'Lake': 0.9303801537540805}, '249': {'River': 0.06794376025334521, 'Lake': 0.9079814093218103, '2': 0.024074830424844474}, '219': {'River': 0.06961984624591948, 'Lake': 0.9303801537540805}, '225': {'River': 0.06961984624591948, 'Lake': 0.9303801537540806}, '243': {'River': 0.06961984624591946, 'Lake': 0.9303801537540805}, '247': {'River': 0.030503133134582493, 'Lake': 0.40763534000762625, '2': 0.5618615268577912}, 'Lake': {'Lake': 1.0}, '10': {'Lake': 1.0}, '101': {'Lake': 1.0}, '103': {'Lake': 1.0}, '105': {'Lake': 1.0}, '109': {'Lake': 1.0}, '107': {'Lake': 1.0}, '263': {'Lake': 1.0}, '259': {'Lake': 1.0}, '261': {'Lake': 1.0}, '257': {'Lake': 1.0}, '2': {'2': 1.0}, '50': {'2': 1.0}, '255': {'2': 1.0}, '253': {'2': 1.0}, '251': {'2': 1.0}} + + self.leave(p) + + + # 37 virtual_district + + + def test_calculate_virtual_district(self): + p = 'test_calculate_virtual_district' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + result = calculate_virtual_district(p, ['107', '139', '267', '211']) + assert result['isolated_nodes'] == [] + vds = result['virtual_districts'] + assert len(vds) == 4 + assert vds[0]['center'] == '107' + assert vds[0]['nodes'] == ['10', '101', '103', '105', '107', '109', '111', '115', '117', '119', '120', '257', '259', '261', '263', 'Lake'] + assert vds[1]['center'] == '139' + assert vds[1]['nodes'] == ['15', '20', '60', '601', '61', '121', '123', '125', '127', '129', '131', '139', '141', '143', '145', '147', '149', '151', '153', 'River', '3'] + assert vds[2]['center'] == '267' + assert vds[2]['nodes'] == ['35', '40', '113', '157', '159', '161', '163', '164', '166', '167', '169', '171', '173', '177', '179', '181', '183', '184', '185', '187', '189', '191', '193', '195', '197', '204', '265', '267', '269', '271', '1'] + assert vds[3]['center'] == '211' + assert vds[3]['nodes'] == ['50', '199', '201', '203', '205', '206', '207', '208', '209', '211', '213', '215', '217', '219', '225', '229', '231', '237', '239', '241', '243', '247', '249', '251', '253', '255', '273', '275', '2'] + + self.leave(p) + + if __name__ == '__main__': pytest.main() diff --git a/tjnetwork.py b/tjnetwork.py index 76e06eb..208fe9c 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -151,18 +151,26 @@ SCADA_DEVICE_TYPE_LEVEL = api.SCADA_DEVICE_TYPE_LEVEL SCADA_DEVICE_TYPE_FLOW = api.SCADA_DEVICE_TYPE_FLOW -SCADA_MODEL_TYPE_JUNCTION = api.SCADA_MODEL_TYPE_JUNCTION +SCADA_MODEL_TYPE_JUNCTION = api.SCADA_MODEL_TYPE_JUNCTION SCADA_MODEL_TYPE_RESERVOIR = api.SCADA_MODEL_TYPE_RESERVOIR -SCADA_MODEL_TYPE_TANK = api.SCADA_MODEL_TYPE_TANK -SCADA_MODEL_TYPE_PIPE = api.SCADA_MODEL_TYPE_PIPE -SCADA_MODEL_TYPE_PUMP = api.SCADA_MODEL_TYPE_PUMP -SCADA_MODEL_TYPE_VALVE = api.SCADA_MODEL_TYPE_VALVE +SCADA_MODEL_TYPE_TANK = api.SCADA_MODEL_TYPE_TANK +SCADA_MODEL_TYPE_PIPE = api.SCADA_MODEL_TYPE_PIPE +SCADA_MODEL_TYPE_PUMP = api.SCADA_MODEL_TYPE_PUMP +SCADA_MODEL_TYPE_VALVE = api.SCADA_MODEL_TYPE_VALVE SCADA_ELEMENT_STATUS_ONLINE = api.SCADA_ELEMENT_STATUS_ONLINE SCADA_ELEMENT_STATUS_OFFLINE = api.SCADA_ELEMENT_STATUS_OFFLINE +DISTRIBUTION_TYPE_ADD = api.DISTRIBUTION_TYPE_ADD +DISTRIBUTION_TYPE_OVERRIDE = api.DISTRIBUTION_TYPE_OVERRIDE + + +PARTITION_TYPE_RB = api.PARTITION_TYPE_RB +PARTITION_TYPE_KWAY = api.PARTITION_TYPE_KWAY + + ############################################################ # project ############################################################ @@ -300,6 +308,18 @@ def set_restore_operation_to_current(name: str) -> None: def restore(name: str, discard: bool = False) -> ChangeSet: return api.restore(name, discard) +def read(name: str, sql: str): + return api.read(name, sql) + +def try_read(name: str, sql: str): + return api.try_read(name, sql) + +def read_all(name: str, sql: str): + return api.read_all(name, sql) + +def write(name: str, sql: str): + return api.write(name, sql) + ############################################################ # type @@ -772,7 +792,7 @@ def get_option(name: str) -> dict[str, Any]: return api.get_option(name) def set_option(name: str, cs: ChangeSet) -> ChangeSet: - return api.set_option(name, cs) + return api.set_option_ex(name, cs) ############################################################ @@ -786,7 +806,7 @@ def get_option_v3(name: str) -> dict[str, Any]: return api.get_option_v3(name) def set_option_v3(name: str, cs: ChangeSet) -> ChangeSet: - return api.set_option_v3(name, cs) + return api.set_option_v3_ex(name, cs) ############################################################ @@ -933,3 +953,82 @@ def delete_scada_element(name: str, cs: ChangeSet) -> ChangeSet: def clean_scada_element(name: str) -> ChangeSet: return api.clean_scada_element(name) + + +############################################################ +# region_util 32 +############################################################ + +def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]: + return api.get_nodes_in_boundary(name, boundary) + +def get_nodes_in_region(name: str, region_id: str) -> list[str]: + return api.get_nodes_in_region(name, region_id) + +def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]: + return api.calculate_convex_hull(name, nodes) + +def calculate_boundary(name: str, nodes: list[str]) -> list[tuple[float, float]]: + return api.calculate_boundary(name, nodes) + +def inflate_boundary(name: str, boundary: list[tuple[float, float]], delta: float = 0.5) -> list[tuple[float, float]]: + return api.inflate_boundary(name, boundary, delta) + +def inflate_region(name: str, region_id: str, delta: float = 0.5) -> list[tuple[float, float]]: + return api.inflate_region(name, region_id, delta) + + +############################################################ +# general_region 33 +############################################################ + +def get_region_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_region_schema(name) + +def get_region(name: str, id: str) -> dict[str, Any]: + return api.get_region(name, id) + +def set_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_region(name, cs) + +# example: add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})) +def add_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.add_region(name, cs) + +def delete_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.delete_region(name, cs) + + +############################################################ +# water_distribution 34 +############################################################ + +def distribute_demand_to_nodes(name: str, demand: float, nodes: list[str], type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet: + return api.distribute_demand_to_nodes(name, demand, nodes, type) + +def distribute_demand_to_region(name: str, demand: float, region: str, type: str = DISTRIBUTION_TYPE_ADD) -> ChangeSet: + return api.distribute_demand_to_region(name, demand, region, type) + + +############################################################ +# district_metering_area 35 +############################################################ + +def calculate_district_metering_area(name: str, nodes: list[str], part_count: int = 1, part_type: int = PARTITION_TYPE_RB) -> list[list[str]]: + return api.calculate_district_metering_area(name, nodes, part_count, part_type) + + +############################################################ +# service_area 36 +############################################################ + +def calculate_service_area(name: str, time_index: int = 0) -> dict[str, Any]: + return api.calculate_service_area(name, time_index) + + +############################################################ +# virtual_district 37 +############################################################ + +def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[Any]]: + return api.calculate_virtual_district(name, centers)