zhangyu 4 rokov pred
rodič
commit
263ee275eb

+ 568 - 0
docs/.vuepress/components/example/web/graph/scene/SEditPolygon.vue

@@ -0,0 +1,568 @@
+<template>
+  <div>
+    <el-button @click="show">展示</el-button>
+    <el-button @click="create">创建</el-button>
+    <el-button @click="edit">编辑</el-button>
+    <canvas id="editPolygon" width="800" height="400"  tabindex="0" />
+  </div>
+</template>
+<script lang='ts'>
+import { SGraphItem, SGraphView, SGraphScene } from "@saga-web/graph/";
+import { SMouseEvent } from "@saga-web/base/";
+import {
+  SColor,
+  SLineCapStyle,
+  SPainter,
+  SPath2D,
+  SPoint,
+  SPolygonUtil,
+  SRect,
+  SLine
+} from "@saga-web/draw";
+import { SRelationState } from "@saga-web/big/lib/enums/SRelationState";
+import { SMathUtil } from "@saga-web/big/lib/utils/SMathUtil";
+
+export default {
+  data() {
+    return {
+      scene: null,
+      view: null
+    };
+  },
+  mounted(): void {
+    this.view = new SGraphView("editPolygon");
+  },
+  methods: {
+    show() {
+      const scene = new SGraphScene();
+      this.view.scene = scene;
+      const spolygonItem = new SPolygonItem(null);
+      spolygonItem.setStatus = SRelationState.Normal;
+      const PointList: SPoint[] = [
+        new SPoint(0, 0),
+        new SPoint(50, 0),
+        new SPoint(50, 50),
+        new SPoint(0, 50)
+      ];
+      spolygonItem.setPointList = PointList;
+      scene.addItem(spolygonItem);
+      this.view.fitSceneToView();
+    },
+    create() {
+      const scene = new SGraphScene();
+      this.view.scene = scene;
+      const spolygonItem = new SPolygonItem(null);
+      spolygonItem.setStatus = SRelationState.Create;
+      scene.addItem(spolygonItem);
+      scene.grabItem = spolygonItem;
+      this.view.fitSceneToView();
+    },
+    edit() {
+      const scene = new SGraphScene();
+      this.view.scene = scene;
+      const spolygonItem = new SPolygonItem(null);
+      spolygonItem.setStatus = SRelationState.Edit;
+      const PointList: SPoint[] = [
+        new SPoint(0, 0),
+        new SPoint(50, 0),
+        new SPoint(50, 60),
+        new SPoint(0, 50)
+      ];
+      spolygonItem.setPointList = PointList;
+      scene.addItem(spolygonItem);
+      scene.grabItem = spolygonItem;
+      this.view.fitSceneToView();
+    }
+  }
+};
+
+/**
+ * @author hanyaolong
+ */
+
+class SPolygonItem extends SGraphItem {
+  /** X坐标最小值  */
+  private minX = Number.MAX_SAFE_INTEGER;
+  /** X坐标最大值  */
+  private maxX = Number.MIN_SAFE_INTEGER;
+  /** Y坐标最小值  */
+  private minY = Number.MAX_SAFE_INTEGER;
+  /** Y坐标最大值  */
+  private maxY = Number.MIN_SAFE_INTEGER;
+  /** 轮廓线坐标  */
+  private pointList: SPoint[] = [];
+  // 获取当前状态
+  get getPointList(): SPoint[] {
+    return this.pointList;
+  }
+  // 编辑当前状态
+  set setPointList(arr: SPoint[]) {
+    this.pointList = arr;
+    if (arr) {
+      this._xyzListToSPointList(arr);
+    }
+    this.update();
+  }
+  /** 是否闭合    */
+  closeFlag: boolean = false;
+  // 当前状态 1:show 2 创建中,3 编辑中
+  _status: number = SRelationState.Normal;
+  // 获取当前状态
+  get getStatus(): number {
+    return this._status;
+  }
+  // 编辑当前状态
+  set setStatus(value: number) {
+    this._status = value;
+    this.update();
+  }
+  /** 边框颜色 */
+  borderColor: SColor = new SColor("#0091FF");
+  /** 填充颜色 */
+  brushColor: SColor = new SColor("#1EE887");
+  /** border宽 只可输入像素宽*/
+  borderLine: number = 4;
+  /** 鼠标移动点  */
+  private lastPoint = new SPoint();
+  /** 当前鼠标获取顶点对应索引 */
+  private curIndex: number = -1;
+  /** 灵敏像素 */
+  private len: number = 15;
+  /** 场景像素  */
+  private scenceLen: number = 15;
+  /** 场景像素  */
+  private isAlt: boolean = false;
+
+  /**
+   * 构造函数
+   *
+   * @param parent    指向父对象
+   */
+
+  constructor(parent: SGraphItem | null) {
+    super(parent);
+  }
+
+  ///////////////以下为对pointList 数组的操作方法
+
+  /**
+   * 储存新的多边形顶点
+   * @param x   点位得x坐标
+   * @param y   点位得y坐标
+   * @param i   储存所在索引
+   * @return SPoint
+   */
+
+  insertPoint(x: number, y: number, i: number | null = null): SPoint {
+    const point = new SPoint(x, y);
+    if (i == null) {
+      this.pointList.push(point);
+    } else {
+      this.pointList.splice(i, 0, point);
+    }
+    this.update();
+    return point;
+  }
+
+  /**
+   * 删除点位
+   * @param i   删除点所在的索引
+   * @return SPoint
+   */
+
+  deletePoint(i: number | null = null): SPoint | null | undefined {
+    let point = null;
+    if (i != null) {
+      if (this.pointList.length - 1 >= i) {
+        point = this.pointList[i];
+        this.pointList.splice(i, 1);
+      } else {
+        point = null;
+      }
+    } else {
+      point = this.pointList.pop();
+    }
+    this.update();
+    return point;
+  }
+
+  /**
+   * 多边形顶点的移动位置
+   * @param x   点位得x坐标
+   * @param y   点位得y坐标
+   * @param i   点位得i坐标
+   * @return SPoint
+   */
+
+  movePoint(x: number, y: number, i: number): SPoint | null | undefined {
+    let point = null;
+    if (this.pointList.length) {
+      this.pointList[i].x = x;
+      this.pointList[i].y = y;
+    }
+    point = this.pointList[i];
+    return point;
+  }
+
+  /**
+   * 打印出多边形数组
+   * @return SPoint[]
+   */
+
+  PrintPointList(): SPoint[] {
+    return this.pointList;
+  }
+
+  ///////////////////////////
+
+  /**
+   * xyz数据转换为SPoint格式函数;获取外接矩阵参数
+   *
+   * @param arr    外层传入的数据PointList
+   */
+
+  protected _xyzListToSPointList(arr: SPoint[]): void {
+    if (arr.length) {
+      this.pointList = arr.map(it => {
+        let x = it.x,
+          y = it.y;
+        if (x < this.minX) {
+          this.minX = x;
+        }
+        if (y < this.minY) {
+          this.minY = y;
+        }
+        if (x > this.maxX) {
+          this.maxX = x;
+        }
+        if (y > this.maxY) {
+          this.maxY = y;
+        }
+        return new SPoint(x, y);
+      });
+    } else {
+      this.pointList = [];
+    }
+  }
+
+  ////////////以下为三种状态下的绘制多边形方法
+
+  /**
+   * 展示状态 --绘制多边形数组
+   * @param painter    绘制类
+   * @param pointList    绘制多边形数组
+   *
+   */
+
+  protected drawShowPolygon(painter: SPainter, pointList: SPoint[]): void {
+    painter.pen.lineCapStyle = SLineCapStyle.Square;
+    painter.pen.color = this.borderColor;
+    painter.pen.lineWidth = painter.toPx(this.borderLine);
+    painter.brush.color = this.brushColor;
+    painter.drawPolygon([...pointList]);
+  }
+
+  /**
+   *
+   * 创建状态 --绘制多边形数组
+   * @param painter    绘制类
+   * @param pointList    绘制多边形数组
+   *
+   */
+
+  protected drawCreatePolygon(painter: SPainter, pointList: SPoint[]): void {
+    painter.pen.lineCapStyle = SLineCapStyle.Square;
+    painter.pen.color = this.borderColor;
+    painter.pen.lineWidth = painter.toPx(4);
+    painter.drawPolyline(pointList);
+    if (this.lastPoint) {
+      painter.drawLine(pointList[pointList.length - 1], this.lastPoint);
+    }
+    painter.pen.color = SColor.Transparent;
+    painter.brush.color = this.brushColor;
+    painter.pen.lineWidth = painter.toPx(4);
+    painter.drawPolygon([...pointList, this.lastPoint]);
+  }
+
+  /**
+   *
+   * 编辑状态 --绘制多边形数组
+   *
+   * @param painter    绘制类
+   * @param pointList    绘制多边形数组
+   *
+   * */
+
+  protected drawEditPolygon(painter: SPainter, pointList: SPoint[]): void {
+    // 展示多边形
+    this.drawShowPolygon(painter, pointList);
+    // 绘制顶点块
+    pointList.forEach((item, index) => {
+      painter.drawCircle(item.x, item.y, painter.toPx(8));
+    });
+  }
+
+  ////////////////////////////////
+
+  /**
+   * 编辑状态操作多边形数组
+   *
+   * @param event    鼠标事件
+   *
+   *
+   * */
+
+  protected editPolygonPoint(event: SMouseEvent): void {
+    //  判断是否为删除状态 isAlt = true为删除状态
+    if (this.isAlt) {
+      // 1 判断是否点击在多边形顶点
+      let lenIndex = -1; // 当前点击到的点位索引;
+      let curenLen = this.scenceLen; // 当前的灵敏度
+      this.pointList.forEach((item, index) => {
+        let dis = SMathUtil.pointDistance(event.x, event.y, item.x, item.y);
+        if (dis < curenLen) {
+          curenLen = dis;
+          lenIndex = index;
+        }
+      });
+      // 若点击到,对该索引对应的点做删除
+      if (lenIndex != -1) {
+        if (this.pointList.length <= 3) {
+          return;
+        }
+        this.deletePoint(lenIndex);
+      }
+    } else {
+      // 1 判断是否点击在多边形顶点
+      this.curIndex = -1;
+      let lenIndex = -1; // 当前点击到的点位索引;
+      let curenLen = this.scenceLen; // 当前的灵敏度
+      this.pointList.forEach((item, index) => {
+        let dis = SMathUtil.pointDistance(event.x, event.y, item.x, item.y);
+        if (dis < curenLen) {
+          curenLen = dis;
+          lenIndex = index;
+        }
+      });
+      this.curIndex = lenIndex;
+
+      // 2判断是否点击在多边形得边上
+      if (-1 == lenIndex) {
+        let len = SMathUtil.pointToLine(
+            new SPoint(event.x, event.y),
+            new SLine(this.pointList[0], this.pointList[1])
+          ),
+          index = 0;
+        if (this.pointList.length > 2) {
+          for (let i = 1; i < this.pointList.length; i++) {
+            let dis = SMathUtil.pointToLine(
+              new SPoint(event.x, event.y),
+              new SLine(this.pointList[i], this.pointList[i + 1])
+            );
+            if (i + 1 == this.pointList.length) {
+              dis = SMathUtil.pointToLine(
+                new SPoint(event.x, event.y),
+                new SLine(this.pointList[i], this.pointList[0])
+              );
+            }
+            if (dis.MinDis < len.MinDis) {
+              len = dis;
+              index = i;
+            }
+          }
+        }
+        // 如果再线上则为新增点
+        if (len.Point) {
+          if (len.MinDis <= this.scenceLen) {
+            this.pointList.splice(index + 1, 0, len.Point);
+          }
+        }
+      }
+      // 刷新视图
+      this.update();
+    }
+  }
+
+  ///////////////////////////////以下为鼠标事件
+
+  /**
+   * 鼠标双击事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onDoubleClick(event: SMouseEvent): boolean {
+    // 如果位show状态 双击改对象则需改为编辑状态
+    if (SRelationState.Normal == this._status) {
+      this._status = SRelationState.Edit;
+    } else if (SRelationState.Edit == this._status) {
+      this._status = SRelationState.Normal;
+    }
+    this.update();
+    return true;
+  } // Function onDoubleClick()
+
+  /**
+   * 键盘事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onKeyDown(event: KeyboardEvent): boolean {
+    console.log(event);
+    if (this._status == SRelationState.Normal) {
+      return false;
+    } else if (this._status == SRelationState.Create) {
+      if (event.code == "Enter") {
+        this._status = SRelationState.Normal;
+      }
+    } else if (this._status == SRelationState.Edit) {
+      if (event.key == "Alt") {
+        this.isAlt = true;
+      }
+    } else {
+    }
+    this.update();
+    return true;
+  } // Function onKeyDown()
+
+  /**
+   * 键盘键抬起事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+  onKeyUp(event: KeyboardEvent): boolean {
+    if (this._status == SRelationState.Edit) {
+      if (event.key == "Alt") {
+        this.isAlt = false;
+      }
+    }
+    this.update();
+    return true;
+  } // Function onKeyUp()
+
+  /**
+   * 鼠标按下事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+  onMouseDown(event: SMouseEvent): boolean {
+    console.log("aaaaaa");
+    // 如果状态为编辑状态则添加点
+    if (this._status == SRelationState.Create) {
+      // 新增顶点
+      this.insertPoint(event.x, event.y);
+    } else if (this._status == SRelationState.Edit) {
+      // 对多边形数组做编辑操作
+      this.editPolygonPoint(event);
+    }
+    return true;
+  } // Function onMouseDown()
+
+  /**
+   * 鼠标移入事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+  onMouseEnter(event: SMouseEvent): boolean {
+    return true;
+  } // Function onMouseEnter()
+
+  /**
+   * 鼠标移出事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onMouseLeave(event: SMouseEvent): boolean {
+    return true;
+  } // Function onMouseLeave()
+
+  /**
+   * 鼠标移动事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onMouseMove(event: SMouseEvent): boolean {
+    if (this._status == SRelationState.Create) {
+      this.lastPoint.x = event.x;
+      this.lastPoint.y = event.y;
+    } else if (this._status == SRelationState.Edit) {
+      if (-1 != this.curIndex) {
+        this.pointList[this.curIndex].x = event.x;
+        this.pointList[this.curIndex].y = event.y;
+      }
+    }
+    this.update();
+    return true;
+  } // Function onMouseMove()
+
+  /**
+   * 鼠标抬起事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onMouseUp(event: SMouseEvent): boolean {
+    if (this._status == SRelationState.Edit) {
+      this.curIndex = -1;
+    }
+    return true;
+  } // Function onMouseUp()
+
+  /**
+   * 适配事件
+   *
+   * @param	event         事件参数
+   * @return	boolean
+   */
+
+  onResize(event: SMouseEvent): boolean {
+    return true;
+  } // Function onResize()
+
+  /**
+   * Item对象边界区域
+   *
+   * @return SRect
+   */
+  boundingRect(): SRect {
+    return new SRect(
+      this.minX,
+      this.minY,
+      this.maxX - this.minX,
+      this.maxY - this.minY
+    );
+  } // Function boundingRect()
+
+  /**
+   * Item绘制操作
+   *
+   * @param   painter       painter对象
+   */
+
+  onDraw(painter: SPainter): void {
+    this.scenceLen = painter.toPx(this.len);
+    // 当状态为展示状态
+    if (this._status == SRelationState.Normal) {
+      // 闭合多边形
+      this.drawShowPolygon(painter, this.pointList);
+    } else if (this._status == SRelationState.Create) {
+      // 创建状态
+      this.drawCreatePolygon(painter, this.pointList);
+    } else {
+      // 编辑状态
+      this.drawEditPolygon(painter, this.pointList);
+    }
+  } // Function onDraw()
+}
+</script>

+ 285 - 27
docs/dev/data-center/relations/belongs/Sp2Sp2.md

@@ -4,45 +4,303 @@
     2. 业务空间有外轮廓
 ## 处理流程
     1. 查出所有有所在楼层, 并且外轮廓不是null的业务空间
-    2. 根据所在楼层, 业务空间分区来将业务空间分组
+    2. 根据所在楼层, 业务空间分区类型来将业务空间分组
     3. 计算每个分组内的业务空间的相邻关系
-    计算相邻算法:
-    1. 首先判断围成业务空间的线段, 两两空间之间是否有近似平行的线段(线段偏转误差小于1度)
-        1). 近似平行判断: 首先获取两个线段的斜率, 再计算斜率的反正切(即与x轴的角度, 不过是以pi为单位), 再判断两个角度差的绝对值是否小于1度
-    2. 如果有近似平行的线段, 判断是否相互有投影在线段上, 有投影在线段上, 则认为是两平行线段有重合部分, 业务空间有相邻的可能性
-    3. 在判断互相有投影点在对方线段上之后, 判断投影线的长度, 是否小于250mm(墙的最大厚度), 如果小于250mm则认为两空间相邻
+    计算相邻算法 (如下图):
+    1. 在一个分组内, 遍历每个业务空间上组成外轮廓的每条线段, 根据线段的长度, 是否超过delta_distance来区分成两种情况判断
+        1). 如果线段长度大于delta_distance, 假如图中线段AB长度大于delta_distance, 则在距离A点delta_distance的距离找到点P5, 
+        在P5点处作AB线段的垂线线段, 上下两边长度均为door_length长, 找到点P4和P6. 同理可以找到点P7和P9. 
+        2). 如果线段长度小于或等于delta_distance, 假设线段AE长度小于delta_distance, 则在线段中间点P2处作垂直于AE的垂线线段, 
+        上下两边长度均为door_length长, 找到点P1和P3.
+    2. 将上面找到的点, 依次与其他业务空间外轮廓做一次点是否在多边形内的判断, 如果在, 则认为该多边形与图中多边形相邻
+    
+   
+  ![Image text](img/sp2sp2-1.PNG)
+
 ## 函数
 ```
-create or replace function public.rel_sp2sp1(project_id character varying) returns boolean
+create function rel_sp2sp_v2(project_id character varying) returns boolean
+    language plpython3u
 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:
+import math
+import json
+from shapely.geometry import Polygon
+from matplotlib.path import Path
+
+column_project_id = 'project_id'
+column_location_one = 'location_one'
+column_location_two = 'location_two'
+column_space_id_one = 'space_id_one'
+column_space_id_two = 'space_id_two'
+column_zone_type = 'zone_type'
+column_added_polygon = 'polygon'
+
+column_id = 'id'
+column_bim_location = 'bim_location'
+column_floor_id = 'floor_id'
+column_object_type = 'object_type'
+column_outline = 'outline'
+key_x = 'X'
+key_y = 'Y'
+
+delta_distance = 200
+door_length = 1500
+
+
+# 获取两点之间的距离
+def get_segment_distance(x1, y1, x2, y2):
+    x_diff = x1 - x2
+    y_diff = y1 - y2
+    return math.sqrt(x_diff ** 2 + y_diff ** 2)
+
+
+# 获取垂直方向的斜率
+def get_vertical_k(k):
+    if k is None:
+        return 0
+    if k == 0:
+        return None
+    return 1 / k
+
+# 根据点 x, y 计算 y = kx + a 中的 a
+def get_a_by_point_k(x, y, k):
+    if k is None:
+        return None
+    return y - k * x
+
+
+# 在直线   y = kx + a 上找到距离点 base_x, base_y 距离为distance 的坐标点(两个坐标点)
+def get_point_by_distance(base_x, base_y, k, a, distance):
+    if k is None:
+        return base_x, base_y + distance, base_x, base_y - distance
+    vector_x1 = math.sqrt(distance ** 2 / (1 + k ** 2))
+    vector_x2 = -vector_x1
+    vector_y1 = k * vector_x1
+    vector_y2 = k * vector_x2
+    return base_x + vector_x1, base_y + vector_y1, base_x + vector_x2, base_y + vector_y2
+
+# 在直线 y = kx + a 上找到一个点, 该点距离点(base_x, base_y) 长度为 distance, 并且距离vec_x, vec_y最近
+def get_point_by_distance_on_segment(base_x, base_y, k, a, distance, vec_x, vec_y):
+    x1, y1, x2, y2 = get_point_by_distance(base_x, base_y, k, a, distance)
+    distance1 = get_segment_distance(x1, y1, vec_x, vec_y)
+    distance2 = get_segment_distance(x2, y2, vec_x, vec_y)
+    if distance1 > distance2:
+        return x2, y2
+    return x1, y1
+
+
+# 获取输入的两点之间开门之后门的坐标点
+# 返回格式
+# 如果线段距离小于等于door_length的情况 : x1, y1, x2, y2
+# 如果线段距离大于door_length的情况 (两端各开一个门): x1, y1, x2, y2, x3, y3, x4, y4
+def get_points(x1, y1, x2, y2):
+    # 如果两个点是同一个点
+    if x1 == y1 and x2 == y2:
+        return None, None, None, None
+    # 计算线段的距离
+    distance = get_segment_distance(x1, y1, x2, y2)
+    # 计算当前线段的 k
+    if x1 == x2:
+        k = None
+    else:
+        k = (y1 - y2) / (x1 - x2)
+    # 计算垂直方向的k
+    vertical_k = get_vertical_k(k)
+    # 计算当前线段的 a
+    a = get_a_by_point_k(x1, y1, k)
+    # 距离大于delta_distance, 则足够开两个门
+    if distance > delta_distance:
+        seg_x1, seg_y1 = get_point_by_distance_on_segment(x1, y1, k, a, delta_distance, x2, y2)
+        seg_x2, seg_y2 = get_point_by_distance_on_segment(x2, y2, k, a, delta_distance, x1, y1)
+        vertical_a1 = get_a_by_point_k(seg_x1, seg_y1, vertical_k)
+        vertical_a2 = get_a_by_point_k(seg_x2, seg_y2, vertical_k)
+        dest_x1, dest_y1, dest_x2, dest_y2 = get_point_by_distance(seg_x1, seg_y1, vertical_k, vertical_a1, door_length)
+        dest_x3, dest_y3, dest_x4, dest_y4 = get_point_by_distance(seg_x2, seg_y2, vertical_k, vertical_a2, door_length)
+        return [dest_x1, dest_x2, dest_x3, dest_x4], [dest_y1, dest_y2, dest_y3, dest_y4]
+    else:
+        # 距离太小, 在中间开门
+        seg_x1, seg_y1 = get_point_by_distance_on_segment(x1, y1, k, a, distance/2, x2, y2)
+        vertical_a1 = get_a_by_point_k(seg_x1, seg_y1, vertical_k)
+        dest_x1, dest_y1, dest_x2, dest_y2 = get_point_by_distance(seg_x1, seg_y1, vertical_k, vertical_a1, door_length)
+        return [dest_x1, dest_x2], [dest_y1, dest_y2]
+
+# 获取Polygon对象
+def get_polygon(single_poly):
+    poly_len = len(single_poly)
+    # plpy.info("poly_len : {0}, single_poly : {1} ".format(poly_len, single_poly))
+    poly = []
+    for i in range(poly_len):
+        pair = single_poly[i]
+        poly.append((pair[key_x], pair[key_y]))
+        # plpy.info((pair[key_x], pair[key_y]))
+    return Path(poly)
+
+
+# 获取业务空间每个块的最外层轮廓, 组成arr返回, 如果数据不合法发生异常则返回None
+def get_outer_polygon_arr(raw_outline):
+    try:
+        arr = []
+        outline_json = json.loads(raw_outline)
+        for i in range(len(outline_json)):
+            try:
+                single_polygon = get_polygon(outline_json[i][0])
+            except Exception as ex:
+                # plpy.info("error getting polygon : {0}".format(outline_json))
+                continue
+            arr.append(single_polygon)
+        return arr
+    except Exception as e:
+        return []
+
+
+# 判断一个点是否在多边形数组中的某一个多边形内
+def is_point_in_polygons(point, polygon_arr):
+    try:
+        for polygon in polygon_arr:
+            try:
+                if polygon.contains_points([point], None, -0.0001):
+                    return True
+            except Exception as ee:
+                plpy.warning("point in polygon : {0}".format(ee))
+        return False
+    except Exception as e:
+        plpy.warning(e)
+        return False
+
+
+# 检查是否已经加过这个关系(单向检查)
+def check_is_in_rel(space_adjacent, probe_id, id):
+    if probe_id in space_adjacent:
+        rel_set = space_adjacent[probe_id]
+        if id in rel_set:
+            return True
+    return False
+
+# 检查是否关系存在(双向检查)
+def check_is_in_rel_bidirection(space_adjacent, probe_id, id):
+    is_in = check_is_in_rel(space_adjacent, probe_id, id)
+    is_in = is_in or check_is_in_rel(space_adjacent, id, probe_id)
+    return is_in
+
+# 将业务空间相邻关系插入map中
+def insert_into_rel(space_adjacent, probe_id, id):
+    if check_is_in_rel_bidirection(space_adjacent, probe_id, id):
+        return
+    if probe_id not in space_adjacent:
+        space_adjacent[probe_id] = set()
+    rel_set = space_adjacent[probe_id]
+    rel_set.add(id)
+
+# 计算一个分组内的邻接关系, space_adjacent用来保存关系, 结构是  space_id  -->  set(space_id)
+def calc_adjacent_sub_arr(space_sub_arr, space_adjacent, space_info):
+    # space_sub_arr 是一个楼层内的一类业务空间
+    for space_row in space_sub_arr:
+        raw_outline = space_row.get(column_outline)
+        id = space_row.get(column_id)
+        space_info[id] = space_row
+        outline_json = json.loads(raw_outline)
+        #plpy.info("outline_json : {0}".format(len(outline_json)))
+        for i in range(len(outline_json)):
+            try:
+                single_poly = outline_json[i][0]
+            except Exception as ee :
+                # plpy.warning("error outline : {0}, id : {1}".format(outline_json, id))
+                continue
+            #single_poly = outline_json[i][0]
+            poly_len = len(single_poly)
+            for idx in range(1, poly_len):
+                # 线段
+                segment_point1 = single_poly[idx - 1]
+                segment_point2 = single_poly[idx]
+                # segment_length = get_segment_distance(segment_point1[key_x], segment_point1[key_y], segment_point2[key_x], segment_point2[key_y])
+                x_arr, y_arr = get_points(segment_point1[key_x], segment_point1[key_y], segment_point2[key_x], segment_point2[key_y])
+                # plpy.info("x_arr : {0} , y_arr : {1}".format(x_arr, y_arr))
+                for probe_space_row in space_sub_arr:
+                    probe_polygon_arr = probe_space_row.get(column_added_polygon)
+                    probe_id = probe_space_row.get(column_id)
+                    # 如果自己跟自己比较或者已经存在该关系的话, 跳过
+                    if probe_id == id or check_is_in_rel_bidirection(space_adjacent, probe_id, id):
+                        continue
+                    for arr_index in range(0, len(x_arr)):
+                        prob_x = x_arr[arr_index]
+                        prob_y = y_arr[arr_index]
+                        is_hit = is_point_in_polygons((prob_x, prob_y), probe_polygon_arr)
+                        # plpy.info("is hit : {0}".format(is_hit))
+                        if is_hit:
+                            # plpy.info("hit")
+                            insert_into_rel(space_adjacent, probe_id, id)
+                            break
+
+# 将输入数据按照楼层id, 业务空间类型分类
+def classify(space_list):
+    current_floor_id = ''
+    current_object_type = ''
+    current_sub_arr = []
+    space_arr = []
+    for row in space_list:
+        if row.get(column_floor_id) == current_floor_id and row.get(column_object_type) == current_object_type:
+            current_sub_arr.append(row)
+        else:
+            current_floor_id = row.get(column_floor_id)
+            current_object_type = row.get(column_object_type)
+            current_sub_arr = [row]
+            space_arr.append(current_sub_arr)
+        row[column_added_polygon] = get_outer_polygon_arr(row.get(column_outline))
+    for sub_arr in space_arr:
+        if len(sub_arr) == 1:
+            space_arr.remove(sub_arr)
+    return space_arr
+
+# 计算业务空间相邻
+def calc_space_adjacent(space_list):
+    # 给业务空间按照所属楼层和业务空间类型分组
+    space_arr = classify(space_list)
+    space_adjacent = dict()
+    space_info = dict()
+    for space_sub_arr in space_arr:
+        calc_adjacent_sub_arr(space_sub_arr, space_adjacent, space_info)
+    return space_adjacent, space_info
+
+
+#try:
+# 将下面对数据库的操作作为一个事务, 出异常则自动rollback
+with plpy.subtransaction():
+    # 获取有所在楼层的所有业务空间, 并按照所在楼层和业务空间类型排序, 方便分组
+    sql_str = "SELECT sp.id, sp.project_id, rel.floor_id, sp.object_type, sp.bim_location, sp.outline FROM zone_space_base sp inner join r_sp_in_fl rel on rel.space_id = sp.id where sp.project_id = $1 and rel.project_id = $1 and outline is not null order by floor_id, object_type"
+    space_data_plan = plpy.prepare(sql_str, ["text"])
+    space_data = space_data_plan.execute([project_id])
+    plpy.info("space data : {0}".format(len(space_data)))
+    rel_data, space_info = calc_space_adjacent(space_data)
+    # 删除以前的业务空间相邻关系
+    delete_plan = plpy.prepare("delete from r_spatial_connection where project_id = $1 and sign = 2", ["text"])
+    delete_plan.execute([project_id])
+    plpy.info("rel_data : {0}".format(len(rel_data)))
+    for space_id1, to_space_set in rel_data.items():
+        space1 = space_info[space_id1]
+        for space_id2 in to_space_set:
+            space2 = space_info[space_id2]
             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']])
+            delete_duplicate_plan.execute([space_id1, space_id2])
             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:
+            # plpy.info("{0}, {1}, {2}, {3}, {4}, {5}, {6}".format(project_id, space1.get(column_bim_location), space2.get(column_bim_location), space_id1, space2, space1.get(column_floor_id), space1.get(column_object_type)))
+            insert_plan.execute([project_id, space1.get(column_bim_location), space2.get(column_bim_location), space_id1, space2.get(column_id), space1.get(column_floor_id), space1.get(column_object_type)])
     return True
-$$
-LANGUAGE 'plpython3u' VOLATILE;
+#except Exception as e:
+#    plpy.warning(e)
+#    return False
+#else:
+#    return True
+$$;
+
+alter function rel_sp2sp_v2(varchar) owner to postgres;
+
 
 
-select public.rel_sp2sp1('Pj1101050001')
+select public.rel_sp2sp_v2('Pj1101050001')
 ```
 
 ## 入参
     1. 项目id
 ## 例子
-    select public.rel_sp2sp1('Pj1102290002');
+    select public.rel_sp2sp_v2('Pj1102290002');

+ 1 - 0
docs/dev/saga-graphy/index.js

@@ -25,6 +25,7 @@ const content = [
                     ["/dev/saga-graphy/scene-manage/items/clock", "时钟"],
                     ["/dev/saga-graphy/scene-manage/items/text", "文本"],
                     ["/dev/saga-graphy/scene-manage/items/image", "图片"],
+                    ["/dev/saga-graphy/scene-manage/items/edit-polygon", "可编辑多边形示例"]
                 ]
             },
             ["/dev/saga-graphy/scene-manage/undo", "Undo示例"]

+ 17 - 0
docs/dev/saga-graphy/scene-manage/items/edit-polygon.md

@@ -0,0 +1,17 @@
+# 可编辑多边形示例
+::: details 目录
+[[toc]]
+:::
+
+<example-web-graph-scene-SEditPolygon/>
+
+::: tip 注
+1、可以通过双击table在编辑状态和展示状态来回切换,<br/>
+2、创建状态时;enter + 左键闭合多边形 <br/>
+3、编辑状态时:鼠标点击在边上可加点,可拖动点;<br/>
+4、编辑状态时:通过ALT键 + 鼠标左键 删除顶点
+:::
+
+::: details 查看代码
+<<< @/docs/.vuepress/components/example/web/graph/scene/ClockItem.vue
+:::

+ 6 - 3
package.json

@@ -12,9 +12,12 @@
     "publish": "node publish.js"
   },
   "dependencies": {
-    "@saga-web/base": "^2.1.14",
-    "@saga-web/draw": "^2.1.83",
-    "@saga-web/graphy": "^2.1.58",
+    "@saga-web/base": "^2.1.9",
+    "@saga-web/big": "^1.0.11",
+    "@saga-web/draw": "^2.1.82",
+    "@saga-web/graph": "^2.1.54",
+    "@saga-web/feng-map": "1.0.6",
+    "@saga-web/topology": "1.0.84",
     "axios": "^0.18.1",
     "element-ui": "^2.12.0",
     "vue": "^2.6.10",

+ 13 - 16
publish.js

@@ -1,22 +1,19 @@
 /*
  * ********************************************************************************************************************
  *
- *               iFHS7.
- *              ;BBMBMBMc                  rZMBMBR              BMB
- *              MBEr:;PBM,               7MBMMEOBB:             BBB                       RBW
- *     XK:      BO     SB.     :SZ       MBM.       c;;     ir  BBM :FFr       :SSF:    ;xBMB:r   iuGXv.    i:. iF2;
- *     DBBM0r.  :D     S7   ;XMBMB       GMBMu.     MBM:   BMB  MBMBBBMBMS   WMBMBMBBK  MBMBMBM  BMBRBMBW  .MBMBMBMBB
- *      :JMRMMD  ..    ,  1MMRM1;         ;MBMBBR:   MBM  ;MB:  BMB:   MBM. RMBr   sBMH   BM0         UMB,  BMB.  KMBv
- *     ;.   XOW  B1; :uM: 1RE,   i           .2BMBs  rMB. MBO   MBO    JMB; MBB     MBM   BBS    7MBMBOBM:  MBW   :BMc
- *     OBRJ.SEE  MRDOWOR, 3DE:7OBM       .     ;BMB   RMR7BM    BMB    MBB. BMB    ,BMR  .BBZ   MMB   rMB,  BMM   rMB7
- *     :FBRO0D0  RKXSXPR. JOKOOMPi       BMBSSWBMB;    BMBB:    MBMB0ZMBMS  .BMBOXRBMB    MBMDE RBM2;SMBM;  MBB   xBM2
- *         iZGE  O0SHSPO. uGZ7.          sBMBMBDL      :BMO     OZu:BMBK,     rRBMB0;     ,EBMB  xBMBr:ER.  RDU   :OO;
- *     ,BZ, 1D0  RPSFHXR. xWZ .SMr                  . .BBB
- *      :0BMRDG  RESSSKR. 2WOMBW;                   BMBMR
- *         i0BM: SWKHKGO  MBDv
- *           .UB  OOGDM. MK,                                          Copyright (c) 2015-2019.  斯伯坦机器人
- *              ,  XMW  ..
- *                  r                                                                     All rights reserved.
+ *                      :*$@@%$*:                         ;:                ;;    ;;
+ *                    :@@%!  :!@@%:                       %!             ;%%@@%$ =@@@@@@@%;     @%@@@%%%%@@@@@
+ *                   :@%;       :$=                       %%$$$%$$         ;$$  ;$@=   !@$
+ *                   =@!                                  %!              @ $=;%   !@@@%:      !$$$$$$$$$$$$$$=
+ *                   =@*                                  %!              @ $= % %@=   =%@!      %=
+ *              *$%%! @@=        ;=$%%%$*:                %!              @ $= % =%%%%%%@$      *%:         =%
+ *            %@@!:    !@@@%=$@@@@%!  :*@@$:              %!              @ $= % $*     ;@      @*          :%*
+ *          ;@@!          ;!!!;:         ;@%:      =======@%========*     @ $$ % $%*****$@     :@$=*********=@$
+ *          $@*   ;@@@%=!:                *@*
+ *          =@$    ;;;!=%@@@@=!           =@!
+ *           %@$:      =@%: :*@@@*       %@=                    Copyright (c) 2016-2019.  北京上格云技术有限公司
+ *            ;%@@$=$@@%*       *@@@$=%@@%;
+ *               ::;::             ::;::                                              All rights reserved.
  *
  * ********************************************************************************************************************
  */