From 04a3d0057e2deaa311ec960ac2a3b7fdd493580a Mon Sep 17 00:00:00 2001 From: "WQY\\qiong" Date: Sat, 29 Apr 2023 19:15:34 +0800 Subject: [PATCH] Support more region utils, such as convex hull --- api/__init__.py | 2 ++ api/s32_region_util.py | 34 ++++++++++++++++++++++++++ api/s37_virtual_district.py | 36 +++++++--------------------- script/sql/create/32.region.sql | 24 +++++++++++++++++++ script/sql/drop/32.region.sql | 8 +++++++ test_tjnetwork.py | 42 ++++++++++++++++++++++++++++++++- tjnetwork.py | 9 +++++++ 7 files changed, 126 insertions(+), 29 deletions(-) diff --git a/api/__init__.py b/api/__init__.py index ac46621..86f7d27 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -132,6 +132,8 @@ from .s31_scada_element import SCADA_ELEMENT_STATUS_OFFLINE, SCADA_ELEMENT_STATU from .s31_scada_element import get_scada_element_schema, get_scada_elements, get_scada_element, set_scada_element, add_scada_element, delete_scada_element from .clean_api import clean_scada_element +from .s32_region_util import get_nodes_in_boundary, get_nodes_in_region, calculate_convex_hull + from .s33_region import get_region_schema, get_region, set_region, add_region, delete_region from .s37_virtual_district import calculate_virtual_district diff --git a/api/s32_region_util.py b/api/s32_region_util.py index 7a32c65..4dfe5fe 100644 --- a/api/s32_region_util.py +++ b/api/s32_region_util.py @@ -1,3 +1,4 @@ +from .database import read, read_all, write def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]: boundary = polygon.lower().removeprefix('polygon((').removesuffix('))').split(',') @@ -13,3 +14,36 @@ def to_postgis_polygon(boundary: list[tuple[float, float]]) -> str: for pt in boundary: polygon += f'{pt[0]} {pt[1]},' return f'polygon(({polygon[:-1]}))' + + +def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]: + api = 'get_nodes_in_boundary' + write(name, f"delete from temp_region where id = '{api}'") + write(name, f"insert into temp_region (id, boundary) values ('{api}', '{to_postgis_polygon(boundary)}')") + + nodes: list[str] = [] + for row in read_all(name, f"select c.node from coordinates as c, temp_region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{api}'"): + nodes.append(row['node']) + + write(name, f"delete from temp_region where id = '{api}'") + + return nodes + + +def get_nodes_in_region(name: str, id: str) -> list[str]: + nodes: list[str] = [] + for row in read_all(name, f"select c.node from coordinates as c, region as r where ST_Intersects(c.coord, r.boundary) and r.id = '{id}'"): + nodes.append(row['node']) + return nodes + + +def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]: + write(name, f'delete from temp_node') + for node in nodes: + write(name, f"insert into temp_node values ('{node}')") + + # TODO: check none + polygon = read(name, f'select st_astext(st_convexhull(st_collect(array(select coord from coordinates where node in (select * from temp_node))))) as boundary' )['boundary'] + write(name, f'delete from temp_node') + + return from_postgis_polygon(polygon) diff --git a/api/s37_virtual_district.py b/api/s37_virtual_district.py index 93e8d1d..f878c36 100644 --- a/api/s37_virtual_district.py +++ b/api/s37_virtual_district.py @@ -1,19 +1,10 @@ from .database import * from .s0_base import get_node_links - - -def _polygon_to_nodes(polygon: str) -> list[tuple[float, float]]: - boundary = polygon.removeprefix('POLYGON((').removesuffix('))').split(',') - xys = [] - for pt in boundary: - xy = pt.split(' ') - xys.append((float(xy[0]), float(xy[1]))) - return xys +from .s32_region_util import calculate_convex_hull def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]: - write(name, 'drop table if exists vd_graph') - write(name, 'create table vd_graph (id serial, source integer, target integer, cost numeric)') + write(name, 'delete from temp_vd_topology') # map node name to index i = 0 @@ -33,17 +24,17 @@ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]: source = node_index[str(pipe['node1'])] target = node_index[str(pipe['node2'])] cost = float(pipe['length']) - write(name, f"insert into vd_graph (source, target, cost) values ({source}, {target}, {cost})") + 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 vd_graph (source, target, cost) values ({source}, {target}, 0.0)") + 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 vd_graph (source, target, cost) values ({source}, {target}, 0.0)") + 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]] = {} @@ -52,14 +43,13 @@ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]: if node == center: continue # TODO: check none - distance = float(read(name, f"select max(agg_cost) as distance from pgr_dijkstraCost('select id, source, target, cost from vd_graph', {index}, {node_index[center]}, false)")['distance']) + 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 } - # destroy topology graph - write(name, 'drop table vd_graph') + write(name, 'delete from temp_vd_topology') # reorganize the distance result center_node: dict[str, list[str]] = {} @@ -71,17 +61,7 @@ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]: vds: list[dict[str, Any]] = [] for center, value in center_node.items(): - write(name, f'drop table if exists vd_{center}') - write(name, f'create table vd_{center} (node varchar(32) primary key references _node(id))') - - for node in value: - write(name, f"insert into vd_{center} values ('{node}')") - - # TODO: check none - boundary = read(name, f'select st_astext(st_convexhull(st_collect(array(select coord from coordinates where node in (select * from vd_{center}))))) as boundary' )['boundary'] - xys = _polygon_to_nodes(boundary) + xys = calculate_convex_hull(name, value) vds.append({ 'center': center, 'nodes': value, 'boundary': xys }) - write(name, f'drop table vd_{center}') - return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes } diff --git a/script/sql/create/32.region.sql b/script/sql/create/32.region.sql index 0fcf92c..eea878e 100644 --- a/script/sql/create/32.region.sql +++ b/script/sql/create/32.region.sql @@ -5,3 +5,27 @@ create table region ); create index region_gist on region using gist(boundary); + + +create table temp_region +( + id text primary key +, boundary geometry not null unique +); + +create index temp_region_gist on temp_region using gist(boundary); + + +create table temp_node +( + node varchar(32) primary key references _node(id) +); + + +create table temp_vd_topology +( + id serial +, source integer +, target integer +, cost numeric +); diff --git a/script/sql/drop/32.region.sql b/script/sql/drop/32.region.sql index b30dd82..71bc330 100644 --- a/script/sql/drop/32.region.sql +++ b/script/sql/drop/32.region.sql @@ -1,3 +1,11 @@ +drop table if exists temp_vd_topology; + +drop table if exists temp_node; + +drop index if exists temp_region_gist; + +drop table if exists temp_region; + drop index if exists region_gist; drop table if exists region; diff --git a/test_tjnetwork.py b/test_tjnetwork.py index cdaf9ee..de1d392 100644 --- a/test_tjnetwork.py +++ b/test_tjnetwork.py @@ -5564,6 +5564,47 @@ class TestApi: self.leave(p) + # 32 region_util + + + def test_get_nodes_in_boundary(self): + p = 'test_get_nodes_in_boundary' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + nodes = get_nodes_in_boundary(p, vds[0]['boundary']) + assert nodes == ['10', '101', '103', '105', '107', '109', '111', '113', '115', '117', '119', '120', '121', '125', '139', '149', '151', '153', '157', '159', '161', '191', '193', '195', '197', '257', '259', '261', '263', '267', 'Lake'] + + self.leave(p) + + + def test_get_nodes_in_region(self): + p = 'test_get_nodes_in_region' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + vds = calculate_virtual_district(p, ['107', '139', '267', '211'])['virtual_districts'] + add_region(p, ChangeSet({'id': 'r', 'boundary': vds[0]['boundary']})) + + nodes = get_nodes_in_region(p, 'r') + assert nodes == ['10', '101', '103', '105', '107', '109', '111', '113', '115', '117', '119', '120', '121', '125', '139', '149', '151', '153', '157', '159', '161', '191', '193', '195', '197', '257', '259', '261', '263', '267', 'Lake'] + + self.leave(p) + + + def test_calculate_convex_hull(self): + p = 'test_calculate_convex_hull' + read_inp(p, f'./inp/net3.inp', '3') + open_project(p) + + nodes = ['10', '101', '103', '105', '109', '111', '115', '117', '119', '120', '139', '257', '259', '261', '263', '267', 'Lake'] + ch = calculate_convex_hull(p, nodes) + assert ch == [(23.38, 12.95), (12.96, 21.31), (8.0, 27.53), (9.0, 27.85), (33.28, 24.54), (23.38, 12.95)] + + self.leave(p) + + # 33 region @@ -5652,7 +5693,6 @@ class TestApi: def test_virtual_district(self): p = 'test_virtual_district' read_inp(p, f'./inp/net3.inp', '3') - open_project(p) result = calculate_virtual_district(p, ['107', '139', '267', '211']) diff --git a/tjnetwork.py b/tjnetwork.py index 874c71d..7c5c298 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -939,6 +939,15 @@ def clean_scada_element(name: str) -> ChangeSet: # region_util 32 ############################################################ +def get_nodes_in_boundary(name: str, boundary: list[tuple[float, float]]) -> list[str]: + return api.get_nodes_in_boundary(name, boundary) + +def get_nodes_in_region(name: str, id: str) -> list[str]: + return api.get_nodes_in_region(name, id) + +def calculate_convex_hull(name: str, nodes: list[str]) -> list[tuple[float, float]]: + return api.calculate_convex_hull(name, nodes) + ############################################################ # general_region 33