Browse Source

init commit

jxing 4 years ago
parent
commit
e5e090fcfe
26 changed files with 1601 additions and 2 deletions
  1. 5 2
      docs/dev/data-center/index.js
  2. 2 0
      docs/dev/data-center/relations/README.md
  3. 45 0
      docs/dev/data-center/relations/belongs/Bd2Sp.md
  4. 47 0
      docs/dev/data-center/relations/belongs/Eq2Bd.md
  5. 109 0
      docs/dev/data-center/relations/belongs/Eq2Fl.md
  6. 50 0
      docs/dev/data-center/relations/belongs/Eq2Sh.md
  7. 114 0
      docs/dev/data-center/relations/belongs/Eq2Sp.md
  8. 49 0
      docs/dev/data-center/relations/belongs/Fl2Fl.md
  9. 36 0
      docs/dev/data-center/relations/belongs/Pe2Bd.md
  10. 35 0
      docs/dev/data-center/relations/belongs/Pe2Fl.md
  11. 38 0
      docs/dev/data-center/relations/belongs/Pe2Sh.md
  12. 43 0
      docs/dev/data-center/relations/belongs/Pe2Sp.md
  13. 57 0
      docs/dev/data-center/relations/belongs/Sh2Sh.md
  14. 48 0
      docs/dev/data-center/relations/belongs/Sp2Sp.md
  15. 48 0
      docs/dev/data-center/relations/belongs/Sy2Bd.md
  16. 48 0
      docs/dev/data-center/relations/belongs/Sy2Fl.md
  17. 48 0
      docs/dev/data-center/relations/belongs/Sy2Sh.md
  18. 54 0
      docs/dev/data-center/relations/belongs/Sy2Sp.md
  19. 15 0
      docs/dev/data-center/relations/belongs/rel_dependency
  20. 65 0
      docs/dev/data-center/relations/other/CalcArea.md
  21. 72 0
      docs/dev/data-center/relations/other/calc_area_v3.md
  22. 157 0
      docs/dev/data-center/relations/other/calc_vertically_overlap.md
  23. 106 0
      docs/dev/data-center/relations/other/rel_affected_space.md
  24. 80 0
      docs/dev/data-center/relations/other/rel_str_similar.md
  25. 145 0
      docs/dev/data-center/relations/topology/sys_block.md
  26. 85 0
      docs/dev/data-center/relations/topology/sys_direction.md

+ 5 - 2
docs/dev/data-center/index.js

@@ -15,9 +15,12 @@ const content = [
     },
     {
         title: "关系计算",
-        path: "/dev/data-center/relation/",
+        path: "/dev/data-center/relations/",
         children: [
-            ["/dev/data-center/relation/swagger", "关系计算"]
+            ["/dev/data-center/relations/belongs/Bd2Sp", "建筑下的业务空间"],
+            ["/dev/data-center/relations/belongs/CalcArea", "计算面积"],
+            ["/dev/data-center/relations/belongs/Eq2Bd", "设备所在建筑"],
+            ["/dev/data-center/relations/belongs/Eq2Fl", "设备所在楼层"]
         ]
     },
     {

+ 2 - 0
docs/dev/data-center/relations/README.md

@@ -0,0 +1,2 @@
+# 开发文档
+

+ 45 - 0
docs/dev/data-center/relations/belongs/Bd2Sp.md

@@ -0,0 +1,45 @@
+建筑下的业务空间
+## 前置条件
+有所属楼层的业务空间(所有业务空间表都需要处理)
+```
+FROM zone_* WHERE project_id='Pj4201050001' AND floor_id is not null
+```
+## 处理方式
+
+从绑定的楼层里取建筑ID,
+将建筑ID填入设备ID(建筑ID为空,则设备的建筑ID也为空)。
+
+## 实现方式
+SQL
+```
+update zone_air_conditioning zone set zone.building_id = floor.building_id from floor where zone.floor_id = floor.id and zone.project_id = 'Pj4201050001' and zone.floor_id is not null
+```
+
+# 函数
+```
+create or replace function public.rel_bd2sp(tables text, project_id character varying) returns boolean
+as
+$$
+try:
+    list = tables.split(',')
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        for table in list:
+            str = "UPDATE {0} as zone SET building_id = floor.building_id from public.floor as floor where floor_id = floor.id and zone.project_id = $1 and floor_id is not null".format(table.strip())
+            plan = plpy.prepare(str, ["text"])
+            data = plan.execute([project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 输入
+    1. 参与计算的表的全名称, 带schema名, 以英文逗号隔开
+    2. 项目id
+## 返回结果
+    true    成功
+    false   失败

+ 47 - 0
docs/dev/data-center/relations/belongs/Eq2Bd.md

@@ -0,0 +1,47 @@
+设备所在建筑
+## 前置条件
+设备已经绑定楼层
+```
+FROM equipment WHERE project_id='Pj4201050001' AND floor_id is not null
+```
+
+## 依赖函数
+    Eq2Fl
+## 处理方式
+
+从绑定的楼层里取建筑ID,
+将建筑ID填入设备ID(建筑ID为空,则设备的建筑ID也为空)。
+
+## 实现方式
+SQL
+```
+UPDATE equipment SET building_id = (SELECT building_id FROM "floor" WHERE id = equipment.floor_id) WHERE project_id='Pj4201050001' AND floor_id is not null
+```
+
+# 函数
+
+```plpython
+create or replace function public.rel_eq2bd(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        str = "UPDATE public.equipment SET building_id = (SELECT building_id FROM public.floor WHERE id = equipment.floor_id) WHERE project_id=$1 AND floor_id is not null"
+        plan = plpy.prepare(str, ["text"])
+        data = plan.execute([project_id])
+except Exception as e:
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+
+## 入参
+    1. 项目id
+
+## 返回结果
+    true    成功
+    false   失败

+ 109 - 0
docs/dev/data-center/relations/belongs/Eq2Fl.md

@@ -0,0 +1,109 @@
+设备所在楼层
+## 前置条件
+    1. 设备有ModelId信息点
+    2. 楼层有ModelId信息点
+    3. 楼层的ModelId信息点等于设备的ModelId所属模型楼层的信息点
+    4. 设备没有所在楼层关系
+    5. 设备有Location(非必要)
+    6. 楼层有Outline(非必要)
+    
+
+## 处理方式
+    1. 如果楼层有Outline, 设备有Location, 做点(Location)在多边形(Outline)内的判断, 在多边形内则认为有关系
+    2. 如果不满足条件1, 则判断ModelId信息点的值, 相等则认为有关系
+
+# 函数
+
+## 代码
+```
+-- 设备所在楼层
+CREATE OR REPLACE FUNCTION "public"."rel_eq2fl"("project_id" varchar)
+  RETURNS "pg_catalog"."bool" AS $BODY$
+from matplotlib.path import Path
+import json
+
+def is_in_meta_polygon(x, y, json_poly):
+    try:
+        poly = []
+        polygon = json.loads(json_poly)
+        l = len(polygon)
+        points = (float(x), float(y))
+        for i in range(l):
+            pair = polygon[i]
+            poly.append((pair["X"], pair["Y"]))
+
+        p = Path(poly)
+    except Exception as e:
+        plpy.info(e)
+        return False
+    else:
+        return p.contains_points([points], None, -0.0001)
+
+
+model_floor_map = dict()
+
+def get_model_floor(model_id):
+    try:
+        if model_id in model_floor_map :
+            return model_floor_map[model_id]
+        model_floor_plan = plpy.prepare("select model_floor_id from revit.model_file where id = $1", ["text"])
+        model_floor_arr = model_floor_plan.execute([model_id])
+        if len(model_floor_arr) == 0:
+            return None
+        model_floor = model_floor_arr[0].get('model_floor_id')
+        model_floor_map[model_id] = model_floor
+        return model_floor
+    except Exception as e:
+        return None
+
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        floor_plan = plpy.prepare("select id, outline, model_id from floor where project_id = $1 and model_id is not null", ["text"])
+        floors = floor_plan.execute([project_id])
+        equip_plan = plpy.prepare("select id, bim_location, model_id from equipment where project_id = $1 and model_id is not null", ["text"])
+        equips = equip_plan.execute([project_id])
+        for floor in floors:
+            for equip in equips:
+                outline = floor['outline']
+                location = equip['bim_location']
+                if outline == None or len(outline) == 0 or location == None or len(location) == 0:
+                    model_floor_id = get_model_floor(equip['model_id'])
+                    if model_floor_id is None:
+                        continue
+                    if floor['model_id'] == model_floor_id:
+                        equip['floor_id'] = floor['id']
+                    continue
+                location = location.split(',')
+                try:
+                    if floor['model_id'] == model_floor_id:
+                        if is_in_meta_polygon(location[0], location[1], outline):
+                            equip['floor_id'] = floor['id']
+                except Exception as ex:
+                    continue
+        for equip in equips:
+            if equip.__contains__('floor_id'):
+                try:
+                    #plpy.info("equip id {0}, floor id {1}".format(equip['id'], equip['floor_id']))
+                    plan = plpy.prepare("update equipment set floor_id = $1 where id = $2", ["text", "text"])
+                    plan.execute([equip['floor_id'], equip['id']])
+                except Exception as ex:
+                    continue
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$BODY$
+  LANGUAGE plpython3u VOLATILE
+  COST 100
+
+
+select public.rel_eq2fl('Pj1101010015');
+```
+
+## 输入
+    1. 项目id
+## 返回结果
+    true    成功
+    false   失败

+ 50 - 0
docs/dev/data-center/relations/belongs/Eq2Sh.md

@@ -0,0 +1,50 @@
+设备所在竖井关系计算
+## 前置条件
+    1. 首先需要有设备所在业务空间关系
+    2. 需要有竖井包含业务空间关系
+    
+## 依赖函数
+    Eq2Sp
+## 计算过程
+    1. 通过 竖井--> 业务空间 --> 设备的间接关系, 堆出竖井和设备的关系
+    2. 结果保存到r_eq_in_sh表中
+## 函数
+```
+-- 设备所在竖井
+create or replace function public.rel_eq2sh(project_id character varying) returns boolean
+as
+$$
+
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_eq_in_sh where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        join_plan = plpy.prepare("select sh.shaft_id as shaft_id, eq.equip_id as equip_id from r_sh_contain_sp_base as sh inner join r_eq_in_sp_base as eq on sh.space_id = eq.space_id where eq.project_id = $1 and sh.project_id = $1", ["text"])
+        rel = join_plan.execute([project_id])
+        eq2sh = dict()
+        for row in rel:
+            shaft = row['shaft_id']
+            equip = row['equip_id']
+            if shaft not in eq2sh:
+                eq2sh[shaft] = set()
+            eq_set = eq2sh[shaft]
+            eq_set.add(equip)
+        for sid, eq_set in eq2sh.items():
+            for eq_id in eq_set:
+                plan = plpy.prepare("insert into r_eq_in_sh(shaft_id, equip_id, project_id, sign) values($1, $2, $3, 2)", ["text", "text", "text"])
+                plan.execute([sid, eq_id, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 入参
+    1. 项目id
+    
+## 例子
+    1. select public.rel_eq2sh('Pj1101010015');

+ 114 - 0
docs/dev/data-center/relations/belongs/Eq2Sp.md

@@ -0,0 +1,114 @@
+设备所在业务空间
+## 前置条件
+    1. 业务空间有所属楼层
+    2. 设备有所属楼层
+    3. 业务空间有轮廓数据(outline)
+    4. 设备有坐标信息(bimLocation)
+## 依赖函数
+    Eq2Fl
+## 处理方式
+    将在同一楼层内的设备和业务空间取出, 判断设备的bim_location是否在业务空间的outline内, 
+    如果在, 则添加进对应的关系表内, 控制sign = 2
+
+# 函数
+## 代码
+```
+create or replace function public.rel_eq2sp(tables text, out_tables text, project_id character varying, sign1 integer, sign2 integer) returns boolean
+as
+$$
+from matplotlib.path import Path
+import json
+
+def is_in_meta_polygon(point, single_poly, radius):
+    poly_len = len(single_poly)
+    poly = []
+    for i in range(poly_len):
+        pair = single_poly[i]
+        poly.append((pair["X"], pair["Y"]))
+    p = Path(poly)
+    return p.contains_points([point], None, radius)
+
+
+def is_in_polygon(point, polygons):
+    polygons_length = len(polygons)
+    if polygons_length == 0:
+        return False
+    for j in range(polygons_length):
+        polygon = polygons[j]
+        if j == 0:
+            if not is_in_meta_polygon(point, polygon, -0.001):
+                return False
+        else:
+            if is_in_meta_polygon(point, polygon, 0.001):
+                return False
+    return True
+
+
+def is_point_in_polygon(x, y, json_poly):
+    try:
+        polygon_list = json.loads(json_poly)
+        total_len = len(polygon_list)
+        point_pair = (float(x), float(y))
+        for index in range(total_len):
+            if is_in_polygon(point_pair, polygon_list[index]):
+                return True
+        return False
+    except Exception as e:
+        plpy.info(e)
+        return False
+
+
+# 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+input_table_list = tables.split(',')
+output_table_list = out_tables.split(',')
+with plpy.subtransaction():
+    for i in range(0, len(input_table_list)):
+        in_table_name = input_table_list[i]
+        out_table_name = output_table_list[i]
+        # 删除原来关系表中的数据
+        plan1 = plpy.prepare("delete from {0} where project_id = $1 and (sign = $2 or sign = $3)".format(out_table_name.strip()), ["text", "integer", "integer"])
+        plan1.execute([project_id, sign1, sign2])
+        # 计算关系
+        plan_floor = plpy.prepare("select id from floor where project_id = $1", ["text"])
+        floors = plan_floor.execute([project_id])
+        # 按楼层计算
+        for floor in floors:
+            floor_id = floor['id']
+            # 获取楼层下的设备
+            plan_equip = plpy.prepare("select id, bim_location from equipment where project_id = $1 and bim_location is not null and floor_id = $2", ["text", "text"])
+            equips = plan_equip.execute([project_id, floor_id])
+            if len(equips) == 0:
+                continue
+            # 获取楼层下的业务空间
+            space_plan = plpy.prepare("select id, outline from {0} as sp where project_id = $1 and outline is not null and floor_id = $2".format(in_table_name), ["text", "text"])
+            spaces = space_plan.execute([project_id, floor_id])
+            if len(spaces) == 0:
+                continue
+            # 判断设备的bim_location是否在业务空间的outline内
+            for equip in equips:
+                for space in spaces:
+                    try:
+                        location = equip['bim_location'].split(',')
+                        if is_point_in_polygon(location[0], location[1], space['outline']):
+                            # 设备在业务空间内, 添加关系
+                            insert_plan = plpy.prepare("insert into {0}(equip_id, space_id, project_id, sign) values($1, $2, $3, 2) ".format(out_table_name.strip()), ["text", "text", 'text'])
+                            insert_plan.execute([equip['id'], space['id'], project_id])
+                    except Exception as ex:
+                        continue
+return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+
+例子:
+select public.rel_eq2sp('zone_general,zone_lighting', 'r_eq_in_sp_zone_general,r_eq_in_sp_zone_lighting', 'Pj1101010015', 2, 2);
+```
+## 输入
+    1. 参与计算的业务空间表的全名称, 带schema名, 以英文逗号隔开
+    2. 关系计算结果存储的表, 跟第一个参数一一对应, 带schema名, 以英文逗号隔开
+    3. 项目id
+    4. 要被删除的sign, 只能是int型, 值为1或2 (1表示手动维护的关系, 2表示自动计算的关系)
+    5. 第二个要被删除的sign, 只能是int型, 值为1或2 (1表示手动维护的关系, 2表示自动计算的关系)
+## 返回结果
+    true    成功
+    false   失败

+ 49 - 0
docs/dev/data-center/relations/belongs/Fl2Fl.md

@@ -0,0 +1,49 @@
+楼层贯通关系
+## 前置条件
+两个楼层的model_id信息点需要一致并不能为空, 则认为两个楼层是贯通关系
+```
+FROM floor WHERE project_id='Pj4201050001' AND model_id is not null
+```
+## 处理方式
+    1. 先删除项目下所有的贯通关系(只删除sign为2的关系)
+    2. 多表连接更新:
+        1. 根据model_id信息点多表连接
+        2. 将结果插入到r_fl_through_fl表中, sign置为2
+
+# 函数
+
+```
+create or replace function public.rel_fl2fl(project_id character varying, sign1 integer, sign2 integer) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        # 删除原来楼层贯通关系
+        plan1 = plpy.prepare("delete from r_fl_through_fl where project_id = $1 and (sign = $2 or sign = $3)", ["text", "integer", "integer"])
+        plan1.execute([project_id, sign1, sign2])
+        # 计算贯通关系
+        plan2 = plpy.prepare("select f1.id as lid, f2.id as rid from floor f1 left join floor f2 on f1.model_id = f2.model_id where f1.id != f2.id and f1.project_id = $1 and f2.project_id = $1", ["text"])
+        relation = plan2.execute([project_id])
+        if len(relation) == 0:
+            # plpy.info("计算出没有关系")
+            return True
+        for row in relation:
+            plan3 = plpy.prepare("insert into r_fl_through_fl(floor_id, floor_other_id, project_id, sign) values($1, $2, $3, 2) ", ["text", "text", 'text'])
+            plan3.execute([row['lid'], row['rid'], project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 输入
+    1. 项目id
+    2. 表示r_fl_through_fl表中要被删除的sign值
+    3. 同上, 与2是逻辑或的关系
+## 返回结果
+    true    成功
+    false   失败

+ 36 - 0
docs/dev/data-center/relations/belongs/Pe2Bd.md

@@ -0,0 +1,36 @@
+资产所在建筑
+## 前置条件
+    1. 资产有绑定的设备或部件
+    2. 绑定的设备或部件有所在建筑
+## 依赖函数
+    Eq2Bd
+## 计算流程
+    根据 资产 --> 设备/部件 --> 建筑 的间接关系, 来获取资产和建筑的所在关系
+    
+## 函数
+```
+-- 资产属于建筑体
+create or replace function public.rel_pe2bd(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        plan = plpy.prepare("update property as pe set building_id = eq.building_id from equipment eq where eq.id = pe.equip_id and eq.building_id is not null and eq.project_id = $1 and pe.project_id = $1", ["text"])
+        rel = plan.execute([project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+
+select public.rel_pe2bd('Pj1101010015');
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_pe2bd('Pj1101010015');

+ 35 - 0
docs/dev/data-center/relations/belongs/Pe2Fl.md

@@ -0,0 +1,35 @@
+资产所在楼层
+## 前置条件
+    1. 资产有绑定的设备或部件
+    2. 绑定的设备或部件有所在楼层
+## 依赖函数
+    Eq2Fl
+## 计算流程
+    根据 资产 --> 设备/部件 --> 楼层 的间接关系, 来获取资产所在建筑的关系
+    
+## 函数
+```
+-- 资产属于楼层
+create or replace function public.rel_pe2fl(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        plan = plpy.prepare("update property as pe set floor_id = eq.floor_id from equipment eq where eq.id = pe.equip_id and eq.floor_id is not null and eq.project_id = $1 and pe.project_id = $1", ["text"])
+        rel = plan.execute([project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+select public.rel_pe2fl('Pj1101010015');
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_pe2fl('Pj1101010015');

+ 38 - 0
docs/dev/data-center/relations/belongs/Pe2Sh.md

@@ -0,0 +1,38 @@
+资产所在竖井
+## 前置条件
+    1. 资产有绑定的设备或部件
+    2. 绑定的设备或部件有所在竖井关系
+## 依赖函数
+    Eq2Sh
+## 计算流程
+    根据 资产 --> 设备/部件 --> 竖井 的间接关系, 来获取资产所在竖井的关系
+    
+## 函数
+```
+-- 资产所在竖井
+create or replace function public.rel_pe2sh(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_pe_in_sh where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        plan = plpy.prepare("insert into r_pe_in_sh(equip_id, shaft_id, project_id, sign) select pe.id, rel.shaft_id, rel.project_id, 2 from property pe inner join r_eq_in_sh rel on pe.equip_id = rel.equip_id where pe.project_id = $1 and rel.project_id = $1", ["text"])
+        plan.execute([project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+
+select public.rel_pe2sh('Pj1101010015');
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_pe2sh('Pj1101010015');

+ 43 - 0
docs/dev/data-center/relations/belongs/Pe2Sp.md

@@ -0,0 +1,43 @@
+资产所在业务空间
+## 前置条件
+    1. 资产有绑定的设备或部件
+    2. 绑定的设备或部件有所在业务空间关系
+## 依赖函数
+    Eq2Sp
+## 计算流程
+    根据 资产 --> 设备/部件 --> 业务空间 的间接关系, 来获取资产所在业务空间的关系
+    
+## 函数
+```
+-- 资产所在业务空间
+create or replace function public.rel_pe2sp(project_id character varying) returns boolean
+as
+$$
+try:
+    input_tables = ['r_eq_in_sp_zone_air_conditioning', 'r_eq_in_sp_zone_clean', 'r_eq_in_sp_zone_domestic_water_supply', 'r_eq_in_sp_zone_fire', 'r_eq_in_sp_zone_function',
+        'r_eq_in_sp_zone_general', 'r_eq_in_sp_zone_heating', 'r_eq_in_sp_zone_lighting', 'r_eq_in_sp_zone_network', 'r_eq_in_sp_zone_power_supply', 'r_eq_in_sp_zone_security', 'r_eq_in_sp_zone_tenant']
+    output_tables = ['r_pe_in_sp_zone_air_conditioning', 'r_pe_in_sp_zone_clean', 'r_pe_in_sp_zone_domestic_water_supply',
+        'r_pe_in_sp_zone_fire', 'r_pe_in_sp_zone_function', 'r_pe_in_sp_zone_general', 'r_pe_in_sp_zone_heating', 'r_pe_in_sp_zone_lighting',
+        'r_pe_in_sp_zone_network', 'r_pe_in_sp_zone_power_supply', 'r_pe_in_sp_zone_security', 'r_pe_in_sp_zone_tenant']
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        for i in range(len(output_tables)):
+            delete_plan = plpy.prepare("delete from {0} where project_id = $1 and sign = 2".format(output_tables[i]), ["text"])
+            delete_plan.execute([project_id])
+            join_plan = plpy.prepare("insert into {1}(equip_id, space_id, project_id, sign) select pe.id, rel.space_id, pe.project_id, 2 from property pe inner join {0} rel on pe.equip_id = rel.equip_id where pe.project_id = $1 and rel.project_id = $1".format(input_tables[i], output_tables[i]), ["text"])
+            rel = join_plan.execute([project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+select public.rel_pe2sp('Pj1101010015');
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_pe2sp('Pj1101010015');

+ 57 - 0
docs/dev/data-center/relations/belongs/Sh2Sh.md

@@ -0,0 +1,57 @@
+竖井贯通关系
+## 前置条件
+    1. 竖井包含的有元空间(竖井在r_sh_contain_si表中有数据)
+## 处理流程
+    1. 竖井包含有相同的元空间, 则认为两竖井贯通
+## 函数
+```
+-- 竖井贯通关系
+create or replace function public.rel_sh2sh(project_id character varying) returns boolean
+as
+$$
+def is_exist(shaft1, shaft2, rel_dict):
+    if shaft1 in rel_dict:
+        if shaft2 in rel_dict[shaft1]:
+            return True
+    if shaft2 in rel_dict:
+        if shaft1 in rel_dict[shaft2]:
+            return True
+    return False
+
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_sh_through_sh where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        sh_plan = plpy.prepare("select s1.shaft_id s1, s2.shaft_id s2 from r_sh_contain_si s1 inner join r_sh_contain_si s2 on s1.ispace_id = s2.ispace_id where s1.project_id = $1 and s2.project_id = $1 and s1.shaft_id != s2.shaft_id", ["text"])
+        rel = sh_plan.execute([project_id])
+        rel_dict = dict()
+        for row in rel:
+            shaft1 = row['s1']
+            shaft2 = row['s2']
+            if shaft1 == None or shaft2 == None:
+                continue
+            if not is_exist(shaft1, shaft2, rel_dict):
+                if shaft1 not in rel_dict:
+                    rel_dict[shaft1] = set()
+                rel_dict[shaft1].add(shaft2)
+        for shaft1, sub_set in rel_dict.items():
+            for shaft2 in sub_set:
+                plan = plpy.prepare("insert into r_sh_through_sh(shaft_id, shaft_other_id, project_id, sign) values($1, $2, $3, 2)", ["text", "text", "text"])
+                plan.execute([shaft1, shaft2, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+
+select public.rel_sh2sh('Pj1102290002')
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_sh2sh('Pj1102290002');

+ 48 - 0
docs/dev/data-center/relations/belongs/Sp2Sp.md

@@ -0,0 +1,48 @@
+业务空间邻接关系
+## 前置条件
+    1. 业务空间有所在楼层
+    2. 业务空间有外轮廓
+## 处理流程
+    1. 查出所有有所在楼层, 并且外轮廓不是null的业务空间
+    2. 根据所在楼层, 业务空间分区来将业务空间分组
+    3. 计算每个分组内的业务空间的相邻关系
+    计算相邻算法:
+    1. 首先判断围成业务空间的线段, 两两空间之间是否有近似平行的线段(线段偏转误差小于1度)
+        1). 近似平行判断: 首先获取两个线段的斜率, 再计算斜率的反正切(即与x轴的角度, 不过是以pi为单位), 再判断两个角度差的绝对值是否小于1度
+    2. 如果有近似平行的线段, 判断是否相互有投影在线段上, 有投影在线段上, 则认为是两平行线段有重合部分, 业务空间有相邻的可能性
+    3. 在判断互相有投影点在对方线段上之后, 判断投影线的长度, 是否小于250mm(墙的最大厚度), 如果小于250mm则认为两空间相邻
+## 函数
+```
+create or replace function public.rel_sp2sp1(project_id character varying) returns boolean
+as
+$$
+from relations.src.business_space_adjacent.adjacent import calc_space_adjacent
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_spatial_connection where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        space_data_plan = plpy.prepare("SELECT id, project_id, floor_id, object_type, bim_location, outline FROM zone_space_base where project_id = $1 and outline is not null and floor_id is not null order by floor_id, object_type", ["text"])
+        space_data = space_data_plan.execute([project_id])
+        rel_data = calc_space_adjacent(space_data)
+        for single_rel in rel_data:
+            delete_duplicate_plan = plpy.prepare("delete from r_spatial_connection where space_id_one = $1 and space_id_two = $2", ["text", "text"])
+            delete_duplicate_plan.execute([single_rel['space_id_one'], single_rel['space_id_two']])
+            insert_plan = plpy.prepare("insert into r_spatial_connection(project_id, location_one, location_two, space_id_one, space_id_two, sign, graph_type, floor_id, zone_type) values($1, $2, $3, $4, $5, 2, 'SpaceNeighborhood', $6, $7)", ["text", "text", "text", "text", "text", "text", "text"])
+            insert_plan.execute([project_id, single_rel['location_one'], single_rel['location_two'], single_rel['space_id_one'], single_rel['space_id_two'], single_rel['floor_id'], single_rel['zone_type']])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+
+select public.rel_sp2sp1('Pj1101050001')
+```
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_sp2sp1('Pj1102290002');

+ 48 - 0
docs/dev/data-center/relations/belongs/Sy2Bd.md

@@ -0,0 +1,48 @@
+系统所在建筑
+## 前置条件
+    1. 系统下有设备
+    2. 系统下的设备有所属建筑的关系
+## 依赖函数
+    Eq2Bd
+## 计算流程
+    1. 通过 系统 --> 设备 --> 建筑 的间接关系, 获取系统所在建筑的关系
+    2. 结果存储在 r_sy_in_bd 表中
+    
+## 函数
+~~~
+-- 系统所在建筑
+create or replace function public.rel_sy2bd(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_sy_in_bd where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        join_plan = plpy.prepare("select sy.sys_id, eq.building_id from r_sy_eq sy inner join equipment eq on sy.equip_id = eq.id where eq.project_id = $1 and sy.project_id = $1 and eq.building_id is not null", ["text"])
+        rel = join_plan.execute([project_id])
+        sy2bd = dict()
+        for row in rel:
+            sys = row['sys_id']
+            building = row['building_id']
+            if sys not in sy2bd:
+                sy2bd[sys] = set()
+            sys_set = sy2bd[sys]
+            sys_set.add(building)
+        for sid, sys_set in sy2bd.items():
+            for building in sys_set:
+                plan = plpy.prepare("insert into r_sy_in_bd(sys_id, building_id, project_id, sign) values($1, $2, $3, 2)", ["text", "text", "text"])
+                plan.execute([sid, building, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+~~~
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_sy2bd('Pj1101010015');

+ 48 - 0
docs/dev/data-center/relations/belongs/Sy2Fl.md

@@ -0,0 +1,48 @@
+系统所在楼层
+## 前置条件
+    1. 系统下有设备
+    2. 系统下的设备有所属楼层关系
+## 依赖函数
+    Eq2Fl
+## 计算流程
+    1. 通过 系统 --> 设备 --> 楼层 的间接关系, 获取系统所在楼层的关系
+    2. 计算关系存储在表 r_sy_in_fl 中
+## 函数
+~~~
+-- 系统所在楼层
+create or replace function public.rel_sy2fl(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_sy_in_fl where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        join_plan = plpy.prepare("select sy.sys_id, eq.building_id, eq.floor_id from r_sy_eq sy inner join equipment eq on sy.equip_id = eq.id where eq.project_id = $1 and sy.project_id = $1 and eq.floor_id is not null", ["text"])
+        rel = join_plan.execute([project_id])
+        sy2bd = dict()
+        for row in rel:
+            sys = row['sys_id']
+            building = row['building_id']
+            floor = row['floor_id']
+            if sys not in sy2bd:
+                sy2bd[sys] = dict()
+            sys_dict = sy2bd[sys]
+            sys_dict[floor] = building
+        for sid, sys_dict in sy2bd.items():
+            for floor, building in sys_dict.items():
+                plan = plpy.prepare("insert into r_sy_in_fl(sys_id, floor_id, building_id, project_id, sign) values($1, $2, $3, $4, 2)", ["text", "text", "text", "text"])
+                plan.execute([sid, floor, building, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+~~~
+
+## 入参
+    1. 项目id
+## 例子
+    select public.rel_sy2fl('Pj1101010015');

+ 48 - 0
docs/dev/data-center/relations/belongs/Sy2Sh.md

@@ -0,0 +1,48 @@
+系统所在竖井
+## 前置条件
+    1. 系统下有设备
+    2. 系统下的设备有所在竖井关系
+## 依赖函数
+    Eq2Sh
+## 计算流程
+    1. 根据 系统 --> 设备 --> 竖井 来间接获取系统所属竖井关系
+    2. 计算后的关系存储在表 r_sy_in_sh
+## 函数
+```
+-- 系统所在竖井
+create or replace function public.rel_sy2sh(project_id character varying) returns boolean
+as
+$$
+try:
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        delete_plan = plpy.prepare("delete from r_sy_in_sh where project_id = $1 and sign = 2", ["text"])
+        delete_plan.execute([project_id])
+        join_plan = plpy.prepare("select sy.sys_id, rel.shaft_id from r_sy_eq sy inner join r_eq_in_sh rel on sy.equip_id = rel.equip_id where rel.project_id = $1 and sy.project_id = $1", ["text"])
+        rel = join_plan.execute([project_id])
+        sy2sh = dict()
+        for row in rel:
+            sys = row['sys_id']
+            shaft = row['shaft_id']
+            if sys not in sy2sh:
+                sy2sh[sys] = set()
+            shaft_set = sy2sh[sys]
+            shaft_set.add(shaft)
+        for sid, shaft_set in sy2sh.items():
+            for shaft in shaft_set:
+                plan = plpy.prepare("insert into r_sy_in_sh(sys_id, shaft_id, project_id, sign) values($1, $2, $3, 2)", ["text", "text", "text"])
+                plan.execute([sid, shaft, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 输入
+    1. 项目id
+
+## 例子
+    select public.rel_sy2sh('Pj1101010015');

+ 54 - 0
docs/dev/data-center/relations/belongs/Sy2Sp.md

@@ -0,0 +1,54 @@
+系统所在业务空间
+## 前置条件
+    1. 系统下有设备
+    2. 系统下的设备有所在业务空间关系
+## 依赖函数
+    Eq2Sp
+## 计算流程
+    1. 根据 系统 --> 设备 --> 业务空间 的间接关系, 获取系统和业务空间的关系
+    2. 计算结果存储在r_sy_in_sp_*中
+## 函数
+```
+-- 系统所在楼层
+create or replace function public.rel_sy2sp(project_id character varying) returns boolean
+as
+$$
+try:
+    input_tables = ['r_eq_in_sp_zone_air_conditioning', 'r_eq_in_sp_zone_clean', 'r_eq_in_sp_zone_domestic_water_supply', 'r_eq_in_sp_zone_fire', 'r_eq_in_sp_zone_function',
+        'r_eq_in_sp_zone_general', 'r_eq_in_sp_zone_heating', 'r_eq_in_sp_zone_lighting', 'r_eq_in_sp_zone_network', 'r_eq_in_sp_zone_power_supply', 'r_eq_in_sp_zone_security', 'r_eq_in_sp_zone_tenant']
+    output_tables = ['r_sy_in_sp_zone_air_conditioning', 'r_sy_in_sp_zone_clean', 'r_sy_in_sp_zone_domestic_water_supply',
+        'r_sy_in_sp_zone_fire', 'r_sy_in_sp_zone_function', 'r_sy_in_sp_zone_general', 'r_sy_in_sp_zone_heating', 'r_sy_in_sp_zone_lighting',
+        'r_sy_in_sp_zone_network', 'r_sy_in_sp_zone_power_supply', 'r_sy_in_sp_zone_security', 'r_sy_in_sp_zone_tenant']
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        for i in range(len(output_tables)):
+            delete_plan = plpy.prepare("delete from {0} where project_id = $1 and sign = 2".format(output_tables[i]), ["text"])
+            delete_plan.execute([project_id])
+            join_plan = plpy.prepare("select sy.sys_id, rel.space_id from r_sy_eq sy inner join {0} rel on sy.equip_id = rel.equip_id where rel.project_id = $1 and sy.project_id = $1".format(input_tables[i]), ["text"])
+            rel = join_plan.execute([project_id])
+            sy2sp = dict()
+            for row in rel:
+                sys = row['sys_id']
+                space = row['space_id']
+                if sys not in sy2sp:
+                    sy2sp[sys] = set()
+                space_set = sy2sp[sys]
+                space_set.add(space)
+            for sid, space_set in sy2sp.items():
+                for space in space_set:
+                    plan = plpy.prepare("insert into {0}(sys_id, space_id, project_id, sign) values($1, $2, $3, 2)".format(output_tables[i]), ["text", "text", "text"])
+                    plan.execute([sid, space, project_id])
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 输入
+    1. 项目id
+
+## 例子
+    select public.rel_sy2sp('Pj1101010015');

+ 15 - 0
docs/dev/data-center/relations/belongs/rel_dependency

@@ -0,0 +1,15 @@
+# 函数依赖
+
+|函数  |依赖于  |
+| ---- | ------ |
+|Eq2Bd |Eq2Fl   |
+|Eq2Sh |Eq2Sp   |
+|Eq2Sp |Eq2Fl   |
+|Pe2Bd |Eq2Bd   |
+|Pe2Fl |Eq2Fl   |
+|Pe2Sh |Eq2Sh   |
+|Pe2Sp |Eq2Sp   |
+|Sy2Bd |Eq2Bd   |
+|Sy2Fl |Eq2Fl   |
+|Sy2Sh |Eq2Sh   |
+|Sy2Sp |Eq2Sp   |

+ 65 - 0
docs/dev/data-center/relations/other/CalcArea.md

@@ -0,0 +1,65 @@
+计算业务空间面积
+## 前置条件 
+    outline信息点不为空, 且格式正确
+    
+## 函数
+
+```
+CREATE OR REPLACE FUNCTION calc_area_v3(json_poly jsonb) RETURNS float8 AS
+$$
+from shapely.geometry import Polygon
+import json
+
+def meta_polygon_area(single_poly):
+    try:
+        poly_len = len(single_poly)
+        poly = []
+        for i in range(poly_len):
+            pair = single_poly[i]
+            poly.append((pair["X"], pair["Y"]))
+        p = Polygon(poly)
+        return p.area
+    except Exception as e:
+        plpy.info(e)
+        return 0.0
+
+
+def get_area(polygons):
+    length = len(polygons)
+    if length == 0:
+        return 0.0
+    total_area = 0.0
+    for j in range(length):
+        polygon = polygons[j]
+        single_area = meta_polygon_area(polygon)
+        if single_area == 0.0:
+            return 0.0
+        if j == 0:
+            total_area = single_area
+        else:
+            total_area -= single_area
+    return total_area
+
+try:
+    polygon_list = json.loads(json_poly)
+    total_len = len(polygon_list)
+    area = 0.0
+    for index in range(total_len):
+        single_area = get_area(polygon_list[index])
+        if single_area == 0:
+            return None
+        area += single_area
+    return area
+except Exception as e:
+    plpy.info(e)
+    return None
+$$
+    LANGUAGE 'plpython3u' VOLATILE;
+```
+## 入参
+    1. 业务空间的outline
+## 例子
+    select id, outline, local_name, calc_area_v3(outline)/1000000 as "area(m2)" from public.zone_air_conditioning where outline is not null;
+    
+## 其他
+    Python的Shapely包文档地址   https://shapely.readthedocs.io/en/latest/manual.html

+ 72 - 0
docs/dev/data-center/relations/other/calc_area_v3.md

@@ -0,0 +1,72 @@
+计算业务空间的面积
+## 前置条件
+
+无
+
+## 处理方式
+
+根据业务空间轮廓的格式, 以此计算内部每个小轮廓的面积, 加和在一起为最终结果
+小轮廓面积计算: 计算首个子轮廓面积, 并减去随后所有子轮廓的面积. 结果为小轮廓面积
+
+## 实现方式
+
+
+# 函数
+```
+CREATE OR REPLACE FUNCTION "public"."calc_area_v3"("json_poly" jsonb)
+  RETURNS "pg_catalog"."float8" AS $BODY$
+from shapely.geometry import Polygon
+import json
+
+def meta_polygon_area(single_poly):
+    try:
+        poly_len = len(single_poly)
+        poly = []
+        for i in range(poly_len):
+            pair = single_poly[i]
+            poly.append((pair["X"], pair["Y"]))
+        p = Polygon(poly)
+        return p.area
+    except Exception as e:
+        plpy.info(e)
+        return 0.0
+
+
+def get_area(polygons):
+    length = len(polygons)
+    if length == 0:
+        return 0.0
+    total_area = 0.0
+    for j in range(length):
+        polygon = polygons[j]
+        single_area = meta_polygon_area(polygon)
+        if single_area == 0.0:
+            return 0.0
+        if j == 0:
+            total_area = single_area
+        else:
+            total_area -= single_area
+    return total_area
+
+try:
+    polygon_list = json.loads(json_poly)
+    total_len = len(polygon_list)
+    area = 0.0
+    for index in range(total_len):
+        single_area = get_area(polygon_list[index])
+        if single_area == 0:
+            return None
+        area += single_area
+    return area
+except Exception as e:
+    plpy.info(e)
+    return None
+$BODY$
+  LANGUAGE plpython3u VOLATILE
+  COST 100
+```
+
+## 输入
+    1. 业务空间的轮廓
+## 返回结果
+    面积 float8

+ 157 - 0
docs/dev/data-center/relations/other/calc_vertically_overlap.md

@@ -0,0 +1,157 @@
+计算所有业务空间, 在竖直方向上的面积重叠关系
+## 前置条件
+只有有轮廓的业务空间才能参与计算
+```
+
+```
+## 处理方式
+
+获取所有建筑, for循环获取每个建筑下所有的业务空间, 按楼层分类
+每个楼层的每个业务空间分别和别的楼层的每个业务空间判断is_vertically_overlap
+将结果是true的两个业务空间保存起来
+删除旧业务空间的垂直交通关系(自动计算的), 添加新关系
+
+## 实现方式
+
+
+# 函数
+```
+create or replace function public.is_vertically_overlap(project_id character varying) returns boolean
+as
+$$
+from shapely.geometry import Polygon
+import json
+
+# 获取Polygon对象
+def get_polygon(single_poly):
+    poly_len = len(single_poly)
+    poly = []
+    for i in range(poly_len):
+        pair = single_poly[i]
+        poly.append((pair["X"], pair["Y"]))
+    return Polygon(poly)
+
+# 在polygon1包含polygon2的时候, 检测是否polygon1内的空洞也包含polygon2
+def is_include(polygon1, poly2):
+    length1 = len(polygon1)
+    for i in range(1, length1):
+        poly1 = get_polygon(polygon1[i])
+        if poly1.overlaps(poly2):
+            return True
+        if poly1.equals(poly2) or poly1.contains(poly2):
+            return False
+    return True
+
+def is_sub_outline_overlap(polygon1, polygon2):
+    poly1 = get_polygon(polygon1[0])
+    poly2 = get_polygon(polygon2[0])
+    if poly1.overlaps(poly2) or poly1.equals(poly2):
+        return True
+    if poly1.contains(poly2):
+        return is_include(polygon1, poly2)
+    if poly2.contains(poly1):
+        return is_include(polygon2, poly1)
+    return False
+
+# 是否垂直方向上面积有重叠
+def is_vertically_overlap(polygon1, polygon2):
+    length1 = len(polygon1)
+    length2 = len(polygon2)
+    if length1 == 0 or length2 == 0:
+        return False
+
+    for i in range(length1):
+        for j in range(length2):
+            if is_sub_outline_overlap(polygon1[i], polygon2[j]):
+                return True
+    return False
+
+
+# building -> floor -> object_type -> [space_id]
+def compose_dict(zone_data):
+    building_map = dict()
+    for row in zone_data:
+        building_id = row['building_id']
+        floor_id = row['floor_id']
+        object_type = row['object_type']
+        if building_id not in building_map:
+            building_map[building_id] = dict()
+        floor_map = building_map[building_id]
+        if floor_id not in floor_map:
+            floor_map[floor_id] = dict()
+        type_map = floor_map[floor_id]
+        if object_type not in type_map:
+            type_map[object_type] = []
+        arr = type_map[object_type]
+        arr.append(row)
+    return building_map
+
+
+try:
+    # 获取所有建筑, for循环获取每个建筑下所有的业务空间, 按楼层分类
+    zone_plan = plpy.prepare("SELECT rel.space_id, fl.building_id, rel.floor_id, rel.object_type, sp.outline FROM r_sp_in_fl rel LEFT JOIN public.floor fl on fl.id = rel.floor_id left join zone_space_base sp on rel.space_id = sp.id where rel.project_id = $1 and sp.outline is not null", ["text"])
+    zone_data = zone_plan.execute([project_id])
+    if len(zone_data) <2:
+        return True
+    row_map = compose_dict(zone_data)
+
+    space_outline_json_map = dict()
+    result_arr = []
+    # 每个楼层的每个业务空间分别和别的楼层的每个业务空间判断is_vertically_overlap
+    # 将结果是true的两个业务空间保存起来
+    for building_id, floor_map in row_map.items():
+        for floor_id, type_map in floor_map.items():
+            for object_type, row_arr in type_map.items():
+                # 要被对比的楼层
+                for other_floor_id in floor_map.keys():
+                    if other_floor_id == floor_id:
+                        continue
+                    other_type_map = floor_map.get(other_floor_id)
+                    if object_type not in other_type_map:
+                        continue
+                    other_row_arr = other_type_map.get(object_type)
+                    for row in row_arr:
+                        for other_row in other_row_arr:
+                            space_id = row['space_id']
+                            other_space_id = other_row['space_id']
+                            if space_id == other_space_id:
+                                continue
+                            if space_id not in space_outline_json_map:
+                                outline_json = json.loads(row['outline'])
+                                space_outline_json_map[space_id] = outline_json
+                            if other_space_id not in space_outline_json_map:
+                                other_outline_json = json.loads(other_row['outline'])
+                                space_outline_json_map[other_space_id] = other_outline_json
+                            outline = space_outline_json_map[space_id]
+                            other_outline = space_outline_json_map[other_space_id]
+                            if is_vertically_overlap(outline, other_outline):
+                                single_result = []
+                                single_result.append(space_id)
+                                single_result.append(other_space_id)
+                                single_result.append(object_type)
+                                result_arr.append(single_result)
+    if len(result_arr) == 0:
+        return True
+    # 删除旧业务空间的垂直交通关系(自动计算的), 添加新关系
+    # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+    with plpy.subtransaction():
+        del_plan = plpy.prepare("delete from r_sp_vertical_sp where project_id = $1 and sign = 2", ["text"])
+        del_plan.execute([project_id])
+        for single_result in result_arr:
+            del_manual_plan = plpy.prepare("delete from r_sp_vertical_sp where (space_id = $1 and space_other_id = $2) or (space_other_id = $1 and space_id = $2)", ["text", "text"])
+            del_manual_plan.execute([single_result[0], single_result[1]])
+            insert_plan = plpy.prepare("insert into r_sp_vertical_sp(space_id, space_other_id, project_id, sign, object_type) values($1, $2, $3, 2, $4)", ["text", "text", "text", "text"])
+            insert_plan.execute([single_result[0], single_result[1], project_id, single_result[2]])
+    return True
+except Exception as e:
+    plpy.info(e)
+    return False
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+## 输入
+    1. 项目id
+## 返回结果
+    true    成功
+    false   失败

+ 106 - 0
docs/dev/data-center/relations/other/rel_affected_space.md

@@ -0,0 +1,106 @@
+计算当新模型上传之后, 因元空间的变化, 受影响的业务空间
+## 前置条件
+    1. 数据中心楼层绑定了模型服务中的楼层id
+    2. 划分了业务空间
+    3. 该模型上传时, 该楼层上必须已有模型
+    4. 原模型有元空间信息
+```
+```
+
+## 依赖函数
+    无
+## 处理方式
+
+    1. 查询出所有绑定该revit模型楼层的数据中心楼层
+        SELECT id FROM public.floor where model_id = 'revit模型楼层'
+    2. 查询出1中楼层下的所有业务空间
+    3. 获取当前模型和前一个版本的模型id
+    4. 获取3中当前模型和上一个版本模型的所有元空间信息
+    5. 执行get_affected_spaced(space_data, new_ispace_data, prev_ispace_data) 函数
+    
+    get_affected_spaced函数逻辑:
+    1. 根据业务空间的外轮廓和prev_ispace_data的外轮廓是否有重叠来获取 业务空间的原元空间的关系
+    2. 根据元空间的sourceId 来判断删除了哪些元空间
+    3. 根据sourceId匹配出当前模型和上一个模型中都存在的元空间, 根据元空间的外轮廓判断是否发生过改变
+    4. 根据2, 3中改变的元空间来判断 1中获取的关系中哪些业务空间因元空间的改变而发生了改变
+    5. 返回4中得到的业务空间
+
+## 实现方式
+
+```
+```
+
+# 函数
+
+```plpython
+CREATE OR REPLACE FUNCTION rel_affected_space(floor_id character varying) RETURNS boolean AS
+$$
+from relations.src.affected_space.affected_space import get_affected_spaced
+from shapely.geometry import Polygon
+import json
+
+try:
+    # 获取当前楼层绑定的数据中心楼层的id
+    floor_plan = plpy.prepare("SELECT id FROM public.floor where model_id = $1", ["text"])
+    floor_data = floor_plan.execute([floor_id])
+    floor_arr = []
+    for fl_row in floor_data:
+        floor_arr.append(fl_row['id'])
+    
+    if len(floor_arr) == 0:
+        plpy.info("no binding floor")
+        return True
+    floor_str = ""
+    for fl in floor_arr:
+        floor_str = (floor_str + "'" + fl + "',")
+    floor_str = floor_str.strip(',')
+    plpy.info(floor_str)
+    # 获取当前模型和前一个版本的模型id
+    model_plan = plpy.prepare("select id from revit.model_file where model_floor_id = $1 and removed = false and status = 4 and version is not null order by version desc", ["text"])
+    model_data = model_plan.execute([floor_id], 2)
+    if len(model_data) != 2:
+        plpy.info("no previous model")
+        return True
+    sql_str = "select rel.floor_id, rel.space_id, sp.outline from r_sp_in_fl rel left join zone_space_base sp on rel.space_id = sp.id where rel.floor_id in (" + floor_str + ")"
+    # 查询出来的跟模型可能相关的所有业务空间
+    space_data = plpy.execute(sql_str)
+    if len(space_data) == 0:
+        plpy.info("no space relation under binding floor")
+        return True
+    plpy.info('space data count : {0}'.format(len(space_data)))
+    # 获取新模型的元空间
+    new_ispace_plan = plpy.prepare("select source_id, outline from revit.space where model_id = $1", ["text"])
+    new_ispace_data = new_ispace_plan.execute([model_data[0]['id']])
+    plpy.info('new_ispace_data count : {0}'.format(len(new_ispace_data)))
+    # 获取旧模型的所有元空间
+    prev_ispace_plan = plpy.prepare("select source_id, outline from revit.space where model_id = $1", ["text"])
+    prev_ispace_data = prev_ispace_plan.execute([model_data[1]['id']])
+    plpy.info('prev_ispace_data count : {0}'.format(len(prev_ispace_data)))
+    if len(prev_ispace_data) == 0:
+        plpy.info("no prev ispace data")
+        return True
+    
+    affected_space_ids = get_affected_spaced(space_data, new_ispace_data, prev_ispace_data)
+    
+    
+    
+    plpy.info('affected_space_ids count : {0}'.format(len(affected_space_ids)))
+    for space_id in affected_space_ids:
+        space_plan = plpy.prepare("update zone_space_base set state = 1 where id = $1", ["text"])
+        space_plan.execute([space_id])
+    
+    return True
+except Exception as e:
+    plpy.info(e)
+    return False
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+```
+
+
+## 入参
+    1. web模型服务中的floor_id, 格式是uuid
+
+## 返回结果
+    true    成功
+    false   失败

+ 80 - 0
docs/dev/data-center/relations/other/rel_str_similar.md

@@ -0,0 +1,80 @@
+计算字符的相似度 (使用编辑距离)
+## 前置条件
+    无
+```
+```
+
+## 依赖函数
+    无
+## 处理方式
+
+    根据编辑距离来计算相似度
+
+## 实现方式
+
+```
+```
+
+# 函数
+
+```plpython
+CREATE OR REPLACE FUNCTION "public"."rel_str_similar"("word" varchar, "possibilities" _varchar, "num" int4, "sim" float8)
+  RETURNS "pg_catalog"."text" AS $BODY$
+import json
+import logging
+from difflib import SequenceMatcher
+from heapq import nlargest as _nlargest
+
+
+
+def get_close(word, possibilities, n=1, cutoff=0.0):
+    """Use SequenceMatcher to return list of the best "good enough" matches.
+    word is a sequence for which close matches are desired (typically a
+    string).
+    possibilities is a list of sequences against which to match word
+    (typically a list of strings).
+    Optional arg n (default 3) is the maximum number of close matches to
+    return.  n must be > 0.
+    Optional arg cutoff (default 0.6) is a float in [0, 1].  Possibilities
+    that don't score at least that similar to word are ignored.
+    The best (no more than n) matches among the possibilities are returned
+    in a list, sorted by similarity score, most similar first.
+    """
+
+    if not n > 0:
+        raise ValueError("n must be > 0: %r" % (n,))
+    if not 0.0 <= cutoff <= 1.0:
+        raise ValueError("cutoff must be in [0.0, 1.0]: %r" % (cutoff,))
+    result = []
+    s = SequenceMatcher()
+    s.set_seq2(word)
+    for x in possibilities:
+        s.set_seq1(x)
+        if s.real_quick_ratio() >= cutoff and s.quick_ratio() >= cutoff and s.ratio() >= cutoff:
+            result.append((s.ratio(), x))
+    result = _nlargest(n, result)
+    return [{'key': x, 'value': score} for score, x in result]
+
+
+try:
+    result = get_close(word, possibilities, num, sim)
+    return result
+except Exception as e:
+    plpy.warning(e)
+    return ""
+else:
+    plpy.warning(e)
+$BODY$
+  LANGUAGE plpython3u VOLATILE
+  COST 100
+```
+
+
+## 入参
+    1. "word" varchar                    需要匹配的字符串
+    2. "possibilities" _varchar          待匹配的字符串数组
+    3. "num" int4                        指定返回结果个数
+    4. "sim" float8                      只有相似度超过sim的结果才会返回
+    例子: select public.rel_str_similar('北京', ARRAY['青岛', '北京市'], 2, 0.0)
+## 返回结果
+    字符串 eg. [{'key': '北京市', 'value': 0.8}, {'key': '青岛', 'value': 0.0}]

+ 145 - 0
docs/dev/data-center/relations/topology/sys_block.md

@@ -0,0 +1,145 @@
+管网系统设备分块
+## 前置条件
+1. 项目下的模型文件已上传;
+2. 所有管网已正确连接;
+3. 相邻层立管开放端口坐标位置相等(距离差值不能超过0.01)
+4. (非强制)通过模型检查:连接件检查、系统类型名称检查、管网及相关设备检查、管段检查
+
+## 处理方式
+
+1. 获取所有当前项目project_id下的的有模型文件models; 表:revit.model_folder、revit.model_floor、revit.model_file
+2. 遍历models,获取model层的系统图systemGraphs;
+    1. 获取model下且domain、systemName符合条件的connector,connectors,表:revit.connector
+    2. 获取model下所有的设备、部件、其它、风管、水管,表:revit.equipment、revit.component、revit.other、revit.pipe、revit.duct
+    3. 获取model层的系统图数据,存在systemGraphs中。
+3. 获取systemGraphs中所有的开放立管,按开放立管对连接的系统图systemGraphs进行合并;
+4. 删除本项目下project_id、指定系统类型systemName,指定域domain,所有数据,表revit_calc.connected_block
+4. 将合并后的数据存入数据库,表:revit_calc.connected_block。
+
+
+# 函数
+```
+create or replace function public.sys_block(project_id character varying,building_id character varying,system_name character varying,domain character varying) returns boolean
+as
+$$
+from relations.src.system_relation import systemutils, graph_model
+
+def get_models(project_id,building_id):
+  sql ="select " \
+		"id,name,local_id,local_name,sequence_id,model_id,building_id,project_id " \
+		"from floor " \
+		"where project_id=$1 " \
+		"and ($2 is null or building_id=$2) " \
+		"order by sequence_id desc"
+  join_plan=plpy.prepare(sql,["text","text"])
+  data=join_plan.execute([project_id,building_id])
+  models=systemutils.sqldata2objlist(data)
+  return models
+
+def get_connectors(model_id, system_name, domain):
+  sql = "SELECT * FROM revit.connector where model_id=$1 and mep_system_type=$2 and domain=$3"
+  join_plan=plpy.prepare(sql,["text","text","text"])
+  data=join_plan.execute([model_id, system_name, domain])
+  floor_connectors=systemutils.sqldata2objlist(data)
+  return floor_connectors
+def get_elements(model_id):
+  floor_elements=[]
+  sql="select * from revit.equipment where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+
+  sql="select * from revit.component where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+
+  sql="select * from revit.other where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+
+  sql="select * from revit.join_object where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+
+  sql="select * from revit.pipe where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+
+  sql="select * from revit.duct where model_id=$1"
+  join_plan=plpy.prepare(sql,["text"])
+  data=join_plan.execute([model_id])
+  floor_elements.extend(systemutils.sqldata2objlist(data))
+  return floor_elements
+def delete_block_data(project_id,building_id,system_name,domain):
+  sql="delete from revit_calc.connected_block " \
+		  "where project_id=$1"\
+		  "and mep_system_type=$2" \
+		  "and domain=$3" \
+		  "and ($4 is null or building_id=$4)"
+  join_plan=plpy.prepare(sql,["text","text","text","text"])
+  join_plan.execute([project_id,system_name,domain,building_id])
+def save_edge_data(edge,block_id,system_name,domain,building_id):
+  vertex1=edge.start_vertex
+  vertex2=edge.end_vertex
+  c1=vertex1.system_data
+  c2=vertex2.system_data
+  floor1 = vertex1.get_parent_floor()
+  floor2 = vertex2.get_parent_floor()
+  model1= floor1.model
+  model2 = floor2.model
+  sql="insert into revit_calc.connected_block" \
+    "(id1,type1,model_id1,id2,type2,model_id2,block_id,project_id,mep_system_type,domain,building_id) values " \
+    "($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)"
+  join_plan=plpy.prepare(sql,["text","text","text","text","text","text","text","text","text","text","text"])
+  join_plan.execute([c1.id,c1.type,model1.model_id,c2.id,c2.type,model2.model_id,block_id,model1.project_id,floor1.system_name,floor1.domain,building_id])
+
+try:
+  # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+  with plpy.subtransaction():
+    models=get_models(project_id,building_id)
+    floorgraphs=[]
+    for model in models:
+      floor_connectors=get_connectors(model.model_id, system_name, domain)
+      floor_elements=get_elements(model.model_id)
+      g= graph_model.FloorGraph(model, system_name, domain,floor_connectors,floor_elements)
+      g.get_floor_graphs()
+      floorgraphs.append(g)
+    projectgraph= graph_model.combine_systemgraph(project_id,floorgraphs)
+
+    delete_block_data(project_id,building_id, system_name, domain)
+    block_id=0
+    for g in projectgraph.groupedsystemgraphs:
+      for s in g.systemgraphs:
+        for e in s.system_edges:
+          save_edge_data(e, block_id,system_name,domain,building_id)
+      for e in g.connectedges:
+        save_edge_data(e,block_id,system_name,domain,building_id)
+      block_id+=1
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+select public.sys_block('Pj1101010015','Bd11010100153c05821ed8fd11e9b8f2d79d0a5b4bf6','冷冻水供水管','DomainPiping')
+```
+
+## 输入
+    1. 项目id
+    2. 需要计算的building_id,如果没有提供时请传入null,不能传""
+    3. 需要计算的系统名称
+    4. 需要计算的管道系统类型,枚举(水管或风管):DomainPiping,DomainHvac
+## 返回结果
+    True    成功
+    False   失败
+    
+## 结果展示
+![原模型图](https://note.youdao.com/yws/public/resource/4f363b5153dec1221ac59c1569282496/xmlnote/6B97DC93F07E4E909EC35C8A4836CA16/12986)
+![抽象后的图](https://note.youdao.com/yws/public/resource/4f363b5153dec1221ac59c1569282496/xmlnote/ADDD4317DDDF450DAA69D4084DDBC615/12984)
+![确定完流向的图](https://note.youdao.com/yws/public/resource/4f363b5153dec1221ac59c1569282496/xmlnote/C1C1B1EC272E4A66B06C298972636A87/13650)

+ 85 - 0
docs/dev/data-center/relations/topology/sys_direction.md

@@ -0,0 +1,85 @@
+管网系统确定流向
+## 前置条件
+
+1. 通过管网系统分块计算;
+2. 指定源。
+
+## 处理方式
+
+1. 从connected_block表中取出所有关系,构建无向图
+2. 从connected_block_source表中取出所有的源
+3. 由图算法确定流向,构建有方向的图
+4. 更新connected_block表
+
+
+# 函数
+```
+create or replace function public.sys_direction(project_id character varying,building_id character varying,block_id character varying, system_name character varying,domain character varying,is_source boolean default true) returns boolean
+as
+$$
+from relations.src.system_relation import calc_flowdirection, systemdatautils,systemutils
+
+def get_connected_block_data(project_id,building_id,block_id,system_name,domain):
+  sql ="select * from revit_calc.connected_block " \
+		  "where project_id=$1" \
+		  "and block_id=$2" \
+		  "and mep_system_type=$3" \
+		  "and domain=$4" \
+		  "and ($5 is null or building_id=$5)" \
+		  "order by cast(depth as int) asc"
+  join_plan=plpy.prepare(sql,["text","text","text","text","text"])
+  data=join_plan.execute([project_id,block_id,system_name,domain,building_id])
+  objs=systemutils.sqldata2objlist(data)
+  return objs
+
+def get_connected_block_source_data(project_id,building_id,block_id,system_name,domain,is_source):
+  sql = "select * from revit_calc.connected_block_source " \
+		  "where project_id=$1" \
+		  "and block_id=$2" \
+		  "and mep_system_type=$3" \
+		  "and domain=$4" \
+		  "and ($5 is null or building_id=$5)" \
+		  "and is_source=$6"
+  join_plan=plpy.prepare(sql,["text","text","text","text","text","boolean"])
+  data=join_plan.execute([project_id,block_id,system_name,domain,building_id,is_source])
+  objs=systemutils.sqldata2objlist(data)
+  return objs
+
+def update_connected_block_data(block_data):
+  direction=block_data.direction
+  depth=block_data.depth
+  id=block_data.id
+  sql="update revit_calc.connected_block set direction=$1,depth=$2 where id=$3"
+  join_plan=plpy.prepare(sql,['int','int','int'])
+  join_plan.execute([direction,depth,id])
+
+try:
+  # 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+  with plpy.subtransaction():
+    block_datas=get_connected_block_data(project_id,building_id, block_id,system_name,domain)
+    source_datas=get_connected_block_source_data(project_id,building_id, block_id,system_name,domain,is_source)
+    block_datas= calc_flowdirection.calc(block_datas, source_datas)
+    for block_data in block_datas:
+      update_connected_block_data(block_data)
+except Exception as e:
+    plpy.warning(e)
+    return False
+else:
+    return True
+$$
+LANGUAGE 'plpython3u' VOLATILE;
+
+select public.sys_direction('Pj1101010015','Bd11010100153c05821ed8fd11e9b8f2d79d0a5b4bf6','0','冷冻水供水管','DomainPiping',true)
+```
+
+## 输入
+    1. 项目id
+    2. 需要计算的building_id,如果没有提供时请传入null,不能传""
+    3. 需要计算的块id    
+    4. 需要计算的系统名称
+    5. 需要计算的管道系统类型,枚举(水管或风管):DomainPiping,DomainHvac    
+    6. 跟据源或端计算:bool。true表示源,false表示端
+## 返回结果
+    True    成功
+    False   失败
+