diff --git a/api/__init__.py b/api/__init__.py index 41455dd..95cc492 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -139,19 +139,22 @@ from .s31_scada_element import get_all_scada_element_ids, get_all_scada_elements 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 .s32_region import get_region_schema, get_region, set_region, add_region, delete_region from .s33_dma_cal import PARTITION_TYPE_RB, PARTITION_TYPE_KWAY from .s33_dma_cal import calculate_district_metering_area_for_nodes, calculate_district_metering_area_for_region, calculate_district_metering_area_for_network - from .s33_dma import get_district_metering_area_schema, get_district_metering_area, set_district_metering_area, add_district_metering_area, delete_district_metering_area from .s33_dma import get_all_district_metering_area_ids, get_all_district_metering_areas - from .s33_dma_gen import generate_district_metering_area, generate_sub_district_metering_area -from .s34_sa import calculate_service_area +from .s34_sa_cal import calculate_service_area +from .s34_sa import get_service_area_schema, get_service_area, set_service_area, add_service_area, delete_service_area +from .s34_sa import get_all_service_area_ids, get_all_service_areas +from .s34_sa_gen import generate_service_area -from .s35_vd import calculate_virtual_district +from .s35_vd_cal import calculate_virtual_district +from .s35_vd import get_virtual_district_schema, get_virtual_district, set_virtual_district, add_virtual_district, delete_virtual_district +from .s35_vd import get_all_virtual_district_ids, get_all_virtual_districts +from .s35_vd_gen import generate_virtual_district from .s36_wda_cal import calculate_demand_to_nodes, calculate_demand_to_region, calculate_demand_to_network diff --git a/api/batch_exe.py b/api/batch_exe.py index 49c5001..080688e 100644 --- a/api/batch_exe.py +++ b/api/batch_exe.py @@ -32,6 +32,8 @@ from .s30_scada_device_data import set_scada_device_data, add_scada_device_data, from .s31_scada_element import set_scada_element, add_scada_element, delete_scada_element from .s32_region import set_region, add_region, delete_region from .s33_dma import set_district_metering_area, add_district_metering_area, delete_district_metering_area +from .s34_sa import set_service_area, add_service_area, delete_service_area +from .s35_vd import set_virtual_district, add_virtual_district, delete_virtual_district from .batch_api_cs import rewrite_batch_api @@ -112,6 +114,10 @@ def _execute_add_command(name: str, cs: ChangeSet) -> ChangeSet: return add_region(name, cs) elif type == s33_dma: return add_district_metering_area(name, cs) + elif type == s34_sa: + return add_service_area(name, cs) + elif type == s35_vd: + return add_virtual_district(name, cs) return ChangeSet() @@ -195,6 +201,10 @@ def _execute_update_command(name: str, cs: ChangeSet) -> ChangeSet: return set_region(name, cs) elif type == s33_dma: return set_district_metering_area(name, cs) + elif type == s34_sa: + return set_service_area(name, cs) + elif type == s35_vd: + return set_virtual_district(name, cs) return ChangeSet() @@ -276,6 +286,10 @@ def _execute_delete_command(name: str, cs: ChangeSet) -> ChangeSet: return delete_region(name, cs) elif type == s33_dma: return delete_district_metering_area(name, cs) + elif type == s34_sa: + return delete_service_area(name, cs) + elif type == s35_vd: + return delete_virtual_district(name, cs) return ChangeSet() diff --git a/api/s33_dma.py b/api/s33_dma.py index 838ed0f..49198c9 100644 --- a/api/s33_dma.py +++ b/api/s33_dma.py @@ -7,7 +7,7 @@ def get_district_metering_area_schema(name: str) -> dict[str, dict[str, Any]]: return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, 'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False }, 'parent' : {'type': 'str' , 'optional': True , 'readonly': False }, - 'level' : {'type': 'str' , 'optional': False , 'readonly': True } } + 'level' : {'type': 'int' , 'optional': False , 'readonly': True } } def get_district_metering_area(name: str, id: str) -> dict[str, Any]: @@ -29,7 +29,7 @@ def get_district_metering_area(name: str, id: str) -> dict[str, Any]: return dma -# no update for nodes + def _set_district_metering_area(name: str, cs: ChangeSet) -> DbChangeSet: id = cs.operations[0]['id'] diff --git a/api/s34_sa.py b/api/s34_sa.py index ec012e5..ec54c2c 100644 --- a/api/s34_sa.py +++ b/api/s34_sa.py @@ -1,119 +1,217 @@ -import sys -import json -from queue import Queue from .database import * -from .s0_base import get_node_links, get_link_nodes +from .s0_base import is_node +from .s32_region_util import to_postgis_polygon +from .s32_region import get_region -sys.path.append('..') -from epanet.epanet import run_project +def get_service_area_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False }, + 'source' : {'type': 'str' , 'optional': False , 'readonly': False }, + 'time_index' : {'type': 'int' , 'optional': False , 'readonly': False } } -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: +def get_service_area(name: str, id: str) -> dict[str, Any]: + sa = get_region(name, id) + if sa == {}: return {} + r = try_read(name, f"select * from region_sa where id = '{id}'") + if r == None: + return {} + sa['source'] = r['source'] + sa['nodes'] = list(eval(r['nodes'])) + sa['time_index'] = r['time_index'] + return sa - 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']] = [] +def _set_service_area(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] - 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']) + new_boundary = cs.operations[0]['boundary'] + old_boundary = get_region(name, id)['boundary'] - # build source to nodes map - for source in sources: - queue = Queue() - queue.put(source) + new_source = cs.operations[0]['source'] + f_new_source = f"'{new_source}'" - while not queue.empty(): - cursor = queue.get() - if cursor not in sources[source]: - sources[source].append(cursor) + new_nodes = cs.operations[0]['nodes'] + str_new_nodes = str(new_nodes).replace("'", "''") - 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) + new_time_index = cs.operations[0]['time_index'] - # 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) + old = get_service_area(name, id) + old_source = old['source'] + f_old_source = f"'{old_source}'" - # 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 + old_nodes = old['nodes'] + str_old_nodes = str(old_nodes).replace("'", "''") - node_upstream : dict[str, list[tuple[str, str]]] = {} - for node in node_wip: - if node not in node_upstream: - node_upstream[node] = [] + old_time_index = old['time_index'] - 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)) + redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';" + redo_sql += f"update region_sa set time_index = {new_time_index}, source = {f_new_source}, nodes = '{str_new_nodes}' where id = '{id}';" - 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]]) + undo_sql = f"update region_sa set time_index = {old_time_index}, source = {f_old_source}, nodes = '{str_old_nodes}' where id = '{id}';" + undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';" - # 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 + redo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': new_boundary, 'time_index': new_time_index, 'source': new_source, 'nodes': new_nodes } + undo_cs = g_update_prefix | { 'type': 'service_area', 'id': id, 'boundary': old_boundary, 'time_index': old_time_index, 'source': old_source, 'nodes': old_nodes } - done.append(node) + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) - 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) +def set_service_area(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations - sas: list[dict[str, Any]] = [] - for source, nodes in source_to_main_node.items(): - sas.append({ 'source': source, 'nodes': nodes }) + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] - return { 'service_areas' : sas, 'concentrations': concentration_map } + if 'id' not in op: + return ChangeSet() + + sa = get_service_area(name, op['id']) + if sa == {}: + return ChangeSet() + + if 'boundary' not in op: + op['boundary'] = sa['boundary'] + else: + b = op['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() + + if 'time_index' not in op: + op['time_index'] = sa['time_index'] + + if 'source' not in op: + op['source'] = sa['source'] + + if not is_node(name, op['source']): + return ChangeSet() + + if 'nodes' not in op: + op['nodes'] = sa['nodes'] + else: + for node in op['nodes']: + if not is_node(name, node): + return ChangeSet() + + return execute_command(name, _set_service_area(name, cs)) + + +def _add_service_area(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + + boundary = cs.operations[0]['boundary'] + + time_index = cs.operations[0]['time_index'] + + source = cs.operations[0]['source'] + f_source = f"'{source}'" + + nodes = cs.operations[0]['nodes'] + str_nodes = str(nodes).replace("'", "''") + + redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');" + redo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');" + + undo_sql = f"delete from region_sa where id = '{id}';" + undo_sql += f"delete from region where id = '{id}';" + + redo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes } + undo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def add_service_area(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations + + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] + + if 'id' not in op: + return ChangeSet() + + sa = get_service_area(name, op['id']) + if sa != {}: + return ChangeSet() + + if 'boundary' not in op: + return ChangeSet() + else: + b = op['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() + + if 'time_index' not in op: + return ChangeSet() + + if 'source' not in op: + return ChangeSet() + + if not is_node(name, op['source']): + return ChangeSet() + + if 'nodes' not in op: + op['nodes'] = [] + else: + for node in op['nodes']: + if not is_node(name, node): + return ChangeSet() + + return execute_command(name, _add_service_area(name, cs)) + + +def _delete_service_area(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + sa = get_service_area(name, id) + boundary = sa['boundary'] + time_index = sa['time_index'] + source = sa['source'] + f_source = f"'{source}'" + nodes = sa['nodes'] + str_nodes = str(nodes).replace("'", "''") + + redo_sql = f"delete from region_sa where id = '{id}';" + redo_sql += f"delete from region where id = '{id}';" + + undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'SA');" + undo_sql += f"insert into region_sa (id, time_index, source, nodes) values ('{id}', {time_index}, {f_source}, '{str_nodes}');" + + redo_cs = g_delete_prefix | { 'type': 'service_area', 'id': id } + undo_cs = g_add_prefix | { 'type': 'service_area', 'id': id, 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def delete_service_area(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations + + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] + + if 'id' not in op: + return ChangeSet() + + sa = get_service_area(name, op['id']) + if sa == {}: + return ChangeSet() + + return execute_command(name, _delete_service_area(name, cs)) + + +def get_all_service_area_ids(name: str) -> list[str]: + ids = [] + for row in read_all(name, f"select id from region_sa"): + ids.append(row['id']) + return ids + + +def get_all_service_areas(name: str) -> list[dict[str, Any]]: + result = [] + for id in get_all_service_area_ids(name): + result.append(get_service_area(name, id)) + return result diff --git a/api/s34_sa_cal.py b/api/s34_sa_cal.py new file mode 100644 index 0000000..88705c5 --- /dev/null +++ b/api/s34_sa_cal.py @@ -0,0 +1,132 @@ +import sys +import json +from queue import Queue +from .database import * +from .s0_base import get_node_links, get_link_nodes, get_nodes + +sys.path.append('..') +from epanet.epanet import run_project + + +def _calculate_service_area(name: str, inp, time_index: int = 0) -> dict[str, list[str]]: + 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) + + return sources + + # 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: + if link_node[1] not in concentration_map.keys(): + continue + 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 } + + +def calculate_service_area(name: str) -> list[dict[str, list[str]]]: + inp = json.loads(run_project(name)) + + result: list[dict[str, list[str]]] = [] + + time_count = len(inp['node_results'][0]['result']) + + for i in range(time_count): + sas = _calculate_service_area(name, inp, i) + result.append(sas) + + return result diff --git a/api/s34_sa_gen.py b/api/s34_sa_gen.py new file mode 100644 index 0000000..a500f47 --- /dev/null +++ b/api/s34_sa_gen.py @@ -0,0 +1,22 @@ +from .s32_region_util import calculate_boundary, inflate_boundary +from .s34_sa_cal import * +from .s34_sa import get_all_service_area_ids +from .batch_exe import execute_batch_command + +def generate_service_area(name: str) -> ChangeSet: + cs = ChangeSet() + + for id in get_all_service_area_ids(name): + cs.delete({'type': 'service_area', 'id': id}) + + sass = calculate_service_area(name) + + time_index = 0 + for sas in sass: + for source, nodes in sas.items(): + boundary = calculate_boundary(name, nodes) + boundary = inflate_boundary(name, boundary) + cs.add({ 'type': 'service_area', 'id': f"SA_{source}_{time_index}", 'boundary': boundary, 'time_index': time_index, 'source': source, 'nodes': nodes }) + time_index += 1 + + return execute_batch_command(name, cs) diff --git a/api/s35_vd.py b/api/s35_vd.py index b115814..feddd1d 100644 --- a/api/s35_vd.py +++ b/api/s35_vd.py @@ -1,66 +1,202 @@ from .database import * -from .s0_base import get_node_links +from .s0_base import is_node +from .s32_region_util import to_postgis_polygon +from .s32_region import get_region + +def get_virtual_district_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False }, + 'center' : {'type': 'str' , 'optional': False , 'readonly': False } } + +def get_virtual_district(name: str, id: str) -> dict[str, Any]: + vd = get_region(name, id) + if vd == {}: + return {} + r = try_read(name, f"select * from region_vd where id = '{id}'") + if r == None: + return {} + vd['center'] = r['center'] + vd['nodes'] = list(eval(r['nodes'])) + return vd + +def _set_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + + new_boundary = cs.operations[0]['boundary'] + old_boundary = get_region(name, id)['boundary'] + + new_center = cs.operations[0]['center'] + f_new_center = f"'{new_center}'" + + new_nodes = cs.operations[0]['nodes'] + str_new_nodes = str(new_nodes).replace("'", "''") + + old = get_virtual_district(name, id) + old_center = old['center'] + f_old_center = f"'{old_center}'" + + old_nodes = old['nodes'] + str_old_nodes = str(old_nodes).replace("'", "''") + + redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new_boundary)}') where id = '{id}';" + redo_sql += f"update region_vd set center = {f_new_center}, nodes = '{str_new_nodes}' where id = '{id}';" + + undo_sql = f"update region_vd set center = {f_old_center}, nodes = '{str_old_nodes}' where id = '{id}';" + undo_sql += f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old_boundary)}') where id = '{id}';" + + redo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': new_boundary, 'center': new_center, 'nodes': new_nodes } + undo_cs = g_update_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': old_boundary, 'center': old_center, 'nodes': old_nodes } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) -def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[Any]]: - write(name, 'delete from temp_vd_topology') +def set_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations - # 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 + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] - # 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)") + if 'id' not in op: + return ChangeSet() + + vd = get_virtual_district(name, op['id']) + if vd == {}: + return ChangeSet() - # 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 } + if 'boundary' not in op: + op['boundary'] = vd['boundary'] + else: + b = op['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() - write(name, 'delete from temp_vd_topology') + if 'center' not in op: + op['center'] = vd['center'] - # 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) + if not is_node(name, op['center']): + return ChangeSet() - vds: list[dict[str, Any]] = [] + if 'nodes' not in op: + op['nodes'] = vd['nodes'] + else: + for node in op['nodes']: + if not is_node(name, node): + return ChangeSet() - for center, value in center_node.items(): - vds.append({ 'center': center, 'nodes': value }) + return execute_command(name, _set_virtual_district(name, cs)) - return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes } + +def _add_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + + boundary = cs.operations[0]['boundary'] + + center = cs.operations[0]['center'] + f_center = f"'{center}'" + + nodes = cs.operations[0]['nodes'] + str_nodes = str(nodes).replace("'", "''") + + redo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');" + redo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');" + + undo_sql = f"delete from region_vd where id = '{id}';" + undo_sql += f"delete from region where id = '{id}';" + + redo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes } + undo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def add_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations + + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] + + if 'id' not in op: + return ChangeSet() + + vd = get_virtual_district(name, op['id']) + if vd != {}: + return ChangeSet() + + if 'boundary' not in op: + return ChangeSet() + else: + b = op['boundary'] + if len(b) < 4 or b[0] != b[-1]: + return ChangeSet() + + if 'center' not in op: + return ChangeSet() + + if not is_node(name, op['center']): + return ChangeSet() + + if 'nodes' not in op: + op['nodes'] = [] + else: + for node in op['nodes']: + if not is_node(name, node): + return ChangeSet() + + return execute_command(name, _add_virtual_district(name, cs)) + + +def _delete_virtual_district(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + vd = get_virtual_district(name, id) + boundary = vd['boundary'] + center = vd['center'] + f_center = f"'{center}'" + nodes = vd['nodes'] + str_nodes = str(nodes).replace("'", "''") + + redo_sql = f"delete from region_vd where id = '{id}';" + redo_sql += f"delete from region where id = '{id}';" + + undo_sql = f"insert into region (id, boundary, r_type) values ('{id}', '{to_postgis_polygon(boundary)}', 'VD');" + undo_sql += f"insert into region_vd (id, center, nodes) values ('{id}', {f_center}, '{str_nodes}');" + + redo_cs = g_delete_prefix | { 'type': 'virtual_district', 'id': id } + undo_cs = g_add_prefix | { 'type': 'virtual_district', 'id': id, 'boundary': boundary, 'center': center, 'nodes': nodes } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def delete_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: + ops = cs.operations + + if len(cs.operations) == 0: + return ChangeSet() + + op = ops[0] + + if 'id' not in op: + return ChangeSet() + + vd = get_virtual_district(name, op['id']) + if vd == {}: + return ChangeSet() + + return execute_command(name, _delete_virtual_district(name, cs)) + + +def get_all_virtual_district_ids(name: str) -> list[str]: + ids = [] + for row in read_all(name, f"select id from region_vd"): + ids.append(row['id']) + return ids + + +def get_all_virtual_districts(name: str) -> list[dict[str, Any]]: + result = [] + for id in get_all_virtual_district_ids(name): + result.append(get_virtual_district(name, id)) + return result diff --git a/api/s35_vd_cal.py b/api/s35_vd_cal.py new file mode 100644 index 0000000..b115814 --- /dev/null +++ b/api/s35_vd_cal.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/s35_vd_gen.py b/api/s35_vd_gen.py new file mode 100644 index 0000000..639ffd1 --- /dev/null +++ b/api/s35_vd_gen.py @@ -0,0 +1,21 @@ +from .s32_region_util import calculate_boundary, inflate_boundary +from .s35_vd_cal import * +from .s35_vd import get_all_virtual_district_ids +from .batch_exe import execute_batch_command + +def generate_virtual_district(name: str, centers: list[str]) -> ChangeSet: + cs = ChangeSet() + + for id in get_all_virtual_district_ids(name): + cs.delete({'type': 'virtual_district', 'id': id}) + + vds = calculate_virtual_district(name, centers)['virtual_districts'] + + for vd in vds: + center = vd['center'] + nodes = vd['nodes'] + boundary = calculate_boundary(name, nodes) + boundary = inflate_boundary(name, boundary) + cs.add({ 'type': 'virtual_district', 'id': f"VD_{center}", 'boundary': boundary, 'center': center, 'nodes': nodes }) + + return execute_batch_command(name, cs) diff --git a/api/sections.py b/api/sections.py index d64ca0c..7cd74f6 100644 --- a/api/sections.py +++ b/api/sections.py @@ -35,6 +35,8 @@ s30_scada_device_data = 'scada_device_data' s31_scada_element = 'scada_element' s32_region = 'region' s33_dma = 'district_metering_area' +s34_sa = 'service_area' +s35_vd = 'virtual_district' TITLE = 'TITLE' JUNCTIONS = 'JUNCTIONS' diff --git a/script/sql/create/32.region.sql b/script/sql/create/32.region.sql index 78e0bfd..9f52f6a 100644 --- a/script/sql/create/32.region.sql +++ b/script/sql/create/32.region.sql @@ -3,7 +3,7 @@ create type region_type as enum ('NONE', 'DMA', 'SA', 'VD', 'WDA'); create table region ( id text primary key -, boundary geometry not null unique +, boundary geometry not null --unique , r_type region_type not null default 'NONE' ); diff --git a/test_tjnetwork.py b/test_tjnetwork.py index 7360dba..53026c1 100644 --- a/test_tjnetwork.py +++ b/test_tjnetwork.py @@ -6130,18 +6130,182 @@ class TestApi: 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'] + sass = calculate_service_area(p) + assert len(sass) == 25 + assert sass[0] == {'River': ['River', '60', '61', '123', '601', '121', '120', '119', '125', '117', '257', '151', '157', '127', '153', '115', '261', '259', '149', '159', '20', '129', '111', '113', '107', '263', '147', '161', '3', '131', '139', '109', '197', '193', '105', '145', '163', '195', '141', '103', '191', '101', '164', '265', '143', '187', '166', '169', '267', '15', '204', '167', '171', '269', '189', '185', '173', '271', '183', '184', '199', '181', '179', '205', '201', '273', '35', '177', '40', '207', '203', '275', '1', '206', '208', '209', '211', '213', '237', '215', '229', '217', '231', '219', '225'], '2': ['2', '50', '255', '247', '253', '251', '241', '249', '239', '243', '237', '229', '231']} + assert sass[1] == {'River': ['River', '60', '61', '123', '601', '121', '120', '119', '125', '117', '151', '157', '127', '153', '115', '149', '159', '20', '129', '111', '113', '147', '161', '3', '131', '139', '197', '193', '145', '163', '195', '141', '191', '267', '164', '265', '143', '187', '189', '166', '169', '15', '204', '183', '167', '171', '269', '185', '179', '173', '271', '184', '40', '199', '181', '205', '1', '201', '273', '35', '177', '207', '203', '275', '206', '208', '209', '211', '213', '237', '215', '229', '239', '217', '231', '241', '249', '219', '225', '243', '247'], 'Lake': ['Lake', '10', '101', '103', '105', '109', '107', '263', '111', '115', '259', '261', '113', '197', '257', '117', '193', '191', '120', '267', '187', '119', '189', '265', '204', '151', '157', '183', '169', '185', '149', '159', '179', '167', '171', '269', '184', '147', '161', '40', '173', '271', '205', '145', '163', '195', '1', '199', '181', '207', '141', '164', '201', '273', '35', '177', '206', '143', '166', '203', '275', '208', '15', '209', '211', '213', '237', '215', '229', '239', '217', '231', '241', '249', '219', '225', '243', '247'], '2': ['2', '50', '255', '247', '253', '251', '249']} - 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) + + + def test_service_area(self): + p = 'test_service_area' + self.enter(p) + + sa = get_service_area(p, 'sa') + assert sa == {} + + add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]})) + sa = get_service_area(p, 'sa') + assert sa == {} + + add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0'})) + sa = get_service_area(p, 'sa') + assert sa == {} + + add_junction(p, ChangeSet({'id': 'j0', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0'})) + sa = get_service_area(p, 'sa') + assert sa == {} + + add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0', 'nodes' : ['x']})) + sa = get_service_area(p, 'sa') + assert sa == {} + + add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0', 'time_index' : 0, 'nodes': ['j0']})) + sa = get_service_area(p, 'sa') + assert sa['id'] == 'sa' + assert sa['time_index'] == 0 + assert sa['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert sa['source'] == 'j0' + assert sa['nodes'] == ['j0'] + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + set_service_area(p, ChangeSet({'id': 'sa', 'source': 'j1', 'time_index' : 1, 'nodes': ['j1']})) + sa = get_service_area(p, 'sa') + assert sa['id'] == 'sa' + assert sa['time_index'] == 1 + assert sa['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert sa['source'] == 'j1' + assert sa['nodes'] == ['j1'] + + assert get_all_service_area_ids(p) == ['sa'] + sas = get_all_service_areas(p) + assert len(sas) == 1 + sa = sas[0] + assert sa['id'] == 'sa' + assert sa['time_index'] == 1 + assert sa['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert sa['source'] == 'j1' + assert sa['nodes'] == ['j1'] + + delete_service_area(p, ChangeSet({'id': 'sa'})) + sa = get_service_area(p, 'sa') + assert sa == {} + + self.leave(p) + + + def test_service_area_op(self): + p = 'test_service_area_op' + self.enter(p) + + cs = add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]})).operations + assert len(cs) == 0 + + cs = add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0'})).operations + assert len(cs) == 0 + + add_junction(p, ChangeSet({'id': 'j0', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + cs = add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0'})).operations + assert len(cs) == 0 + + cs = add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0', 'nodes' : ['x']})).operations + assert len(cs) == 0 + + cs = add_service_area(p, ChangeSet({'id': 'sa', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'source': 'j0', 'time_index' : 0, 'nodes': ['j0']})).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 0 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j0' + assert cs['nodes'] == ['j0'] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 0 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j0' + assert cs['nodes'] == ['j0'] + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + cs = set_service_area(p, ChangeSet({'id': 'sa', 'source': 'j1', 'time_index' : 1, 'nodes': ['j1']})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 1 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 0 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j0' + assert cs['nodes'] == ['j0'] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 1 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = delete_service_area(p, ChangeSet({'id': 'sa'})).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + assert cs['time_index'] == 1 + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['source'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'service_area' + assert cs['id'] == 'sa' + + self.leave(p) + + + def test_service_area_gen(self): + p = 'test_service_area_gen' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + cs = generate_service_area(p).operations + assert len(cs) == 78 + + assert len(get_all_service_area_ids(p)) == 78 + assert len(get_all_service_areas(p)) == 78 + + cs = generate_service_area(p).operations + assert len(cs) == 78 * 2 + + assert len(get_all_service_area_ids(p)) == 78 + assert len(get_all_service_areas(p)) == 78 self.leave(p) @@ -6170,6 +6334,162 @@ class TestApi: self.leave(p) + def test_virtual_district(self): + p = 'test_virtual_district' + self.enter(p) + + vd = get_virtual_district(p, 'vd') + assert vd == {} + + add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]})) + vd = get_virtual_district(p, 'vd') + assert vd == {} + + add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0'})) + vd = get_virtual_district(p, 'vd') + assert vd == {} + + add_junction(p, ChangeSet({'id': 'j0', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0', 'nodes' : ['x']})) + vd = get_virtual_district(p, 'vd') + assert vd == {} + + add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0', 'nodes': ['j0']})) + vd = get_virtual_district(p, 'vd') + assert vd['id'] == 'vd' + assert vd['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert vd['center'] == 'j0' + assert vd['nodes'] == ['j0'] + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + set_virtual_district(p, ChangeSet({'id': 'vd', 'center': 'j1', 'nodes': ['j1']})) + vd = get_virtual_district(p, 'vd') + assert vd['id'] == 'vd' + assert vd['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert vd['center'] == 'j1' + assert vd['nodes'] == ['j1'] + + assert get_all_virtual_district_ids(p) == ['vd'] + vds = get_all_virtual_districts(p) + assert len(vds) == 1 + vd = vds[0] + assert vd['id'] == 'vd' + assert vd['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert vd['center'] == 'j1' + assert vd['nodes'] == ['j1'] + + delete_virtual_district(p, ChangeSet({'id': 'vd'})) + vd = get_virtual_district(p, 'vd') + assert vd == {} + + self.leave(p) + + + def test_virtual_district_op(self): + p = 'test_virtual_district_op' + self.enter(p) + + cs = add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)]})).operations + assert len(cs) == 0 + + cs = add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0'})).operations + assert len(cs) == 0 + + add_junction(p, ChangeSet({'id': 'j0', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + cs = add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0', 'nodes' : ['x']})).operations + assert len(cs) == 0 + + cs = add_virtual_district(p, ChangeSet({'id': 'vd', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)], 'center': 'j0', 'nodes': ['j0']})).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j0' + assert cs['nodes'] == ['j0'] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j0' + assert cs['nodes'] == ['j0'] + + add_junction(p, ChangeSet({'id': 'j1', 'x': 0.0, 'y': 10.0, 'elevation': 20.0})) + + cs = set_virtual_district(p, ChangeSet({'id': 'vd', 'center': 'j1', 'nodes': ['j1']})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j0' + assert cs['nodes'] == ['j0'] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = delete_virtual_district(p, ChangeSet({'id': 'vd'})).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + assert cs['center'] == 'j1' + assert cs['nodes'] == ['j1'] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'virtual_district' + assert cs['id'] == 'vd' + + self.leave(p) + + + def test_virtual_district_gen(self): + p = 'test_virtual_district_gen' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + cs = generate_virtual_district(p, ['107', '139', '267', '211']).operations + assert len(cs) == 4 + + assert len(get_all_virtual_district_ids(p)) == 4 + assert len(get_all_virtual_districts(p)) == 4 + + cs = generate_virtual_district(p, ['107', '139', '267', '211']).operations + assert len(cs) == 8 + + assert len(get_all_virtual_district_ids(p)) == 4 + assert len(get_all_virtual_districts(p)) == 4 + + self.leave(p) + + # 36 water_distribution diff --git a/tjnetwork.py b/tjnetwork.py index 11b2d21..ac12cb3 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -1068,29 +1068,32 @@ def generate_sub_district_metering_area(name: str, dma: str, part_count: int = 1 # service_area 34 ############################################################ -def calculate_service_area(name: str, time_index: int = 0) -> dict[str, Any]: - return api.calculate_service_area(name, time_index) +def calculate_service_area(name: str) -> list[dict[str, list[str]]]: + return api.calculate_service_area(name) def get_service_area_schema(name: str) -> dict[str, dict[str, Any]]: - return {} + return api.get_service_area_schema(name) def get_service_area(name: str, id: str) -> dict[str, Any]: - return {} + return api.get_service_area(name, id) def set_service_area(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.set_service_area(name, cs) def add_service_area(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.add_service_area(name, cs) def delete_service_area(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.delete_service_area(name, cs) + +def get_all_service_area_ids(name: str) -> list[str]: + return api.get_all_service_area_ids(name) def get_all_service_areas(name: str) -> list[dict[str, Any]]: - return [] + return api.get_all_service_areas(name) def generate_service_area(name: str) -> ChangeSet: - return ChangeSet() + return api.generate_service_area(name) ############################################################ @@ -1101,25 +1104,28 @@ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, list[ return api.calculate_virtual_district(name, centers) def get_virtual_district_schema(name: str) -> dict[str, dict[str, Any]]: - return {} + return api.get_virtual_district_schema(name) def get_virtual_district(name: str, id: str) -> dict[str, Any]: - return {} + return api.get_virtual_district(name, id) def set_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.set_virtual_district(name, cs) def add_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.add_virtual_district(name, cs) def delete_virtual_district(name: str, cs: ChangeSet) -> ChangeSet: - return ChangeSet() + return api.delete_virtual_district(name, cs) -def get_all_virtual_district(name: str) -> list[dict[str, Any]]: - return [] +def get_all_virtual_district_ids(name: str) -> list[str]: + return api.get_all_virtual_district_ids(name) + +def get_all_virtual_districts(name: str) -> list[dict[str, Any]]: + return api.get_all_virtual_districts(name) def generate_virtual_district(name: str, centers: list[str]) -> ChangeSet: - return ChangeSet() + return api.generate_virtual_district(name, centers) ############################################################