Accept Merge Request #244: (region -> master)
Merge Request: Support SA and VD Created By: @王琼钰 Accepted By: @王琼钰 URL: https://tjwater.coding.net/p/tjwatercloud/d/TJWaterServer/git/merge/244?initial=true
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
296
api/s34_sa.py
296
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()
|
||||
|
||||
return { 'service_areas' : sas, 'concentrations': concentration_map }
|
||||
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:
|
||||
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
|
||||
|
||||
132
api/s34_sa_cal.py
Normal file
132
api/s34_sa_cal.py
Normal file
@@ -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
|
||||
22
api/s34_sa_gen.py
Normal file
22
api/s34_sa_gen.py
Normal file
@@ -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)
|
||||
246
api/s35_vd.py
246
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()
|
||||
|
||||
# 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)")
|
||||
op = ops[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 }
|
||||
if 'id' not in op:
|
||||
return ChangeSet()
|
||||
|
||||
write(name, 'delete from temp_vd_topology')
|
||||
vd = get_virtual_district(name, op['id'])
|
||||
if vd == {}:
|
||||
return ChangeSet()
|
||||
|
||||
# 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 'boundary' not in op:
|
||||
op['boundary'] = vd['boundary']
|
||||
else:
|
||||
b = op['boundary']
|
||||
if len(b) < 4 or b[0] != b[-1]:
|
||||
return ChangeSet()
|
||||
|
||||
vds: list[dict[str, Any]] = []
|
||||
if 'center' not in op:
|
||||
op['center'] = vd['center']
|
||||
|
||||
for center, value in center_node.items():
|
||||
vds.append({ 'center': center, 'nodes': value })
|
||||
if not is_node(name, op['center']):
|
||||
return ChangeSet()
|
||||
|
||||
return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes }
|
||||
if 'nodes' not in op:
|
||||
op['nodes'] = vd['nodes']
|
||||
else:
|
||||
for node in op['nodes']:
|
||||
if not is_node(name, node):
|
||||
return ChangeSet()
|
||||
|
||||
return execute_command(name, _set_virtual_district(name, cs))
|
||||
|
||||
|
||||
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
|
||||
|
||||
66
api/s35_vd_cal.py
Normal file
66
api/s35_vd_cal.py
Normal file
@@ -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 }
|
||||
21
api/s35_vd_gen.py
Normal file
21
api/s35_vd_gen.py
Normal file
@@ -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)
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
40
tjnetwork.py
40
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)
|
||||
|
||||
|
||||
############################################################
|
||||
|
||||
Reference in New Issue
Block a user