Support more region utils, such as convex hull
This commit is contained in:
@@ -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 .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 .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 .s33_region import get_region_schema, get_region, set_region, add_region, delete_region
|
||||||
|
|
||||||
from .s37_virtual_district import calculate_virtual_district
|
from .s37_virtual_district import calculate_virtual_district
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .database import read, read_all, write
|
||||||
|
|
||||||
def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]:
|
def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]:
|
||||||
boundary = polygon.lower().removeprefix('polygon((').removesuffix('))').split(',')
|
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:
|
for pt in boundary:
|
||||||
polygon += f'{pt[0]} {pt[1]},'
|
polygon += f'{pt[0]} {pt[1]},'
|
||||||
return f'polygon(({polygon[:-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)
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
from .database import *
|
from .database import *
|
||||||
from .s0_base import get_node_links
|
from .s0_base import get_node_links
|
||||||
|
from .s32_region_util import calculate_convex_hull
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]:
|
def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]:
|
||||||
write(name, 'drop table if exists vd_graph')
|
write(name, 'delete from temp_vd_topology')
|
||||||
write(name, 'create table vd_graph (id serial, source integer, target integer, cost numeric)')
|
|
||||||
|
|
||||||
# map node name to index
|
# map node name to index
|
||||||
i = 0
|
i = 0
|
||||||
@@ -33,17 +24,17 @@ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]:
|
|||||||
source = node_index[str(pipe['node1'])]
|
source = node_index[str(pipe['node1'])]
|
||||||
target = node_index[str(pipe['node2'])]
|
target = node_index[str(pipe['node2'])]
|
||||||
cost = float(pipe['length'])
|
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')
|
pumps = read_all(name, 'select node1, node2 from pumps')
|
||||||
for pump in pumps:
|
for pump in pumps:
|
||||||
source = node_index[str(pump['node1'])]
|
source = node_index[str(pump['node1'])]
|
||||||
target = node_index[str(pump['node2'])]
|
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')
|
valves = read_all(name, 'select node1, node2 from valves')
|
||||||
for valve in valves:
|
for valve in valves:
|
||||||
source = node_index[str(valve['node1'])]
|
source = node_index[str(valve['node1'])]
|
||||||
target = node_index[str(valve['node2'])]
|
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
|
# dijkstra distance
|
||||||
node_distance: dict[str, dict[str, Any]] = {}
|
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:
|
if node == center:
|
||||||
continue
|
continue
|
||||||
# TODO: check none
|
# 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:
|
if node not in node_distance:
|
||||||
node_distance[node] = { 'center': center, 'distance' : distance }
|
node_distance[node] = { 'center': center, 'distance' : distance }
|
||||||
elif distance < node_distance[node]['distance']:
|
elif distance < node_distance[node]['distance']:
|
||||||
node_distance[node] = { 'center': center, 'distance' : distance }
|
node_distance[node] = { 'center': center, 'distance' : distance }
|
||||||
|
|
||||||
# destroy topology graph
|
write(name, 'delete from temp_vd_topology')
|
||||||
write(name, 'drop table vd_graph')
|
|
||||||
|
|
||||||
# reorganize the distance result
|
# reorganize the distance result
|
||||||
center_node: dict[str, list[str]] = {}
|
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]] = []
|
vds: list[dict[str, Any]] = []
|
||||||
|
|
||||||
for center, value in center_node.items():
|
for center, value in center_node.items():
|
||||||
write(name, f'drop table if exists vd_{center}')
|
xys = calculate_convex_hull(name, value)
|
||||||
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)
|
|
||||||
vds.append({ 'center': center, 'nodes': value, 'boundary': xys })
|
vds.append({ 'center': center, 'nodes': value, 'boundary': xys })
|
||||||
|
|
||||||
write(name, f'drop table vd_{center}')
|
|
||||||
|
|
||||||
return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes }
|
return { 'virtual_districts': vds, 'isolated_nodes': isolated_nodes }
|
||||||
|
|||||||
@@ -5,3 +5,27 @@ create table region
|
|||||||
);
|
);
|
||||||
|
|
||||||
create index region_gist on region using gist(boundary);
|
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
|
||||||
|
);
|
||||||
|
|||||||
@@ -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 index if exists region_gist;
|
||||||
|
|
||||||
drop table if exists region;
|
drop table if exists region;
|
||||||
|
|||||||
@@ -5564,6 +5564,47 @@ class TestApi:
|
|||||||
self.leave(p)
|
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
|
# 33 region
|
||||||
|
|
||||||
|
|
||||||
@@ -5652,7 +5693,6 @@ class TestApi:
|
|||||||
def test_virtual_district(self):
|
def test_virtual_district(self):
|
||||||
p = 'test_virtual_district'
|
p = 'test_virtual_district'
|
||||||
read_inp(p, f'./inp/net3.inp', '3')
|
read_inp(p, f'./inp/net3.inp', '3')
|
||||||
|
|
||||||
open_project(p)
|
open_project(p)
|
||||||
|
|
||||||
result = calculate_virtual_district(p, ['107', '139', '267', '211'])
|
result = calculate_virtual_district(p, ['107', '139', '267', '211'])
|
||||||
|
|||||||
@@ -939,6 +939,15 @@ def clean_scada_element(name: str) -> ChangeSet:
|
|||||||
# region_util 32
|
# 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
|
# general_region 33
|
||||||
|
|||||||
Reference in New Issue
Block a user