From d66087225f84ec377cd90193b00303ca557a9570 Mon Sep 17 00:00:00 2001 From: "WQY\\qiong" Date: Sat, 29 Apr 2023 17:51:34 +0800 Subject: [PATCH] Support database region --- api/__init__.py | 4 +- api/s32_region_util.py | 15 ++++++++ api/s33_region.py | 81 +++++++++++++++++++++++++++++++++++++++++ test_tjnetwork.py | 83 ++++++++++++++++++++++++++++++++++++++++++ tjnetwork.py | 43 +++++++++++++++++++++- 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 api/s32_region_util.py create mode 100644 api/s33_region.py diff --git a/api/__init__.py b/api/__init__.py index e8c72de..ac46621 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -132,4 +132,6 @@ 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 .s37_virtual_district import calculate_virtual_district \ No newline at end of file +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 new file mode 100644 index 0000000..7a32c65 --- /dev/null +++ b/api/s32_region_util.py @@ -0,0 +1,15 @@ + +def from_postgis_polygon(polygon: str) -> list[tuple[float, float]]: + boundary = polygon.lower().removeprefix('polygon((').removesuffix('))').split(',') + xys = [] + for pt in boundary: + xy = pt.split(' ') + xys.append((float(xy[0]), float(xy[1]))) + return xys + + +def to_postgis_polygon(boundary: list[tuple[float, float]]) -> str: + polygon = '' + for pt in boundary: + polygon += f'{pt[0]} {pt[1]},' + return f'polygon(({polygon[:-1]}))' diff --git a/api/s33_region.py b/api/s33_region.py new file mode 100644 index 0000000..7ef0f1e --- /dev/null +++ b/api/s33_region.py @@ -0,0 +1,81 @@ +from .database import * +from .s32_region_util import from_postgis_polygon, to_postgis_polygon + +def get_region_schema(name: str) -> dict[str, dict[str, Any]]: + return { 'id' : {'type': 'str' , 'optional': False , 'readonly': True }, + 'boundary' : {'type': 'tuple_list' , 'optional': False , 'readonly': False} } + + +def get_region(name: str, id: str) -> dict[str, Any]: + r = try_read(name, f"select id, st_astext(boundary) as boundary_geom from region where id = '{id}'") + if r == None: + return {} + d = {} + d['id'] = str(r['id']) + d['boundary'] = from_postgis_polygon(str(r['boundary_geom'])) + return d + + +def _set_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + new = cs.operations[0]['boundary'] + old = get_region(name, id)['boundary'] + + redo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(new)}') where id = '{id}';" + undo_sql = f"update region set boundary = st_geomfromtext('{to_postgis_polygon(old)}') where id = '{id}';" + redo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': new } + undo_cs = g_update_prefix | { 'type': 'region', 'id': id, 'boundary': old } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def set_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]: + return ChangeSet() + if len(cs.operations[0]['boundary']) < 3: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) == {}: + return ChangeSet() + return execute_command(name, _set_region(name, cs)) + + +def _add_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + new = cs.operations[0]['boundary'] + + redo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(new)}');" + undo_sql = f"delete from region where id = '{id}';" + redo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': new } + undo_cs = g_delete_prefix | { 'type': 'region', 'id': id } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def add_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0] or 'boundary' not in cs.operations[0]: + return ChangeSet() + if len(cs.operations[0]['boundary']) < 3: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) != {}: + return ChangeSet() + return execute_command(name, _add_region(name, cs)) + + +def _delete_region(name: str, cs: ChangeSet) -> DbChangeSet: + id = cs.operations[0]['id'] + old = get_region(name, id)['boundary'] + + redo_sql = f"delete from region where id = '{id}';" + undo_sql = f"insert into region (id, boundary) values ('{id}', '{to_postgis_polygon(old)}');" + redo_cs = g_delete_prefix | { 'type': 'region', 'id': id } + undo_cs = g_add_prefix | { 'type': 'region', 'id': id, 'boundary': old } + + return DbChangeSet(redo_sql, undo_sql, [redo_cs], [undo_cs]) + + +def delete_region(name: str, cs: ChangeSet) -> ChangeSet: + if 'id' not in cs.operations[0]: + return ChangeSet() + if get_region(name, cs.operations[0]['id']) == {}: + return ChangeSet() + return execute_command(name, _delete_region(name, cs)) diff --git a/test_tjnetwork.py b/test_tjnetwork.py index df4ea32..cdaf9ee 100644 --- a/test_tjnetwork.py +++ b/test_tjnetwork.py @@ -5564,6 +5564,89 @@ class TestApi: self.leave(p) + # 33 region + + + def test_region(self): + p = 'test_region' + self.enter(p) + + r = get_region(p, 'r') + assert r == {} + + add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})) + r = get_region(p, 'r') + assert r == { 'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] } + + set_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)]})) + r = get_region(p, 'r') + assert r == { 'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] } + + delete_region(p, ChangeSet({'id': 'r'})) + r = get_region(p, 'r') + assert r == {} + + self.leave(p) + + + def test_region_op(self): + p = 'test_region_op' + self.enter(p) + + cs = add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = set_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)]})).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_UPDATE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = delete_region(p, ChangeSet({'id': 'r'})).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + cs = execute_undo(p).operations[0] + assert cs['operation'] == API_ADD + assert cs['type'] == 'region' + assert cs['id'] == 'r' + assert cs['boundary'] == [(0.0, 0.0), (1.0, 0.0), (1.0, 2.0), (0.0, 0.0)] + + cs = execute_redo(p).operations[0] + assert cs['operation'] == API_DELETE + assert cs['type'] == 'region' + assert cs['id'] == 'r' + + self.leave(p) + + # 37 virtual_district def test_virtual_district(self): diff --git a/tjnetwork.py b/tjnetwork.py index ab98e66..874c71d 100644 --- a/tjnetwork.py +++ b/tjnetwork.py @@ -936,7 +936,48 @@ def clean_scada_element(name: str) -> ChangeSet: ############################################################ -# virtual_district 32 +# region_util 32 +############################################################ + + +############################################################ +# general_region 33 +############################################################ + +def get_region_schema(name: str) -> dict[str, dict[str, Any]]: + return api.get_region_schema(name) + +def get_region(name: str, id: str) -> dict[str, Any]: + return api.get_region(name, id) + +def set_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.set_region(name, cs) + +# example: add_region(p, ChangeSet({'id': 'r', 'boundary': [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]})) +def add_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.add_region(name, cs) + +def delete_region(name: str, cs: ChangeSet) -> ChangeSet: + return api.delete_region(name, cs) + + +############################################################ +# water_distribution 34 +############################################################ + + +############################################################ +# district_metering_area 35 +############################################################ + + +############################################################ +# service_area 36 +############################################################ + + +############################################################ +# virtual_district 37 ############################################################ def calculate_virtual_district(name: str, centers: list[str]) -> dict[str, Any]: