Sfoglia il codice sorgente

Merge branch 'master' of http://192.168.0.115:3000/bill/traffic-laser

bill 2 anni fa
parent
commit
fb9ede324f
48 ha cambiato i file con 19344 aggiunte e 0 eliminazioni
  1. 27 0
      src/graphic/Constant.js
  2. 293 0
      src/graphic/Controls/AddRoad.js
  3. 1433 0
      src/graphic/Controls/MoveRoad.js
  4. 15 0
      src/graphic/Controls/MoveTag.js
  5. 132 0
      src/graphic/Controls/UIControl.js
  6. 136 0
      src/graphic/Coordinate.js
  7. 21 0
      src/graphic/Geometry/Edge.js
  8. 302 0
      src/graphic/Geometry/Furniture.js
  9. 61 0
      src/graphic/Geometry/Geometry.js
  10. 27 0
      src/graphic/Geometry/Line.js
  11. 22 0
      src/graphic/Geometry/Point.js
  12. 33 0
      src/graphic/Geometry/Road.js
  13. 108 0
      src/graphic/Geometry/Tag.js
  14. 348 0
      src/graphic/History/Change.js
  15. 497 0
      src/graphic/History/History.js
  16. 223 0
      src/graphic/History/HistoryUtil.js
  17. 1045 0
      src/graphic/Layer.js
  18. 623 0
      src/graphic/ListenLayer.js
  19. 411 0
      src/graphic/Load.js
  20. 6861 0
      src/graphic/Renderer/Draw.js
  21. 389 0
      src/graphic/Renderer/Render.js
  22. 131 0
      src/graphic/Service/AnalyService.js
  23. 47 0
      src/graphic/Service/CameraService.js
  24. 25 0
      src/graphic/Service/CompassService.js
  25. 107 0
      src/graphic/Service/ComponentService.js
  26. 528 0
      src/graphic/Service/DataService.js
  27. 626 0
      src/graphic/Service/EdgeService.js
  28. 308 0
      src/graphic/Service/ElementService.js
  29. 178 0
      src/graphic/Service/FurnitureService.js
  30. 75 0
      src/graphic/Service/HistoryService.js
  31. 104 0
      src/graphic/Service/KeyService.js
  32. 522 0
      src/graphic/Service/MeasureService.js
  33. 16 0
      src/graphic/Service/PointService.js
  34. 506 0
      src/graphic/Service/RoadService.js
  35. 96 0
      src/graphic/Service/StateService.js
  36. 1149 0
      src/graphic/Service/SymbolService.js
  37. 93 0
      src/graphic/Service/TagService.js
  38. 362 0
      src/graphic/Service/UoMService.js
  39. 353 0
      src/graphic/Style.js
  40. 15 0
      src/graphic/Util/EdgeUtil.js
  41. 938 0
      src/graphic/Util/MathUtil.js
  42. 13 0
      src/graphic/enum/ElementEvents.js
  43. 28 0
      src/graphic/enum/HistoryEvents.js
  44. 11 0
      src/graphic/enum/LayerEvents.js
  45. 25 0
      src/graphic/enum/SelectState.js
  46. 5 0
      src/graphic/enum/SymbolEvents.js
  47. 30 0
      src/graphic/enum/UIEvents.js
  48. 46 0
      src/graphic/enum/VectorType.js

+ 27 - 0
src/graphic/Constant.js

@@ -0,0 +1,27 @@
+const Constant = {
+    /*
+        最小距离
+        1.合并
+        2.拖拽
+    */
+    minPixDistance: 10, //最小距离
+    minPixLen: 3, //吸附
+    minScreenDis: 2, //layer.js里,判断是否拖拽了,目前暂时不用
+    minAdsorbRec: 3, //拖拽家具,构件的时候,吸附的距离
+    furnitureWidth: 32,
+    minRealDis: 0.1,
+    minAdsorb: 0.1,
+    minAngle: 15,
+    maxAngle: 165,
+    defaultZoom: 100,
+    minSymbolLen: 0.2,
+    maxSymbolLen: 10,
+    ratio: 3, //1,
+    cadImg_Width: 5760, //1920,
+    cadImg_Height: 3240, //937,
+    // cadImg_Width: 3000,
+    // cadImg_Height: 3000,
+    miniMap_Width: 1200,
+    miniMap_Height: 1200,
+}
+export default Constant

+ 293 - 0
src/graphic/Controls/AddRoad.js

@@ -0,0 +1,293 @@
+import { listenLayer } from "../ListenLayer";
+import Constant from "../Constant";
+import { mathUtil } from "../MathUtil";
+import { dataService } from "../Service/DataService";
+import { pointService } from "../Service/PointService";
+import { elementService } from "../Service/ElementService";
+import { wallService } from "../Service/WallService";
+import { stateService } from "../Service/StateService";
+import { symbolService } from "../Service/SymbolService";
+import VectorType from "../enum/VectorType";
+import { roadService } from "../Service/RoadService";
+
+export default class AddRoad {
+  constructor() {
+    this.startInfo = {};
+    this.endInfo = {};
+    this.canAdd = false;
+  }
+
+  setPointInfo(dir, pointInfo) {
+    if (dir == "start") {
+      this.startInfo = {
+        position: {
+          x: pointInfo.x,
+          y: pointInfo.y,
+        },
+        linkedPointId: pointInfo.linkedPointId,
+        linkedRoadId: pointInfo.linkedRoadId,
+      };
+    } else if (dir == "end") {
+      this.endInfo = {
+        position: {
+          x: pointInfo.x,
+          y: pointInfo.y,
+        },
+        linkedPointId: pointInfo.linkedPointId,
+        linkedRoadId: pointInfo.linkedRoadId,
+      };
+    }
+  }
+
+  buildRoad() {
+    console.log("添加路段!");
+    let joinInfos = this.getJoinsForRoads();
+    let splitPointIds = this.splitAllJoins(joinInfos);
+    this.creatNewRoads(splitPointIds);
+    this.canAdd = false;
+    this.updateStart(
+      this.endInfo.position,
+      splitPointIds[splitPointIds.length - 1]
+    );
+    listenLayer.clear();
+  }
+
+  //开始新建公路,可能是多个
+  creatNewRoads(splitPointIds) {
+    for (let i = 0; i < splitPointIds.length - 1; ++i) {
+      let pointId1 = splitPointIds[i];
+      let pointId2 = splitPointIds[i + 1];
+      let roadId = roadService.getRoadId(pointId1, pointId2);
+      if (!roadId) {
+        let road = roadService.create(pointId1, pointId2);
+      }
+    }
+  }
+
+  canAddRoadForEnd(endPt) {
+    if (listenLayer.modifyPoint) {
+      if (
+        mathUtil.getDistance(this.startInfo.position, listenLayer.modifyPoint) <
+        Constant.minAdsorb
+      ) {
+        return false;
+      }
+    } else {
+      if (
+        mathUtil.getDistance(this.startInfo.position, endPt) <
+        Constant.minAdsorb
+      ) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  getJoinsForRoads() {
+    let result = [];
+    const roads = dataService.getRoads();
+    let hasComputerPointIds = [];
+    let hasExitPointIds = [];
+
+    if (this.startInfo.linkedPointId) {
+      hasComputerPointIds.push(this.startInfo.linkedPointId);
+      hasExitPointIds.push(this.startInfo.linkedPointId);
+      result.push({
+        join: { x: this.startInfo.position.x, y: this.startInfo.position.y },
+        pointId: this.startInfo.linkedPointId,
+      });
+    } else if (this.startInfo.linkedRoadId) {
+      result.push({
+        join: { x: this.startInfo.position.x, y: this.startInfo.position.y },
+        roadId: this.startInfo.linkedRoadId,
+      });
+    } else {
+      result.push({
+        join: { x: this.startInfo.position.x, y: this.startInfo.position.y },
+      });
+    }
+
+    if (this.endInfo.linkedPointId) {
+      hasComputerPointIds.push(this.endInfo.linkedPointId);
+      hasExitPointIds.push(this.endInfo.linkedPointId);
+      result.push({
+        join: { x: this.endInfo.position.x, y: this.endInfo.position.y },
+        pointId: this.endInfo.linkedPointId,
+      });
+    } else if (this.endInfo.linkedRoadId) {
+      result.push({
+        join: { x: this.endInfo.position.x, y: this.endInfo.position.y },
+        roadId: this.endInfo.linkedRoadId,
+      });
+    } else {
+      result.push({
+        join: { x: this.endInfo.position.x, y: this.endInfo.position.y },
+      });
+    }
+
+    const line = mathUtil.createLine1(
+      this.startInfo.position,
+      this.endInfo.position
+    );
+    for (let key in roads) {
+      if (this.startInfo.linkedRoadId && this.startInfo.linkedRoadId == key) {
+        continue;
+      } else if (
+        this.endInfo.linkedRoadId &&
+        this.endInfo.linkedRoadId == key
+      ) {
+        continue;
+      }
+
+      let road = roads[key];
+      if (
+        hasExitPointIds.indexOf(road.startId) > -1 ||
+        hasExitPointIds.indexOf(road.endId) > -1
+      ) {
+        continue;
+      }
+
+      let startPoint = dataService.getPoint(road.startId);
+      let endPoint = dataService.getPoint(road.endId);
+      let flag = false;
+
+      if (hasComputerPointIds.indexOf(road.startId) < 0) {
+        hasComputerPointIds.push(road.startId);
+        const startJoin = mathUtil.getJoinLinePoint(startPoint, line);
+        if (
+          mathUtil.PointInSegment(
+            startJoin,
+            this.startInfo.position,
+            this.endInfo.position,
+            Constant.minAdsorb
+          ) &&
+          mathUtil.getDistance(startPoint, startJoin) < Constant.minAdsorb
+        ) {
+          result.push({
+            join: { x: startJoin.x, y: startJoin.y },
+            pointId: road.startId,
+          });
+          flag = true;
+
+          hasExitPointIds.push(road.startId);
+        }
+      }
+
+      if (hasComputerPointIds.indexOf(road.endId) < 0) {
+        hasComputerPointIds.push(road.endId);
+        const endJoin = mathUtil.getJoinLinePoint(endPoint, line);
+        if (
+          mathUtil.PointInSegment(
+            endJoin,
+            this.startInfo.position,
+            this.endInfo.position,
+            Constant.minAdsorb
+          ) &&
+          mathUtil.getDistance(endPoint, endJoin) < Constant.minAdsorb
+        ) {
+          result.push({
+            join: { x: endJoin.x, y: endJoin.y },
+            pointId: road.endId,
+          });
+          flag = true;
+
+          hasExitPointIds.push(road.endId);
+        }
+      }
+
+      if (!flag) {
+        //不与墙key的端点相交
+        if (
+          hasExitPointIds.indexOf(road.startId) < 0 &&
+          hasExitPointIds.indexOf(road.endId) < 0
+        ) {
+          let join = mathUtil.getIntersectionPoint3(
+            startPoint,
+            endPoint,
+            this.startInfo.position,
+            this.endInfo.position
+          );
+          if (join) {
+            result.push({ join: { x: join.x, y: join.y }, roadId: key });
+          }
+        }
+      }
+    }
+    return result;
+  }
+
+  splitAllJoins(joinInfos) {
+    // 对joinInfos要进行排序
+    joinInfos = joinInfos.sort(sortNumber.bind(this));
+    function sortNumber(a, b) {
+      return (
+        mathUtil.getDistance(this.startInfo.position, a.join) -
+        mathUtil.getDistance(this.startInfo.position, b.join)
+      );
+    }
+
+    const splitPointIds = [];
+    for (let i = 0; i < joinInfos.length; ++i) {
+      const info = joinInfos[i];
+      const join = info.join;
+      const roadId = info.roadId;
+      const pointId = info.pointId;
+
+      if (pointId) {
+        splitPointIds.push(pointId);
+      } else if (roadId) {
+        // 拆分roadId
+        const splitPoint = pointService.create(join);
+        const splitPointId = splitPoint.vectorId;
+        //可能joinInfos的两个点都在roadId上
+        let newRoadId = null;
+        if (
+          joinInfos[i + 1] &&
+          joinInfos[i].roadId == joinInfos[i + 1].roadId
+        ) {
+          let road = dataService.getRoad(roadId);
+          let startPoint = dataService.getPoint(road.startId);
+          if (
+            mathUtil.getDistance(startPoint, joinInfos[i].join) <
+            mathUtil.getDistance(startPoint, joinInfos[i + 1].join)
+          ) {
+            newRoadId = roadService.splitRoad(roadId, splitPointId, "end");
+          } else {
+            newRoadId = roadService.splitRoad(roadId, splitPointId, "start");
+          }
+        } else {
+          newRoadId = roadService.splitRoad(roadId, splitPointId, "start");
+        }
+
+        if (newRoadId == null) {
+          dataService.deletePoint(splitPointId);
+          continue;
+        }
+        splitPointIds.push(splitPointId);
+      }
+      //首尾没有吸附的情况
+      else {
+        const splitPoint = pointService.create(join);
+        splitPointIds.push(splitPoint.vectorId);
+      }
+    }
+    return splitPointIds;
+  }
+
+  updateStart(position, linkedPointId) {
+    mathUtil.clonePoint(this.startInfo.position, position);
+    this.startInfo.linkedPointId = linkedPointId;
+    //elementService.setNewWallStartPosition(this.startInfo.position);
+  }
+
+  clear() {
+    this.startInfo = {};
+    this.endInfo = {};
+    this.canAdd = false;
+    //elementService.hideNewWall();
+    //elementService.hideStartAddWall();
+  }
+}
+
+const addRoad = new AddRoad();
+export { addRoad };

File diff suppressed because it is too large
+ 1433 - 0
src/graphic/Controls/MoveRoad.js


+ 15 - 0
src/graphic/Controls/MoveTag.js

@@ -0,0 +1,15 @@
+import { dataService } from "../Service/DataService";
+import { mathUtil } from "../MathUtil.js";
+
+export default class MoveTag {
+  constructor() {}
+
+  moveFullTag(position, tagId) {
+    let tag = dataService.getTag(tagId);
+    mathUtil.clonePoint(tag.center, position);
+    tag.setPoints2d();
+  }
+}
+
+const moveTag = new MoveTag();
+export { moveTag };

+ 132 - 0
src/graphic/Controls/UIControl.js

@@ -0,0 +1,132 @@
+import { coordinate } from "../Coordinate.js";
+import LayerEvents from "../enum/LayerEvents.js";
+import UIEvents from "../enum/UIEvents.js";
+import VectorType from "../enum/VectorType.js";
+import SymbolEvents from "../enum/SymbolEvents.js";
+import { stateService } from "../Service/StateService.js";
+import { measureService } from "../Service/MeasureService.js";
+import { dataService } from "../Service/DataService.js";
+import { historyService } from "../Service/HistoryService.js";
+import { symbolService } from "../Service/SymbolService.js";
+import { componentService } from "../Service/ComponentService.js";
+import { elementService } from "../Service/ElementService";
+import { mathUtil } from "../MathUtil.js";
+import { wallService } from "../Service/WallService.js";
+import { tagService } from "../Service/TagService.js";
+import Constant from "../Constant";
+import { roomsUtil } from "../Room/RoomsUtil.js";
+import { addRoad } from "../Controls/AddRoad";
+import { floorplanData } from "../VectorData.js";
+import { furnitureService } from "../Service/FurnitureService.js";
+
+export default class UIControl {
+  constructor(layer) {
+    this.layer = layer;
+  }
+
+  /**
+   * 获取选中要操作的UI
+   */
+  get currentUI() {}
+
+  /**
+   * 设置选中要操作的UI
+   */
+  set currentUI(value) {}
+
+  //点击左侧栏后,更新事件
+  updateEventNameForSelectUI() {
+    //elementService.hideAll();
+    //正在添加tag的时候,需要先删除
+    const eventName = stateService.getEventName();
+    if (eventName == LayerEvents.AddTag) {
+      let item = stateService.getDraggingItem();
+      if (item && item.type == VectorType.Tag) {
+        dataService.deleteTag(item.vectorId);
+      }
+    }
+    stateService.clearItems();
+    if (this.selectUI == UIEvents.Road) {
+      stateService.setEventName(LayerEvents.AddWall);
+    }
+  }
+
+  /****************************************************************************针对菜单*******************************************************************************/
+
+  // execute(name, value) {
+  //   stateService.clearFocusItem();
+  //   stateService.clearSelectItem();
+  //   //this.layer.$xui.hideProps()
+  //   this.layer.uiControl.currentUI = null;
+  //   switch (name) {
+  //     case "recall": //撤销
+  //       this.menu_revoke();
+  //       break;
+  //     case "recover": //恢复
+  //       this.menu_recovery();
+  //       break;
+  //     case "default": //恢复默认
+  //       this.menu_default();
+  //       break;
+  //     case "download": //下载
+  //       this.layer.div.style.visibility = "hidden";
+  //       this.menu_screenShot(value).then(() => {
+  //         this.layer.div.style.visibility = "visible";
+  //       });
+  //       break;
+  //     case "texture": //底图
+  //       this.showTexture = value;
+  //       this.layer.app.dom.querySelector(
+  //         '.player[name="main"]'
+  //       ).style.visibility = this.showTexture ? "visible" : "hidden";
+  //       break;
+  //     case "clear": //清空
+  //       this.menu_clear();
+  //       break;
+  //     case "panos": //漫游点
+  //       this.menu_panos(value);
+  //       break;
+  //     case "rotate": //旋转
+  //       this.menu_rotate();
+  //       break;
+  //     case "flex": //适应视图
+  //       this.menu_flex();
+  //       break;
+  //   }
+  // }
+
+  // //撤销
+  // menu_revoke() {
+  //   // this.layer.history.goPreState()
+  //   // this.layer.renderer.autoRedraw()
+
+  //   // const historyState = historyService.getHistoryState()
+  //   // if (historyState.pre) {
+  //   //     this.layer.$xui.toolbar.recall = true
+  //   // } else {
+  //   //     this.layer.$xui.toolbar.recall = false
+  //   // }
+  //   // this.layer.$xui.toolbar.recover = true
+  //   this.layer.stopAddVector();
+  //   this.layer.revokeHistory();
+  // }
+
+  // //恢复
+  // menu_recovery() {
+  //   // this.layer.history.goNextState()
+  //   // this.layer.renderer.autoRedraw()
+
+  //   // const historyState = historyService.getHistoryState()
+  //   // if (historyState.next) {
+  //   //     this.layer.$xui.toolbar.recover = true
+  //   // } else {
+  //   //     this.layer.$xui.toolbar.recover = false
+  //   // }
+  //   // this.layer.$xui.toolbar.recall = true
+  //   this.layer.recoveryHistory();
+  // }
+
+  // menu_default() {}
+
+  /******************************************************************************************************************************************************************/
+}

+ 136 - 0
src/graphic/Coordinate.js

@@ -0,0 +1,136 @@
+import { dataService } from "./Service/DataService.js";
+import { floorplanData } from "./VectorData.js";
+import { mathUtil } from "./Util/MathUtil.js/index.js";
+import Constant from "./Constant";
+import { symbolService } from "./Service/SymbolService.js";
+
+// const pad = {
+//     top: 40,
+//     bottom: 40,
+//     left: 40,
+//     right: 40,
+// }
+const defaultZoom = 100;
+export default class Coordinate {
+  constructor() {
+    this.center = null; // 世界坐标,中心点
+    this.zoom = defaultZoom; // 当前缩放比例,不会改变世界坐标系的坐标,只是改变渲染时转换的屏幕坐标
+    this.res = 80; //比例尺。实际尺寸与屏幕像素的比例,用于测量。与之前的绘制不同,目前存储的数据与像素的比例是1:1,只是最后测量值再除以这个比例
+    this.ratio = 1; //不同硬件设备,像素率不同
+
+    this.width == null;
+    this.height == null;
+  }
+
+  // 初始化
+  init(canvas) {
+    this.updateForCanvas(canvas);
+  }
+
+  setRes(res) {
+    this.res = res;
+  }
+
+  // 世界坐标转换成屏幕坐标
+  getScreenXY(point) {
+    if (this.width == null || this.height == null) {
+      return null;
+    }
+
+    let pt = {
+      x: point.x,
+      y: point.y,
+    };
+
+    let x = this.width / 2 + ((pt.x - this.center.x) * this.zoom) / defaultZoom;
+    let y =
+      this.height / 2 - ((pt.y - this.center.y) * this.zoom) / defaultZoom;
+
+    x *= this.ratio;
+    y *= this.ratio;
+
+    x = (0.5 + x) << 0;
+    y = (0.5 + y) << 0;
+    return {
+      x: Math.floor(x),
+      y: Math.floor(y),
+    };
+  }
+
+  // 屏幕坐标转换成世界坐标
+  getXYFromScreen(screenPoint) {
+    const point = {};
+    point.x =
+      ((screenPoint.x - this.width / 2) * defaultZoom) / this.zoom +
+      this.center.x;
+    point.y =
+      ((this.height / 2 - screenPoint.y) * defaultZoom) / this.zoom +
+      this.center.y;
+    return point;
+  }
+
+  getPointForRevRotate(point, angle) {}
+
+  getVectorForRotate(point, angle) {}
+
+  updateCenterForRotate(angle) {}
+
+  setCenter(canvas) {
+    if (canvas) {
+      this.center = {
+        x: canvas.clientWidth / 2,
+        y: canvas.clientHeight / 2,
+      };
+    }
+  }
+
+  updateForCanvas(canvas) {
+    if (canvas) {
+      this.width = canvas.clientWidth;
+      this.height = canvas.clientHeight;
+    }
+    this.setCenter(canvas);
+  }
+
+  updateZoom(zoom) {
+    this.zoom = zoom;
+  }
+
+  moveTo(zoom, screenPosition) {
+    const position = this.getXYFromScreen(screenPosition);
+    this.zoom = zoom;
+    const dx = (screenPosition.x * defaultZoom) / this.zoom - this.width / 2;
+    const dy = this.height / 2 - (screenPosition.y * defaultZoom) / this.zoom;
+    this.center.x = position.x - dx;
+    this.center.y = position.y - dy;
+  }
+
+  getPixelRatio(context) {
+    var backingStore =
+      context.backingStorePixelRatio ||
+      context.webkitBackingStorePixelRatio ||
+      context.mozBackingStorePixelRatio ||
+      context.msBackingStorePixelRatio ||
+      context.oBackingStorePixelRatio ||
+      context.backingStorePixelRatio ||
+      1;
+    return (window.devicePixelRatio || 1) / backingStore;
+  }
+
+  reSet(canvas) {
+    this.updateForCanvas(canvas);
+    this.zoom = defaultZoom;
+  }
+
+  updateForRotate(angle) {}
+
+  clear() {
+    this.center = null;
+    this.zoom = 100;
+    this.res = 80;
+    this.ratio = 1;
+  }
+}
+
+const coordinate = new Coordinate();
+export { coordinate };

+ 21 - 0
src/graphic/Geometry/Edge.js

@@ -0,0 +1,21 @@
+//墙的边缘线
+import Geometry from "./Geometry.js";
+import { mathUtil } from "../Util/MathUtil";
+
+export default class Edge extends Geometry {
+  constructor(start, end, parentId) {
+    super();
+    this.parent = parentId;
+    this.start = start;
+    this.end = end;
+    this.vectorId = null;
+    this.geoType = VectorType.Edge;
+  }
+
+  init = function () {};
+
+  getLine = function () {
+    let line = mathUtil.createLine(this.start, this.end);
+    return line;
+  };
+}

+ 302 - 0
src/graphic/Geometry/Furniture.js

@@ -0,0 +1,302 @@
+import Geometry from "./Geometry";
+import { mathUtil } from "../Util/MathUtil.js/index.js";
+import SelectState from "../enum/SelectState.js";
+import VectorType from "../enum/VectorType.js";
+import Constant from "../Constant.js";
+import { coordinate } from "../Coordinate";
+
+export default class Furniture extends Geometry {
+  constructor(center, vectorId, type) {
+    super();
+    this.center = center;
+    this.geoType = type;
+    //this.boundingVertexs = []
+    this.angle = 0; //逆时针为负,顺时针为正。单位是:°
+    this.zoom = 1; //缩放比例
+    this.setId(vectorId);
+  }
+
+  isContain(position) {
+    // const dis = mathUtil.getDistance(position, this.center)
+    // let len = this.getScale() * this.zoom
+    // if (dis < len / 2) {
+    //     return SelectState.Select
+    // } else {
+    //     return null
+    // }
+    const points2d = this.getBoundingVertexs(this.center);
+    return mathUtil.isPointInPoly(position, points2d);
+  }
+
+  getScale() {
+    switch (this.geoType) {
+      case VectorType.TV: //电视
+        return 1.3;
+      case VectorType.CombinationSofa: //组合沙发
+        return 3.4;
+      case VectorType.SingleSofa: //单人沙发
+        return 1;
+      case VectorType.TeaTable: //茶几
+        return 0.8;
+      case VectorType.Carpet: //地毯
+        return 2;
+      case VectorType.Plant: //植物
+        return 1;
+      case VectorType.DiningTable: //餐桌
+        return 2.4;
+      case VectorType.DoubleBed: //双人床
+        return 2;
+      case VectorType.SingleBed: //单人床
+        return 2;
+      case VectorType.Wardrobe: //衣柜
+        return 1.2;
+      case VectorType.Dresser: //梳妆台
+        return 0.8;
+      case VectorType.BedsideCupboard: //床头柜
+        return 0.4;
+      case VectorType.Pillow: //抱枕
+        return 0.6;
+      case VectorType.GasStove: //燃气灶
+        return 1;
+      case VectorType.Cupboard: //橱柜
+        return 1.2;
+      case VectorType.Bathtub: //浴缸
+        return 1.6;
+      case VectorType.Closestool: //马桶
+        return 0.63;
+      case VectorType.Washstand: //洗漱台
+        return 0.8;
+      case VectorType.Desk: //书桌
+        return 1.2;
+      case VectorType.BalconyChair: //阳台椅
+        return 2;
+      case VectorType.Elevator: //电梯
+        return 1.5;
+    }
+  }
+
+  getLengthWidth() {
+    switch (this.geoType) {
+      case VectorType.TV: //电视
+        return {
+          length: 32,
+          width: 10,
+        };
+      case VectorType.CombinationSofa: //组合沙发
+        return {
+          length: 32,
+          width: 28,
+        };
+      case VectorType.SingleSofa: //单人沙发
+        return {
+          length: 26,
+          width: 20,
+        };
+      case VectorType.TeaTable: //茶几
+        return {
+          length: 30,
+          width: 16,
+        };
+      case VectorType.Carpet: //地毯
+        return {
+          length: 32,
+          width: 20,
+        };
+      case VectorType.Plant: //植物
+        return {
+          length: 28,
+          width: 28,
+        };
+      case VectorType.DiningTable: //餐桌
+        return {
+          length: 28,
+          width: 26,
+        };
+      case VectorType.DoubleBed: //双人床
+        return {
+          length: 24,
+          width: 26,
+        };
+      case VectorType.SingleBed: //单人床
+        return {
+          length: 18,
+          width: 26,
+        };
+      case VectorType.Wardrobe: //衣柜
+        return {
+          length: 30,
+          width: 18,
+        };
+      case VectorType.Dresser: //梳妆台
+        return {
+          length: 28,
+          width: 26,
+        };
+      case VectorType.BedsideCupboard: //床头柜
+        return {
+          length: 28,
+          width: 28,
+        };
+      case VectorType.Pillow: //抱枕
+        return {
+          length: 26,
+          width: 26,
+        };
+      case VectorType.GasStove: //燃气灶
+        return {
+          length: 32,
+          width: 20,
+        };
+      case VectorType.Cupboard: //橱柜
+        return {
+          length: 12,
+          width: 32,
+        };
+      case VectorType.Bathtub: //浴缸
+        return {
+          length: 32,
+          width: 18,
+        };
+      case VectorType.Closestool: //马桶
+        return {
+          length: 22,
+          width: 28,
+        };
+      case VectorType.Washstand: //洗漱台
+        return {
+          length: 28,
+          width: 20,
+        };
+      case VectorType.Desk: //书桌
+        return {
+          length: 26,
+          width: 20,
+        };
+      case VectorType.BalconyChair: //阳台椅
+        return {
+          length: 32,
+          width: 10,
+        };
+      case VectorType.Elevator: //电梯
+        return {
+          length: 28,
+          width: 28,
+        };
+    }
+  }
+
+  /*
+    getBoundingVertexs(center) {
+        //this.boundingVertexs = []
+        const boundingVertexs = []
+        //const width = (((this.getScale() * Constant.furnitureWidth) / coordinate.res) * this.zoom)
+        const width = this.getScale() * this.zoom
+
+        const minX = center.x - width / 2
+        const minY = center.y - width / 2
+        const maxX = center.x + width / 2
+        const maxY = center.y + width / 2
+
+        const point1 = this.rotatePoint(
+            {
+                x: minX,
+                y: maxY,
+            },
+            center,
+            this.angle
+        )
+
+        const point2 = this.rotatePoint(
+            {
+                x: maxX,
+                y: maxY,
+            },
+            center,
+            this.angle
+        )
+
+        const point3 = this.rotatePoint(
+            {
+                x: maxX,
+                y: minY,
+            },
+            center,
+            this.angle
+        )
+
+        const point4 = this.rotatePoint(
+            {
+                x: minX,
+                y: minY,
+            },
+            center,
+            this.angle
+        )
+
+        boundingVertexs.push(point1)
+        boundingVertexs.push(point2)
+        boundingVertexs.push(point3)
+        boundingVertexs.push(point4)
+
+        return boundingVertexs
+    }
+    */
+  getBoundingVertexs(center) {
+    //this.boundingVertexs = []
+    const boundingVertexs = [];
+    const rec = this.getLengthWidth();
+    //const width = (((this.getScale() * Constant.furnitureWidth) / coordinate.res) * this.zoom)
+    const length =
+      (this.getScale() * rec.length * this.zoom) / Constant.furnitureWidth;
+    const width =
+      (this.getScale() * rec.width * this.zoom) / Constant.furnitureWidth;
+
+    const minX = center.x - length / 2;
+    const minY = center.y - width / 2;
+    const maxX = center.x + length / 2;
+    const maxY = center.y + width / 2;
+
+    const point1 = this.rotatePoint(
+      {
+        x: minX,
+        y: maxY,
+      },
+      center,
+      this.angle
+    );
+
+    const point2 = this.rotatePoint(
+      {
+        x: maxX,
+        y: maxY,
+      },
+      center,
+      this.angle
+    );
+
+    const point3 = this.rotatePoint(
+      {
+        x: maxX,
+        y: minY,
+      },
+      center,
+      this.angle
+    );
+
+    const point4 = this.rotatePoint(
+      {
+        x: minX,
+        y: minY,
+      },
+      center,
+      this.angle
+    );
+
+    boundingVertexs.push(point1);
+    boundingVertexs.push(point2);
+    boundingVertexs.push(point3);
+    boundingVertexs.push(point4);
+
+    return boundingVertexs;
+  }
+}

+ 61 - 0
src/graphic/Geometry/Geometry.js

@@ -0,0 +1,61 @@
+import { dataService } from "../Service/DataService";
+import { mathUtil } from "../Util/MathUtil.js/index.js";
+import SymbolEvents from "../enum/SymbolEvents.js";
+
+export default class Geometry {
+  constructor() {
+    this.len = null;
+  }
+
+  setId(vectorId) {
+    if (vectorId == null || typeof vectorId === "undefined") {
+      vectorId = dataService.getCurrentId();
+      dataService.updateCurrentId();
+      this.vectorId = this.geoType + vectorId;
+    } else {
+      this.vectorId = vectorId;
+    }
+  }
+
+  setPointParent(parentId, dir) {
+    if (this.parent == null) {
+      this.parent = {};
+    }
+
+    this.parent[parentId] = dir;
+  }
+
+  setLeftEdge(edgeId) {
+    this.leftEdgeId = edgeId;
+  }
+
+  setRightEdge(edgeId) {
+    this.rightEdgeId = edgeId;
+  }
+
+  getParent() {
+    return this.parent;
+  }
+
+  // ptSrc: 圆上某点(初始点);
+  // ptRotationCenter: 圆心点;
+  // angle: 旋转角度°  -- [angle * M_PI / 180]:将角度换算为弧度
+  // 【注意】angle 逆时针为正,顺时针为负
+  rotatePoint(ptSrc, ptRotationCenter, angle) {
+    angle = -1 * angle; //设计是逆时针为负,顺时针为正
+    var a = ptRotationCenter.x;
+    var b = ptRotationCenter.y;
+    var x0 = ptSrc.x;
+    var y0 = ptSrc.y;
+    var rx =
+      a +
+      (x0 - a) * Math.cos((angle * Math.PI) / 180) -
+      (y0 - b) * Math.sin((angle * Math.PI) / 180);
+    var ry =
+      b +
+      (x0 - a) * Math.sin((angle * Math.PI) / 180) +
+      (y0 - b) * Math.cos((angle * Math.PI) / 180);
+    var json = { x: rx, y: ry };
+    return json;
+  }
+}

+ 27 - 0
src/graphic/Geometry/Line.js

@@ -0,0 +1,27 @@
+import VectorType from '../enum/VectorType.js'
+import SelectState from '../enum/SelectState.js'
+import Geometry from './Geometry'
+import { mathUtil } from '../MathUtil.js'
+import Constant from '../Constant.js'
+
+//不靠墙
+export default class Line extends Geometry {
+    constructor(start, end, vectorId) {
+        super()
+        this.start = start
+        this.end = end
+
+        this.name = null
+        this.display = false
+        this.geoType = VectorType.Line
+        this.setId(vectorId)
+    }
+
+    setPositions(point1, point2) {
+        this.start.x = point1.x
+        this.start.y = point1.y
+
+        this.end.x = point2.x
+        this.end.y = point2.y
+    }
+}

+ 22 - 0
src/graphic/Geometry/Point.js

@@ -0,0 +1,22 @@
+import VectorType from "../enum/VectorType.js";
+import Geometry from "./Geometry";
+
+export default class Point extends Geometry {
+  constructor(x, y, vectorId) {
+    super();
+    this.x = x;
+    this.y = y;
+
+    this.display = false;
+
+    this.name = null;
+    this.parent = {};
+    this.geoType = VectorType.Point;
+    this.setId(vectorId);
+  }
+
+  setPosition(position) {
+    this.x = position.x;
+    this.y = position.y;
+  }
+}

+ 33 - 0
src/graphic/Geometry/Road.js

@@ -0,0 +1,33 @@
+import VectorType from "../enum/VectorType.js";
+import Geometry from "./Geometry.js";
+
+export default class Road extends Geometry {
+  constructor(startId, endId, vectorId) {
+    super();
+    this.startId = startId;
+    this.endId = endId;
+    this.leftEdgeId = null;
+    this.rightEdgeId = null;
+    this.width = 10; //默认宽度
+    this.geoType = VectorType.Road;
+    this.setId(vectorId);
+  }
+
+  getOtherPointId(pointId) {
+    if (this.startId == pointId) {
+      return this.endId;
+    } else if (this.endId == pointId) {
+      return this.startId;
+    } else {
+      return null;
+    }
+  }
+
+  getPointId(dir) {
+    if (dir == "start") {
+      return this.startId;
+    } else {
+      return this.endId;
+    }
+  }
+}

+ 108 - 0
src/graphic/Geometry/Tag.js

@@ -0,0 +1,108 @@
+import VectorType from '../enum/VectorType.js'
+import Geometry from './Geometry'
+import { mathUtil } from '../MathUtil.js'
+import { coordinate } from '../Coordinate'
+import Constant from '../Constant.js'
+
+//不靠墙
+export default class Tag extends Geometry {
+    constructor(center, vectorId) {
+        super()
+        this.center = center
+        this.points2d = []
+        //this.title = KanKan.Config.i18n('cad.input')
+        this.title = ''
+        this.des = '' //面积
+        this.unit = 'm' //或者ft
+        this.name = '标注'
+        this.adding = true
+
+        this.sideWidth = 30 //像素
+        this.sideThickness = 30 //像素
+
+        this.geoType = VectorType.Tag
+        this.setId(vectorId)
+    }
+
+    isContain(position) {
+        let points = []
+        points.push(this.points2d[0])
+        points.push(this.points2d[1])
+        points.push(this.points2d[2])
+        points.push(this.points2d[3])
+        return mathUtil.isPointInPoly(position, points)
+    }
+
+    setPoints2d() {
+        this.points2d = []
+        const minX = this.center.x - ((this.sideWidth / coordinate.res) * Constant.defaultZoom) / coordinate.zoom / 2
+        const minY = this.center.y - ((this.sideThickness / coordinate.res) * Constant.defaultZoom) / coordinate.zoom / 2
+        const maxX = this.center.x + ((this.sideWidth / coordinate.res) * Constant.defaultZoom) / coordinate.zoom / 2
+        const maxY = this.center.y + ((this.sideThickness / coordinate.res) * Constant.defaultZoom) / coordinate.zoom / 2
+
+        const point1 = {
+            x: minX,
+            y: maxY,
+        }
+
+        const point2 = {
+            x: maxX,
+            y: maxY,
+        }
+
+        const point3 = {
+            x: maxX,
+            y: minY,
+        }
+
+        const point4 = {
+            x: minX,
+            y: minY,
+        }
+
+        this.points2d.push(point1)
+        this.points2d.push(point2)
+        this.points2d.push(point3)
+        this.points2d.push(point4)
+
+        //-
+        let dx = (point1.x - this.center.x) / 2
+        //+
+        let dy = (point1.y - this.center.y) / 2
+
+        this.points2d.push({
+            x: point1.x - dx,
+            y: point1.y - dy,
+        })
+
+        this.points2d.push({
+            x: point2.x + dx,
+            y: point1.y - dy,
+        })
+
+        this.points2d.push({
+            x: this.center.x,
+            y: point1.y - dy,
+        })
+
+        this.points2d.push({
+            x: this.center.x,
+            y: point3.y + dy,
+        })
+    }
+
+    setTitle(title) {
+        this.title = title
+    }
+
+    setDes(des) {
+        this.des = des
+    }
+    setUnit(unit) {
+        this.unit = unit
+    }
+
+    setAdding(flag) {
+        this.adding = flag
+    }
+}

+ 348 - 0
src/graphic/History/Change.js

@@ -0,0 +1,348 @@
+import { dataService } from "../Service/DataService";
+import { elementService } from "../Service/ElementService";
+import { wallService } from "../Service/WallService";
+import { historyUtil } from "./HistoryUtil";
+import { symbolService } from "../Service/SymbolService";
+import HistoryEvents from "../enum/HistoryEvents";
+import { coordinate } from "../Coordinate";
+
+export default class Change {
+  constructor() {
+    this.lastData = {}; // 每次都是当前数据和lastData进行比较,一般在mouseDown的时候存储进来
+    this.elements = {}; // 当前的变化
+  }
+
+  // 保存当前记录
+  saveCurrentInfo() {
+    //this.lastData.subFloorNum = dataService.getSubFloor();
+    this.lastData.currentFloor = dataService.getCurrentFloor();
+    this.lastData.points = JSON.parse(JSON.stringify(dataService.getPoints()));
+    this.lastData.walls = JSON.parse(JSON.stringify(dataService.getWalls()));
+    this.lastData.symbols = JSON.parse(
+      JSON.stringify(dataService.getSymbols())
+    );
+    this.lastData.components = JSON.parse(
+      JSON.stringify(dataService.getComponents())
+    );
+    this.lastData.furnitures = JSON.parse(
+      JSON.stringify(dataService.getFurnitures())
+    );
+    this.lastData.tags = JSON.parse(JSON.stringify(dataService.getTags()));
+    this.lastData.angle = dataService.getAngle();
+    this.lastData.res = coordinate.res;
+  }
+
+  operate() {
+    //
+    this.elements = {};
+    let flag = this.compareAngle();
+    if (!flag) {
+      this.comparePoints();
+      this.compareSymbols();
+      this.compareWalls();
+      this.compareComponents();
+      this.compareTags();
+      this.compareFurnitures();
+    }
+    //旋转了
+    else {
+      this.lastData = {};
+      return true;
+    }
+
+    if (
+      this.elements.points.length == 0 &&
+      this.elements.walls.length == 0 &&
+      this.elements.symbols.length == 0 &&
+      this.elements.components.length == 0 &&
+      this.elements.tags.length == 0 &&
+      this.elements.furnitures.length == 0
+    ) {
+      this.saveCurrentInfo();
+      return false;
+    }
+    this.lastData = {};
+    // 这里不能取this.records.length-1,因为可能撤销后操作,这时候应该是覆盖,而不是往后面添加
+    return true;
+  }
+
+  comparePoints() {
+    //const currentFloor = dataService.getCurrentFloorNum();
+    const points = dataService.getPoints();
+    this.elements.points = [];
+
+    for (const key in points) {
+      const point = points[key];
+      // 不存在意味着增加
+      if (!this.lastData.points[key]) {
+        const item = {
+          handle: HistoryEvents.AddPoint,
+          point: historyUtil.getDataForPoint(point),
+        };
+        this.elements.points.push(item);
+      } else {
+        const lastPoint = this.lastData.points[key];
+        if (
+          point.x == lastPoint.x &&
+          point.y == lastPoint.y &&
+          JSON.stringify(point.parent) == JSON.stringify(lastPoint.parent)
+        ) {
+          delete this.lastData.points[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyPoint,
+            prePoint: historyUtil.getDataForPoint(lastPoint),
+            curPoint: historyUtil.getDataForPoint(point),
+          };
+          this.elements.points.push(item);
+        }
+      }
+      delete this.lastData.points[key];
+    }
+
+    for (const key in this.lastData.points) {
+      const item = {
+        handle: HistoryEvents.DeletePoint,
+        point: historyUtil.getDataForPoint(this.lastData.points[key]),
+      };
+      this.elements.points.push(item);
+    }
+  }
+
+  compareWalls() {
+    //const currentFloor = dataService.getCurrentFloorNum();
+    this.elements.walls = [];
+    const walls = dataService.getWalls();
+    for (const key in walls) {
+      const wall = walls[key];
+      const lastWall = this.lastData.walls[key];
+
+      // 不存在意味着增加
+      if (!lastWall) {
+        const item = {
+          handle: HistoryEvents.AddRoad,
+          wall: historyUtil.getDataForWall(wall),
+        };
+        this.elements.walls.push(item);
+      } else {
+        if (!historyUtil.isDifferentForWalls(wall, lastWall)) {
+          delete this.lastData.walls[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyWall,
+            preWall: historyUtil.getDataForWall(lastWall),
+            curWall: historyUtil.getDataForWall(wall),
+          };
+          this.elements.walls.push(item);
+        }
+      }
+      delete this.lastData.walls[key];
+    }
+
+    for (const key in this.lastData.walls) {
+      const item = {
+        handle: HistoryEvents.DeleteWall,
+        wall: historyUtil.getDataForWall(this.lastData.walls[key]),
+      };
+      this.elements.walls.push(item);
+    }
+  }
+
+  compareSymbols() {
+    //const currentFloor = dataService.getCurrentFloorNum();
+
+    this.elements.symbols = [];
+    const symbols = dataService.getSymbols();
+
+    const wallIds = [];
+
+    for (const key in symbols) {
+      const symbol = symbols[key];
+      const lastSymbol = this.lastData.symbols[key];
+
+      // 不存在意味着增加
+      if (!lastSymbol) {
+        const item = {
+          handle: HistoryEvents.AddSymbol,
+          symbol: historyUtil.getDataForSymbol(symbol),
+        };
+        this.elements.symbols.push(item);
+      } else {
+        if (!historyUtil.isDifferentForSymbols(symbol, lastSymbol)) {
+          delete this.lastData.symbols[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifySymbol,
+            preSymbol: historyUtil.getDataForSymbol(lastSymbol),
+            curSymbol: historyUtil.getDataForSymbol(symbol),
+          };
+          this.elements.symbols.push(item);
+        }
+      }
+      delete this.lastData.symbols[key];
+    }
+
+    for (const key in this.lastData.symbols) {
+      const item = {
+        handle: HistoryEvents.DeleteSymbol,
+        symbol: historyUtil.getDataForSymbol(this.lastData.symbols[key]),
+      };
+      this.elements.symbols.push(item);
+    }
+  }
+
+  compareComponents() {
+    //const currentFloor = dataService.getCurrentFloorNum();
+    this.elements.components = [];
+    const components = dataService.getComponents();
+
+    for (const key in components) {
+      const component = components[key];
+      const lastComponent = this.lastData.components[key];
+
+      // 不存在意味着增加
+      if (!lastComponent) {
+        const item = {
+          handle: HistoryEvents.AddComponent,
+          component: historyUtil.getDataForComponent(component),
+        };
+        this.elements.components.push(item);
+      } else {
+        if (!historyUtil.isDifferentForComponents(component, lastComponent)) {
+          delete this.lastData.components[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyComponent,
+            preComponent: historyUtil.getDataForComponent(lastComponent),
+            curComponent: historyUtil.getDataForComponent(component),
+          };
+          this.elements.components.push(item);
+        }
+      }
+      delete this.lastData.components[key];
+    }
+
+    for (const key in this.lastData.components) {
+      const item = {
+        handle: HistoryEvents.DeleteComponent,
+        component: historyUtil.getDataForComponent(
+          this.lastData.components[key]
+        ),
+      };
+      this.elements.components.push(item);
+    }
+  }
+
+  compareTags() {
+    this.elements.tags = [];
+    const tags = dataService.getTags();
+
+    for (const key in tags) {
+      const tag = tags[key];
+      const lastTag = this.lastData.tags[key];
+
+      // 不存在意味着增加
+      if (!lastTag) {
+        const item = {
+          handle: HistoryEvents.AddTag,
+          tag: historyUtil.getDataForTag(tag),
+        };
+        this.elements.tags.push(item);
+      } else {
+        if (!historyUtil.isDifferentForTags(tag, lastTag)) {
+          delete this.lastData.tags[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyTag,
+            preTag: historyUtil.getDataForTag(lastTag),
+            curTag: historyUtil.getDataForTag(tag),
+          };
+          this.elements.tags.push(item);
+        }
+      }
+      delete this.lastData.tags[key];
+    }
+
+    for (const key in this.lastData.tags) {
+      const item = {
+        handle: HistoryEvents.DeleteTag,
+        tag: historyUtil.getDataForTag(this.lastData.tags[key]),
+      };
+      this.elements.tags.push(item);
+    }
+  }
+
+  compareFurnitures() {
+    //const currentFloor = dataService.getCurrentFloorNum();
+    this.elements.furnitures = [];
+    const furnitures = dataService.getFurnitures();
+
+    for (const key in furnitures) {
+      const furniture = furnitures[key];
+      const lastFurniture = this.lastData.furnitures[key];
+
+      // 不存在意味着增加
+      if (!lastFurniture) {
+        const item = {
+          handle: HistoryEvents.AddFurniture,
+          furniture: historyUtil.getDataForFurniture(furniture),
+        };
+        this.elements.furnitures.push(item);
+      } else {
+        if (!historyUtil.isDifferentForFurnitures(furniture, lastFurniture)) {
+          delete this.lastData.furnitures[key];
+          continue;
+        } else {
+          const item = {
+            handle: HistoryEvents.ModifyFurniture,
+            preFurniture: historyUtil.getDataForFurniture(lastFurniture),
+            curFurniture: historyUtil.getDataForFurniture(furniture),
+          };
+          this.elements.furnitures.push(item);
+        }
+      }
+      delete this.lastData.furnitures[key];
+    }
+
+    for (const key in this.lastData.furnitures) {
+      const item = {
+        handle: HistoryEvents.DeleteFurniture,
+        furniture: historyUtil.getDataForFurniture(
+          this.lastData.furnitures[key]
+        ),
+      };
+      this.elements.furnitures.push(item);
+    }
+  }
+
+  compareAngle() {
+    const angle = dataService.getAngle();
+    const lastAngle = this.lastData.angle;
+    const lastRes = this.lastData.res;
+    if (historyUtil.isDifferentForAngle(angle, lastAngle)) {
+      const item = {
+        handle: HistoryEvents.ModifyAngle,
+        preState: {
+          angle: historyUtil.getDataForAngle(lastAngle),
+          res: historyUtil.getDataForRes(lastRes),
+        },
+        curState: {
+          angle: historyUtil.getDataForAngle(angle),
+          res: historyUtil.getDataForRes(coordinate.res),
+        },
+      };
+      this.elements.rotate = item;
+      return true;
+    } else {
+      return false;
+    }
+  }
+}
+
+const change = new Change();
+export { change };

+ 497 - 0
src/graphic/History/History.js

@@ -0,0 +1,497 @@
+import { dataService } from "../Service/DataService";
+import { elementService } from "../Service/ElementService";
+import { wallService } from "../Service/WallService";
+import { historyUtil } from "./HistoryUtil";
+import { change } from "./Change";
+import { symbolService } from "../Service/SymbolService";
+import { stateService } from "../Service/StateService";
+import HistoryEvents from "../enum/HistoryEvents";
+import { componentService } from "../Service/ComponentService";
+import { historyService } from "../Service/HistoryService";
+import { tagService } from "../Service/TagService";
+import { coordinate } from "../Coordinate";
+import { measureService } from "../Service/MeasureService";
+import { furnitureService } from "../Service/FurnitureService";
+
+export default class History {
+  constructor(layer) {
+    this.layer = layer;
+  }
+
+  init() {
+    change.saveCurrentInfo();
+    const points = dataService.getPoints();
+    if (Object.keys(points).length > 0) {
+      this.layer.$xui.toolbar.clear = true;
+      this.layer.$xui.toolbar.download = true;
+    } else {
+      this.layer.$xui.toolbar.clear = false;
+      this.layer.$xui.toolbar.download = false;
+    }
+  }
+
+  save() {
+    const flag = change.operate();
+    if (!flag) {
+      return;
+    }
+    measureService.update();
+    historyService.addHistoryRecord(change.elements);
+    change.saveCurrentInfo();
+    this.setState();
+    const historyState = historyService.getHistoryState();
+    if (historyState.pre) {
+      this.layer.$xui.toolbar.recall = true;
+    }
+    this.layer.$xui.toolbar.recover = false;
+
+    const points = dataService.getPoints();
+    if (Object.keys(points).length > 0) {
+      this.layer.$xui.toolbar.clear = true;
+      this.layer.$xui.toolbar.download = true;
+    } else {
+      this.layer.$xui.toolbar.clear = false;
+      this.layer.$xui.toolbar.download = false;
+    }
+
+    //给UI发送事件
+    this.layer.emit("change");
+
+    return change.elements;
+  }
+
+  setState() {
+    const state = {
+      pre: 0,
+      next: 0,
+    };
+
+    const currentRecordIndex = historyService.getCurrentRecordIndex();
+    const records = historyService.getHistoryRecords();
+
+    if (currentRecordIndex > -1) {
+      state.pre = 1;
+    }
+
+    if (currentRecordIndex < records.length - 1) {
+      state.next = 1;
+    }
+    const lastState = historyService.getHistoryState();
+    if (lastState.pre != state.pre || lastState.next != state.next) {
+      historyService.setHistoryState(state.pre, state.next);
+    }
+  }
+
+  // 是否可以撤销
+  canUndo() {
+    const state = this.setState();
+    if (state.pre == 0) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  // 是否可以恢复
+  canRedo() {
+    const state = this.setState();
+    if (state.next == 0) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  // 撤销
+  handleUndo() {
+    this.goPreState();
+  }
+
+  // 恢复
+  handleRedo() {
+    this.goNextState();
+  }
+
+  // 撤销
+  goPreState() {
+    const item = historyService.getHistoryRecord();
+    if (item) {
+      stateService.clearFocusItem();
+      stateService.clearEventName();
+      //this.layer.$xui.hideProps()
+      this.layer.uiControl.currentUI = null;
+      item.type = "pre";
+      let flag = false;
+      if (item.rotate == null) {
+        flag = false;
+      } else {
+        flag = this.goPreForAngle(item.rotate);
+      }
+
+      if (!flag) {
+        this.goPreForPoints(item.points);
+        this.goPreForWalls(item.walls);
+        // 必须在墙的后面,因为删除带symbol的墙的时候,撤销时,必须是先有墙才有symbol
+        this.goPreForSymbols(item.symbols);
+        this.goPreForComponents(item.components);
+        this.goPreForTags(item.tags);
+        this.goPreForFurnitures(item.furnitures);
+      }
+
+      measureService.update();
+      historyService.undoHistoryRecord();
+      change.saveCurrentInfo();
+      this.setState();
+
+      const points = dataService.getPoints();
+      if (Object.keys(points).length > 0) {
+        this.layer.$xui.toolbar.clear = true;
+        this.layer.$xui.toolbar.download = true;
+      } else {
+        this.layer.$xui.toolbar.clear = false;
+        this.layer.$xui.toolbar.download = false;
+      }
+    } else {
+      console.error("goPreState超出范围!");
+    }
+  }
+
+  goPreForPoints(itemForPoints) {
+    for (let i = 0; i < itemForPoints.length; ++i) {
+      const item = itemForPoints[i];
+      if (item.handle == HistoryEvents.AddPoint) {
+        historyUtil.deletePoint(item.point.id);
+      } else if (item.handle == HistoryEvents.DeletePoint) {
+        let point = wallService.createPoint(
+          item.point.x,
+          item.point.y,
+          item.point.id
+        );
+        point.parent = JSON.parse(JSON.stringify(item.point.parent));
+      } else if (item.handle == HistoryEvents.ModifyPoint) {
+        const prePoint = item.prePoint;
+        let currentPoint = dataService.getPoint(item.curPoint.id);
+        historyUtil.assignPointFromPoint(currentPoint, prePoint);
+      }
+    }
+  }
+
+  goPreForWalls(itemForWalls) {
+    for (let i = 0; i < itemForWalls.length; ++i) {
+      const item = itemForWalls[i];
+      if (item.handle == HistoryEvents.AddRoad) {
+        dataService.deleteWallNoSymbol(item.wall.id);
+      } else if (item.handle == HistoryEvents.DeleteWall) {
+        const preWall = item.wall;
+        let newWall = wallService.createWall(
+          preWall.start,
+          preWall.end,
+          preWall.id
+        );
+        historyUtil.assignWallFromWall(newWall, preWall);
+      } else if (item.handle == HistoryEvents.ModifyWall) {
+        const preWall = item.preWall;
+        let currentWall = dataService.getWall(item.curWall.id);
+        historyUtil.assignWallFromWall(currentWall, preWall);
+      }
+    }
+  }
+
+  goPreForSymbols(itemForSymbols) {
+    for (let i = 0; i < itemForSymbols.length; ++i) {
+      const item = itemForSymbols[i];
+      if (item.handle == HistoryEvents.AddSymbol) {
+        symbolService.deleteSymbol(item.symbol.id);
+      } else if (item.handle == HistoryEvents.DeleteSymbol) {
+        let newSymbol = symbolService.createSymbol(
+          item.symbol.start,
+          item.symbol.end,
+          item.symbol.type,
+          item.symbol.parent,
+          item.symbol.id
+        );
+        historyUtil.assignSymbolFromSymbol(newSymbol, item.symbol);
+      } else if (item.handle == HistoryEvents.ModifySymbol) {
+        const preSymbol = item.preSymbol;
+        let currentSymbol = dataService.getSymbol(item.curSymbol.id);
+        historyUtil.assignSymbolFromSymbol(currentSymbol, preSymbol);
+      }
+    }
+  }
+
+  goPreForComponents(itemForComponents) {
+    for (let i = 0; i < itemForComponents.length; ++i) {
+      const item = itemForComponents[i];
+      if (item.handle == HistoryEvents.AddComponent) {
+        dataService.deleteComponent(item.component.id);
+      } else if (item.handle == HistoryEvents.DeleteComponent) {
+        let vComponent = componentService.createComponent(
+          item.component.center,
+          item.component.type,
+          item.component.id
+        );
+        historyUtil.assignComponentFromComponent(vComponent, item.component);
+      } else if (item.handle == HistoryEvents.ModifyComponent) {
+        const preComponent = item.preComponent;
+        let currentComponent = dataService.getComponent(item.curComponent.id);
+        historyUtil.assignComponentFromComponent(
+          currentComponent,
+          preComponent
+        );
+      }
+    }
+  }
+
+  goPreForFurnitures(itemForFurnitures) {
+    for (let i = 0; i < itemForFurnitures.length; ++i) {
+      const item = itemForFurnitures[i];
+      if (item.handle == HistoryEvents.AddFurniture) {
+        dataService.deleteFurniture(item.furniture.id);
+      } else if (item.handle == HistoryEvents.DeleteFurniture) {
+        let vFurniture = furnitureService.createFurniture(
+          item.furniture.center,
+          item.furniture.type,
+          item.furniture.id
+        );
+        historyUtil.assignFurnitureFromFurniture(vFurniture, item.furniture);
+      } else if (item.handle == HistoryEvents.ModifyFurniture) {
+        const preFurniture = item.preFurniture;
+        let currentFurniture = dataService.getFurniture(item.curFurniture.id);
+        historyUtil.assignFurnitureFromFurniture(
+          currentFurniture,
+          preFurniture
+        );
+      }
+    }
+  }
+
+  goPreForTags(itemForTags) {
+    for (let i = 0; i < itemForTags.length; ++i) {
+      const item = itemForTags[i];
+      if (item.handle == HistoryEvents.AddTag) {
+        tagService.deleteTag(item.tag.id);
+      } else if (item.handle == HistoryEvents.DeleteTag) {
+        let newTag = tagService.createTag(item.tag.center, item.tag.id);
+        historyUtil.assignTagFromTag(newTag, item.tag);
+      } else if (item.handle == HistoryEvents.ModifyTag) {
+        const preTag = item.preTag;
+        let currentTag = dataService.getTag(item.curTag.id);
+        historyUtil.assignTagFromTag(currentTag, preTag);
+      }
+    }
+  }
+
+  goPreForAngle(itemForAngle) {
+    if (itemForAngle.handle == HistoryEvents.ModifyAngle) {
+      coordinate.reSet();
+      coordinate._setRes(itemForAngle.preState.res);
+      //旋转cad
+      dataService.setAngle(itemForAngle.preState.angle);
+      coordinate.updateForRotate(
+        itemForAngle.preState.angle - itemForAngle.curState.angle
+      );
+      //旋转三维模型
+      let info = coordinate.getScreenInfoForCAD();
+      info.floorPlanAngle = itemForAngle.preState.angle;
+      this.layer.app.core
+        .get("CameraControls")
+        .emit("syncCadAnd3DForRotate", info);
+      this.layer.app.store.getValue("metadata").floorPlanAngle =
+        itemForAngle.preState.angle;
+      this.layer.initPanos(dataService.getCurrentFloor());
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  goNextForPoints(itemForPoints) {
+    for (let i = 0; i < itemForPoints.length; ++i) {
+      const item = itemForPoints[i];
+      if (item.handle == HistoryEvents.AddPoint) {
+        let newPoint = wallService.createPoint(
+          item.point.x,
+          item.point.y,
+          item.point.id
+        );
+        historyUtil.assignPointFromPoint(newPoint, item.point);
+      } else if (item.handle == HistoryEvents.DeletePoint) {
+        historyUtil.deletePoint(item.point.id);
+      } else if (item.handle == HistoryEvents.ModifyPoint) {
+        const currentPoint = item.curPoint;
+        let prePoint = dataService.getPoint(item.curPoint.id);
+        historyUtil.assignPointFromPoint(prePoint, currentPoint);
+      }
+    }
+  }
+
+  goNextForWalls(itemForWalls) {
+    for (let i = 0; i < itemForWalls.length; ++i) {
+      const item = itemForWalls[i];
+      if (item.handle == HistoryEvents.AddRoad) {
+        const preWall = item.wall;
+        let newWall = wallService.createWall(
+          preWall.start,
+          preWall.end,
+          preWall.id
+        );
+        historyUtil.assignWallFromWall(newWall, preWall);
+      } else if (item.handle == HistoryEvents.DeleteWall) {
+        dataService.deleteWallNoSymbol(item.wall.id);
+      } else if (item.handle == HistoryEvents.ModifyWall) {
+        const currentWall = item.curWall;
+        let preWall = dataService.getWall(item.preWall.id);
+        historyUtil.assignWallFromWall(preWall, currentWall);
+      }
+    }
+  }
+
+  goNextForSymbols(itemForSymbols) {
+    for (let i = 0; i < itemForSymbols.length; ++i) {
+      const item = itemForSymbols[i];
+      if (item.handle == HistoryEvents.AddSymbol) {
+        let newSymbol = symbolService.createSymbol(
+          item.symbol.start,
+          item.symbol.end,
+          item.symbol.type,
+          item.symbol.parent,
+          item.symbol.id
+        );
+        historyUtil.assignSymbolFromSymbol(newSymbol, item.symbol);
+      } else if (item.handle == HistoryEvents.DeleteSymbol) {
+        symbolService.deleteSymbol(item.symbol.id);
+      } else if (item.handle == HistoryEvents.ModifySymbol) {
+        const currentSymbol = item.curSymbol;
+        let preSymbol = dataService.getSymbol(item.curSymbol.id);
+        historyUtil.assignSymbolFromSymbol(preSymbol, currentSymbol);
+      }
+    }
+  }
+
+  goNextForComponents(itemForComponents) {
+    for (let i = 0; i < itemForComponents.length; ++i) {
+      const item = itemForComponents[i];
+      if (item.handle == HistoryEvents.AddComponent) {
+        let vComponent = componentService.createComponent(
+          item.component.center,
+          item.component.type,
+          item.component.id
+        );
+        historyUtil.assignComponentFromComponent(vComponent, item.component);
+      } else if (item.handle == HistoryEvents.DeleteComponent) {
+        dataService.deleteComponent(item.component.id);
+      } else if (item.handle == HistoryEvents.ModifyComponent) {
+        const currentComponent = item.curComponent;
+        let preComponent = dataService.getComponent(item.curComponent.id);
+        historyUtil.assignComponentFromComponent(
+          preComponent,
+          currentComponent
+        );
+      }
+    }
+  }
+
+  goNextForTags(itemForTags) {
+    for (let i = 0; i < itemForTags.length; ++i) {
+      const item = itemForTags[i];
+      if (item.handle == HistoryEvents.AddTag) {
+        let vTag = tagService.createTag(item.tag.center, item.tag.id);
+        historyUtil.assignTagFromTag(vTag, item.tag);
+      } else if (item.handle == HistoryEvents.DeleteTag) {
+        dataService.deleteTag(item.tag.id);
+      } else if (item.handle == HistoryEvents.ModifyTag) {
+        const currentTag = item.curTag;
+        let preTag = dataService.getTag(item.curTag.id);
+        historyUtil.assignTagFromTag(preTag, currentTag);
+      }
+    }
+  }
+
+  goNextForFurnitures(itemForFurnitures) {
+    for (let i = 0; i < itemForFurnitures.length; ++i) {
+      const item = itemForFurnitures[i];
+      if (item.handle == HistoryEvents.AddFurniture) {
+        let vFurniture = furnitureService.createFurniture(
+          item.furniture.center,
+          item.furniture.type,
+          item.furniture.id
+        );
+        historyUtil.assignFurnitureFromFurniture(vFurniture, item.furniture);
+      } else if (item.handle == HistoryEvents.DeleteFurniture) {
+        dataService.deleteFurniture(item.furniture.id);
+      } else if (item.handle == HistoryEvents.ModifyFurniture) {
+        const currentFurniture = item.curFurniture;
+        let preFurniture = dataService.getFurniture(item.curFurniture.id);
+        historyUtil.assignFurnitureFromFurniture(
+          preFurniture,
+          currentFurniture
+        );
+      }
+    }
+  }
+
+  goNextForAngle(itemForAngle) {
+    if (itemForAngle.handle == HistoryEvents.ModifyAngle) {
+      coordinate.reSet();
+      coordinate._setRes(itemForAngle.curState.res);
+      //旋转cad
+      dataService.setAngle(itemForAngle.curState.angle);
+      coordinate.updateForRotate(
+        itemForAngle.curState.angle - itemForAngle.preState.angle
+      );
+      //旋转三维模型
+      let info = coordinate.getScreenInfoForCAD();
+      info.floorPlanAngle = itemForAngle.curState.angle;
+      this.layer.app.core
+        .get("CameraControls")
+        .emit("syncCadAnd3DForRotate", info);
+      this.layer.app.store.getValue("metadata").floorPlanAngle =
+        itemForAngle.curState.angle;
+      this.layer.initPanos(dataService.getCurrentFloor());
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // 恢复
+  goNextState() {
+    historyService.redoHistoryRecord();
+    const item = historyService.getHistoryRecord();
+    if (item) {
+      stateService.clearFocusItem();
+      stateService.clearEventName();
+      //this.layer.$xui.hideProps()
+      this.layer.uiControl.currentUI = null;
+      let flag = false;
+      if (item.rotate == null) {
+        flag = false;
+      } else {
+        flag = this.goNextForAngle(item.rotate);
+      }
+      if (!flag) {
+        this.goNextForPoints(item.points);
+        this.goNextForWalls(item.walls);
+        this.goNextForSymbols(item.symbols);
+        this.goNextForComponents(item.components);
+        this.goNextForTags(item.tags);
+        this.goNextForFurnitures(item.furnitures);
+      }
+      measureService.update();
+      change.saveCurrentInfo();
+      this.setState();
+
+      const points = dataService.getPoints();
+      if (Object.keys(points).length > 0) {
+        this.layer.$xui.toolbar.clear = true;
+        this.layer.$xui.toolbar.download = true;
+      } else {
+        this.layer.$xui.toolbar.clear = false;
+        this.layer.$xui.toolbar.download = false;
+      }
+    } else {
+      historyService.undoHistoryRecord();
+      console.error("goNextState超出范围!");
+    }
+  }
+}

+ 223 - 0
src/graphic/History/HistoryUtil.js

@@ -0,0 +1,223 @@
+import { mathUtil } from "../MathUtil";
+import { componentService } from "../Service/ComponentService";
+import { dataService } from "../Service/DataService";
+import { furnitureService } from "../Service/FurnitureService";
+import { symbolService } from "../Service/SymbolService";
+import { tagService } from "../Service/TagService";
+import { wallService } from "../Service/WallService";
+
+export default class HistoryUtil {
+  constructor() {}
+
+  isDifferentForWalls(wall1, wall2) {
+    if (
+      wall1.start == wall2.start &&
+      wall1.end == wall2.end &&
+      wall1.out == wall2.out &&
+      wall1.important == wall2.important
+    ) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  isDifferentForSymbols(symbol1, symbol2) {
+    if (
+      mathUtil.equalPoint(symbol1.startPoint, symbol2.startPoint) &&
+      mathUtil.equalPoint(symbol1.endPoint, symbol2.endPoint) &&
+      symbol1.openSide == symbol2.openSide &&
+      symbol1.parent == symbol2.parent &&
+      symbol1.enter == symbol2.enter
+    ) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  isDifferentForComponents(component1, component2) {
+    if (
+      JSON.stringify(component1.points2d) ==
+        JSON.stringify(component2.points) &&
+      component1.angle == component2.angle
+    ) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  isDifferentForTags(tag1, tag2) {
+    if (
+      mathUtil.equalPoint(tag1.center, tag2.center) &&
+      tag1.title == tag2.title &&
+      tag1.des == tag2.des &&
+      tag1.unit == tag2.unit
+    ) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+  isDifferentForFurnitures(furniture1, furniture2) {
+    if (
+      furniture1.zoom == furniture2.zoom &&
+      JSON.stringify(furniture1.center) == JSON.stringify(furniture2.center) &&
+      furniture1.angle == furniture2.angle
+    ) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  isDifferentForAngle(angle1, angle2) {
+    if (angle1 == angle2) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  // wall2赋值给wall1
+  assignWallFromWall(wall1, wall2) {
+    const wallInfo = {};
+    wallInfo.vectorId = wall1.vectorId;
+    wallInfo.children = JSON.parse(JSON.stringify(wall2.children));
+    wallInfo.out = wall2.out;
+    wallInfo.important = wall2.important;
+    wallInfo.start = wall2.start;
+    wallInfo.end = wall2.end;
+    wallService.setWallInfo(wallInfo);
+  }
+  assignPointFromPoint(point1, point2) {
+    const pointInfo = {};
+    pointInfo.vectorId = point1.vectorId;
+    pointInfo.position = { x: point2.x, y: point2.y };
+    pointInfo.parent = JSON.parse(JSON.stringify(point2.parent));
+    wallService.setPointInfo(pointInfo);
+  }
+  assignSymbolFromSymbol(symbol1, symbol2) {
+    const symbolInfo = {};
+    symbolInfo.vectorId = symbol1.vectorId;
+    symbolInfo.openSide = symbol2.openSide;
+    symbolInfo.start = JSON.parse(JSON.stringify(symbol2.start));
+    symbolInfo.end = JSON.parse(JSON.stringify(symbol2.end));
+    symbolInfo.points2d = JSON.parse(JSON.stringify(symbol2.points));
+    if (symbol2.position3d) {
+      symbolInfo.position3d = JSON.parse(JSON.stringify(symbol2.position3d));
+    }
+    symbolInfo.enter = symbol2.enter;
+    symbolInfo.parent = symbol2.parent;
+    symbolService.setSymbolInfo(symbolInfo);
+  }
+  assignComponentFromComponent(component1, component2) {
+    const componentInfo = {};
+    componentInfo.vectorId = component1.vectorId;
+    componentInfo.angle = component2.angle;
+    componentInfo.center = JSON.parse(JSON.stringify(component2.center));
+    componentInfo.points2d = JSON.parse(JSON.stringify(component2.points));
+    componentService.setComponentInfo(componentInfo);
+  }
+  assignTagFromTag(tag1, tag2) {
+    const tagInfo = {};
+    tagInfo.vectorId = tag1.vectorId;
+    tagInfo.title = tag2.title;
+    tagInfo.des = tag2.des;
+    tagInfo.unit = tag2.unit;
+    tagInfo.center = JSON.parse(JSON.stringify(tag2.center));
+    tagInfo.points2d = JSON.parse(JSON.stringify(tag2.points));
+    tagInfo.adding = false;
+    tagService.setTagInfo(tagInfo);
+  }
+  assignFurnitureFromFurniture(furniture1, furniture2) {
+    const furnitureInfo = {};
+    furnitureInfo.vectorId = furniture1.vectorId;
+    furnitureInfo.angle = furniture2.angle;
+    furnitureInfo.center = JSON.parse(JSON.stringify(furniture2.center));
+    furnitureInfo.zoom = furniture2.zoom;
+    furnitureService.setFurnitureInfo(furnitureInfo);
+  }
+  deletePoint(pointId) {
+    const point = dataService.getPoint(pointId);
+    const parent = point.parent;
+    for (const key in parent) {
+      dataService.deletePoint(pointId, key);
+    }
+  }
+  getDataForPoint(point) {
+    const data = {};
+    data.id = point.vectorId;
+    mathUtil.clonePoint(data, point);
+    data.parent = JSON.parse(JSON.stringify(point.parent));
+    data.type = point.geoType;
+    return data;
+  }
+  getDataForWall(wall) {
+    const data = {};
+    data.id = wall.vectorId;
+    data.start = wall.start;
+    data.end = wall.end;
+    data.out = wall.out;
+    data.important = wall.important;
+    data.children = JSON.parse(JSON.stringify(wall.children));
+    data.type = wall.geoType;
+    return data;
+  }
+
+  getDataForSymbol(symbol) {
+    const data = {};
+    data.id = symbol.vectorId;
+    data.start = JSON.parse(JSON.stringify(symbol.startPoint));
+    data.end = JSON.parse(JSON.stringify(symbol.endPoint));
+    data.openSide = symbol.openSide;
+    data.enter = symbol.enter;
+    data.points = JSON.parse(JSON.stringify(symbol.points2d));
+    data.position3d = JSON.parse(JSON.stringify(symbol.position3d));
+    data.parent = symbol.parent;
+    data.type = symbol.geoType;
+    return data;
+  }
+
+  getDataForComponent(component) {
+    const data = {};
+    data.id = component.vectorId;
+    data.type = component.geoType;
+    data.center = JSON.parse(JSON.stringify(component.center));
+    data.angle = component.angle;
+    data.points = [].concat(component.points2d);
+    return data;
+  }
+
+  getDataForFurniture(furniture) {
+    const data = {};
+    data.id = furniture.vectorId;
+    data.type = furniture.geoType;
+    data.center = JSON.parse(JSON.stringify(furniture.center));
+    data.zoom = furniture.zoom;
+    data.angle = furniture.angle;
+    return data;
+  }
+
+  getDataForTag(tag) {
+    const data = {};
+    data.id = tag.vectorId;
+    data.type = tag.geoType;
+    data.center = {};
+    mathUtil.clonePoint(data.center, tag.center);
+    data.points = [].concat(tag.points2d);
+    data.title = tag.title;
+    data.des = tag.des;
+    data.unit = tag.unit;
+    return data;
+  }
+
+  getDataForAngle(angle) {
+    return angle;
+  }
+
+  getDataForRes(res) {
+    return res;
+  }
+}
+
+const historyUtil = new HistoryUtil();
+export { historyUtil };

File diff suppressed because it is too large
+ 1045 - 0
src/graphic/Layer.js


+ 623 - 0
src/graphic/ListenLayer.js

@@ -0,0 +1,623 @@
+import { mathUtil } from "./MathUtil.js";
+import { dataService } from "./Service/DataService.js";
+import { stateService } from "./Service/StateService.js";
+import { wallService } from "./Service/WallService.js";
+import Constant from "./Constant.js";
+import VectorType from "./enum/VectorType.js";
+import SelectState from "./enum/SelectState.js";
+
+export default class ListenLayer {
+  constructor() {
+    this.wallInfo = {
+      wallId: null,
+      state: null, // 未选中null
+    };
+
+    this.pointInfo = {
+      pointId: null,
+      state: null,
+    };
+
+    this.symbolInfo = {
+      symbolId: null,
+      state: null, // start,end,all表示哪个部位。与前面的wallInfo和pointInfo不同,上面两个变量一直都有值,因为他们的意义是找到最近的,而symbolInfo是判断是否选中
+    };
+
+    this.componentInfo = {
+      componentId: null,
+      state: null,
+    };
+
+    this.tagInfo = {
+      tagId: null,
+      state: null,
+    };
+
+    this.furnitureInfo = {
+      furnitureId: null,
+      state: null,
+    };
+
+    this.modifyPoint = null;
+  }
+
+  //开始监听,exceptPointId表示不考虑的点,exceptWallIds表示不考虑的墙
+  start(position, exceptPointId, exceptWallIds) {
+    let nearest = this.getNearForVectors(
+      position,
+      exceptPointId,
+      exceptWallIds
+    );
+    /*
+        // getNearForWalls在一定的条件下必须执行两次!
+        // 如果吸附在墙面上,或者吸附在其余墙的顶点(x/y坐标),这时候因为抖动,可能会变成完全吸附在墙角,这时候是要再执行一次getNearForWalls
+        if (
+            nearest.modifyPoint &&
+            (nearest.modifyPoint.hasOwnProperty("linkedPointIdX") || 
+            nearest.modifyPoint.hasOwnProperty("linkedPointIdY") || 
+            nearest.modifyPoint.hasOwnProperty("linkedWallId"))
+        ) {
+            mathUtil.clonePoint(position, nearest.modifyPoint);
+            nearest = this.getNearForVectors(position, exceptPointId, exceptWallIds);
+        }
+        */
+
+    if (
+      nearest.modifyPoint &&
+      (nearest.modifyPoint.hasOwnProperty("linkedPointId") ||
+        nearest.modifyPoint.hasOwnProperty("linkedPointIdX") ||
+        nearest.modifyPoint.hasOwnProperty("linkedPointIdY") ||
+        nearest.modifyPoint.hasOwnProperty("linkedWallId"))
+    ) {
+      this.modifyPoint = {
+        x: nearest.modifyPoint.x,
+        y: nearest.modifyPoint.y,
+      };
+      if (
+        nearest.modifyPoint.hasOwnProperty("linkedPointId") &&
+        nearest.modifyPoint.linkedPointId != null
+      ) {
+        this.modifyPoint.linkedPointId = nearest.modifyPoint.linkedPointId;
+      } else if (
+        nearest.modifyPoint.hasOwnProperty("linkedWallId") &&
+        nearest.modifyPoint.linkedWallId != null
+      ) {
+        this.modifyPoint.linkedWallId = nearest.modifyPoint.linkedWallId;
+      } else {
+        if (
+          nearest.modifyPoint.hasOwnProperty("linkedPointIdX") &&
+          nearest.modifyPoint.linkedPointIdX != null
+        ) {
+          this.modifyPoint.linkedPointIdX = nearest.modifyPoint.linkedPointIdX;
+        }
+        if (
+          nearest.modifyPoint.hasOwnProperty("linkedPointIdY") &&
+          nearest.modifyPoint.linkedPointIdY != null
+        ) {
+          this.modifyPoint.linkedPointIdY = nearest.modifyPoint.linkedPointIdY;
+        }
+      }
+    } else {
+      this.modifyPoint = null;
+    }
+
+    const flag = this.updateSelectInfos(nearest, Constant.minAdsorb);
+    this.updateSelectItem();
+    return flag;
+  }
+
+  // 获得最近的墙面和墙角
+  // 同时获得吸附的相关信息
+  // 找到选中的symbol(只有选中了,才算是最近的)
+  getNearForVectors(position, exceptPointId, exceptWallIds) {
+    let min1 = null; // 与墙角的距离
+    let min2 = null; // 与墙面的距离
+    // 纠正
+    // 垂直,水平
+    const modifyPoint = {};
+    // 吸附在墙面上
+    let _modifyPoint = null;
+    const hasPointIds = [];
+    if (exceptPointId) {
+      hasPointIds.push(exceptPointId);
+    }
+
+    const walls = dataService.getWalls();
+    for (const wallId in walls) {
+      if (exceptWallIds && exceptWallIds.hasOwnProperty(wallId)) {
+        continue;
+      }
+
+      const wall = dataService.getWall(wallId);
+
+      const startPoint = dataService.getPoint(wall.start);
+      const endPoint = dataService.getPoint(wall.end);
+      let distance = null;
+      const line = wallService.getLine(wall);
+      if (!line) {
+        //debugger
+        //删除墙
+        dataService.deleteWall(wallId);
+        continue;
+        console.error("getNearForVectors************************************");
+      }
+      const join = mathUtil.getJoinLinePoint(position, line);
+
+      if (hasPointIds.indexOf(wall.start) == -1) {
+        hasPointIds.push(wall.start);
+        distance = mathUtil.getDistance(position, startPoint);
+
+        if (min1 == null || min1.distance > distance) {
+          min1 = {
+            distance: distance,
+            pointId: wall.start,
+          };
+
+          //start部分找到了墙的端点
+          if (
+            (mathUtil.getDistance(join, position) < Constant.minAdsorb &&
+              mathUtil.getDistance(join, startPoint) < Constant.minAdsorb) ||
+            min1.distance < Constant.minAdsorb
+          ) {
+            modifyPoint.linkedPointId = wall.start;
+            modifyPoint.x = startPoint.x;
+            modifyPoint.y = startPoint.y;
+
+            delete modifyPoint.linkedPointIdX;
+            delete modifyPoint.linkedPointIdY;
+            break;
+          }
+        }
+        //start部分找到了与x接近的其他点
+        if (Math.abs(position.x - startPoint.x) < Constant.minAdsorb) {
+          if (!modifyPoint.linkedPointIdX) {
+            modifyPoint.x = startPoint.x;
+            modifyPoint.linkedPointIdX = wall.start;
+          } else {
+            const linkedPointX = dataService.getPoint(
+              modifyPoint.linkedPointIdX
+            );
+            if (
+              mathUtil.getDistance(position, linkedPointX) >
+              mathUtil.getDistance(position, startPoint)
+            ) {
+              modifyPoint.x = startPoint.x;
+              modifyPoint.linkedPointIdX = wall.start;
+            }
+          }
+        }
+        //start部分找到了与y接近的其他点
+        if (Math.abs(position.y - startPoint.y) < Constant.minAdsorb) {
+          if (!modifyPoint.linkedPointIdY) {
+            modifyPoint.y = startPoint.y;
+            modifyPoint.linkedPointIdY = wall.start;
+          } else {
+            const linkedPointY = dataService.getPoint(
+              modifyPoint.linkedPointIdY
+            );
+            if (
+              mathUtil.getDistance(position, linkedPointY) >
+              mathUtil.getDistance(position, startPoint)
+            ) {
+              modifyPoint.y = startPoint.y;
+              modifyPoint.linkedPointIdY = wall.start;
+            }
+          }
+        }
+      }
+
+      if (hasPointIds.indexOf(wall.end) == -1) {
+        hasPointIds.push(wall.end);
+        distance = mathUtil.getDistance(position, endPoint);
+
+        if (min1 == null || min1.distance > distance) {
+          min1 = {
+            distance: distance,
+            pointId: wall.end,
+          };
+          //end部分找到了墙的端点
+          if (
+            (mathUtil.getDistance(join, position) < Constant.minAdsorb &&
+              mathUtil.getDistance(join, endPoint) < Constant.minAdsorb) ||
+            min1.distance < Constant.minAdsorb
+          ) {
+            modifyPoint.linkedPointId = wall.end;
+            modifyPoint.x = endPoint.x;
+            modifyPoint.y = endPoint.y;
+            delete modifyPoint.linkedPointIdX;
+            delete modifyPoint.linkedPointIdY;
+            break;
+          }
+        }
+        //end部分找到了与x接近的其他点
+        if (Math.abs(position.x - endPoint.x) < Constant.minAdsorb) {
+          if (!modifyPoint.linkedPointIdX) {
+            modifyPoint.x = endPoint.x;
+            modifyPoint.linkedPointIdX = wall.end;
+          } else {
+            const linkedPointX = dataService.getPoint(
+              modifyPoint.linkedPointIdX
+            );
+            if (
+              mathUtil.getDistance(position, linkedPointX) >
+              mathUtil.getDistance(position, endPoint)
+            ) {
+              modifyPoint.x = endPoint.x;
+              modifyPoint.linkedPointIdX = wall.end;
+            }
+          }
+        }
+        //end部分找到了与y接近的其他点
+        if (Math.abs(position.y - endPoint.y) < Constant.minAdsorb) {
+          if (!modifyPoint.linkedPointIdY) {
+            modifyPoint.y = endPoint.y;
+            modifyPoint.linkedPointIdY = wall.end;
+          } else {
+            const linkedPointY = dataService.getPoint(
+              modifyPoint.linkedPointIdY
+            );
+            if (
+              mathUtil.getDistance(position, linkedPointY) >
+              mathUtil.getDistance(position, endPoint)
+            ) {
+              modifyPoint.y = endPoint.y;
+              modifyPoint.linkedPointIdY = wall.end;
+            }
+          }
+        }
+      }
+
+      distance = mathUtil.getDistance(position, join);
+      //是否在墙上,可能在墙外
+      const _flag = wallService.isContain(wall, join);
+
+      if (_flag && (min2 == null || min2.distance > distance)) {
+        min2 = {
+          distance: distance,
+          wallId: wallId,
+        };
+      }
+
+      if (_flag && mathUtil.getDistance(position, join) < Constant.minAdsorb) {
+        _modifyPoint = join;
+        _modifyPoint.linkedWallId = wallId;
+      }
+    }
+    const result = {
+      minPoint: min1,
+      minWall: min2,
+      symbolInfo: {},
+      componentInfo: {},
+      tagInfo: {},
+      furnitureInfo: {},
+    };
+
+    if (_modifyPoint != null) {
+      result.modifyPoint = JSON.parse(JSON.stringify(_modifyPoint));
+    } else if (
+      modifyPoint.hasOwnProperty("x") &&
+      modifyPoint.hasOwnProperty("y")
+    ) {
+      result.modifyPoint = JSON.parse(JSON.stringify(modifyPoint));
+    } else if (modifyPoint.hasOwnProperty("x")) {
+      result.modifyPoint = JSON.parse(JSON.stringify(modifyPoint));
+      result.modifyPoint.x = modifyPoint.x;
+      result.modifyPoint.y = position.y;
+    } else if (modifyPoint.hasOwnProperty("y")) {
+      result.modifyPoint = JSON.parse(JSON.stringify(modifyPoint));
+      result.modifyPoint.x = position.x;
+      result.modifyPoint.y = modifyPoint.y;
+    }
+
+    //是否在门/窗 上
+    const symbols = dataService.getSymbols();
+    for (const symbolId in symbols) {
+      const symbol = dataService.getSymbol(symbolId);
+      const location = symbol.isContain(position);
+      if (location != null) {
+        result.symbolInfo = {
+          symbolId: symbolId,
+          state: location,
+        };
+        break;
+      }
+    }
+
+    const components = dataService.getComponents();
+    for (const componentId in components) {
+      const component = dataService.getComponent(componentId);
+      const location = component.isContain(position);
+      if (location) {
+        result.componentInfo = {
+          componentId: componentId,
+          state: "all",
+        };
+        break;
+      }
+    }
+
+    const tags = dataService.getTags();
+    for (const tagId in tags) {
+      const tag = dataService.getTag(tagId);
+      const location = tag.isContain(position);
+      if (location) {
+        result.tagInfo = {
+          tagId: tagId,
+          state: "all",
+        };
+        break;
+      }
+    }
+
+    const furnitures = dataService.getFurnitures();
+    for (const furnitureId in furnitures) {
+      const furniture = dataService.getFurniture(furnitureId);
+      const location = furniture.isContain(position);
+      if (location) {
+        result.furnitureInfo = {
+          furnitureId: furnitureId,
+          state: "all",
+        };
+        break;
+      }
+    }
+
+    return result;
+  }
+
+  // position用来判断是否在墙的symbol上
+  updateSelectInfos(nearest, minDistance) {
+    // 墙角状态是否改变
+    let flag1 = false;
+    if (nearest.minPoint != null) {
+      if (nearest.minPoint.distance < minDistance) {
+        flag1 = this.isChanged(nearest.minPoint.pointId, SelectState.Select, 1);
+        this.pointInfo = {
+          pointId: nearest.minPoint.pointId,
+          state: SelectState.Select,
+        };
+      } else {
+        flag1 = this.isChanged(nearest.minPoint.pointId, null, 1);
+        this.pointInfo = {
+          pointId: nearest.minPoint.pointId,
+          state: null,
+        };
+      }
+    } else {
+      flag1 = this.isChanged(null, null, 1);
+      this.pointInfo = {
+        pointId: null,
+        state: null,
+      };
+    }
+    // 墙面状态是否改变
+    let flag2 = false;
+    if (nearest.minWall != null) {
+      if (nearest.minWall.distance < minDistance) {
+        flag2 = this.isChanged(nearest.minWall.wallId, SelectState.Select, 2);
+        this.wallInfo = {
+          wallId: nearest.minWall.wallId,
+          state: SelectState.Select,
+        };
+      } else {
+        flag2 = this.isChanged(nearest.minWall.wallId, null, 2);
+        this.wallInfo = {
+          wallId: nearest.minWall.wallId,
+          state: null,
+        };
+      }
+    } else {
+      flag2 = this.isChanged(null, null, 2);
+      this.wallInfo = {
+        wallId: null,
+        state: null,
+      };
+    }
+    // symbols状态是否改变
+    const flag3 = this.isChanged(
+      nearest.symbolInfo.symbolId,
+      nearest.symbolInfo.state,
+      3
+    );
+    this.symbolInfo = {
+      symbolId: nearest.symbolInfo.symbolId,
+      state: nearest.symbolInfo.state,
+    };
+
+    const flag4 = this.isChanged(
+      nearest.componentInfo.componentId,
+      nearest.componentInfo.state,
+      4
+    );
+    this.componentInfo = {
+      componentId: nearest.componentInfo.componentId,
+      state: nearest.componentInfo.state,
+    };
+
+    const flag5 = this.isChanged(
+      nearest.tagInfo.tagId,
+      nearest.tagInfo.state,
+      5
+    );
+    this.tagInfo = {
+      tagId: nearest.tagInfo.tagId,
+      state: nearest.tagInfo.state,
+    };
+
+    const flag6 = this.isChanged(
+      nearest.furnitureInfo.furnitureId,
+      nearest.furnitureInfo.state,
+      6
+    );
+    this.furnitureInfo = {
+      furnitureId: nearest.furnitureInfo.furnitureId,
+      state: nearest.furnitureInfo.state,
+    };
+
+    return flag1 || flag2 || flag3 || flag4 || flag5 || flag6;
+  }
+
+  // type是1表示点,2表示墙,3表示symbol,4表示component, 5表示tag,6表示furniture
+  isChanged(vectorId, state, type) {
+    let flag = false;
+    if (type == 1) {
+      if (state == null && state == this.pointInfo.state) {
+        flag = false;
+      } else if (
+        this.pointInfo.pointId == vectorId &&
+        state == this.pointInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    } else if (type == 2) {
+      if (state == null && state == this.wallInfo.state) {
+        flag = false;
+      } else if (
+        this.wallInfo.wallId == vectorId &&
+        state == this.wallInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    } else if (type == 3) {
+      if (state == null && state == this.symbolInfo.state) {
+        flag = false;
+      } else if (
+        this.symbolInfo.symbolId == vectorId &&
+        state == this.symbolInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    } else if (type == 4) {
+      if (state == null && state == this.componentInfo.state) {
+        flag = false;
+      } else if (
+        this.componentInfo.componentId == vectorId &&
+        state == this.componentInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    } else if (type == 5) {
+      if (state == null && state == this.tagInfo.state) {
+        flag = false;
+      } else if (
+        this.tagInfo.tagId == vectorId &&
+        state == this.tagInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    } else if (type == 6) {
+      if (state == null && state == this.furnitureInfo.state) {
+        flag = false;
+      } else if (
+        this.furnitureInfo.furnitureId == vectorId &&
+        state == this.furnitureInfo.state
+      ) {
+        flag = false;
+      } else {
+        flag = true;
+      }
+    }
+
+    return flag;
+  }
+
+  updateSelectItem() {
+    if (this.tagInfo.tagId != null && this.tagInfo.state != null) {
+      const tag = dataService.getTag(this.tagInfo.tagId);
+      stateService.setSelectItem(
+        this.tagInfo.tagId,
+        tag.geoType,
+        this.tagInfo.state
+      );
+    } else if (
+      this.componentInfo.componentId != null &&
+      this.componentInfo.state != null
+    ) {
+      const component = dataService.getComponent(
+        this.componentInfo.componentId
+      );
+      stateService.setSelectItem(
+        this.componentInfo.componentId,
+        component.geoType,
+        this.componentInfo.state
+      );
+    } else if (
+      this.furnitureInfo.furnitureId != null &&
+      this.furnitureInfo.state != null
+    ) {
+      const furniture = dataService.getFurniture(
+        this.furnitureInfo.furnitureId
+      );
+      stateService.setSelectItem(
+        this.furnitureInfo.furnitureId,
+        furniture.geoType,
+        this.furnitureInfo.state
+      );
+    } else if (
+      this.symbolInfo.symbolId != null &&
+      this.symbolInfo.state != null
+    ) {
+      const symbol = dataService.getSymbol(this.symbolInfo.symbolId);
+      stateService.setSelectItem(
+        this.symbolInfo.symbolId,
+        symbol.geoType,
+        this.symbolInfo.state
+      );
+    } else if (this.pointInfo.pointId != null && this.pointInfo.state != null) {
+      stateService.setSelectItem(
+        this.pointInfo.pointId,
+        VectorType.WallCorner,
+        SelectState.Select
+      );
+    } else if (this.wallInfo.wallId != null && this.wallInfo.state != null) {
+      stateService.setSelectItem(
+        this.wallInfo.wallId,
+        VectorType.Wall,
+        SelectState.Select
+      );
+    } else {
+      stateService.clearSelectItem();
+    }
+  }
+
+  clear() {
+    this.wallInfo = {
+      wallId: null,
+      state: null,
+    };
+
+    this.pointInfo = {
+      pointId: null,
+      state: null,
+    };
+
+    this.symbolInfo = {
+      symbolId: null,
+      state: null,
+    };
+
+    this.componentInfo = {
+      componentId: null,
+      state: null,
+    };
+
+    this.furnitureInfo = {
+      furnitureId: null,
+      state: null,
+    };
+
+    this.modifyPoint = null;
+  }
+}
+const listenLayer = new ListenLayer();
+export { listenLayer };

+ 411 - 0
src/graphic/Load.js

@@ -0,0 +1,411 @@
+import { floorplanData } from "./VectorData";
+import { dataService } from "./Service/DataService.js";
+import { wallService } from "./Service/WallService.js";
+import { stateService } from "./Service/StateService.js";
+import { symbolService } from "./Service/SymbolService.js";
+import { componentService } from "./Service/ComponentService.js";
+import { coordinate } from "./Coordinate.js";
+import Constant from "./Constant";
+import { tagService } from "./Service/TagService";
+import { mathUtil } from "./MathUtil";
+import { measureService } from "./Service/MeasureService";
+import { cameraService } from "./Service/CameraService";
+import { furnitureService } from "./Service/FurnitureService";
+
+export default class Load {
+  constructor(layer) {
+    this.layer = layer;
+    this.version = "v1.0";
+    this.vectorsJson = null;
+    this.newVectorId = null;
+  }
+
+  load(floors) {
+    for (let i = 0; i < floors.length; ++i) {
+      let floor = floors[i];
+      let subgroup = i;
+      if (floor.hasOwnProperty("subgroup")) {
+        subgroup = floor.subgroup;
+      }
+      dataService.initFloor(subgroup);
+      dataService.setCurrentFloor(subgroup);
+      for (let key in floor.points) {
+        wallService.createPoint(
+          floor.points[key].x,
+          floor.points[key].y,
+          floor.points[key].vectorId,
+          subgroup
+        );
+      }
+
+      for (let key in floor.walls) {
+        let wall = wallService.createWall(
+          floor.walls[key].start,
+          floor.walls[key].end,
+          floor.walls[key].vectorId,
+          subgroup
+        );
+        wall.setImportant(floor.walls[key].important);
+        wall.setOut(floor.walls[key].out);
+        wall.setChildren(floor.walls[key].children);
+      }
+
+      dataService.setFloorHeight(
+        dataService.currentFloor,
+        this.layer.player.model.floors.index[dataService.currentFloor].center.y
+      );
+      for (let key in floor.symbols) {
+        let symbol = symbolService.createSymbol(
+          floor.symbols[key].startPoint,
+          floor.symbols[key].endPoint,
+          floor.symbols[key].geoType,
+          floor.symbols[key].parent,
+          floor.symbols[key].vectorId
+        );
+        symbol.openSide = floor.symbols[key].openSide;
+        symbol.enter = floor.symbols[key].enter;
+        symbol.points2d = JSON.parse(
+          JSON.stringify(floor.symbols[key].points2d)
+        );
+      }
+
+      for (let key in floor.components) {
+        let component = componentService.createComponent(
+          floor.components[key].center,
+          floor.components[key].geoType,
+          floor.components[key].vectorId
+        );
+        component.angle = floor.components[key].angle;
+        //component.setPoints2d()
+        component.sideThickness = floor.components[key].sideThickness;
+        component.sideWidth = floor.components[key].sideWidth;
+        component.points2d = JSON.parse(
+          JSON.stringify(floor.components[key].points2d)
+        );
+      }
+
+      if (floor.boundingBox) {
+        dataService.setBoundingBox(floor.boundingBox, subgroup);
+      } else {
+        floor.boundingBox = mathUtil.getBoundingBox2(floor.points);
+        dataService.setBoundingBox(floor.boundingBox, subgroup);
+      }
+
+      for (let key in floor.tags) {
+        let tag = tagService.createTag(
+          floor.tags[key].center,
+          floor.tags[key].vectorId,
+          subgroup
+        );
+        tag.setPoints2d();
+        tag.setTitle(floor.tags[key].title);
+        tag.setDes(floor.tags[key].des);
+        tag.setUnit(floor.tags[key].unit);
+        tag.setAdding(false);
+      }
+
+      for (let key in floor.furnitures) {
+        let furniture = furnitureService.createFurniture(
+          floor.furnitures[key].center,
+          floor.furnitures[key].geoType,
+          floor.furnitures[key].vectorId
+        );
+        furniture.angle = floor.furnitures[key].angle;
+        if (floor.furnitures[key].scale) {
+          furniture.zoom = floor.furnitures[key].scale;
+        } else {
+          furniture.zoom = floor.furnitures[key].zoom;
+        }
+      }
+
+      if (floor.hasOwnProperty("name")) {
+        dataService.setFloorName(floor.name, subgroup);
+      }
+
+      if (floor.hasOwnProperty("subgroup")) {
+        dataService.setFloorSubgroup(floor.subgroup, subgroup);
+      }
+
+      if (floor.hasOwnProperty("id")) {
+        dataService.setFloorId(floor.id, subgroup);
+      }
+    }
+  }
+
+  uploadCad() {
+    this.initCameraService();
+    this.uploadData.cadInfo = [];
+    return this.exportCadImgs();
+    // 开始上传:this.uploadData
+    //this.uploadData = {}
+  }
+
+  initCameraService() {
+    const modelSize = this.layer.app.core.get("Player").model.size;
+    const currentScale = cameraService.getCurrentScale(modelSize);
+    cameraService.setCamera(currentScale);
+  }
+
+  base64ToBlob(base64) {
+    let arr = base64.split(","),
+      mime = arr[0].match(/:(.*?);/)[1],
+      bstr = atob(arr[1]),
+      n = bstr.length,
+      u8arr = new Uint8Array(n);
+    while (n--) {
+      u8arr[n] = bstr.charCodeAt(n);
+    }
+    return new Blob([u8arr], { type: mime });
+  }
+
+  exportImg(canvas) {
+    // 图片导出为 png 格式
+    var type = "png";
+    var imgData = canvas.toDataURL(type, 3);
+    let blobImg = this.base64ToBlob(imgData);
+    return blobImg;
+  }
+
+  exportCadImgs() {
+    this.layer.uiControl.menu_flex();
+    tagService.clearDefaultTags();
+    this.layer.history.save();
+    stateService.clearItems();
+
+    measureService.updateRegion(true);
+    measureService.update();
+
+    let canvas = this.layer.canvas;
+
+    // coordinate.ratio = Constant.ratio
+    // canvas.width = canvas.width * coordinate.ratio
+    // canvas.height = canvas.height * coordinate.ratio
+
+    canvas.width = Constant.cadImg_Width;
+    canvas.height = Constant.cadImg_Height;
+    coordinate.width = Constant.cadImg_Width / Constant.ratio;
+    coordinate.height = Constant.cadImg_Height / Constant.ratio;
+
+    coordinate.ratio = Constant.ratio;
+    coordinate.res = Math.min(
+      Constant.cadImg_Width /
+        Constant.ratio /
+        Math.abs(cameraService.right - cameraService.left),
+      Constant.cadImg_Height /
+        Constant.ratio /
+        Math.abs(cameraService.top - cameraService.bottom)
+    );
+
+    this.layer.renderer.autoRedrawForImg();
+
+    var deferred = window.KanKan.Deferred();
+
+    setTimeout(() => {
+      let subFloor = dataService.getFloorNum();
+      this.uploadData.files = [];
+      if (subFloor == 1) {
+        let blobImg = this.exportImg(canvas);
+        this.uploadData.files.push(blobImg);
+        historyService.clearHistoryRecord();
+        this.layer.$xui.toolbar.recall = false;
+        this.layer.$xui.toolbar.recover = false;
+      } else {
+        let blobImg = this.exportImg(canvas);
+        let currentFloor = dataService.getCurrentFloor();
+        for (let i = 0; i < subFloor; ++i) {
+          if (i == currentFloor) {
+            this.uploadData.files.push(blobImg);
+            continue;
+          } else {
+            this.layer.uiControl.currentFloor = i;
+            this.layer.renderer.autoRedrawForImg();
+            let _blobImg = this.exportImg(canvas);
+            this.uploadData.files.push(_blobImg);
+          }
+        }
+        this.layer.uiControl.currentFloor = currentFloor;
+      }
+      this.uploadData.cadInfo = dataService.getCadInfo(canvas);
+      coordinate.updateForCanvas(canvas);
+      coordinate.setRes(cameraService.left, cameraService.right);
+      coordinate.ratio = 1;
+      this.layer.renderer.autoRedraw();
+
+      this.exportMiniMap(deferred);
+    }, 100);
+
+    return deferred;
+  }
+
+  exportMiniMap(deferred) {
+    deferred = deferred || window.KanKan.Deferred();
+
+    //this.layer.uiControl.menu_flex()
+    stateService.clearItems();
+    let canvas = this.layer.canvas;
+
+    canvas.width = Constant.miniMap_Width;
+    canvas.height = Constant.miniMap_Height;
+    coordinate.width = Constant.miniMap_Width / Constant.ratio;
+    coordinate.height = Constant.miniMap_Height / Constant.ratio;
+
+    coordinate.ratio = Constant.ratio;
+    coordinate.res = Math.min(
+      Constant.miniMap_Width /
+        Constant.ratio /
+        Math.abs(cameraService.right - cameraService.left),
+      Constant.miniMap_Height /
+        Constant.ratio /
+        Math.abs(cameraService.top - cameraService.bottom)
+    );
+    this.layer.renderer.redrawCore();
+
+    setTimeout(() => {
+      let subFloor = dataService.getFloorNum();
+      this.uploadData.miniMaps = [];
+      if (subFloor == 1) {
+        let blobImg = this.exportImg(canvas);
+        this.uploadData.miniMaps.push(blobImg);
+      } else {
+        let blobImg = this.exportImg(canvas);
+        let currentFloor = dataService.getCurrentFloor();
+        for (let i = 0; i < subFloor; ++i) {
+          if (i == currentFloor) {
+            this.uploadData.miniMaps.push(blobImg);
+            continue;
+          } else {
+            this.layer.uiControl.currentFloor = i;
+            let _blobImg = this.exportImg(canvas);
+            this.layer.renderer.redrawCore();
+            this.uploadData.miniMaps.push(_blobImg);
+          }
+        }
+        this.layer.uiControl.currentFloor = currentFloor;
+      }
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight;
+      coordinate.updateForCanvas(canvas);
+
+      let floorplanCamera =
+        this.layer.app.core.get("CameraControls").activeControl.camera;
+      coordinate.setRes(floorplanCamera.left, floorplanCamera.right);
+      coordinate.ratio = 1;
+      this.layer.renderer.autoRedraw();
+
+      deferred.resolve(this.uploadData);
+    }, 100);
+
+    return deferred;
+  }
+
+  getCustomCadInfoItem(floorIndex, cadImg_Width, cadImg_Height) {
+    //let cadImg_Width = Constant.cadImg_Width
+    //let cadImg_Height = Constant.cadImg_Height
+
+    // let cadImg_Width = 3840
+    // let cadImg_Height = 2160
+
+    function getScreenXY(point, res) {
+      let pt = {
+        x: point.x,
+        y: point.y,
+      };
+
+      let x = cadImg_Width / 2 + (pt.x - coordinate.center.x) * res;
+      let y = cadImg_Height / 2 - (pt.y - coordinate.center.y) * res;
+
+      x = (0.5 + x) << 0;
+      y = (0.5 + y) << 0;
+
+      return {
+        x: Math.floor(x),
+        y: Math.floor(y),
+      };
+    }
+
+    //let cadInfo = []
+    let bounds = dataService.getAllBoundingBox(); //只是跟户型数据相关
+
+    let res = Math.min(
+      cadImg_Width / Math.abs(cameraService.right - cameraService.left),
+      cadImg_Height / Math.abs(cameraService.top - cameraService.bottom)
+    );
+
+    //for (let i = 0; i < bounds.length; ++i) {
+    let item = {};
+
+    let leftTop = getScreenXY(
+      {
+        x: bounds[floorIndex].left,
+        y: bounds[floorIndex].top,
+      },
+      res
+    );
+
+    let rightBottom = getScreenXY(
+      {
+        x: bounds[floorIndex].right,
+        y: bounds[floorIndex].bottom,
+      },
+      res
+    );
+
+    let left = leftTop.x;
+    let top = leftTop.y;
+
+    let right = cadImg_Width - rightBottom.x;
+    let bottom = cadImg_Height - rightBottom.y;
+
+    item.left = left;
+    item.top = top;
+    item.right = right;
+    item.bottom = bottom;
+
+    item.bound = bounds[floorIndex];
+    item.bound.top = -1 * item.bound.top;
+    item.bound.bottom = -1 * item.bound.bottom;
+    return item;
+    //cadInfo.push(item)
+    //}
+    //return cadInfo   //和图片相关
+  }
+
+  //上传图片
+  uploadCadImg(floorIndex, imgUrl, cadImg_Width, cadImg_Height) {
+    //let floorIndex = 0;
+    this.initCameraService();
+    let cadInfoItem = this.getCustomCadInfoItem(
+      floorIndex,
+      cadImg_Width,
+      cadImg_Height
+    );
+    //let cadInfos = this.getCustomCadInfoItem(3840,2160)     //上传图片的分辨率
+    this.layer.app.core
+      .get("Player")
+      .model.floorplanCadImg.createCustomPlane(
+        floorIndex,
+        imgUrl,
+        cadInfoItem,
+        cadImg_Width,
+        cadImg_Height
+      );
+  }
+
+  //恢复图片
+  recoverForCadImg(floorIndex, imgUrl) {
+    this.layer.app.core
+      .get("Player")
+      .model.floorplanCadImg.updateCustomFloorTexture(floorIndex, imgUrl);
+  }
+
+  uploadCadImg2(floorIndex, imgUrl, cadImg_Width, cadImg_Height) {
+    this.layer.app.core
+      .get("Player")
+      .model.floorplanCadImg.createCustomPlane(
+        floorIndex,
+        imgUrl,
+        cadImg_Width,
+        cadImg_Height
+      );
+  }
+}

File diff suppressed because it is too large
+ 6861 - 0
src/graphic/Renderer/Draw.js


+ 389 - 0
src/graphic/Renderer/Render.js

@@ -0,0 +1,389 @@
+import VectorType from "../enum/VectorType.js";
+import { dataService } from "../Service/DataService.js";
+import { elementService } from "../Service/ElementService.js";
+import { measureService } from "../Service/MeasureService";
+import { coordinate } from "../Coordinate.js";
+import { draw } from "./Draw.js";
+import { roomService } from "../Service/RoomService.js";
+import { roomsUtil } from "../Room/RoomsUtil.js";
+import { floorplanData } from "../VectorData.js";
+import { furnitureService } from "../Service/FurnitureService.js";
+
+export default class Render {
+  constructor(layer) {
+    this.layer = layer;
+    this.displayPanos = false;
+  }
+
+  //绘制户型
+  drawGeometry(vector, styleType, flag) {
+    if (draw.context == null) {
+      return;
+    }
+    switch (vector.geoType) {
+      case VectorType.Wall:
+        draw.drawWall(vector, styleType);
+        //draw.drawMeasures(vector);
+        return;
+      case VectorType.Point:
+        draw.drawPoint(vector);
+        return;
+      case VectorType.SingleDoor:
+        draw.drawSingleDoor(vector, styleType, flag);
+        return;
+      case VectorType.DoubleDoor:
+        draw.drawDoubleDoor(vector, styleType, flag);
+        return;
+      case VectorType.SlideDoor:
+        draw.drawSlideDoor(vector, styleType, flag);
+        return;
+      case VectorType.SingleWindow:
+        draw.drawSingleWindow(vector, styleType);
+        return;
+      case VectorType.FrenchWindow:
+        draw.drawFrenchWindow(vector, styleType);
+        return;
+      case VectorType.BayWindow:
+        draw.drawBayWindow(vector, styleType);
+        return;
+      case VectorType.Pass:
+        draw.drawPass(vector, styleType);
+        return;
+      case VectorType.Beam:
+        draw.drawBeam(vector, styleType);
+        return;
+      case VectorType.Flue:
+        draw.drawFlue(vector, styleType);
+        return;
+      case VectorType.Corridor:
+        draw.drawCorridor(vector, styleType);
+        return;
+      case VectorType.Tag:
+        draw.drawTag(vector, styleType, flag);
+        return;
+    }
+
+    if (furnitureService.isFurniture(vector.geoType)) {
+      draw.drawFurniture(vector, styleType);
+    }
+  }
+
+  //绘制交互的元素
+  drawElement(vector) {
+    if (draw.context == null) {
+      return;
+    }
+    switch (vector.geoType) {
+      case VectorType.Point:
+        draw.drawCircle(vector);
+        break;
+      case VectorType.Line:
+        draw.drawLine(vector);
+        break;
+    }
+  }
+
+  drawPanos(panos) {
+    // const angle = dataService.getAngle()
+    // for (let i = 0; i < panos.length; ++i) {
+    //     let pano = JSON.parse(JSON.stringify(panos[i]))
+    //     //可能要旋转角度
+    //     pano = coordinate.getVectorForRotate(pano, angle)
+    //     draw.drawCircle(pano)
+    //     //draw.drawText(pano, pano.vectorId)
+    // }
+
+    for (let i = 0; i < panos.length; ++i) {
+      draw.drawCircle(panos[i]);
+    }
+  }
+
+  redrawElements() {
+    if (elementService.vCheckLines.X && elementService.vCheckLines.X.display) {
+      this.drawElement(elementService.vCheckLines.X);
+    }
+
+    if (elementService.vCheckLines.Y && elementService.vCheckLines.Y.display) {
+      this.drawElement(elementService.vCheckLines.Y);
+    }
+
+    if (elementService.startAddWall && elementService.startAddWall.display) {
+      this.drawElement(elementService.startAddWall);
+    }
+
+    if (elementService.newWall && elementService.newWall.display) {
+      this.drawElement(elementService.newWall);
+    }
+
+    if (
+      elementService.symbolPoints.Start &&
+      elementService.symbolPoints.Start.display
+    ) {
+      this.drawElement(elementService.symbolPoints.Start);
+    }
+
+    if (
+      elementService.symbolPoints.End &&
+      elementService.symbolPoints.End.display
+    ) {
+      this.drawElement(elementService.symbolPoints.End);
+    }
+
+    if (elementService.checkLines.X && elementService.checkLines.X.display) {
+      this.drawElement(elementService.checkLines.X);
+    }
+
+    if (elementService.checkLines.Y && elementService.checkLines.Y.display) {
+      this.drawElement(elementService.checkLines.Y);
+    }
+
+    if (elementService.signLine1 && elementService.signLine1.display) {
+      this.drawElement(elementService.signLine1);
+    }
+
+    if (elementService.signLine2 && elementService.signLine2.display) {
+      this.drawElement(elementService.signLine2);
+    }
+  }
+
+  redrawMeasures(styleType) {
+    //
+    draw.drawMeasure(measureService.measureLines.top, "top", styleType);
+    draw.drawMeasure(measureService.measureLines.bottom, "bottom", styleType);
+    draw.drawMeasure(measureService.measureLines.left, "left", styleType);
+    draw.drawMeasure(measureService.measureLines.right, "right", styleType);
+  }
+
+  redrawRooms(floor, styleType) {
+    let rooms = roomService.getRooms(floor);
+    for (let i = 0; i < rooms.length; ++i) {
+      let img = null;
+      for (let j = 0; j < this.layer.app.CadManager.labels.length; ++j) {
+        if (
+          rooms[i].tagName != null &&
+          rooms[i].tagName.trim() == this.layer.app.CadManager.labels[j].text
+        ) {
+          if (this.layer.app.CadManager.labels[j].type == "hall") {
+            img = roomService.getHallImg();
+            draw.drawRoomBackGround(rooms[i], img);
+          } else if (this.layer.app.CadManager.labels[j].type == "room") {
+            img = roomService.getDefaultImg();
+            draw.drawRoomBackGround(rooms[i], img);
+          } else if (this.layer.app.CadManager.labels[j].type == "other") {
+            if (styleType == "style-1") {
+              img = roomService.getOtherWhiteImg();
+            } else {
+              img = roomService.getOtherBlackImg();
+            }
+            draw.drawRoomBackGround(rooms[i], img);
+          }
+          break;
+        }
+      }
+
+      if (img == null) {
+        if (styleType == "style-1") {
+          img = roomService.getOtherWhiteImg();
+        } else {
+          img = roomService.getOtherBlackImg();
+        }
+        draw.drawRoomBackGround(rooms[i], img);
+      }
+    }
+  }
+
+  autoRedraw() {
+    draw.clear();
+    if (this.displayPanos) {
+      this.drawPanos(floorplanData.panos);
+    }
+    let data = dataService.getFloorData();
+    if (!data) {
+      return;
+    }
+    let walls = data.walls;
+    for (let key in walls) {
+      this.drawGeometry(walls[key]);
+    }
+
+    let points = data.points;
+    for (let key in points) {
+      this.drawGeometry(points[key]);
+    }
+
+    draw.drawSpecialPoint();
+
+    let symbols = data.symbols;
+    for (let key in symbols) {
+      this.drawGeometry(symbols[key]);
+      draw.drawSymbolPoint(symbols[key]);
+    }
+    draw.drawSelectSymbolPoint();
+
+    let components = data.components;
+    for (let key in components) {
+      this.drawGeometry(components[key]);
+    }
+
+    let furnitures = data.furnitures;
+    for (let key in furnitures) {
+      this.drawGeometry(furnitures[key]);
+    }
+
+    let tags = data.tags;
+    for (let key in tags) {
+      this.drawGeometry(tags[key]);
+    }
+
+    this.redrawElements();
+    this.redrawMeasures();
+
+    // let _rooms = dataService.getRooms()
+    // if(_rooms.length == 0){
+    //     roomsUtil.start()
+    // }
+    // let rooms = dataService.getRooms()
+    // for (let i = 0; i < rooms.length; ++i) {
+    //     draw.drawText(rooms[i].center, rooms[i].roomId, false, 0)
+    // }
+  }
+
+  autoRedrawForImg() {
+    draw.clear();
+
+    // if (this.displayPanos) {
+    //     this.drawPanos(this.layer.panos[dataService.currentFloor])
+    // }
+    let data = dataService.getFloorData();
+    if (!data) {
+      return;
+    }
+    let walls = data.walls;
+    for (let key in walls) {
+      this.drawGeometry(walls[key]);
+    }
+
+    // let points = data.points
+    // for (let key in points) {
+    //     this.drawGeometry(points[key])
+    // }
+
+    let symbols = data.symbols;
+    let noEnter = true;
+    for (let key in symbols) {
+      this.drawGeometry(symbols[key], null, noEnter);
+    }
+
+    let components = data.components;
+    for (let key in components) {
+      this.drawGeometry(components[key]);
+    }
+
+    let furnitures = data.furnitures;
+    for (let key in furnitures) {
+      this.drawGeometry(furnitures[key]);
+    }
+
+    let tags = data.tags;
+    for (let key in tags) {
+      this.drawGeometry(tags[key], null, true);
+    }
+
+    //this.redrawElements()
+  }
+
+  //下载图片
+  //style表示风格
+  autoRedrawForDownLoadImg(styleType) {
+    draw.clear();
+
+    if (styleType == "style-1") {
+      draw.drawBackGround("#FFFFFF");
+      this.redrawRooms(dataService.getCurrentFloor(), styleType);
+    } else if (styleType == "style-2") {
+      draw.drawBackGround("#000000");
+      this.redrawRooms(dataService.getCurrentFloor(), styleType);
+    } else if (styleType == "style-3") {
+      draw.drawBackGround("#FFFFFF");
+    } else if (styleType == "style-4") {
+      draw.drawBackGround("#000000");
+    }
+
+    let data = dataService.getFloorData();
+    if (!data) {
+      return;
+    }
+    let walls = data.walls;
+    for (let key in walls) {
+      this.drawGeometry(walls[key], styleType);
+    }
+
+    let symbols = data.symbols;
+    let noEnter = false;
+    for (let key in symbols) {
+      this.drawGeometry(symbols[key], styleType, noEnter);
+    }
+
+    let components = data.components;
+    for (let key in components) {
+      this.drawGeometry(components[key], styleType);
+    }
+
+    let furnitures = data.furnitures;
+    for (let key in furnitures) {
+      this.drawGeometry(furnitures[key], styleType);
+    }
+    let tags = data.tags;
+    for (let key in tags) {
+      this.drawGeometry(tags[key], styleType);
+    }
+
+    this.redrawMeasures(styleType);
+    draw.drawCompass(styleType);
+  }
+
+  redrawCore() {
+    console.log("重绘!");
+    draw.clear();
+
+    let data = dataService.getFloorData();
+    if (!data) {
+      return;
+    }
+    let walls = data.walls;
+    for (let key in walls) {
+      this.drawGeometry(walls[key]);
+    }
+
+    let points = data.points;
+    for (let key in points) {
+      this.drawGeometry(points[key]);
+    }
+
+    let symbols = data.symbols;
+    for (let key in symbols) {
+      this.drawGeometry(symbols[key]);
+    }
+
+    let components = data.components;
+    for (let key in components) {
+      this.drawGeometry(components[key]);
+    }
+
+    let furnitures = data.furnitures;
+    for (let key in furnitures) {
+      this.drawGeometry(furnitures[key]);
+    }
+  }
+
+  clear() {
+    draw.clear();
+  }
+
+  getContext() {
+    return draw.context;
+  }
+}
+
+// const render = new Render()
+// export { render }

+ 131 - 0
src/graphic/Service/AnalyService.js

@@ -0,0 +1,131 @@
+//解析来自算法部的数据:https://4dkk.4dage.com/data/datat-uXiVK7k/floorplan_cad.json?_=0
+import { dataService } from "./FloorplanService";
+import { wallService } from "./WallService";
+import { floorplanData } from "../FloorplanData";
+import VectorType from "../enum/VectorType.js";
+
+export default class AnalyService {
+  constructor(layer) {
+    this.layer = layer;
+    this.app = this.layer.app;
+    this.houseData = null;
+  }
+
+  initVectors(data) {
+    let floors = data.floors;
+    let currentId = -1;
+    let offWallId = 0; //算法部的id都是从0开始,且每层楼的id都是从0开始
+    let offPointId = 0;
+    for (let i = 0; i < floors.length; ++i) {
+      let floorNum = floors[i].subgroup;
+      let floor = floors[i];
+      floorplanData.floors[floorNum] = {};
+      floorplanData.floors[floorNum].points = {};
+      floorplanData.floors[floorNum].walls = {};
+      floorplanData.floors[floorNum].symbols = {};
+      floorplanData.floors[floorNum].components = {};
+      floorplanData.floors[floorNum].tags = {};
+      floorplanData.floors[floorNum].furnitures = {};
+      floorplanData.floors[floorNum].boundingBox = {};
+      floorplanData.floors[floorNum].rooms = [];
+      let minX = null,
+        minY = null,
+        maxX = null,
+        maxY = null;
+
+      for (let j = 0; j < floor["vertex-xy"].length; ++j) {
+        let vectorId = floor["vertex-xy"][j].id + offPointId;
+        if (currentId < vectorId) {
+          currentId = vectorId;
+        }
+        vectorId = VectorType.Point + vectorId;
+        let point = {
+          x: floor["vertex-xy"][j].x,
+          y: floor["vertex-xy"][j].y,
+        };
+        wallService.createPoint(point.x, point.y, vectorId, floorNum);
+        if (minX == null || minX > point.x) {
+          minX = point.x;
+        }
+        if (minY == null || minY > point.y) {
+          minY = point.y;
+        }
+
+        if (maxX == null || maxX < point.x) {
+          maxX = point.x;
+        }
+        if (maxY == null || maxY < point.y) {
+          maxY = point.y;
+        }
+      }
+      offWallId = currentId + 1;
+
+      floorplanData.floors[floorNum].boundingBox.minX = minX;
+      floorplanData.floors[floorNum].boundingBox.minY = minY;
+      floorplanData.floors[floorNum].boundingBox.maxX = maxX;
+      floorplanData.floors[floorNum].boundingBox.maxY = maxY;
+
+      for (let j = 0; j < floor["segment"].length; ++j) {
+        let vectorId = floor["segment"][j].id + offWallId;
+        if (currentId < vectorId) {
+          currentId = vectorId;
+        }
+        vectorId = VectorType.Wall + vectorId;
+        let start = VectorType.Point + (floor["segment"][j].a + offPointId);
+        let end = VectorType.Point + (floor["segment"][j].b + offPointId);
+
+        wallService.createWall(start, end, vectorId, floorNum);
+      }
+
+      offWallId = currentId + 1;
+      offPointId = currentId + 1;
+    }
+    dataService.setCurrentId(currentId + 1);
+    const currentFloorNum =
+      this.app.core.get("Player").model.currentFloor.floorIndex;
+    dataService.setCurrentFloor(currentFloorNum);
+    //随心装的json数据
+    //this.houseData = this.createDecorateData(floorplanData)
+  }
+
+  //随心装数据,houseType.json
+  createDecorateData(floorplanData) {
+    let house = {};
+    house.name = "houseType.json";
+    house.version = "2.1";
+    house.floors = [];
+
+    for (let i = 0; i < floorplanData.floors.length; ++i) {
+      let item = {};
+      item.points = [];
+      item.walls = [];
+
+      //floorplanData.floors.points
+      //floorplanData.floors.walls
+      for (let key in floorplanData.floors[i].points) {
+        let point = {};
+        point.x = floorplanData.floors[i].points[key].x;
+        point.y = floorplanData.floors[i].points[key].y;
+        point.parent = floorplanData.floors[i].points[key].parent;
+        point.vectorId = floorplanData.floors[i].points[key].vectorId;
+
+        item.points.push(point);
+      }
+
+      for (let key in floorplanData.floors[i].walls) {
+        let wall = {};
+        wall.start = floorplanData.floors[i].walls[key].start;
+        wall.end = floorplanData.floors[i].walls[key].end;
+
+        wall.children = [];
+        wall.vectorId = floorplanData.floors[i].walls[key].vectorId;
+        wall.width = 0.2;
+
+        item.walls.push(wall);
+      }
+
+      house.floors.push(item);
+    }
+    return house;
+  }
+}

+ 47 - 0
src/graphic/Service/CameraService.js

@@ -0,0 +1,47 @@
+import Constant from "../Constant";
+import { dataService } from "./DataService";
+const orthoBase = 10;
+const maxSize = 800; //模型最大占据像素
+
+export default class CameraService {
+  constructor() {
+    this.aspect = Constant.cadImg_Width / Constant.cadImg_Height;
+    this.top = null;
+    this.bottom = null;
+    this.left = null;
+    this.right = null;
+  }
+
+  //modelSize:{x,y,z}
+  getCurrentScale(modelSize) {
+    let angle = dataService.getAngle();
+    modelSize = modelSize.clone().applyEuler(new THREE.Euler(0, angle, 0));
+    var n = Math.max(
+      Math.abs(modelSize.x),
+      Math.abs(modelSize.z) * this.aspect
+    ); //视口宽高比 >= 1 情况下,模型所占最大视口尺寸
+    let screenSize = Math.min(
+      Constant.cadImg_Width / Constant.ratio,
+      Constant.cadImg_Height / Constant.ratio
+    );
+    let ratio = Math.max((screenSize * 1.2) / maxSize, 1.2);
+    let currentScale = (n / 2 / orthoBase) * ratio; //根据模型所占最大视口尺寸调整缩放
+
+    return currentScale;
+  }
+
+  setCamera(currentScale) {
+    let cameraLeft = -10 * currentScale;
+    let cameraRight = 10 * currentScale;
+    let cameraTop = (10 * currentScale) / this.aspect;
+    let cameraBottom = (-10 * currentScale) / this.aspect;
+
+    this.top = cameraTop;
+    this.bottom = cameraBottom;
+    this.left = cameraLeft;
+    this.right = cameraRight;
+  }
+}
+
+const cameraService = new CameraService();
+export { cameraService };

+ 25 - 0
src/graphic/Service/CompassService.js

@@ -0,0 +1,25 @@
+export default class CompassService {
+    constructor() {
+        this.whiteImg = null
+        this.blackImg = null
+    }
+
+    setWhiteImg(img) {
+        this.whiteImg = img
+    }
+
+    setBlackImg(img) {
+        this.blackImg = img
+    }
+
+    getWhiteImg() {
+        return this.whiteImg
+    }
+
+    getBlackImg() {
+        return this.blackImg
+    }
+}
+
+const compassService = new CompassService()
+export { compassService }

+ 107 - 0
src/graphic/Service/ComponentService.js

@@ -0,0 +1,107 @@
+import VectorType from "../enum/VectorType.js";
+import Beam from "../Geometry/Beam.js";
+import Flue from "../Geometry/Flue.js";
+import Corridor from "../Geometry/Corridor.js";
+import { dataService } from "./DataService.js";
+
+export class ComponentService {
+  constructor() {
+    this.sideWidth = 0.65;
+    this.sideThickness = 0.65;
+  }
+
+  // 新建component
+  createComponent(position, geoType, vectorId) {
+    let component = null;
+    switch (geoType) {
+      case VectorType.Beam:
+        component = new Beam(position, vectorId);
+        break;
+      case VectorType.Flue:
+        component = new Flue(position, vectorId);
+        break;
+      case VectorType.Corridor:
+        component = new Corridor(position, vectorId);
+        break;
+    }
+
+    component.setPoints2d();
+    dataService.addComponent(component);
+    return component;
+  }
+
+  isComponent(geoType) {
+    switch (geoType) {
+      case VectorType.Beam:
+        return true;
+      case VectorType.Flue:
+        return true;
+      case VectorType.Corridor:
+        return true;
+    }
+    return false;
+  }
+
+  setComponentInfo(componentInfo) {
+    let component = dataService.getComponent(componentInfo.vectorId);
+    component.vectorId = componentInfo.vectorId;
+    component.angle = componentInfo.angle;
+    component.center = JSON.parse(JSON.stringify(componentInfo.center));
+    component.points2d = JSON.parse(JSON.stringify(componentInfo.points2d));
+  }
+
+  getBoundingVertexs(componentId, center) {
+    let component = dataService.getComponent(componentId);
+    const minX = center.x - component.sideWidth / 2;
+    const minY = center.y - component.sideThickness / 2;
+    const maxX = center.x + component.sideWidth / 2;
+    const maxY = center.y + component.sideThickness / 2;
+
+    const point1 = component.rotatePoint(
+      {
+        x: minX,
+        y: maxY,
+      },
+      center,
+      component.angle
+    );
+
+    const point2 = component.rotatePoint(
+      {
+        x: maxX,
+        y: maxY,
+      },
+      center,
+      component.angle
+    );
+
+    const point3 = component.rotatePoint(
+      {
+        x: maxX,
+        y: minY,
+      },
+      center,
+      component.angle
+    );
+
+    const point4 = component.rotatePoint(
+      {
+        x: minX,
+        y: minY,
+      },
+      center,
+      component.angle
+    );
+
+    const boundingVertexs = [];
+    boundingVertexs.push(point1);
+    boundingVertexs.push(point2);
+    boundingVertexs.push(point3);
+    boundingVertexs.push(point4);
+
+    return boundingVertexs;
+  }
+}
+
+const componentService = new ComponentService();
+export { componentService };

+ 528 - 0
src/graphic/Service/DataService.js

@@ -0,0 +1,528 @@
+import { coordinate } from "../Coordinate.js";
+import Constant from "../Constant";
+
+export class DataService {
+  constructor() {
+    this.vectorData = {};
+    this.currentId = 0; // 当前可用id
+    this.currentFloor = 0; // 当前楼层,第一层是0
+    this.angle = 0; //旋转角度
+  }
+
+  setCurrentId(id) {
+    this.currentId = id;
+  }
+
+  getCurrentId() {
+    return this.currentId;
+  }
+
+  updateCurrentId() {
+    ++this.currentId;
+  }
+
+  getRoads() {
+    return this.vectorData.roads;
+  }
+
+  getRoad(roadId) {
+    if (roadId) {
+      return this.vectorData.roads[roadId];
+    } else {
+      return null;
+    }
+  }
+
+  addRoad(road) {
+    this.vectorData.roads[road.vectorId] = road;
+  }
+
+  getCompass() {
+    return this.vectorData.compass;
+  }
+
+  setCompass(angle) {
+    this.vectorData.compass = angle;
+  }
+
+  getPoint(pointId) {
+    if (pointId) {
+      return this.vectorData.points[pointId];
+    } else {
+      return null;
+    }
+  }
+
+  deletePoint(pointId, roadId) {
+    let point = this.getPoint(pointId);
+    //有可能先删除墙,导致点没了
+    if (point) {
+      if (Object.keys(point.parent).length == 0) {
+        point = null;
+        delete this.vectorData.points[pointId];
+      } else if (Object.keys(point.parent).length == 1 && !roadId) {
+        delete this.vectorData.points[pointId];
+      } else if (
+        Object.keys(point.parent).length == 1 &&
+        point.parent[roadId]
+      ) {
+        delete this.vectorData.points[pointId];
+      } else if (
+        Object.keys(point.parent).length == 1 &&
+        !point.parent[roadId]
+      ) {
+        return;
+      } else {
+        delete point.parent[roadId];
+      }
+    }
+  }
+
+  addPoint(point) {
+    this.vectorData.points[point.vectorId] = point;
+  }
+
+  deleteRoad(roadId) {
+    let road = this.getRoad(roadId);
+    this.deletePoint(road.startId, roadId);
+    this.deletePoint(road.endId, roadId);
+    delete this.vectorData.roads[roadId];
+  }
+
+  getEdge(edgeId) {
+    return this.vectorData.edges[edgeId];
+  }
+
+  addEdge(edge) {
+    this.vectorData.edges[edge.vectorId] = edge;
+  }
+
+  getAngle() {
+    return this.angle;
+  }
+
+  setAngle(angle) {
+    this.angle = angle;
+  }
+
+  setBoundingBox(boundingBox, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].boundingBox = JSON.parse(
+      JSON.stringify(boundingBox)
+    );
+  }
+
+  // getBoundingBox(floor) {
+  //     if (floor == null || typeof floor == 'undefined') {
+  //         floor = this.currentFloor
+  //     }
+  //     if (
+  //         this.vectorData.floors[floor].boundingBox.hasOwnProperty('maxX') &&
+  //         this.vectorData.floors[floor].boundingBox.hasOwnProperty('maxY') &&
+  //         this.vectorData.floors[floor].boundingBox.hasOwnProperty('minX') &&
+  //         this.vectorData.floors[floor].boundingBox.hasOwnProperty('minY')
+  //     ) {
+  //         return this.vectorData.floors[floor].boundingBox
+  //     } else {
+  //         this.updateBoundingBox(floor)
+  //         return this.vectorData.floors[floor].boundingBox
+  //     }
+  // }
+
+  getBoundingBox(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      let minX = null,
+        maxX = null,
+        minY = null,
+        maxY = null;
+      for (let i = 0; i < this.vectorData.floors.length; ++i) {
+        if (
+          !this.vectorData.floors[i].boundingBox.hasOwnProperty("maxX") ||
+          !this.vectorData.floors[i].boundingBox.hasOwnProperty("minX") ||
+          !this.vectorData.floors[i].boundingBox.hasOwnProperty("maxY") ||
+          !this.vectorData.floors[i].boundingBox.hasOwnProperty("minY")
+        ) {
+          this.updateBoundingBox(i);
+        }
+        if (minX == null || minX > this.vectorData.floors[i].boundingBox.minX) {
+          minX = this.vectorData.floors[i].boundingBox.minX;
+        }
+        if (maxX == null || maxX < this.vectorData.floors[i].boundingBox.maxX) {
+          maxX = this.vectorData.floors[i].boundingBox.maxX;
+        }
+        if (minY == null || minY > this.vectorData.floors[i].boundingBox.minY) {
+          minY = this.vectorData.floors[i].boundingBox.minY;
+        }
+        if (maxY == null || maxY < this.vectorData.floors[i].boundingBox.maxY) {
+          maxY = this.vectorData.floors[i].boundingBox.maxY;
+        }
+      }
+
+      return {
+        minX: minX,
+        maxX: maxX,
+        minY: minY,
+        maxY: maxY,
+      };
+    } else {
+      return this.vectorData.floors[floor].boundingBox;
+    }
+  }
+
+  updateBoundingBox(floor) {
+    const points = this.getPoints(floor);
+    let minX = null,
+      maxX = null,
+      minY = null,
+      maxY = null;
+    for (let key in points) {
+      let point = points[key];
+      if (minX == null || minX > point.x) {
+        minX = point.x;
+      }
+      if (maxX == null || maxX < point.x) {
+        maxX = point.x;
+      }
+      if (minY == null || minY > point.y) {
+        minY = point.y;
+      }
+      if (maxY == null || maxY < point.y) {
+        maxY = point.y;
+      }
+    }
+    this.setBoundingBox(
+      {
+        minX: minX,
+        maxX: maxX,
+        minY: minY,
+        maxY: maxY,
+      },
+      floor
+    );
+  }
+
+  getFloorData(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor];
+  }
+
+  getWalls(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].walls;
+  }
+
+  getPoints(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].points;
+  }
+
+  getSymbol(symbolId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].symbols[symbolId];
+  }
+
+  addRoad(wall, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].walls[wall.vectorId] = wall;
+  }
+
+  addPoint(point) {
+    this.vectorData.points[point.vectorId] = point;
+  }
+
+  addSymbol(symbol, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].symbols[symbol.vectorId] = symbol;
+  }
+
+  addComponent(component, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].components[component.vectorId] = component;
+  }
+
+  addFurniture(furniture, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].furnitures[furniture.vectorId] = furniture;
+  }
+
+  deleteSymbol(symbolId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    let symbol = this.getSymbol(symbolId, floor);
+    symbol = null;
+    delete this.vectorData.floors[floor].symbols[symbolId];
+  }
+
+  getComponent(componentId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].components[componentId];
+  }
+
+  deleteComponent(componentId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    let component = this.getComponent(componentId, floor);
+    component = null;
+    delete this.vectorData.floors[floor].components[componentId];
+  }
+
+  getFurniture(furnitureId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].furnitures[furnitureId];
+  }
+
+  deleteFurniture(furnitureId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    let furniture = this.getFurniture(furnitureId, floor);
+    furniture = null;
+    delete this.vectorData.floors[floor].furnitures[furnitureId];
+  }
+
+  addTag(tag, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    this.vectorData.floors[floor].tags[tag.vectorId] = tag;
+  }
+
+  getTag(tagId, floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].tags[tagId];
+  }
+
+  deleteTag(tagId) {
+    let tag = this.getTag(tagId);
+    tag = null;
+    delete this.vectorData.tags[tagId];
+  }
+
+  getSymbols(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].symbols;
+  }
+
+  getComponents(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].components;
+  }
+
+  getTags(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].tags;
+  }
+
+  getFurnitures(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].furnitures;
+  }
+
+  getAllBoundingBox() {
+    let bounds = [];
+    for (let i = 0; i < this.vectorData.floors.length; ++i) {
+      let minX, minY, maxX, maxY;
+      let bound = {};
+      for (let key in this.vectorData.floors[i].points) {
+        let x = this.vectorData.floors[i].points[key].x;
+        let y = this.vectorData.floors[i].points[key].y;
+        if (typeof minX == "undefined" || minX > x) {
+          minX = x;
+        }
+        if (typeof maxX == "undefined" || maxX < x) {
+          maxX = x;
+        }
+        if (typeof minY == "undefined" || minY > y) {
+          minY = y;
+        }
+        if (typeof maxY == "undefined" || maxY < y) {
+          maxY = y;
+        }
+      }
+
+      bound.left = minX;
+      bound.top = maxY;
+      bound.right = maxX;
+      bound.bottom = minY;
+
+      bounds.push(bound);
+    }
+    return bounds;
+  }
+
+  getCadInfo(canvas) {
+    let cadInfo = [];
+    let bounds = this.getAllBoundingBox();
+    for (let i = 0; i < bounds.length; ++i) {
+      let item = {};
+
+      let leftTop = coordinate.getScreenXY({
+        x: bounds[i].left,
+        y: bounds[i].top,
+      });
+
+      let rightBottom = coordinate.getScreenXY({
+        x: bounds[i].right,
+        y: bounds[i].bottom,
+      });
+
+      let left = leftTop.x;
+      let top = leftTop.y;
+
+      let right = canvas.width - rightBottom.x;
+      let bottom = canvas.height - rightBottom.y;
+
+      // item.left = Constant.ratio * left
+      // item.top = Constant.ratio * top
+      // item.right = Constant.ratio * right
+      // item.bottom = Constant.ratio * bottom
+      item.left = left;
+      item.top = top;
+      item.right = right;
+      item.bottom = bottom;
+
+      item.bound = bounds[i];
+      item.bound.top = -1 * item.bound.top;
+      item.bound.bottom = -1 * item.bound.bottom;
+      cadInfo.push(item);
+    }
+    return cadInfo;
+  }
+
+  getRooms(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].rooms;
+  }
+
+  clear(saveBoundingBox) {
+    // for (let i = 0; i < this.vectorData.floors.length; ++i) {
+    //     this.vectorData.floors[i].points = {}
+    //     this.vectorData.floors[i].walls = {}
+    //     this.vectorData.floors[i].symbols = {}
+    //     this.vectorData.floors[i].components = {}
+    //     this.vectorData.floors[i].tags = {}
+    //     this.vectorData.floors[i].boundingBox = {}
+    //     this.vectorData.floors[i].rooms = []
+    // }
+    if (this.vectorData.floors[this.currentFloor]) {
+      this.vectorData.floors[this.currentFloor].points = {};
+      this.vectorData.floors[this.currentFloor].walls = {};
+      this.vectorData.floors[this.currentFloor].symbols = {};
+      this.vectorData.floors[this.currentFloor].components = {};
+      this.vectorData.floors[this.currentFloor].tags = {};
+      this.vectorData.floors[this.currentFloor].furnitures = {};
+      if (!saveBoundingBox) {
+        this.vectorData.floors[this.currentFloor].boundingBox = {};
+      }
+      this.vectorData.floors[this.currentFloor].rooms = [];
+    }
+
+    //this.vectorData.angle = 0
+  }
+
+  // getBoundingBox2DPosition(floorIndex) {
+  //     let bound = this.vectorData.floors[floorIndex].boundingBox
+  //     let leftTop = coordinate.getScreenXY({
+  //         x: bound.minX,
+  //         y: bound.maxY,
+  //     })
+
+  //     let rightBottom = coordinate.getScreenXY({
+  //         x: bound.maxX,
+  //         y: bound.minY,
+  //     })
+
+  //     return {
+  //         top: leftTop.y,
+  //         bottom: rightBottom.y,
+  //         left: leftTop.x,
+  //         right: rightBottom.x,
+  //     }
+  // }
+
+  //boundingBox3d是__sdk.core.get('Player').model.floors.index[floorIndex].boundingBox.Box3
+  getBoundingBox2DPosition(boundingBox3d) {
+    let leftTop = coordinate.getScreenXY({
+      x: boundingBox3d.min.x,
+      y: boundingBox3d.max.z,
+    });
+
+    let rightBottom = coordinate.getScreenXY({
+      x: boundingBox3d.max.x,
+      y: boundingBox3d.min.z,
+    });
+
+    return {
+      top: leftTop.y,
+      bottom: rightBottom.y,
+      left: leftTop.x,
+      right: rightBottom.x,
+    };
+  }
+
+  deleteFloorData() {
+    this.vectorData.floors = [];
+  }
+
+  setFloorName(floorName, floor) {
+    this.vectorData.floors[floor].name = floorName;
+  }
+
+  setFloorId(floorId, floor) {
+    this.vectorData.floors[floor].id = floorId;
+  }
+
+  setFloorSubgroup(floorSubgroup, floor) {
+    this.vectorData.floors[floor].subgroup = floorSubgroup;
+  }
+
+  setFloorHeight(subgroup, height) {
+    this.vectorData.floors[subgroup].height = height;
+  }
+
+  getFloorHeight(floor) {
+    if (floor == null || typeof floor == "undefined") {
+      floor = this.currentFloor;
+    }
+    return this.vectorData.floors[floor].height;
+  }
+}
+
+const dataService = new DataService();
+export { dataService };

+ 626 - 0
src/graphic/Service/EdgeService.js

@@ -0,0 +1,626 @@
+import Point from "../Geometry/Point.js";
+import Road from "../Geometry/Road.js";
+import { dataService } from "./DataService.js";
+import { roadService } from "./RoadService.js";
+import { mathUtil } from "../Util/MathUtil.js";
+
+export default class EdgeService {
+  constructor() {}
+
+  getMidLine(edge) {
+    let line = mathUtil.createLine(edge.start, edge.end);
+    return line;
+  }
+
+  computerDefaultEdge(roadId) {
+    console.log("开始执行EdgeUtil.computerDefaultEdge");
+    let road = dataService.getRoad(roadId);
+    let line = roadService.getMidLine(road);
+
+    let lines = mathUtil.getParallelLineForDistance(line, road.width / 2);
+    let startPoint = dataService.getPoint(road.startId);
+    let endPoint = dataService.getPoint(road.endId);
+
+    let leftEdge = dataService.getEdge(road.leftEdgeId);
+    let rightEdge = dataService.getEdge(road.rightEdgeId);
+
+    let point = null;
+    let points = [];
+
+    //先计算start部分
+    point = startPoint;
+    points.push(endPoint);
+    points.push(startPoint);
+    let point1 = mathUtil.getJoinLinePoint(point, lines.line1);
+    let point2 = mathUtil.getJoinLinePoint(point, lines.line2);
+    points[2] = point1;
+
+    if (mathUtil.isClockwise(points)) {
+      mathUtil.clonePoint(rightEdge.start, point1);
+      mathUtil.clonePoint(leftEdge.start, point2);
+    } else {
+      mathUtil.clonePoint(leftEdge.start, point1);
+      mathUtil.clonePoint(rightEdge.start, point2);
+    }
+
+    //再计算end部分
+    points = [];
+    point = endPoint;
+    points.push(startPoint);
+    points.push(endPoint);
+    point1 = mathUtil.getJoinLinePoint(point, lines.line1);
+    point2 = mathUtil.getJoinLinePoint(point, lines.line2);
+    points[2] = point1;
+
+    if (mathUtil.isClockwise(points)) {
+      mathUtil.clonePoint(leftEdge.end, point1);
+      mathUtil.clonePoint(rightEdge.end, point2);
+    } else {
+      mathUtil.clonePoint(rightEdge.end, point1);
+      mathUtil.clonePoint(leftEdge.end, point2);
+    }
+  }
+
+  computerDefaultEdgeForOneDir(roadId, dir) {
+    //console.log('计算'+roadId+' '+dir+'方向的默认Edge端点');
+    console.log("开始执行EdgeUtil.computerDefaultEdgeForOneDir");
+    let road = dataService.getRoad(roadId);
+    let line = roadService.getMidLine(road);
+    let lines = mathUtil.getParallelLineForDistance(line, road.width / 2);
+    let startPoint = dataService.getPoint(road.startId);
+    let endPoint = dataService.getPoint(road.endId);
+
+    let leftEdge = dataService.getEdge(road.leftEdgeId);
+    let rightEdge = dataService.getEdge(road.rightEdgeId);
+
+    let point = null;
+    let points = [];
+    if (dir == "start") {
+      points.push(endPoint);
+      points.push(startPoint);
+
+      point = startPoint;
+    } else if (dir == "end") {
+      points.push(startPoint);
+      points.push(endPoint);
+
+      point = endPoint;
+    } else {
+      console.error(
+        "EdgeUtil.computerDefaultEdgeForOneDir***************************************************************"
+      );
+    }
+
+    let point1 = mathUtil.getJoinLinePoint(point, lines.line1);
+    let point2 = mathUtil.getJoinLinePoint(point, lines.line2);
+
+    points.push(point1);
+
+    if (mathUtil.isClockwise(points)) {
+      if (dir == "start") {
+        mathUtil.clonePoint(rightEdge.start, point1);
+        mathUtil.clonePoint(leftEdge.start, point2);
+      } else if (dir == "end") {
+        mathUtil.clonePoint(leftEdge.end, point1);
+        mathUtil.clonePoint(rightEdge.end, point2);
+      }
+    } else {
+      if (dir == "start") {
+        mathUtil.clonePoint(leftEdge.start, point1);
+        mathUtil.clonePoint(rightEdge.start, point2);
+      } else if (dir == "end") {
+        mathUtil.clonePoint(rightEdge.end, point1);
+        mathUtil.clonePoint(leftEdge.end, point2);
+      }
+    }
+  }
+
+  updateDefaultEdge(roadId, dir) {
+    //console.log('计算'+roadId+' '+dir+'方向的默认Edge端点');
+    console.log("开始执行EdgeUtil.updateDefaultEdge");
+
+    let road = dataService.getRoad(roadId);
+    let startPoint = dataService.getPoint(road.startId);
+    let endPoint = dataService.getPoint(road.endId);
+
+    let leftEdge = dataService.getEdge(road.leftEdgeId);
+    let rightEdge = dataService.getEdge(road.rightEdgeId);
+
+    let point = null;
+    if (dir == "start") {
+      point = startPoint;
+    } else if (dir == "end") {
+      point = endPoint;
+    } else {
+      console.error(
+        "EdgeUtil.updateDefaultSide***************************************************************"
+      );
+    }
+
+    let line1 = mathUtil.createLine(leftEdge.start, leftEdge.end);
+    let line2 = mathUtil.createLine(rightEdge.start, rightEdge.end);
+    let point1 = mathUtil.getJoinLinePoint(point, line1);
+    let point2 = mathUtil.getJoinLinePoint(point, line2);
+
+    if (dir == "start") {
+      mathUtil.clonePoint(leftEdge.start, point1);
+      mathUtil.clonePoint(rightEdge.start, point2);
+    } else if (dir == "end") {
+      mathUtil.clonePoint(leftEdge.end, point1);
+      mathUtil.clonePoint(rightEdge.end, point2);
+    }
+  }
+
+  computerEdgeForTwoWall(roadId1, roadId2) {
+    //console.log('计算'+roadId1+'和'+roadId2+'相交的edge交点');
+    console.log("开始执行EdgeUtil.computerEdgeForTwoWall");
+    let road1 = dataService.getRoad(roadId1);
+    let startPoint1 = dataService.getPoint(road1.startId);
+    let endPoint1 = dataService.getPoint(road1.endId);
+    this.computerDefaultEdge(roadId1);
+    let leftEdge1 = dataService.getEdge(road1.leftEdgeId);
+    let rightEdge1 = dataService.getEdge(road1.rightEdgeId);
+    let lineLeft1 = this.getMidLine(leftEdge1);
+    let lineRight1 = this.getMidLine(rightEdge1);
+
+    let road2 = dataService.getRoad(roadId2);
+    this.computerDefaultEdge(roadId2);
+    // let startPoint2 = dataService.getPoint(road2.startId);
+    // let endPoint2 = dataService.getPoint(road2.endId);
+    let leftEdge2 = dataService.getEdge(road2.leftEdgeId);
+    let rightEdge2 = dataService.getEdge(road2.rightEdgeId);
+    let lineLeft2 = this.getMidLine(leftEdge2);
+    let lineRight2 = this.getMidLine(rightEdge2);
+
+    let angleInfo = GeoUtil.AngleForWall2(roadId1, roadId2);
+    let angle = null;
+    if (angleInfo == null) {
+      return false;
+    } else {
+      angle = angleInfo.angle;
+    }
+    let newEdgePoint1, newEdgePoint2;
+
+    //start-start
+    //left-right,right-left
+    if (road1.startId == road2.startId) {
+      //如果墙的角度过大,不能计算edge对应的line的交点
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+      mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+
+      mathUtil.clonePoint(rightEdge2.start, newEdgePoint1);
+      mathUtil.clonePoint(leftEdge2.start, newEdgePoint2);
+      return true;
+    }
+    //start-end
+    //left-left,right-right
+    else if (road1.startId == road2.endId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+
+      mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+
+      mathUtil.clonePoint(leftEdge2.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge2.end, newEdgePoint2);
+      return true;
+    }
+    //end-start
+    //left-left,right-right
+    else if (road1.endId == road2.startId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+
+      mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+
+      mathUtil.clonePoint(leftEdge2.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge2.start, newEdgePoint2);
+      return true;
+    }
+    //end-end
+    //left-right,right-left
+    else if (road1.endId == road2.endId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+
+      mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+
+      mathUtil.clonePoint(rightEdge2.end, newEdgePoint1);
+      mathUtil.clonePoint(leftEdge2.end, newEdgePoint2);
+      return true;
+    } else {
+      console.error(
+        "GeoUtil.computerEdgeForTwoWall**********************************************************"
+      );
+      return false;
+    }
+  }
+
+  //wallId1,wallId2已经相交,这里只是为了算出两堵墙edge的start和end
+  //不纠正edge与墙的中心线平行
+  updateEdgeForTwoWall(roadId1, roadId2) {
+    //console.log('计算'+wallId1+'和'+wallId2+'相交的edge交点');
+    console.log("开始执行EdgeUtil.updateEdgeForTwoWall");
+
+    let road1 = dataService.getRoad(roadId1);
+    let startPoint1 = dataService.getPoint(road1.startId);
+    let endPoint1 = dataService.getPoint(road1.endId);
+    this.computerDefaultEdge(roadId1);
+    let leftEdge1 = dataService.getEdge(road1.leftEdgeId);
+    let rightEdge1 = dataService.getEdge(road1.rightEdgeId);
+    let lineLeft1 = this.getMidLine(leftEdge1);
+    let lineRight1 = this.getMidLine(rightEdge1);
+
+    let road2 = dataService.getRoad(roadId2);
+    this.computerDefaultEdge(roadId2);
+    // let startPoint2 = dataService.getPoint(road2.startId);
+    // let endPoint2 = dataService.getPoint(road2.endId);
+    let leftEdge2 = dataService.getEdge(road2.leftEdgeId);
+    let rightEdge2 = dataService.getEdge(road2.rightEdgeId);
+    let lineLeft2 = this.getMidLine(leftEdge2);
+    let lineRight2 = this.getMidLine(rightEdge2);
+
+    let angleInfo = GeoUtil.AngleForWall2(roadId1, roadId2);
+    let angle = null;
+    if (angleInfo == null) {
+      return false;
+    } else {
+      angle = angleInfo.angle;
+    }
+    let newEdgePoint1, newEdgePoint2;
+
+    //start-start
+    //left-right,right-left
+    if (road1.startId == road2.startId) {
+      //如果墙的角度过大,不能计算edge对应的line的交点
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+      mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+
+      mathUtil.clonePoint(rightEdge2.start, newEdgePoint1);
+      mathUtil.clonePoint(leftEdge2.start, newEdgePoint2);
+      return true;
+    }
+    //start-end
+    //left-left,right-right
+    else if (road1.startId == road2.endId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+
+      mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+
+      mathUtil.clonePoint(leftEdge2.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge2.end, newEdgePoint2);
+      return true;
+    }
+    //end-start
+    //left-left,right-right
+    else if (road1.endId == road2.startId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+
+      mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+
+      mathUtil.clonePoint(leftEdge2.start, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge2.start, newEdgePoint2);
+      return true;
+    }
+    //end-end
+    //left-right,right-left
+    else if (road1.endId == road2.endId) {
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+
+      mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+      mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+
+      mathUtil.clonePoint(rightEdge2.end, newEdgePoint1);
+      mathUtil.clonePoint(leftEdge2.end, newEdgePoint2);
+      return true;
+    } else {
+      console.error(
+        "GeoUtil.updateEdgeForTwoWall**********************************************************"
+      );
+      return false;
+    }
+  }
+
+  //wallId1,wallId2已经相交于一点
+  //只是计算wallId1,wallId2之间的一个edge
+  //wallId1和wallId2相交的交点是join。以join为中心,wallId1逆时针旋转到wallId2,他们之间的那个edge
+  //方法是:join,wallId1另一头的点p1,计算后的edge交点p2。这三个点是逆时针即可
+  updateSingleEdgeForTwoWall(roadId1, roadId2) {
+    //console.log('更新'+wallId1+'和'+wallId2+'一侧相交的edge交点');
+    console.log("开始执行EdgeUtil.updateSingleEdgeForTwoWall");
+    let road1 = dataService.getRoad(roadId1);
+    let startPoint1 = dataService.getPoint(road1.startId);
+    let endPoint1 = dataService.getPoint(road1.endId);
+    this.computerDefaultEdge(roadId1);
+    let leftEdge1 = dataService.getEdge(road1.leftEdgeId);
+    let rightEdge1 = dataService.getEdge(road1.rightEdgeId);
+    let lineLeft1 = this.getMidLine(leftEdge1);
+    let lineRight1 = this.getMidLine(rightEdge1);
+
+    let road2 = dataService.getRoad(roadId2);
+    this.computerDefaultEdge(roadId2);
+    // let startPoint2 = dataService.getPoint(road2.startId);
+    // let endPoint2 = dataService.getPoint(road2.endId);
+    let leftEdge2 = dataService.getEdge(road2.leftEdgeId);
+    let rightEdge2 = dataService.getEdge(road2.rightEdgeId);
+    let lineLeft2 = this.getMidLine(leftEdge2);
+    let lineRight2 = this.getMidLine(rightEdge2);
+
+    let angleInfo = GeoUtil.AngleForWall2(roadId1, roadId2);
+    let angle = null;
+    if (angleInfo == null) {
+      return false;
+    } else {
+      angle = angleInfo.angle;
+    }
+    let newEdgePoint1, newEdgePoint2;
+
+    let points = [];
+
+    //start-start
+    //left-right,right-left
+    if (road1.startId == road2.startId) {
+      points.push(startPoint1);
+      points.push(endPoint1);
+      //如果墙的角度过大,不能计算edge对应的line的交点
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+
+      points[2] = newEdgePoint1;
+      if (mathUtil.isClockwise(points)) {
+        mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+        mathUtil.clonePoint(rightEdge2.start, newEdgePoint1);
+      } else {
+        mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+        mathUtil.clonePoint(leftEdge2.start, newEdgePoint2);
+      }
+      return true;
+    }
+    //start-end
+    //left-left,right-right
+    else if (road1.startId == road2.endId) {
+      points.push(startPoint1);
+      points.push(endPoint1);
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(startPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(startPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+      points[2] = newEdgePoint1;
+      if (mathUtil.isClockwise(points)) {
+        mathUtil.clonePoint(leftEdge1.start, newEdgePoint1);
+        mathUtil.clonePoint(leftEdge2.end, newEdgePoint1);
+      } else {
+        mathUtil.clonePoint(rightEdge1.start, newEdgePoint2);
+        mathUtil.clonePoint(rightEdge2.end, newEdgePoint2);
+      }
+      return true;
+    }
+    //end-start
+    //left-left,right-right
+    else if (road1.endId == road2.startId) {
+      points.push(endPoint1);
+      points.push(startPoint1);
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineLeft2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineRight1, lineRight2);
+      }
+      points[2] = newEdgePoint1;
+      if (mathUtil.isClockwise(points)) {
+        mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+        mathUtil.clonePoint(leftEdge2.start, newEdgePoint1);
+      } else {
+        mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+        mathUtil.clonePoint(rightEdge2.start, newEdgePoint2);
+      }
+      return true;
+    }
+    //end-end
+    //left-right,right-left
+    else if (road1.endID == road2.endId) {
+      points.push(endPoint1);
+      points.push(startPoint1);
+      if (angle > Setting.Size.MaxAngle) {
+        newEdgePoint1 = mathUtil.getJoinLinePoint(endPoint1, lineLeft1);
+        newEdgePoint2 = mathUtil.getJoinLinePoint(endPoint1, lineRight1);
+      } else {
+        newEdgePoint1 = mathUtil.getIntersectionPoint(lineLeft1, lineRight2);
+        newEdgePoint2 = mathUtil.getIntersectionPoint(lineLeft2, lineRight1);
+      }
+      points[2] = newEdgePoint1;
+      if (mathUtil.isClockwise(points)) {
+        mathUtil.clonePoint(leftEdge1.end, newEdgePoint1);
+        mathUtil.clonePoint(rightEdge2.end, newEdgePoint1);
+      } else {
+        mathUtil.clonePoint(rightEdge1.end, newEdgePoint2);
+        mathUtil.clonePoint(leftEdge2.end, newEdgePoint2);
+      }
+      return true;
+    } else {
+      console.error(
+        "GeoUtil.wallintersect**********************************************************"
+      );
+      return false;
+    }
+  }
+
+  //和updateSingleEdgeForTwoWall的功能相似,只是更新wallId对应pointId这一头的。pointId的parent无论有几个都支持
+  updateEdgeForSingleWall(roadId, pointId) {
+    //console.log('更新'+pointId+'对应'+wallId+'两侧相交的edge交点');
+    //console.log('开始执行EdgeUtil.updateEdgeForSingleWall');
+    let road = dataService.getRoad(roadId);
+    let point = dataService.getPoint(pointId);
+    let parent = point.getParent();
+    let dir = null;
+    if (road.startId == pointId) {
+      dir = "start";
+    } else if (road.endId == pointId) {
+      dir = "end";
+    } else {
+      console.error(
+        "EdgeUtil.updateEdgeForWall*****************************************************"
+      );
+    }
+
+    if (Object.keys(parent).length == 1) {
+      this.updateDefaultEdge(roadId, dir);
+    } else if (Object.keys(parent).length == 2) {
+      this.updateEdgeForTwoWall(Object.keys(parent)[0], Object.keys(parent)[1]);
+    } else if (Object.keys(parent).length > 2) {
+      let info = GeoUtil.wallIdForMinAngle(pointId, roadId);
+      let roadId1 = info.min0.roadId;
+      let roadId2 = info.min1.roadId;
+
+      this.updateSingleEdgeForTwoWall(roadId, roadId2);
+      this.updateSingleEdgeForTwoWall(roadId1, roadId);
+    }
+  }
+
+  //pointId的parent可以是1,2,>2
+  //更新pointId对应的全部edge端点
+  updateEdgeForMulWall(pointId) {
+    //console.log('更新 '+pointId+' 那一圈的edge端点');
+    console.log("开始执行EdgeUtil.updateEdgeForMulWall");
+    let point = dataService.getPoint(pointId);
+    let parent = point.getParent();
+
+    if (Object.keys(parent).length == 1) {
+      let dir = GeoUtil.getDirction(pointId, Object.keys(parent)[0]);
+      this.updateDefaultEdge(Object.keys(parent)[0], dir);
+    } else if (Object.keys(parent).length == 2) {
+      this.updateEdgeForTwoWall(Object.keys(parent)[0], Object.keys(parent)[1]);
+    } else if (Object.keys(parent).length > 2) {
+      //起始墙:Object.keys(parent)[0]
+      //开始
+      let _startRoadId = Object.keys(parent)[0];
+      let startRoadId = Object.keys(parent)[0];
+      //pointId是圆心
+      let info = GeoUtil.wallIdForMinAngle(pointId, startRoadId);
+      //有错
+      if (
+        info.min0.angle == 0 ||
+        info.min1.angle == 0 ||
+        info.min0.angle == 360 ||
+        info.min1.angle == 360
+      ) {
+        console.error(
+          "EdgeUtil.updateEdgeForMulWall*******************************************************************1"
+        );
+        return false;
+      }
+      //终止
+      let endRoadId = info.min0.roadId;
+      //按照逆时针遍历
+      let count = 0; //防止死循环(最多11个交点)
+      while (info.min1.roadId != endRoadId) {
+        if (count > 10) {
+          console.error("updateEdgeForMulWall出错了!!!");
+        }
+
+        this.updateSingleEdgeForTwoWall(startRoadId, info.min1.roadId);
+        startRoadId = info.min1.roadId;
+        info = GeoUtil.wallIdForMinAngle(pointId, startRoadId);
+        ++count;
+      }
+      this.updateSingleEdgeForTwoWall(startRoadId, endRoadId);
+      this.updateSingleEdgeForTwoWall(endRoadId, _startRoadId);
+    } else {
+      console.error(
+        "EdgeUtil.updateEdgeForMulWall***********************************************************************2"
+      );
+      return false;
+    }
+  }
+
+  // //pointId1和pointId2是wallId的两个端点,pointId1移动到和pointId2重合的位置
+  // //这个函数解决:pointId1和pointId2的坐标重合了edge坐标
+  // updateEdgeForSamePosition(pointId1, pointId2, roadId) {
+  //   let road = dataService.getRoad(roadId);
+  //   let point1 = dataService.getPoint(pointId1);
+  //   let point2 = dataService.getPoint(pointId2);
+  //   if (!mathUtil.equalForPoint(point1, point2)) {
+  //     return;
+  //   }
+
+  //   dataService.getEdge
+
+  //   let vLeft = globalObjects.managerVectors.getVector(this.leftEdgeId);
+  //   let vRight = globalObjects.managerVectors.getVector(this.rightEdgeId);
+
+  //   if (pointId1 == wall.geometry.start && pointId2 == wall.geometry.end) {
+  //     mathUtil.clonePoint(vLeft.geometry.start, vLeft.geometry.end);
+  //     mathUtil.clonePoint(vRight.geometry.start, vRight.geometry.end);
+  //   } else if (
+  //     pointId1 == wall.geometry.end &&
+  //     pointId2 == wall.geometry.start
+  //   ) {
+  //     mathUtil.clonePoint(vLeft.geometry.end, vLeft.geometry.start);
+  //     mathUtil.clonePoint(vRight.geometry.end, vRight.geometry.start);
+  //   }
+  //   return;
+  // }
+}
+
+const edgeService = new EdgeService();
+export { edgeService };

+ 308 - 0
src/graphic/Service/ElementService.js

@@ -0,0 +1,308 @@
+import Point from "../Geometry/Point.js";
+import Line from "../Geometry/Line.js";
+import ElementEvents from "../enum/ElementEvents.js";
+import { listenLayer } from "../ListenLayer";
+import Constant from "../Constant";
+import { dataService } from "./DataService.js";
+import { mathUtil } from "../MathUtil.js";
+import { wallService } from "./WallService.js";
+import { coordinate } from "../Coordinate.js";
+
+export class ElementService {
+  constructor() {
+    this.startAddWall = null;
+    this.newWall = null;
+    this.symbolPoints = {
+      Start: null,
+      End: null,
+    };
+    this.checkLines = {
+      X: null,
+      Y: null,
+    };
+
+    this.vCheckLines = {
+      X: null,
+      Y: null,
+    };
+
+    this.signLine1 = {
+      X: null,
+      Y: null,
+    };
+    this.signLine2 = {
+      X: null,
+      Y: null,
+    };
+    this.init();
+  }
+
+  init() {
+    this.startAddWall = new Point(0, 0);
+    this.startAddWall.name = ElementEvents.StartAddWall;
+
+    this.newWall = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.newWall.name = ElementEvents.NewWall;
+
+    this.symbolPoints.Start = new Point(0, 0);
+    this.symbolPoints.Start.name = ElementEvents.StartSymbolPoints;
+
+    this.symbolPoints.End = new Point(0, 0);
+    this.symbolPoints.End.name = ElementEvents.EndSymbolPoints;
+
+    this.checkLines.X = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.checkLines.X.name = ElementEvents.CheckLinesX;
+
+    this.checkLines.Y = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.checkLines.Y.name = ElementEvents.CheckLinesY;
+
+    this.vCheckLines.X = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.vCheckLines.X.name = ElementEvents.VCheckLinesX;
+
+    this.vCheckLines.Y = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.vCheckLines.Y.name = ElementEvents.VCheckLinesY;
+
+    this.signLine1 = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.signLine1.name = ElementEvents.SignLine1;
+
+    this.signLine2 = new Line({ x: 0, y: 0 }, { x: 1, y: 1 });
+    this.signLine2.name = ElementEvents.SignLine2;
+  }
+
+  showStartAddWall() {
+    this.startAddWall.display = true;
+  }
+
+  hideStartAddWall() {
+    this.startAddWall.display = false;
+  }
+
+  setStartAddWall(position) {
+    this.startAddWall.setPosition(position);
+  }
+
+  showSymbolPoints() {
+    this.symbolPoints.Start.display = true;
+    this.symbolPoints.End.display = true;
+  }
+
+  hideSymbolPoints() {
+    this.symbolPoints.Start.display = false;
+    this.symbolPoints.End.display = false;
+  }
+
+  setSymbolPoints(start, end) {
+    this.symbolPoints.Start.setPosition(start);
+    this.symbolPoints.End.setPosition(end);
+  }
+
+  showNewWall() {
+    this.newWall.display = true;
+  }
+
+  hideNewWall() {
+    this.newWall.display = false;
+  }
+
+  setNewWall(point1, point2) {
+    this.newWall.setPositions(point1, point2);
+  }
+
+  setNewWallStartPosition(startPosition) {
+    this.newWall.start.x = startPosition.x;
+    this.newWall.start.y = startPosition.y;
+  }
+
+  setNewWallEndPosition(endPosition) {
+    this.newWall.end.x = endPosition.x;
+    this.newWall.end.y = endPosition.y;
+  }
+
+  setNewWallState(state) {
+    this.newWall.state = state;
+  }
+
+  showCheckLinesX() {
+    this.checkLines.X.display = true;
+  }
+
+  hideCheckLinesX() {
+    this.checkLines.X.display = false;
+  }
+
+  setCheckLinesX(point1, point2) {
+    this.checkLines.X.setPositions(point1, point2);
+  }
+
+  showCheckLinesY() {
+    this.checkLines.Y.display = true;
+  }
+
+  hideCheckLinesY() {
+    this.checkLines.Y.display = false;
+  }
+
+  setCheckLinesY(point1, point2) {
+    this.checkLines.Y.setPositions(point1, point2);
+  }
+
+  showVCheckLinesX() {
+    this.vCheckLines.X.display = true;
+  }
+
+  hideVCheckLinesX() {
+    this.vCheckLines.X.display = false;
+  }
+
+  setVCheckLinesX(point1, point2) {
+    this.vCheckLines.X.setPositions(point1, point2);
+  }
+
+  showVCheckLinesY() {
+    this.vCheckLines.Y.display = true;
+  }
+
+  hideVCheckLinesY() {
+    this.vCheckLines.Y.display = false;
+  }
+
+  setVCheckLinesY(point1, point2) {
+    this.vCheckLines.Y.setPositions(point1, point2);
+  }
+
+  setSignLine1(point1, point2) {
+    this.signLine1.setPositions(point1, point2);
+  }
+
+  showSignLine1() {
+    this.signLine1.display = true;
+  }
+
+  hideSignLine1() {
+    this.signLine1.display = false;
+  }
+
+  setSignLine2(point1, point2) {
+    this.signLine2.setPositions(point1, point2);
+  }
+
+  showSignLine2() {
+    this.signLine2.display = true;
+  }
+
+  hideSignLine2() {
+    this.signLine2.display = false;
+  }
+
+  hideAll() {
+    this.hideCheckLinesX();
+    this.hideCheckLinesY();
+    this.hideStartAddWall();
+    this.hideNewWall();
+    this.hideSymbolPoints();
+    this.hideVCheckLinesX();
+    this.hideVCheckLinesY();
+    this.hideSignLine1();
+    this.hideSignLine2();
+  }
+
+  execute(startPosition, position) {
+    this.hideVCheckLinesX();
+    this.hideVCheckLinesY();
+    this.hideCheckLinesX();
+    this.hideCheckLinesY();
+
+    //垂直校验
+    if (startPosition) {
+      let start = coordinate.getXYFromScreen({
+        x: 0,
+        y: 0,
+      });
+      let end = coordinate.getXYFromScreen({
+        x: coordinate.width,
+        y: coordinate.height,
+      });
+      if (Math.abs(position.x - startPosition.x) < Constant.minAdsorb) {
+        position.x = startPosition.x;
+
+        start.x = position.x;
+        end.x = position.x;
+        this.setVCheckLinesX(start, end);
+        this.showVCheckLinesX();
+      }
+
+      if (Math.abs(position.y - startPosition.y) < Constant.minAdsorb) {
+        position.y = startPosition.y;
+
+        start.y = position.y;
+        end.y = position.y;
+        this.setVCheckLinesY(start, end);
+        this.showVCheckLinesY();
+      }
+
+      if (mathUtil.equalPoint(position, startPosition)) {
+        this.hideVCheckLinesX();
+        this.hideVCheckLinesY();
+      }
+    }
+
+    if (!this.vCheckLines.Y.display && !this.vCheckLines.Y.display) {
+      if (listenLayer.modifyPoint) {
+        if (listenLayer.modifyPoint.linkedPointIdX) {
+          const linkedPointX = dataService.getPoint(
+            listenLayer.modifyPoint.linkedPointIdX
+          );
+          this.setCheckLinesX(linkedPointX, position);
+          this.showCheckLinesX();
+        }
+
+        if (listenLayer.modifyPoint.linkedPointIdY) {
+          const linkedPointY = dataService.getPoint(
+            listenLayer.modifyPoint.linkedPointIdY
+          );
+          this.setCheckLinesY(linkedPointY, position);
+          this.showCheckLinesY();
+        }
+      }
+    }
+  }
+
+  //pointId是角度的顶点
+  //exceptPointId表示position对应的pointId(如果有的话)
+  checkAngle(position, pointId, exceptPointId) {
+    //type:1表示90°,2表示180°
+    function ajust(position, point1, point2, type) {
+      let line = mathUtil.createLine1(point1, point2);
+      let join = null;
+      if (type == 1) {
+        let vLine = mathUtil.getVerticalLine(line, point1);
+        join = mathUtil.getJoinLinePoint(position, vLine);
+      } else if (type == 2) {
+        join = mathUtil.getJoinLinePoint(position, line);
+      }
+      return join;
+    }
+
+    let points = wallService.getNeighPoints(pointId, exceptPointId);
+    let point = dataService.getPoint(pointId);
+    let newPosition = null;
+    for (let i = 0; i < points.length; ++i) {
+      let angle = mathUtil.Angle(point, position, points[i]);
+      if (Math.abs((angle / Math.PI) * 180 - 90) < 5) {
+        newPosition = ajust(position, point, points[i], 1);
+      } else if (
+        Math.abs((angle / Math.PI) * 180) < 5 ||
+        Math.abs((angle / Math.PI) * 180 - 180) < 5
+      ) {
+        newPosition = ajust(position, point, points[i], 2);
+      }
+      if (newPosition != null) {
+        return newPosition;
+      }
+    }
+    return newPosition;
+  }
+}
+
+const elementService = new ElementService();
+export { elementService };

+ 178 - 0
src/graphic/Service/FurnitureService.js

@@ -0,0 +1,178 @@
+import VectorType from "../enum/VectorType.js";
+import Furniture from "../Geometry/Furniture.js";
+import { dataService } from "./DataService.js";
+import { Furnitures } from "../enum/UIEvents";
+
+export class FurnitureService {
+  constructor() {
+    this.$app = null;
+    this.furnitures = null;
+  }
+  fetchFurnitures() {
+    if (this.furnitures) {
+      return;
+    }
+    this.furnitures = {};
+    for (let key in Furnitures) {
+      // 测试代码
+      if (key != Furnitures.TV) {
+        continue;
+      }
+
+      this.$app.store
+        .getAppImage(`images/cad/furnitures/${Furnitures[key]}.svg`)
+        .then((img) => {
+          this.furnitures[Furnitures[key]] = img;
+          console.log(this.furnitures);
+        })
+        .catch((err) => {
+          console.error(err);
+        });
+    }
+  }
+
+  // 新建component
+  createFurniture(position, geoType, vectorId) {
+    let furniture = null;
+    switch (geoType) {
+      case VectorType.TV:
+        furniture = new Furniture(position, vectorId, VectorType.TV);
+        break;
+      case VectorType.CombinationSofa:
+        furniture = new Furniture(
+          position,
+          vectorId,
+          VectorType.CombinationSofa
+        );
+        break;
+      case VectorType.SingleSofa:
+        furniture = new Furniture(position, vectorId, VectorType.SingleSofa);
+        break;
+      case VectorType.TeaTable:
+        furniture = new Furniture(position, vectorId, VectorType.TeaTable);
+        break;
+      case VectorType.Carpet:
+        furniture = new Furniture(position, vectorId, VectorType.Carpet);
+        break;
+      case VectorType.Plant:
+        furniture = new Furniture(position, vectorId, VectorType.Plant);
+        break;
+      case VectorType.DiningTable:
+        furniture = new Furniture(position, vectorId, VectorType.DiningTable);
+        break;
+
+      case VectorType.DoubleBed:
+        furniture = new Furniture(position, vectorId, VectorType.DoubleBed);
+        break;
+      case VectorType.SingleBed:
+        furniture = new Furniture(position, vectorId, VectorType.SingleBed);
+        break;
+      case VectorType.Wardrobe:
+        furniture = new Furniture(position, vectorId, VectorType.Wardrobe);
+        break;
+      case VectorType.Dresser:
+        furniture = new Furniture(position, vectorId, VectorType.Dresser);
+        break;
+      case VectorType.BedsideCupboard:
+        furniture = new Furniture(
+          position,
+          vectorId,
+          VectorType.BedsideCupboard
+        );
+        break;
+      case VectorType.Pillow:
+        furniture = new Furniture(position, vectorId, VectorType.Pillow);
+        break;
+
+      case VectorType.GasStove:
+        furniture = new Furniture(position, vectorId, VectorType.GasStove);
+        break;
+      case VectorType.Cupboard:
+        furniture = new Furniture(position, vectorId, VectorType.Cupboard);
+        break;
+      case VectorType.Bathtub:
+        furniture = new Furniture(position, vectorId, VectorType.Bathtub);
+        break;
+      case VectorType.Closestool:
+        furniture = new Furniture(position, vectorId, VectorType.Closestool);
+        break;
+      case VectorType.Washstand:
+        furniture = new Furniture(position, vectorId, VectorType.Washstand);
+        break;
+
+      case VectorType.Desk:
+        furniture = new Furniture(position, vectorId, VectorType.Desk);
+        break;
+      case VectorType.BalconyChair:
+        furniture = new Furniture(position, vectorId, VectorType.BalconyChair);
+        break;
+      case VectorType.Elevator:
+        furniture = new Furniture(position, vectorId, VectorType.Elevator);
+        break;
+    }
+
+    dataService.addFurniture(furniture);
+    return furniture;
+  }
+
+  isFurniture(geoType) {
+    switch (geoType) {
+      case VectorType.TV:
+        return true;
+      case VectorType.CombinationSofa:
+        return true;
+      case VectorType.SingleSofa:
+        return true;
+      case VectorType.TeaTable:
+        return true;
+      case VectorType.Carpet:
+        return true;
+      case VectorType.Plant:
+        return true;
+      case VectorType.DiningTable:
+        return true;
+      case VectorType.DoubleBed:
+        return true;
+      case VectorType.SingleBed:
+        return true;
+      case VectorType.Wardrobe:
+        return true;
+      case VectorType.Dresser:
+        return true;
+      case VectorType.BedsideCupboard:
+        return true;
+      case VectorType.Pillow:
+        return true;
+      case VectorType.GasStove:
+        return true;
+      case VectorType.Cupboard:
+        return true;
+      case VectorType.Bathtub:
+        return true;
+      case VectorType.Closestool:
+        return true;
+      case VectorType.Washstand:
+        return true;
+      case VectorType.Desk:
+        return true;
+      case VectorType.BalconyChair:
+        return true;
+      case VectorType.Elevator:
+        return true;
+    }
+    return false;
+  }
+
+  setFurnitureInfo(furnitureInfo) {
+    let furniture = dataService.getFurniture(furnitureInfo.vectorId);
+    furniture.vectorId = furnitureInfo.vectorId;
+    furniture.angle = furnitureInfo.angle;
+    furniture.center = JSON.parse(JSON.stringify(furnitureInfo.center));
+  }
+  getFurniture(name) {
+    return this.furnitures[name];
+  }
+}
+
+const furnitureService = new FurnitureService();
+export { furnitureService };

+ 75 - 0
src/graphic/Service/HistoryService.js

@@ -0,0 +1,75 @@
+export class HistoryService {
+    constructor() {
+        this.history = {
+            records: [],
+            currentRecordIndex: -1,
+            state: {
+                pre: 0,
+                next: 0,
+            },
+        }
+    }
+
+    getCurrentRecordIndex() {
+        return this.history.currentRecordIndex
+    }
+
+    getHistoryRecord() {
+        if (this.history.currentRecordIndex == null || this.history.records.length == 0) {
+            return null
+        } else {
+            return this.history.records[this.history.currentRecordIndex]
+        }
+    }
+
+    getHistoryRecords() {
+        return this.history.records
+    }
+
+    getHistoryState() {
+        return this.history.state
+    }
+
+    addHistoryRecord(item) {
+        const len = this.history.records.length
+        if (len == 0) {
+            this.history.records.push(item)
+            this.history.currentRecordIndex = 0
+        } else if (this.history.currentRecordIndex + 1 == len) {
+            this.history.records.push(item)
+            ++this.history.currentRecordIndex
+        }
+        // 覆盖
+        else {
+            const records = this.history.records.slice(0, this.history.currentRecordIndex + 1)
+            records.push(item)
+            this.history.records = records
+            ++this.history.currentRecordIndex
+        }
+    }
+
+    setHistoryState(pre, next) {
+        this.history.state.pre = pre
+        this.history.state.next = next
+    }
+
+    undoHistoryRecord() {
+        --this.history.currentRecordIndex
+    }
+
+    redoHistoryRecord() {
+        ++this.history.currentRecordIndex
+    }
+
+    clearHistoryRecord() {
+        this.history.records = []
+        this.setHistoryState(0, 0)
+    }
+    hasRecords() {
+        return this.history.records.length > 0
+    }
+}
+
+const historyService = new HistoryService()
+window.historyService = historyService
+export { historyService }

+ 104 - 0
src/graphic/Service/KeyService.js

@@ -0,0 +1,104 @@
+import Constant from "../Constant";
+import { dataService } from "./DataService";
+import { stateService } from "./StateService";
+import { elementService } from "./ElementService";
+import { coordinate } from "../Coordinate";
+import VectorType from "../enum/VectorType";
+import SelectState from "../enum/SelectState.js";
+import LayerEvents from "../enum/LayerEvents.js";
+import { componentService } from "./ComponentService";
+import { symbolService } from "./SymbolService";
+import { furnitureService } from "./FurnitureService";
+import { tagService } from "./TagService";
+import { addRoad } from "../Controls/AddRoad";
+import UIEvents from "../enum/UIEvents.js";
+import { wallService } from "./WallService";
+import { moveRectangle } from "../Controls/MoveRectangle";
+
+export default class KeyService {
+  constructor() {
+    this.wallLineStyle = null;
+    this.targetItem = null;
+  }
+
+  clearTargetItem() {
+    this.targetItem = null;
+  }
+
+  copy(focusItem, mousePosition) {
+    if (focusItem && focusItem.type) {
+      this.targetItem = {
+        vectorId: focusItem.vectorId,
+        type: focusItem.type,
+      };
+    }
+
+    this.paste(mousePosition);
+    this.clearTargetItem();
+  }
+
+  //复制
+  paste(mousePosition) {
+    let flag = false;
+    const position = coordinate.getXYFromScreen(mousePosition);
+
+    // if (this.targetItem && componentService.isComponent(this.targetItem.type)) {
+    //     const component = dataService.getComponent(this.targetItem.vectorId)
+    //     const newComponent = componentService.createComponent(position, this.targetItem.type)
+    //     newComponent.angle = component.angle
+    //     newComponent.sideWidth = component.sideWidth
+    //     newComponent.sideThickness = component.sideThickness
+    //     stateService.setSelectItem(newComponent.vectorId, this.targetItem.type, SelectState.All)
+    //     stateService.setDraggingItem(stateService.selectItem)
+    //     stateService.setEventName(LayerEvents.MoveComponent)
+    //     flag = true
+    // } else if (this.targetItem && this.targetItem.type == VectorType.Tag) {
+    //     let tag = dataService.getTag(this.targetItem.vectorId)
+    //     const newTag = tagService.createTag(position)
+    //     newTag.title = tag.title
+    //     newTag.des = tag.des
+    //     newTag.unit = tag.unit
+    //     newTag.adding = tag.adding
+    //     newTag.name = tag.name
+
+    //     stateService.setSelectItem(newTag.vectorId, this.targetItem.type, SelectState.All)
+    //     stateService.setDraggingItem(stateService.selectItem)
+    //     stateService.setEventName(LayerEvents.MoveTag)
+    //     flag = true
+    // } else if (this.targetItem && furnitureService.isFurniture(this.targetItem.type)) {
+    //     const furniture = dataService.getFurniture(this.targetItem.vectorId)
+    //     const newFurniture = furnitureService.createFurniture(position, this.targetItem.type)
+    //     newFurniture.angle = furniture.angle
+    //     newFurniture.zoom = furniture.zoom
+    //     stateService.setSelectItem(newFurniture.vectorId, this.targetItem.type, SelectState.All)
+    //     stateService.setDraggingItem(stateService.selectItem)
+    //     stateService.setEventName(LayerEvents.MoveFurniture)
+    //     moveRectangle.clear()
+    //     flag = true
+    // }
+
+    if (this.targetItem && furnitureService.isFurniture(this.targetItem.type)) {
+      const furniture = dataService.getFurniture(this.targetItem.vectorId);
+      const newFurniture = furnitureService.createFurniture(
+        position,
+        this.targetItem.type
+      );
+      newFurniture.angle = furniture.angle;
+      newFurniture.zoom = furniture.zoom;
+      stateService.setSelectItem(
+        newFurniture.vectorId,
+        this.targetItem.type,
+        SelectState.All
+      );
+      stateService.setDraggingItem(stateService.selectItem);
+      stateService.setEventName(LayerEvents.MoveFurniture);
+      moveRectangle.clear();
+      flag = true;
+    }
+    stateService.clearFocusItem();
+    return flag;
+  }
+}
+
+const keyService = new KeyService();
+export { keyService };

+ 522 - 0
src/graphic/Service/MeasureService.js

@@ -0,0 +1,522 @@
+import { dataService } from "./DataService";
+import { mathUtil } from "../MathUtil.js";
+import { coordinate } from "../Coordinate";
+import Constant from "../Constant";
+
+/*
+1. 所有的点都投影到上下左右四个测量区域并排序
+2. 分别从起点开始向终点一段段画测量线,如果太短(按照像素来)则合并到上一条线段上。确定好当前线段的起始点后,开始画
+*/
+
+export default class MeasureService {
+  constructor() {
+    this.pad = {
+      top: 60,
+      bottom: 60,
+      left: 265,
+      right: 265,
+    };
+
+    this.region = {};
+    this.measureLines = {
+      top: [],
+      bottom: [],
+      left: [],
+      right: [],
+    };
+    this.minDis = null;
+    //1 英尺=0.3048 米
+    //this.ftUnit = 0.3048
+    this.unit = "m";
+
+    this.defalutMeasurePad = {
+      bottom: 60,
+      right: 265,
+    };
+  }
+
+  /**
+   * 设置边距
+   * @param {*} config
+   */
+  padding(config = {}) {
+    Object.assign(this.pad, config);
+    Object.assign(this.defalutMeasurePad, config);
+  }
+
+  updatePad(pad) {
+    Object.assign(this.pad, pad);
+  }
+
+  updateRegion(download) {
+    this.region.top = this.pad.top;
+    this.region.bottom = coordinate.height - this.pad.bottom;
+    this.region.left = this.pad.left;
+    this.region.right = coordinate.width - this.pad.right;
+    // if (download) {   //不能用下面的,因为Constant.cadImg_Height / Constant.ratio和coordinate.height不同
+    //     this.region.bottom = Constant.cadImg_Height / Constant.ratio - this.pad.bottom
+    //     this.region.right = Constant.cadImg_Width / Constant.ratio - this.pad.right
+    // }
+
+    // let leftTop = coordinate.getXYFromScreen({
+    //     x: this.region.left,
+    //     y: this.region.top,
+    // })
+
+    // let rightBottom = coordinate.getXYFromScreen({
+    //     x: this.region.right,
+    //     y: this.region.bottom,
+    // })
+
+    // this.region.top = leftTop.y
+    // this.region.left = leftTop.x
+    // this.region.bottom = rightBottom.y
+    // this.region.right = rightBottom.x
+  }
+
+  //更新测量线
+  update() {
+    if (this.minDis == null) {
+      this.minDis = 100 / coordinate.res;
+    }
+    let tops = [];
+    let bottoms = [];
+    let lefts = [];
+    let rights = [];
+    let measurePoints = [];
+
+    let data = dataService.getFloorData();
+    if (!data) {
+      return;
+    }
+
+    const points = dataService.getPoints();
+    for (let key in points) {
+      const point = points[key];
+      measurePoints.push({
+        x: point.x,
+        y: point.y,
+      });
+    }
+
+    function sortNumber_topbottom(a, b) {
+      return b.y - a.y;
+    }
+
+    function sortNumber_leftright(a, b) {
+      return a.x - b.x;
+    }
+
+    tops = [].concat(measurePoints).sort(sortNumber_topbottom.bind(this));
+    bottoms = [].concat(tops);
+    bottoms.reverse();
+    lefts = [].concat(measurePoints).sort(sortNumber_leftright.bind(this));
+    rights = [].concat(lefts);
+    rights.reverse();
+
+    let start = null;
+    let end = null;
+
+    this.measureLines.top = [];
+    for (let i = 0; i < tops.length; ++i) {
+      if (i == 0) {
+        start = tops[0].x;
+        end = tops[0].x;
+        this.measureLines.top.push({
+          x: tops[0].x,
+          y: this.region.top,
+        });
+      } else {
+        if (tops[i].x >= start && tops[i].x <= end) {
+          continue;
+        } else {
+          start = Math.min(start, tops[i].x);
+          end = Math.max(end, tops[i].x);
+          if (start != end) {
+            this.measureLines.top.push({
+              x: tops[i].x,
+              y: this.region.top,
+            });
+          }
+        }
+      }
+    }
+    tops = this.measureLines.top.sort(sortNumber_leftright.bind(this));
+    this.measureLines.top = [];
+    this.measureLines.top.push(tops[0]);
+    for (let i = 0; i < tops.length - 1; ++i) {
+      start = tops[i];
+      end = null;
+      for (let j = i + 1; j < tops.length; ++j) {
+        end = tops[j];
+        if (Math.abs(start.x - end.x) < this.minDis) {
+          end = null;
+          ++i;
+          continue;
+        } else {
+          break;
+        }
+      }
+      if (end != null) {
+        this.measureLines.top.push(end);
+      } else if (i == tops.length - 1) {
+        let len = this.measureLines.top.length;
+        this.measureLines.top[len - 1] = tops[i];
+        break;
+      }
+    }
+
+    this.measureLines.bottom = [];
+    for (let i = 0; i < bottoms.length; ++i) {
+      if (i == 0) {
+        start = bottoms[0].x;
+        end = bottoms[0].x;
+        this.measureLines.bottom.push({
+          x: bottoms[0].x,
+          y: this.region.bottom,
+        });
+      } else {
+        if (bottoms[i].x >= start && bottoms[i].x <= end) {
+          continue;
+        } else {
+          start = Math.min(start, bottoms[i].x);
+          end = Math.max(end, bottoms[i].x);
+          if (start != end) {
+            this.measureLines.bottom.push({
+              x: bottoms[i].x,
+              y: this.region.bottom,
+            });
+          }
+        }
+      }
+    }
+    bottoms = this.measureLines.bottom.sort(sortNumber_leftright.bind(this));
+    this.measureLines.bottom = [];
+    this.measureLines.bottom.push(bottoms[0]);
+    for (let i = 0; i < bottoms.length - 1; ++i) {
+      start = bottoms[i];
+      end = null;
+      for (let j = i + 1; j < bottoms.length; ++j) {
+        end = bottoms[j];
+        if (Math.abs(start.x - end.x) < this.minDis) {
+          end = null;
+          ++i;
+          continue;
+        } else {
+          break;
+        }
+      }
+      if (end != null) {
+        this.measureLines.bottom.push(end);
+      } else if (i == bottoms.length - 1) {
+        let len = this.measureLines.bottom.length;
+        this.measureLines.bottom[len - 1] = bottoms[i];
+        break;
+      }
+    }
+
+    this.measureLines.left = [];
+    for (let i = 0; i < lefts.length; ++i) {
+      if (i == 0) {
+        start = lefts[0].y;
+        end = lefts[0].y;
+        this.measureLines.left.push({
+          x: this.region.left,
+          y: lefts[0].y,
+        });
+      } else {
+        if (lefts[i].y >= start && lefts[i].y <= end) {
+          continue;
+        } else {
+          start = Math.min(start, lefts[i].y);
+          end = Math.max(end, lefts[i].y);
+          if (start != end) {
+            this.measureLines.left.push({
+              x: this.region.left,
+              y: lefts[i].y,
+            });
+          }
+        }
+      }
+    }
+    lefts = this.measureLines.left.sort(sortNumber_topbottom.bind(this));
+    this.measureLines.left = [];
+    this.measureLines.left.push(lefts[0]);
+    for (let i = 0; i < lefts.length - 1; ++i) {
+      start = lefts[i];
+      end = null;
+      for (let j = i + 1; j < lefts.length; ++j) {
+        end = lefts[j];
+        if (Math.abs(start.y - end.y) < this.minDis) {
+          end = null;
+          ++i;
+          continue;
+        } else {
+          break;
+        }
+      }
+      if (end != null) {
+        this.measureLines.left.push(end);
+      } else if (i == lefts.length - 1) {
+        let len = this.measureLines.left.length;
+        this.measureLines.left[len - 1] = lefts[i];
+        break;
+      }
+    }
+
+    this.measureLines.right = [];
+    for (let i = 0; i < rights.length; ++i) {
+      if (i == 0) {
+        start = rights[0].y;
+        end = rights[0].y;
+        this.measureLines.right.push({
+          x: this.region.right,
+          y: rights[0].y,
+        });
+      } else {
+        if (rights[i].y >= start && rights[i].y <= end) {
+          continue;
+        } else {
+          start = Math.min(start, rights[i].y);
+          end = Math.max(end, rights[i].y);
+          if (start != end) {
+            this.measureLines.right.push({
+              x: this.region.right,
+              y: rights[i].y,
+            });
+          }
+        }
+      }
+    }
+    rights = this.measureLines.right.sort(sortNumber_topbottom.bind(this));
+    this.measureLines.right = [];
+    this.measureLines.right.push(rights[0]);
+    for (let i = 0; i < rights.length - 1; ++i) {
+      start = rights[i];
+      end = null;
+      for (let j = i + 1; j < rights.length; ++j) {
+        end = rights[j];
+        if (Math.abs(start.y - end.y) < this.minDis) {
+          end = null;
+          ++i;
+          continue;
+        } else {
+          break;
+        }
+      }
+      if (end != null) {
+        this.measureLines.right.push(end);
+      } else if (i == rights.length - 1) {
+        let len = this.measureLines.right.length;
+        this.measureLines.right[len - 1] = rights[i];
+        break;
+      }
+    }
+  }
+
+  /*
+    update() {
+        let tops = []
+        let bottoms = []
+        let lefts = []
+        let rights = []
+
+        let minX = coordinate.center.x;
+        let minY = coordinate.center.y;
+        let maxX = coordinate.center.x;
+        let maxY = coordinate.center.y;
+
+        const points = dataService.getPoints()
+        for (let key in points) {
+            const point = points[key]
+            if(point.y > coordinate.center.y){
+                tops.push({
+                    x: point.x,
+                    y: this.region.top,
+                })
+            }
+            else{
+                bottoms.push({
+                    x:point.x,
+                    y:this.region.bottom
+                })
+            }
+
+            if(point.x<coordinate.center.x){
+                lefts.push({
+                    x: this.region.left,
+                    y: point.y,
+                })
+            }
+            else{
+                rights.push({
+                    x:this.region.right,
+                    y:point.y
+                })
+            }
+
+            if(minX>point.x){
+                minX = point.x
+            }
+            if(maxX<point.x){
+                maxX = point.x
+            }
+
+            if(minY>point.y){
+                minY = point.y
+            }
+            if(maxY<point.y){
+                maxY = point.y
+            }
+        }
+
+        tops.unshift({
+            x:minX,
+            y:this.region.top
+        })
+        tops.push({
+            x:maxX,
+            y:this.region.top
+        })
+
+        bottoms.unshift({
+            x:minX,
+            y:this.region.bottom
+        })
+        bottoms.push({
+            x:maxX,
+            y:this.region.bottom
+        })
+
+        lefts.unshift({
+            x:this.region.left,
+            y:maxY
+        })
+        lefts.push({
+            x:this.region.left,
+            y:minY
+        })
+
+        rights.unshift({
+            x:this.region.right,
+            y:maxY
+        })
+        rights.push({
+            x:this.region.right,
+            y:minY
+        })
+
+        function sortNumber_topbottom(a, b) {
+            return a.x - b.x
+        }
+
+        function sortNumber_leftright(a, b) {
+            return b.y - a.y
+        }
+
+        tops = tops.sort(sortNumber_topbottom.bind(this))
+        bottoms = bottoms.sort(sortNumber_topbottom.bind(this))
+        lefts = lefts.sort(sortNumber_leftright.bind(this))
+        rights = rights.sort(sortNumber_leftright.bind(this))
+
+        this.measureLines.top = []
+        this.measureLines.top.push(tops[0])
+        for (let i = 0; i < tops.length - 1; ++i) {
+            let start = tops[i]
+            let end = null
+            for (let j = i + 1; j < tops.length; ++j) {
+                end = tops[j]
+                if (mathUtil.getDistance(start, end) < this.minDis) {
+                    end = null
+                    ++i
+                    continue
+                } else {
+                    break
+                }
+            }
+            if (end != null) {
+                this.measureLines.top.push(end)
+            } else if (i == tops.length - 1) {
+                let len = this.measureLines.top.length
+                this.measureLines.top[len - 1] = tops[i]
+                break
+            }
+        }
+
+        this.measureLines.bottom = []
+        this.measureLines.bottom.push(bottoms[0])
+        for (let i = 0; i < bottoms.length - 1; ++i) {
+            let start = bottoms[i]
+            let end = null
+            for (let j = i + 1; j < bottoms.length; ++j) {
+                end = bottoms[j]
+                if (mathUtil.getDistance(start, end) < this.minDis) {
+                    end = null
+                    ++i
+                    continue
+                } else {
+                    break
+                }
+            }
+            if (end != null) {
+                this.measureLines.bottom.push(end)
+            } else if (i == bottoms.length - 1) {
+                let len = this.measureLines.bottom.length
+                this.measureLines.bottom[len - 1] = bottoms[i]
+                break
+            }
+        }
+
+        this.measureLines.left = []
+        this.measureLines.left.push(lefts[0])
+        for (let i = 0; i < lefts.length - 1; ++i) {
+            let start = lefts[i]
+            let end = null
+            for (let j = i + 1; j < lefts.length; ++j) {
+                end = lefts[j]
+                if (mathUtil.getDistance(start, end) < this.minDis) {
+                    end = null
+                    ++i
+                    continue
+                } else {
+                    break
+                }
+            }
+            if (end != null) {
+                this.measureLines.left.push(end)
+            } else if (i == lefts.length - 1) {
+                let len = this.measureLines.left.length
+                this.measureLines.left[len - 1] = lefts[i]
+                break
+            }
+        }
+
+        this.measureLines.right = []
+        this.measureLines.right.push(rights[0])
+        for (let i = 0; i < rights.length - 1; ++i) {
+            let start = rights[i]
+            let end = null
+            for (let j = i + 1; j < rights.length; ++j) {
+                end = rights[j]
+                if (mathUtil.getDistance(start, end) < this.minDis) {
+                    end = null
+                    ++i
+                    continue
+                } else {
+                    break
+                }
+            }
+            if (end != null) {
+                this.measureLines.right.push(end)
+            } else if (i == rights.length - 1) {
+                let len = this.measureLines.right.length
+                this.measureLines.right[len - 1] = rights[i]
+                break
+            }
+        }
+    }
+    */
+}
+
+const measureService = new MeasureService();
+export { measureService };

+ 16 - 0
src/graphic/Service/PointService.js

@@ -0,0 +1,16 @@
+import Point from "../Geometry/Point.js";
+import { dataService } from "./DataService.js";
+import { mathUtil } from "../Util/MathUtil";
+
+export default class PointService {
+  constructor() {}
+
+  create(position, vectorId) {
+    let point = new Point(position, vectorId);
+    dataService.addPoint(point);
+    return point;
+  }
+}
+
+const pointService = new PointService();
+export { pointService };

+ 506 - 0
src/graphic/Service/RoadService.js

@@ -0,0 +1,506 @@
+import Point from "../Geometry/Point.js";
+import Road from "../Geometry/Road.js";
+import { dataService } from "./DataService.js";
+import { pointService } from "./PointService.js";
+import { mathUtil } from "../Util/MathUtil.js";
+
+export default class RoadService {
+  constructor() {}
+
+  create(startId, endId, vectorId) {
+    let road = new Road(startId, endId, vectorId);
+    dataService.addRoad(road);
+
+    let startPoint = dataService.getPoint(startId);
+    startPoint.setPointParent(road.vectorId, "start");
+
+    let endPoint = dataService.getPoint(endId);
+    endPoint.setPointParent(road.vectorId, "end");
+
+    let edgePoints = mathUtil.RectangleVertex(startPoint, endPoint, road.width);
+    let leftEdge = new Edge(
+      edgePoints.leftEdgeStart,
+      edgePoints.leftEdgeEnd,
+      road.vectorId
+    );
+    dataService.addEdge(leftEdge);
+
+    let rightEdge = new Edge(
+      edgePoints.rightEdgeStart,
+      edgePoints.rightEdgeEnd,
+      road.vectorId
+    );
+    dataService.addEdge(rightEdge);
+
+    road.setLeftEdge(leftEdge.vectorId);
+    road.setRightEdge(rightEdge.vectorId);
+
+    return road;
+  }
+
+  getMidLine(road) {
+    let startPoint = dataService.getPoint(road.startId);
+    let endPoint = dataService.getPoint(road.endId);
+    let line = mathUtil.createLine(startPoint, endPoint);
+    return line;
+  }
+
+  // roadId对应墙的两个端点。尽量不要改
+  // dir表示:保留start还是保留end(对应端点的parent不用变)
+  splitRoad(roadId, pointId, dir) {
+    const road = dataService.getRoad(roadId);
+    const startPoint = dataService.getPoint(road.startId);
+    const endPoint = dataService.getPoint(road.endId);
+    const point = dataService.getPoint(pointId);
+    if (
+      mathUtil.getDistance(startPoint, point) < Constant.minAdsorb ||
+      mathUtil.getDistance(endPoint, point) < Constant.minAdsorb
+    ) {
+      //console.error('splitRoad********************************************1')
+      //return null
+    }
+
+    let newRoad = null;
+    if (dir == "start") {
+      // 第一步先断开链接
+      delete endPoint.parent[roadId];
+      newRoad = this.create(pointId, road.endId);
+      //修改旧公路的end
+      point.setPointParent(roadId, "end");
+    } else if (dir == "end") {
+      delete startPoint.parent[roadId];
+      newRoad = this.create(road.start, pointId);
+      //修改旧公路的start
+      point.setPointParent(roadId, "start");
+    }
+    return newRoad.vectorId;
+  }
+
+  // roadId的两个端点分别是pointId1,pointId2,获取这个roadId
+  getRoadId(pointId1, pointId2) {
+    const point1 = dataService.getPoint(pointId1);
+    const point2 = dataService.getPoint(pointId2);
+    // 公路可能删除了。
+    if (!point1 || !point2) {
+      console.warn("pointId1或者pointId2不存在");
+      return null;
+    }
+    if (pointId1 == pointId2) {
+      console.warn("给的是同一个point");
+      return null;
+    }
+
+    const parent1 = point1.parent;
+    const parent2 = point2.parent;
+
+    for (const key in parent1) {
+      if (parent2.hasOwnProperty(key)) {
+        return key;
+      }
+    }
+
+    return null;
+  }
+
+  getNeighPoints(pointId, exceptPointId) {
+    let points = [];
+    let point = dataService.getPoint(pointId);
+    for (let key in point.parent) {
+      let road = dataService.getRoad(key);
+      const otherPointId = road.getOtherPointId(pointId);
+      if (exceptPointId && exceptPointId == otherPointId) {
+        continue;
+      }
+      const otherPoint = dataService.getPoint(otherPointId);
+      points.push(otherPoint);
+    }
+    return points;
+  }
+
+  // id1和id2不相交
+  AngleForRoad(id1, id2) {
+    const road1 = dataService.getRoad(id1);
+    const road2 = dataService.getRoad(id2);
+
+    if (
+      road1 == null ||
+      road2 == null ||
+      typeof road1 === "undefined" ||
+      typeof road2 === "undefined"
+    ) {
+      return null;
+    }
+
+    const start1 = dataService.getPoint(road1.startId);
+    const end1 = dataService.getPoint(road1.endId);
+
+    const start2 = dataService.getPoint(road2.startId);
+    const end2 = dataService.getPoint(road2.endId);
+
+    const distance1 = mathUtil.getDistance(start1, start2);
+    const distance2 = mathUtil.getDistance(start1, end2);
+
+    const distance3 = mathUtil.getDistance(end1, start2);
+    const distance4 = mathUtil.getDistance(end1, end2);
+
+    const minDistance = Math.min(distance1, distance2, distance3, distance4);
+    const join = mathUtil.getIntersectionPoint2(start1, end1, start2, end2);
+
+    if (join == null) {
+      return Math.PI;
+    }
+
+    // start和start相交
+    if (distance1 == minDistance) {
+      end1.x += start2.x - start1.x;
+      end1.y += start2.y - start1.y;
+      return mathUtil.Angle(start2, end1, end2);
+    } else if (distance2 == minDistance) {
+      end1.x += end2.x - start1.x;
+      end1.y += end2.y - start1.y;
+      return mathUtil.Angle(end2, end1, start2);
+    } else if (distance3 == minDistance) {
+      start1.x += start2.x - end1.x;
+      start1.y += start2.y - end1.y;
+      return mathUtil.Angle(start2, start1, end2);
+    } else if (distance4 == minDistance) {
+      start1.x += end2.x - end1.x;
+      start1.y += end2.y - end1.y;
+      return mathUtil.Angle(end2, start1, start2);
+    } else {
+      console.error("AngleForRoad**************************1");
+      return null;
+    }
+  }
+
+  // 除了返回角度,还返回:id1对应的start,end和id2对应的另外一点组成的三角形,是顺时针还是逆时针
+  AngleForRoad2(id1, id2) {
+    const road1 = dataService.getRoad(id1);
+    const road2 = dataService.getRoad(id2);
+
+    if (
+      road1 == null ||
+      road2 == null ||
+      typeof road1 === "undefined" ||
+      typeof road2 === "undefined"
+    ) {
+      return null;
+    }
+
+    const start1 = dataService.getPoint(road1.startId);
+    const end1 = dataService.getPoint(road1.endId);
+
+    const start2 = dataService.getPoint(road2.startId);
+    const end2 = dataService.getPoint(road2.endId);
+
+    let angle = null;
+    const points = [];
+    points.push(start1);
+    points.push(end1);
+    if (road1.startId == road2.startId) {
+      angle = mathUtil.Angle(start1, end1, end2);
+      points.push(end2);
+    } else if (road1.startId == road2.endId) {
+      angle = mathUtil.Angle(start1, end1, start2);
+      points.push(start2);
+    } else if (road1.endId == road2.startId) {
+      angle = mathUtil.Angle(end1, start1, end2);
+      points[0] = end1;
+      points[1] = start1;
+      points.push(end2);
+    } else if (road1.endId == road2.endId) {
+      angle = mathUtil.Angle(end1, start1, start2);
+      points[0] = end1;
+      points[1] = start1;
+      points.push(start2);
+    }
+
+    if (angle == null) {
+      return null;
+    } else {
+      const flag = mathUtil.isClockwise(points);
+      if (flag) {
+        return {
+          angle: angle,
+          clockwise: 1,
+        };
+      } else {
+        return {
+          angle: angle,
+          clockwise: 0,
+        };
+      }
+    }
+  }
+
+  // id1的端点坐标不变
+  AngleForRoad3(id1, id2) {
+    const road1 = dataService.getRoad(id1);
+    const road2 = dataService.getRoad(id2);
+
+    if (
+      road1 == null ||
+      road2 == null ||
+      typeof road1 === "undefined" ||
+      typeof road2 === "undefined"
+    ) {
+      return null;
+    }
+
+    const start1 = dataService.getPoint(road1.startId);
+    const end1 = dataService.getPoint(road1.endId);
+
+    const start2 = dataService.getPoint(road2.startId);
+    const end2 = dataService.getPoint(road2.endId);
+
+    const distance1 = mathUtil.getDistance(start1, start2);
+    const distance2 = mathUtil.getDistance(start1, end2);
+
+    const distance3 = mathUtil.getDistance(end1, start2);
+    const distance4 = mathUtil.getDistance(end1, end2);
+
+    const minDistance = Math.min(distance1, distance2, distance3, distance4);
+
+    const _start1 = {};
+    const _end1 = {};
+
+    // start和start相交
+    if (distance1 == minDistance) {
+      _end1.x = end1.x + start2.x - start1.x;
+      _end1.y = end1.y + start2.y - start1.y;
+      return mathUtil.Angle(start2, _end1, end2);
+    } else if (distance2 == minDistance) {
+      _end1.x = end1.x + end2.x - start1.x;
+      _end1.y = end1.y + end2.y - start1.y;
+      return mathUtil.Angle(end2, _end1, start2);
+    } else if (distance3 == minDistance) {
+      _start1.x = start1.x + start2.x - end1.x;
+      _start1.y = start1.y + start2.y - end1.y;
+      return mathUtil.Angle(start2, _start1, end2);
+    } else if (distance4 == minDistance) {
+      _start1.x = start1.x + end2.x - end1.x;
+      _start1.y = start1.y + end2.y - end1.y;
+      return mathUtil.Angle(end2, _start1, start2);
+    } else {
+      console.error(
+        "RoadService.AngleForRoad3************************************1"
+      );
+      return null;
+    }
+  }
+
+  // pointId的parent超过两个
+  // 获取最小角度(顺时针和逆时针)
+  // 可能缺顺时针或者逆时针,这时候对应的取最大角度的逆时针或者顺时针
+  roadIdForMinAngle(pointId, roadId) {
+    const point = dataService.getPoint(pointId);
+    const parent = point.parent;
+
+    let minAngle0 = null;
+    let maxAngle0 = null; // 可能minAngle1不存在,这时候要找逆时针最大的
+    let minAngle1 = null;
+    let maxAngle1 = null;
+
+    let minInfo0, minInfo1;
+    let maxInfo0, maxInfo1;
+
+    if (Object.keys(parent).length > 2) {
+      for (const key in parent) {
+        if (key == roadId) {
+          continue;
+        }
+        const angleInfo = this.AngleForRoad2(roadId, key);
+        if (minAngle1 == null && angleInfo.clockwise == 1) {
+          minInfo1 = angleInfo;
+          minInfo1.roadId = key;
+          minAngle1 = angleInfo.angle;
+
+          maxInfo1 = angleInfo;
+          maxInfo1.roadId = key;
+          maxAngle1 = angleInfo.angle;
+        } else if (minAngle1 > angleInfo.angle && angleInfo.clockwise == 1) {
+          minInfo1 = angleInfo;
+          minInfo1.roadId = key;
+          minAngle1 = angleInfo.angle;
+        } else if (maxAngle1 < angleInfo.angle && angleInfo.clockwise == 1) {
+          maxInfo1 = angleInfo;
+          maxInfo1.roadId = key;
+          maxAngle1 = angleInfo.angle;
+        } else if (minAngle0 == null && angleInfo.clockwise == 0) {
+          minInfo0 = angleInfo;
+          minInfo0.roadId = key;
+          minAngle0 = angleInfo.angle;
+
+          maxInfo0 = angleInfo;
+          maxInfo0.roadId = key;
+          maxAngle0 = angleInfo.angle;
+        } else if (minAngle0 > angleInfo.angle && angleInfo.clockwise == 0) {
+          minInfo0 = angleInfo;
+          minInfo0.roadId = key;
+          minAngle0 = angleInfo.angle;
+        } else if (maxAngle0 < angleInfo.angle && angleInfo.clockwise == 0) {
+          maxInfo0 = angleInfo;
+          maxInfo0.roadId = key;
+          maxAngle0 = angleInfo.angle;
+        }
+      }
+
+      const result = {
+        min0: minInfo0,
+        min1: minInfo1,
+      };
+
+      if (!result.min0) {
+        result.min0 = maxInfo1;
+        result.min0.angle = 360 - maxInfo1.angle;
+      }
+
+      if (!result.min1) {
+        result.min1 = maxInfo0;
+        result.min1.angle = 360 - maxInfo0.angle;
+      }
+
+      return result;
+    } else {
+      console.error(
+        "roadIdForMinAngle*********************************************************"
+      );
+      return null;
+    }
+  }
+
+  isRoadLink(roadId1, roadId2) {
+    let road1 = dataService.getRoad(roadId1);
+    let road2 = dataService.getRoad(roadId2);
+
+    if (
+      road1.startId == road2.startId ||
+      road1.startId == road2.endId ||
+      road1.endId == road2.startId ||
+      road1.endId == road2.endId
+    ) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  //point在road所在的线上,只是不确定是否在线段上
+  isContain(road, point, minDis) {
+    const startPoint = dataService.getPoint(road.startId);
+    const endPoint = dataService.getPoint(road.endId);
+    return mathUtil.isContainForSegment(point, startPoint, endPoint, minDis);
+  }
+
+  getDirction(pointId, roadId) {
+    const road = dataService.getRoad(roadId);
+    if (road.startId == pointId) {
+      return "start";
+    } else if (road.endId == pointId) {
+      return "end";
+    } else {
+      console.error(
+        "RoadService.getDirction*******************************************************************************************"
+      );
+      return null;
+    }
+  }
+
+  subtraRoadFromIntersect(pointId, roadId) {
+    const point = dataService.getPoint(pointId);
+    const parent = point.getParent();
+    const dir = this.getDirction(pointId, roadId);
+
+    if (Object.keys(parent).length == 1) {
+      return;
+    }
+
+    // 第一步先断开链接
+    delete parent[roadId];
+    // 第二步先新建端点
+    const newPoint = pointService.create(point);
+    // 第三步建立链接
+    newPoint.setPointParent(roadId, dir);
+    let road = dataService.getRoad(roadId);
+    if (dir == "start") {
+      road.startId = newPoint.vectorId;
+    } else if (dir == "end") {
+      road.endId = newPoint.vectorId;
+    }
+  }
+
+  // pointId1移动到pointId2
+  // 如果有一堵墙(roadId)的两头是pointId1和pointId2,那么这堵墙会被删除
+  moveTo(pointId1, pointId2) {
+    const roadId = this.getRoadId(pointId1, pointId2);
+    // 不能重合
+    let point1 = dataService.getPoint(pointId1);
+    let point2 = dataService.getPoint(pointId2);
+    const dx = point1.x - point2.x;
+    const dy = point1.y - point2.y;
+    let parent1 = point1.getParent();
+    const parent2 = point2.getParent();
+    //确保pointId1与pointId2重合后,墙的角度不能太小
+    for (const roadId1 in parent1) {
+      if (roadId1 == roadId) {
+        continue;
+      }
+
+      const road1 = dataService.getRoad(roadId1);
+      const otherPointId1 = road1.getOtherPointId(pointId1);
+      const otherPoint1 = dataService.getPoint(otherPointId1);
+
+      for (const roadId2 in parent2) {
+        if (roadId2 == roadId) {
+          continue;
+        }
+        const road2 = dataService.getRoad(roadId2);
+        const otherPointId2 = road2.getOtherPointId(pointId2);
+        const otherPoint2 = dataService.getPoint(otherPointId2);
+        const angle = mathUtil.Angle(point2, otherPoint1, otherPoint2);
+        if (Math.abs(angle) < (Constant.minAngle / 180) * Math.PI) {
+          return false;
+        }
+      }
+    }
+    // pointId1,pointId2属于同一堵墙
+    if (roadId != null) {
+      dataService.deleteRoad(roadId);
+    }
+
+    point1 = dataService.getPoint(pointId1);
+    point2 = dataService.getPoint(pointId2);
+    if (!point1 || !point2) {
+      return false;
+    }
+
+    //准备合并
+    for (const roadId1 in parent1) {
+      const road1 = dataService.getRoad(roadId1);
+      const otherPointId = road1.getOtherPointId(pointId1);
+      const _roadId = this.getRoadId(otherPointId, pointId2);
+      if (_roadId != null) {
+        return false;
+      }
+
+      // wall1上pointId1被pointId2取代
+      if (road1.startId == pointId1) {
+        dataService.deletePoint(road1.startId, roadId1);
+        road1.startId = pointId2;
+        point2.setPointParent(roadId1, "start");
+      } else if (road1.endId == pointId1) {
+        dataService.deletePoint(road1.endId, roadId1);
+        road1.endId = pointId2;
+        point2.setPointParent(roadId1, "end");
+      } else {
+        console.error(
+          "roadService.moveTo****************************************************"
+        );
+      }
+    }
+    return true;
+  }
+}
+
+const roadService = new RoadService();
+export { roadService };

+ 96 - 0
src/graphic/Service/StateService.js

@@ -0,0 +1,96 @@
+import VectorType from '../enum/VectorType.js'
+import SelectState from '../enum/SelectState.js'
+import { symbolService } from './SymbolService.js'
+import { componentService } from './ComponentService.js'
+import { furnitureService } from './FurnitureService.js'
+
+export default class StateService {
+    constructor() {
+        this.eventName = null
+        this.selectItem = null
+        this.focusItem = null
+        this.draggingItem = null
+    }
+
+    getEventName() {
+        return this.eventName
+    }
+
+    setEventName(eventName) {
+        this.eventName = eventName
+    }
+
+    clearEventName() {
+        this.eventName = null
+    }
+
+    // type表示类型,state默认是select,但是有的元素需要知道选中的是哪个顶点
+    setSelectItem(vectorId, type, state) {
+        this.selectItem = {}
+        this.selectItem.vectorId = vectorId
+        this.selectItem.type = type
+
+        if (symbolService.isSymbol(type)) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            } else {
+                this.selectItem.selectIndex = state
+            }
+        } else if (componentService.isComponent(type)) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            }
+        } else if (type == VectorType.Tag) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            } else {
+                this.selectItem.selectIndex = state
+            }
+        } else if (furnitureService.isFurniture(type)) {
+            if (state == SelectState.Select) {
+                this.selectItem.selectIndex = SelectState.All
+            }
+        }
+    }
+
+    getSelectItem() {
+        return this.selectItem
+    }
+
+    clearSelectItem() {
+        this.selectItem = null
+    }
+
+    getDraggingItem() {
+        return this.draggingItem
+    }
+
+    setDraggingItem(draggingItem) {
+        this.draggingItem = draggingItem
+    }
+
+    clearDraggingItem() {
+        this.draggingItem = null
+    }
+
+    getFocusItem() {
+        return this.focusItem
+    }
+
+    setFocusItem(focusItem) {
+        this.focusItem = focusItem
+    }
+
+    clearFocusItem() {
+        this.focusItem = null
+    }
+
+    clearItems() {
+        this.selectItem = null
+        this.focusItem = null
+        this.draggingItem = null
+    }
+}
+
+const stateService = new StateService()
+export { stateService }

File diff suppressed because it is too large
+ 1149 - 0
src/graphic/Service/SymbolService.js


+ 93 - 0
src/graphic/Service/TagService.js

@@ -0,0 +1,93 @@
+import Tag from "../Geometry/Tag.js";
+import { dataService } from "./DataService.js";
+import { floorplanData } from "../VectorData.js";
+import { measureService } from "./MeasureService.js";
+import { mathUtil } from "../MathUtil.js";
+import { uoMService } from "./UoMService.js";
+
+export default class TagService {
+  constructor() {}
+
+  // 新建tag
+  createTag(position, tagId, floor) {
+    let tag = new Tag(position, tagId);
+    tag.setPoints2d();
+    dataService.addTag(tag, floor);
+    return tag;
+  }
+
+  setTagInfo(tagInfo) {
+    let tag = dataService.getTag(tagInfo.vectorId);
+    tag.vectorId = tagInfo.vectorId;
+    tag.center = JSON.parse(JSON.stringify(tagInfo.center));
+    tag.points2d = JSON.parse(JSON.stringify(tagInfo.points2d));
+    tag.title = tagInfo.title;
+    tag.des = tagInfo.des;
+    tag.unit = tagInfo.unit;
+    tag.adding = tagInfo.adding;
+  }
+
+  deleteTag(tagId, floorNum) {
+    dataService.deleteTag(tagId, floorNum);
+  }
+
+  clearDefaultTags() {
+    for (let i = 0; i < floorplanData.floors.length; ++i) {
+      let tags = floorplanData.floors[i].tags;
+      for (let key in tags) {
+        let tag = tags[key];
+        if (
+          (tag.title == null || tag.title.trim() == "") &&
+          (tag.des == null || tag.des == "")
+        ) {
+          this.deleteTag(tag.vectorId, i);
+        }
+      }
+    }
+  }
+
+  convertUnit(unit) {
+    for (let i = 0; i < floorplanData.floors.length; ++i) {
+      let tags = floorplanData.floors[i].tags;
+      for (let key in tags) {
+        let tag = tags[key];
+        if (tag.unit == unit) {
+          continue;
+        } else if (tag.des) {
+          tag.unit = unit;
+          if (unit == "m") {
+            //平方英尺转平方米
+            tag.des = uoMService.convertBack(
+              tag.des,
+              "area",
+              7,
+              "imperial",
+              0.01,
+              false
+            );
+            tag.des = parseFloat(tag.des);
+          } else {
+            //平方米转平方英尺
+            tag.des = uoMService.convert(
+              tag.des,
+              "area",
+              7,
+              "imperial",
+              0.01,
+              false
+            );
+            tag.des = tag.des.replace("ft²", "");
+            //tag.des = parseFloat(tag.des)
+          }
+        }
+      }
+    }
+  }
+
+  getTags(floorNum) {
+    return floorplanData.floors[floorNum].tags;
+  }
+}
+
+const tagService = new TagService();
+export { tagService };

+ 362 - 0
src/graphic/Service/UoMService.js

@@ -0,0 +1,362 @@
+const FEET_TO_INCHES_FACTOR = 12
+const EIGHTHS_SYMBOLS = ['', '⅛', '¼', '⅜', '½', '⅝', '¾', '⅞'] //eighths 八分之……
+
+class UnitOfMeasurement {
+    //转化单位工具
+    constructor(t, e, n, i) {
+        ;(this.name = t), (this.symbol = e), (this.base = n), (this.factor = i)
+    }
+    toBase(t) {
+        //换算到base
+        return t * this.factor
+    }
+
+    fromBase(t) {
+        //换算到当前
+        return t / this.factor
+    }
+}
+
+/*  var o = function t(e) {
+    this.gettext = e,
+    t.METRIC = this.gettext("metric", void 0, "measurement system"),
+    t.IMPERIAL = this.gettext("imperial", void 0, "measurement system") 
+    
+    
+};
+e.UoMSystem = o;
+
+let UoMSystem = {
+    
+} */
+
+/* var MeasurementDomain = {
+
+    DISTANCE : "DISTANCE",
+    t.AREA = "AREA",
+    t.VOLUME = "VOLUME",
+    t.DATA = "DATA",
+    t
+}
+ 
+
+ */
+
+var UnitsOfMeasurement = {
+    MILLIMETER: ['Millimeter', 'mm'],
+    CENTIMETER: ['Centimeter', 'cm'],
+    METER: ['Meter', 'm'],
+    KILOMETER: ['Kilometer', 'km'],
+    INCH: ['Inch', 'in'],
+    FOOT: ['Foot', 'ft'],
+    MILE: ['Mile', 'mi'],
+    SQUAREMETER: ['SquareMeter', 'm²'],
+    SQUAREFOOT: ['SquareFoot', 'ft²'],
+    CUBICMETER: ['CubicMeter', 'm³'],
+    CUBICFOOT: ['CubicFoot', 'ft³'],
+    BYTE: ['Byte', 'B'],
+    KILOBYTE: ['Kilobyte', 'kB'],
+    MEGABYTE: ['Megabyte', 'MB'],
+    GIGABYTE: ['Gigabyte', 'GB'],
+    TERABYTE: ['Terabyte', 'TB'],
+    PETABYTE: ['Petabyte', 'PB'],
+
+    init: function () {
+        var e,
+            n,
+            i,
+            a,
+            s,
+            c,
+            l,
+            u,
+            d,
+            p,
+            h,
+            f = new UnitOfMeasurement(UnitsOfMeasurement.METER[0], UnitsOfMeasurement.METER[1], void 0, 1),
+            g = new UnitOfMeasurement(UnitsOfMeasurement.SQUAREMETER[0], UnitsOfMeasurement.SQUAREMETER[1], void 0, 1),
+            m = new UnitOfMeasurement(UnitsOfMeasurement.CUBICMETER[0], UnitsOfMeasurement.CUBICMETER[1], void 0, 1),
+            v = new UnitOfMeasurement(UnitsOfMeasurement.BYTE[0], UnitsOfMeasurement.BYTE[1], void 0, 1)
+
+        UnitsOfMeasurement.DISTANCE =
+            (((e = {})['metric'] =
+                (((n = {})[UnitsOfMeasurement.MILLIMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MILLIMETER[0], UnitsOfMeasurement.MILLIMETER[1], f, 0.001)),
+                (n[UnitsOfMeasurement.CENTIMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.CENTIMETER[0], UnitsOfMeasurement.CENTIMETER[1], f, 0.01)),
+                (n[UnitsOfMeasurement.METER[0]] = f),
+                (n[UnitsOfMeasurement.KILOMETER[0]] = new UnitOfMeasurement(UnitsOfMeasurement.KILOMETER[0], UnitsOfMeasurement.KILOMETER[1], f, 1e3)),
+                n)),
+            (e['imperial'] =
+                (((i = {})[UnitsOfMeasurement.INCH[0]] = new UnitOfMeasurement(UnitsOfMeasurement.INCH[0], UnitsOfMeasurement.INCH[1], f, 0.0254)),
+                (i[UnitsOfMeasurement.FOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.FOOT[0], UnitsOfMeasurement.FOOT[1], f, 0.3048)),
+                (i[UnitsOfMeasurement.MILE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MILE[0], UnitsOfMeasurement.MILE[1], f, 1609.344)),
+                i)),
+            e)
+
+        UnitsOfMeasurement.AREA =
+            (((a = {})['metric'] = (((s = {})[UnitsOfMeasurement.SQUAREMETER[0]] = g), s)),
+            (a['imperial'] = (((c = {})[UnitsOfMeasurement.SQUAREFOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.SQUAREFOOT[0], UnitsOfMeasurement.SQUAREFOOT[1], g, 0.092903)), c)),
+            a)
+
+        UnitsOfMeasurement.VOLUME =
+            (((l = {})['metric'] = (((u = {})[UnitsOfMeasurement.CUBICMETER[0]] = m), u)),
+            (l['imperial'] = (((d = {})[UnitsOfMeasurement.CUBICFOOT[0]] = new UnitOfMeasurement(UnitsOfMeasurement.CUBICFOOT[0], UnitsOfMeasurement.CUBICFOOT[1], m, 0.0283168)), d)),
+            l)
+
+        //数据大小
+        var y =
+            (((p = {})[UnitsOfMeasurement.BYTE[0]] = v),
+            (p[UnitsOfMeasurement.KILOBYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.KILOBYTE[0], UnitsOfMeasurement.KILOBYTE[1], v, 1e3)),
+            (p[UnitsOfMeasurement.MEGABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.MEGABYTE[0], UnitsOfMeasurement.MEGABYTE[1], v, 1e6)),
+            (p[UnitsOfMeasurement.GIGABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.GIGABYTE[0], UnitsOfMeasurement.GIGABYTE[1], v, 1e9)),
+            (p[UnitsOfMeasurement.TERABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.TERABYTE[0], UnitsOfMeasurement.TERABYTE[1], v, 1e12)),
+            (p[UnitsOfMeasurement.PETABYTE[0]] = new UnitOfMeasurement(UnitsOfMeasurement.PETABYTE[0], UnitsOfMeasurement.PETABYTE[1], v, 1e15)),
+            p)
+        UnitsOfMeasurement.DATA = (((h = {})['metric'] = y), (h['imperial'] = y), h)
+    },
+    getUnitsOfMeasurementByDomain: function (e) {
+        return this[e.toUpperCase()]
+
+        /* switch (e.toUpperCase()) {
+        case a.DISTANCE:
+            return t.DISTANCE;
+        case a.AREA:
+            return t.AREA;
+        case a.VOLUME:
+            return t.VOLUME;
+        case a.DATA:
+            return t.DATA;
+        default:
+            console.error(e + " measurement domain is not supported.")
+        } */
+    },
+    getUnitsOfMeasurementByDomainAndSystem: function (domain, system) {
+        var r = this.getUnitsOfMeasurementByDomain(domain)
+        if (r.hasOwnProperty(system.toLowerCase())) return r[system.toLowerCase()]
+        console.error(n + ' measurement system is not supported.')
+    },
+    getDefaultUnitByDomainAndSystem: function (e, n) {
+        switch (e.toUpperCase()) {
+            case 'DISTANCE':
+                switch (n.toLowerCase()) {
+                    case 'metric':
+                        return this.DISTANCE['metric'][this.METER[0]]
+                    case 'imperial':
+                        return this.DISTANCE['imperial'][this.FOOT[0]]
+                    default:
+                        console.error(n + ' measurement system is not supported.')
+                }
+            case 'AREA':
+                switch (n.toLowerCase()) {
+                    case 'metric':
+                        return this.AREA['metric'][this.SQUAREMETER[0]]
+                    case 'imperial':
+                        return this.AREA['imperial'][this.SQUAREFOOT[0]]
+                    default:
+                        console.error(n + ' measurement system is not supported.')
+                }
+            case 'VOLUME':
+                switch (n.toLowerCase()) {
+                    case 'metric':
+                        return this.VOLUME['metric'][this.CUBICMETER[0]]
+                    case 'imperial':
+                        return this.VOLUME['imperial'][this.CUBICFOOT[0]]
+                    default:
+                        console.error(n + ' measurement system is not supported.')
+                }
+            case 'DATA':
+                switch (n.toLowerCase()) {
+                    case 'metric':
+                        return this.DATA['metric'][this.BYTE[0]]
+                    case 'imperial':
+                        return this.DATA['imperial'][this.BYTE[0]]
+                    default:
+                        console.error(n + ' measurement system is not supported.')
+                }
+            default:
+                console.error(e + ' measurement domain is not supported.')
+        }
+    },
+}
+
+class UnitService {
+    constructor(/* e, n, i */) {
+        //this.LanguageService = e,
+        //this.localStorageService = n,
+        //this.gettext = i,
+        //this.unitChanged = new r.Signal,
+        this.LOCAL_STORAGE_KEY = 'iv_unit_key' //?
+        UnitsOfMeasurement.init()
+        this.unitSystems = ['metric', 'imperial'] //[o.UoMSystem.METRIC, o.UoMSystem.IMPERIAL],
+        this.defaultSystem = 'metric' //'imperial'
+        //var a = this.LanguageService.getBrowserLocaleString().toLowerCase();
+        //this.defaultSystem =  t.isLocaleImperial(a) ? o.UoMSystem.IMPERIAL : o.UoMSystem.METRIC,
+
+        //this.initUnit()
+    }
+
+    /* initUnit() {
+        var t = this.localStorageService.get(this.LOCAL_STORAGE_KEY);
+        if (t)
+            for (var e = 0, n = this.unitSystems; e < n.length; e++) {
+                var i = n[e];
+                if (i === t)
+                    return void this.setUnit(i, !0)
+            }
+        this.setUnit(this.defaultSystem, !1)
+    }
+    setUnit(t, e) {
+        this.currentSystem !== t && (this.currentSystem = t,
+        this.unitChanged.emit()),
+        e && this.localStorageService.set(this.LOCAL_STORAGE_KEY, t)
+    } */
+
+    /*isLocaleImperial(e) {
+        return t.IMPERIAL_LOCALES.indexOf(e.toLowerCase()) >= 0
+    }
+    ,
+     t.IMPERIAL_LOCALES = ["en_us"],
+    t.ɵfac(e) {
+        return new (e || t)(c.ɵɵinject(l.LanguageService),
+        c.ɵɵinject("localStorageService"),c.ɵɵinject("gettext"))
+    }
+     ,
+    t.ɵprov = c.ɵɵdefineInjectable({
+        token: t,
+        factory: t.ɵfac,
+        providedIn: "root"
+    }), */
+}
+
+class UoMService {
+    constructor(/* UnitService */) {
+        this.UnitService = new UnitService() /* UnitService */
+    }
+    scopedConvert(t, n, precision = 2, r, maxFactor) {
+        return e.convert(t, n, precision, r, maxFactor)
+    }
+
+    convert(number, domain, precision = 2, system, maxFactor, ifEighths = !1) {
+        if (!number) return ''
+        var s = this.getMostRelevantMeasurement(domain, system || this.UnitService.currentSystem, number, maxFactor)
+        return this.getFormattedMeasurementString(s[0], s[1], precision, ifEighths)
+    }
+
+    getFormattedMeasurementString(number, unit, precision, ifEighths) {
+        var result
+        if (ifEighths && unit.name === UnitsOfMeasurement.FOOT[0]) {
+            result = this.formatImperialDistance(number * FEET_TO_INCHES_FACTOR)
+        } else if (ifEighths && unit.name === UnitsOfMeasurement.INCH[0]) {
+            result = this.formatImperialDistance(number)
+        } else {
+            result =
+                number.toLocaleString(void 0, {
+                    minimumFractionDigits: precision,
+                    maximumFractionDigits: precision,
+                }) +
+                ' ' +
+                unit.symbol
+        }
+
+        return result
+    }
+
+    formatImperialDistance(e) {
+        var n = Math.round(8 * e),
+            i = Math.floor(n / 8),
+            r = Math.floor(i / FEET_TO_INCHES_FACTOR),
+            o = i - r * FEET_TO_INCHES_FACTOR,
+            a = EIGHTHS_SYMBOLS[n % 8],
+            s = 0 === o && '' !== a ? '' : o
+
+        '' !== s && '' !== a && (a = ' ' + a)
+
+        return 0 !== r ? r + "' " + s + a + '"' : '' + s + a + '"'
+    }
+
+    getMostRelevantMeasurement(t, e, n, maxFactor = 0) {
+        /* var a = r.values(UnitsOfMeasurement.getUnitsOfMeasurementByDomainAndSystem(t, e))
+          , s = r.filter(a, function(t) {
+            return t.factor >= i
+        })
+          , c = r.reduce(s, function(t, e) {
+            return e.fromBase(n) < t.fromBase(n) && e.fromBase(n) >= 1 ? e : t
+        }); */
+        let a = []
+        let u = UnitsOfMeasurement.getUnitsOfMeasurementByDomainAndSystem(t, e)
+        for (let i in u) {
+            a.push(u[i])
+        }
+
+        let s = a.filter(m => m.factor >= maxFactor)
+
+        //有可能会写错,看不懂原函数
+
+        let c = s.reduce(function (i, e) {
+            //reduce计算数组元素相加后的总和
+            return e.fromBase(n) < i.fromBase(n) && e.fromBase(n) >= 1 ? e : i
+        })
+
+        return c ? [c.fromBase(n), c] : void 0
+    }
+
+    getMostRelevantMeasurement2(domain, system, number, minFactor = 0) {
+        //add
+        let a = []
+        let u = UnitsOfMeasurement.getUnitsOfMeasurementByDomainAndSystem(domain, system)
+        for (let i in u) {
+            a.push(u[i])
+        }
+        let s = a.filter(m => m.factor >= minFactor)
+        let c = s.reduce(function (prev, currentValue) {
+            //reduce最终值是最后一次return的值 ( 没看懂这句话作用)
+            return currentValue.toBase(number) < prev.toBase(number) && currentValue.toBase(number) >= 1 ? currentValue : prev
+        })
+        return c ? [c.toBase(number), c] : void 0
+    }
+
+    //this.convertBack(1, 'area', 5, 'imperial')    1英尺-->'0.09290 m²'
+    //this.convertBack(1, 'Distance', 2, 'imperial')  1平方英尺-->'0.03 m'
+    convertBack(number, domain, precision = 2, fromSystem, minFactor) {
+        //从英制转到'metric'
+        if (!number) return ''
+        //number += 'ft²';
+        number = number.trim().replace(',', '')
+        var d = UnitsOfMeasurement.getDefaultUnitByDomainAndSystem(domain, 'metric')
+
+        var s = this.getMostRelevantMeasurement2(domain, fromSystem, number, minFactor)
+        return this.getFormattedMeasurementString(s[0], d, precision)
+    }
+
+    /* ɵfac(e){
+        return new (e || t)(c.ɵɵinject(l.UnitService))
+    }
+    
+    ɵprov = c.ɵɵdefineInjectable({
+        token: t,
+        factory: t.ɵfac,
+        providedIn: "root"
+    }) */
+}
+
+/* 
+使用方法:
+
+
+初始化:
+    var unitConvert = new UoMService();
+ 
+    要设置的类型,两种:var unitSystem = 'metric'(米) || 'imperial' (英制) 
+ 
+
+使用:
+
+    面积:
+        let text = viewer.unitConvert.convert(area, 'area', void 0,  unitSystem,  )  
+
+    长度:
+        var text = viewer.unitConvert.convert(distance, 'distance', void 0, unitSystem, 0.1 , true)//distance要传0.1这个factor。 最后一个参数传true代表使用八分之一表示。
+
+ */
+
+const uoMService = new UoMService()
+export { uoMService }

+ 353 - 0
src/graphic/Style.js

@@ -0,0 +1,353 @@
+const Style = {
+    Wall: {
+        strokeStyle: '#FFFFFF',
+        lineWidth: 4,
+        lineWidth_out: 8,
+        //承重墙
+        important: {
+            strokeStyle: '#FFFFFF',
+            lineWidth: 8,
+        },
+        error: {
+            strokeStyle: 'rgba(255,0,0,0.5)',
+            fillStyle: 'rgba(255,0,0,0.8)',
+        },
+    },
+    Point: {
+        strokeStyle: 'green',
+        fillStyle: 'rgb(0, 200, 175)',
+        radius: 4,
+    },
+    Symbol: {
+        strokeStyle: 'rgba(255,255,255,1)',
+        fillStyle: 'rgba(255,255,255,0)',
+        lineWidth: 1,
+        //垭口
+        Pass: {},
+    },
+    Component: {
+        strokeStyle: 'rgba(255,255,255,1)',
+        fillStyle: 'rgba(255,255,255,0)',
+        lineWidth: 1,
+    },
+    Tag: {
+        strokeStyle: 'rgb(255,255,255,1)',
+        fillStyle: 'rgb(255,255,255,1)',
+        strokeStyle_adding: 'rgba(243, 255, 0, 0.8)',
+        fillStyle_adding: 'rgba(243, 255, 0, 0.8)',
+        lineWidth: 1,
+    },
+    Furniture: {
+        strokeStyle: 'rgb(255,255,255,1)',
+        fillStyle: 'rgba(0,0,0,0)',
+        lineWidth: 1,
+    },
+    Select: {
+        Wall: {
+            strokeStyle: 'rgba(243, 255, 0, 1)',
+        },
+        Wall_out: {
+            strokeStyle: 'rgba(243, 255, 0, 1)',
+        },
+        Symbol: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+            lineWidth: 2,
+        },
+        Component: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+        },
+        Tag: {
+            strokeStyle: '#00C8AF',
+            fillStyle: '#00C8AF',
+        },
+        Furniture: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+        },
+        Point: {
+            radius: 4,
+            lineWidth: 2,
+            fillStyle: 'rgba(245, 255, 0, 1)',
+            strokeStyle: 'rgba(245, 255, 255, 1)',
+        },
+    },
+    Focus: {
+        Wall: {
+            strokeStyle: 'rgba(243, 255, 0, 1)',
+        },
+        Wall_out: {
+            strokeStyle: 'rgba(162, 164, 124, 1)',
+        },
+        Symbol: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+        },
+        Component: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+        },
+        Tag: {
+            strokeStyle: '#00C8AF',
+            fillStyle: '#00C8AF',
+        },
+        Furniture: {
+            strokeStyle: 'rgba(243, 255, 0, 0.8)',
+            fillStyle: 'rgba(243, 255, 0, 0.5)',
+        },
+        Point: {
+            radius: 4,
+            lineWidth: 2,
+            fillStyle: 'rgba(245, 255, 0, 1)',
+            strokeStyle: 'rgba(245, 255, 255, 1)',
+        },
+    },
+    Element: {
+        StartAddWall: {
+            radius: 4,
+            fillStyle: 'yellow',
+            strokeStyle: 'green',
+        },
+        NewWall: {
+            lineWidth: 4,
+            lineWidth_out: 6,
+            strokeStyle: 'rgba(255,255,255,0.3)',
+            strokeStyle_out: 'rgba(102,102,102,0.3)',
+            errorStrokeStyle: 'rgb(250,63,72,0.3)',
+        },
+        StartSymbolPoints: {
+            radius: 4,
+            fillStyle: 'yellow',
+            strokeStyle: 'green',
+        },
+        EndSymbolPoints: {
+            radius: 4,
+            fillStyle: 'yellow',
+            strokeStyle: 'green',
+        },
+        CheckLinesX: {
+            lineWidth: 2,
+            strokeStyle: '#CED806',
+        },
+        CheckLinesY: {
+            lineWidth: 2,
+            strokeStyle: '#CED806',
+        },
+        VCheckLinesX: {
+            lineWidth: 2,
+            strokeStyle: '#CED806',
+            //strokeStyle: 'rgba(100,149,237,0.5)',
+        },
+        VCheckLinesY: {
+            lineWidth: 2,
+            strokeStyle: '#CED806',
+            //strokeStyle: 'rgba(100,149,237,0.5)',
+        },
+        SignLine1: {
+            lineWidth: 2,
+            strokeStyle: 'blue',
+        },
+        SignLine2: {
+            lineWidth: 2,
+            strokeStyle: 'green',
+        },
+    },
+    Font: {
+        font: '14px Microsoft YaHei',
+        fillStyle: '#FFFFFF',
+        strokeStyle: '#FFFFFF',
+        textAlign: 'center',
+        textBaseline: 'middle',
+        miterLimit: 10,
+        direction: 'ltr',
+    },
+    Pano: {
+        radius: 10,
+        lineWidth: 2,
+        strokeStyle: 'rgba(255,255,255,0.4)',
+        fillStyle: 'rgba(255,255,255,0.1)',
+    },
+    Measure: {
+        txt: 'rgba(255,255,255,1)', //画墙/选墙的时候 测量值的颜色
+        strokeStyle: 'rgba(255,255,255,1)',
+        lineWidth: 1,
+    },
+    DownLoad: {
+        style1: {
+            Wall: {
+                strokeStyle: '#666666',
+                lineWidth: 4,
+                lineWidth_out: 6,
+                //承重墙
+                important: {
+                    strokeStyle: '#666666',
+                    lineWidth: 6,
+                },
+            },
+            Symbol: {
+                strokeStyle: '#666666',
+                lineWidth: 1,
+            },
+            Component: {
+                strokeStyle: '#666666',
+                fillStyle: '#666666',
+                lineWidth: 1,
+            },
+            Tag: {
+                strokeStyle: '#000000', //标注的颜色
+                fillStyle: '#000000', //标注的颜色
+                lineWidth: 1,
+            },
+            Font: {
+                font: '14px Microsoft YaHei',
+                fillStyle: '#000000', //上下左右测量值
+                strokeStyle: '#000000',
+                textAlign: 'center',
+                textBaseline: 'middle',
+                miterLimit: 10,
+                direction: 'ltr',
+            },
+            Measure: {
+                strokeStyle: '#CCCCCC',
+                lineWidth: 1,
+            },
+            Furniture: {
+                strokeStyle: '#666666',
+                fillStyle: '#666666',
+            },
+        },
+        style2: {
+            Wall: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 4,
+                lineWidth_out: 6,
+                //承重墙
+                important: {
+                    strokeStyle: '#FFFFFF',
+                    lineWidth: 6,
+                },
+            },
+            Symbol: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Component: {
+                strokeStyle: '#FFFFFF',
+                fillStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Tag: {
+                strokeStyle: '#FFFFFF', //标注的颜色
+                fillStyle: '#FFFFFF', //标注的颜色
+                lineWidth: 1,
+            },
+            Font: {
+                font: '14px Microsoft YaHei',
+                fillStyle: '#FFFFFF', //上下左右测量值
+                strokeStyle: '#FFFFFF',
+                textAlign: 'center',
+                textBaseline: 'middle',
+                miterLimit: 10,
+                direction: 'ltr',
+            },
+            Measure: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Furniture: {
+                strokeStyle: '#FFFFFF',
+                fillStyle: '#FFFFFF',
+            },
+        },
+        style3: {
+            Wall: {
+                strokeStyle: '#666666',
+                lineWidth: 4,
+                lineWidth_out: 6,
+                //承重墙
+                important: {
+                    strokeStyle: '#666666',
+                    lineWidth: 6,
+                },
+            },
+            Symbol: {
+                strokeStyle: '#666666',
+                lineWidth: 1,
+            },
+            Component: {
+                strokeStyle: '#666666',
+                fillStyle: '#666666',
+                lineWidth: 1,
+            },
+            Tag: {
+                strokeStyle: '#000000', //标注的颜色
+                fillStyle: '#000000', //标注的颜色
+                strokeStyle_adding: 'rgba(243, 255, 0, 0.8)',
+                fillStyle_adding: 'rgba(243, 255, 0, 0.8)',
+                lineWidth: 1,
+            },
+            Font: {
+                font: '14px Microsoft YaHei',
+                fillStyle: '#000000', //上下左右测量值
+                strokeStyle: '#000000',
+                textAlign: 'center',
+                textBaseline: 'middle',
+                miterLimit: 10,
+                direction: 'ltr',
+            },
+            Measure: {
+                strokeStyle: '#CCCCCC',
+                lineWidth: 2,
+            },
+            Furniture: {
+                strokeStyle: '#666666',
+                fillStyle: '#666666',
+            },
+        },
+        style4: {
+            Wall: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 4,
+                lineWidth_out: 6,
+                //承重墙
+                important: {
+                    strokeStyle: '#FFFFFF',
+                    lineWidth: 6,
+                },
+            },
+            Symbol: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Component: {
+                strokeStyle: '#FFFFFF',
+                fillStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Tag: {
+                strokeStyle: '#FFFFFF', //标注的颜色
+                fillStyle: '#FFFFFF', //标注的颜色
+                lineWidth: 1,
+            },
+            Font: {
+                font: '14px Microsoft YaHei',
+                fillStyle: '#FFFFFF', //上下左右测量值
+                strokeStyle: '#FFFFFF',
+                textAlign: 'center',
+                textBaseline: 'middle',
+                miterLimit: 10,
+                direction: 'ltr',
+            },
+            Measure: {
+                strokeStyle: '#FFFFFF',
+                lineWidth: 1,
+            },
+            Furniture: {
+                strokeStyle: '#FFFFFF',
+                fillStyle: '#FFFFFF',
+            },
+        },
+    },
+}
+export default Style

+ 15 - 0
src/graphic/Util/EdgeUtil.js

@@ -0,0 +1,15 @@
+import { mathUtil } from "./MathUtil";
+import GeoUtil from "./GeoUtil.js";
+import { dataService } from "../Service/DataService.js";
+import { roadService } from "../Service/RoadService.js";
+
+import Vector from "../Vector/Vector.js";
+import Point from "../Geometry/Point.js";
+import Wall from "../Geometry/Wall.js";
+import Edge from "../Geometry/Edge.js";
+
+//update开头的,edge默认和墙的中心线是平行的
+//compute开头的,edge需要考虑和墙的中心线平行
+var EdgeUtil = {};
+
+export default EdgeUtil;

+ 938 - 0
src/graphic/Util/MathUtil.js

@@ -0,0 +1,938 @@
+import Constant from "../Constant";
+
+export default class MathUtil {
+  constructor() {}
+
+  getFixed(num, decimal) {
+    if (!decimal) {
+      decimal = 5;
+    }
+    // return Math.floor(num * 10000) / 10000;
+    return parseFloat(num.toFixed(decimal));
+  }
+
+  // 求两个点的距离
+  getDistance(p1, p2) {
+    const x1 = p1.x;
+    const y1 = p1.y;
+    const x2 = p2.x;
+    const y2 = p2.y;
+    const num = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
+    return this.getFixed(num);
+  }
+
+  createLine1(point1, point2) {
+    if (point1.x == point2.x && point1.y == point2.y) {
+      return null;
+    } else if (this.getFixed(Math.abs(point1.x - point2.x)) == 0) {
+      return { x: point1.x };
+    } else if (this.getFixed(Math.abs(point1.y - point2.y)) == 0) {
+      return { y: point1.y };
+    }
+
+    const parametera = (point1.y - point2.y) / (point1.x - point2.x);
+    const parameterb =
+      (point1.x * point2.y - point2.x * point1.y) / (point1.x - point2.x);
+    if (this.getFixed(parametera) == 0) {
+      return { y: this.getFixed(parameterb) };
+    }
+    const parameter = {
+      a: this.getFixed(parametera),
+      b: this.getFixed(parameterb),
+    };
+    return parameter;
+  }
+
+  createLine2(point, angle) {
+    if (angle == Math.PI / 2 || angle == 1.5 * Math.PI) {
+      return { x: point.x };
+    }
+    let a = Math.tan(angle);
+    let b = point.y - a * point.x;
+    if (a != 0) {
+      return { a: a, b: b };
+    } else {
+      return { y: point.y };
+    }
+  }
+
+  // 与lineA平行并且point在线上
+  createLine3(lineA, point) {
+    const parameter = {};
+    if (typeof lineA.a === "undefined") {
+      if (typeof lineA.x !== "undefined") {
+        parameter.x = point.x;
+      } else if (typeof lineA.y !== "undefined") {
+        parameter.y = point.y;
+      }
+    } else {
+      parameter.a = lineA.a;
+      parameter.b = point.y - point.x * lineA.a;
+    }
+    return parameter;
+  }
+
+  create2AngleLine(point, angle, driftAngle) {
+    let line1 = this.createLine2(point, angle - driftAngle / 2);
+    let line2 = this.createLine2(point, angle + driftAngle / 2);
+    return { line1: line1, line2: line2 };
+  }
+
+  distanceForPoints(point1, point2) {
+    return Math.sqrt(
+      Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)
+    );
+  }
+
+  //与line平行且两条线直接的距离是distance的两条线
+  getParallelLineForDistance(line, distance) {
+    let line1 = {};
+    let line2 = {};
+    if (!line.hasOwnProperty("a")) {
+      if (line.hasOwnProperty("x")) {
+        let x = line.x;
+        line1.x = x + distance;
+        line2.x = x - distance;
+      } else if (line.hasOwnProperty("y")) {
+        let y = line.y;
+        line1.y = y + distance;
+        line2.y = y - distance;
+      }
+    } else {
+      line1.a = line.a;
+      line1.b = line.b;
+
+      line2.a = line.a;
+      line2.b = line.b;
+
+      let angle = Math.atan(line.a);
+      let db = Math.abs(distance / Math.cos(angle));
+      let b = line.b;
+      line1.b = b + db;
+      line2.b = b - db;
+    }
+
+    return { line1: line1, line2: line2 };
+  }
+
+  //获取扇形的两个端点
+  getEndpoint(point, angle, sectorAngle) {
+    const distance = 15;
+    //line1是减,line2是加
+    let lines1 = this.create2AngleLine(point, angle, sectorAngle);
+    let line = this.createLine2(point, angle);
+    line = this.getLineForPoint(line, point);
+    let lines2 = this.getParallelLineForDistance(line, distance);
+
+    let point1 = this.getIntersectionPoint(lines1.line1, lines2.line1);
+    let point2 = this.getIntersectionPoint(lines1.line1, lines2.line2);
+    let point3 = this.getIntersectionPoint(lines1.line2, lines2.line1);
+    let point4 = this.getIntersectionPoint(lines1.line2, lines2.line2);
+
+    let angle1 = this.Angle(point, point1, { x: point.x + 1, y: point.y });
+    let angle2 = this.Angle(point, point2, { x: point.x + 1, y: point.y });
+    let angle3 = this.Angle(point, point3, { x: point.x + 1, y: point.y });
+    let angle4 = this.Angle(point, point4, { x: point.x + 1, y: point.y });
+    if (angle > Math.PI) {
+      angle = 2 * Math.PI - angle;
+    }
+    if (
+      Math.abs((angle1 + angle3) / 2 - angle) <
+      Math.abs((angle2 + angle4) / 2 - angle)
+    ) {
+      return { p1: point1, p2: point3 };
+    } else {
+      return { p1: point2, p2: point4 };
+    }
+  }
+
+  // true表示逆时针,false表示顺时针
+  isClockwise(vertices) {
+    let area = 0;
+    for (let i = 0; i < vertices.length; i++) {
+      const j = (i + 1) % vertices.length;
+      area += vertices[i].x * vertices[j].y;
+      area -= vertices[j].x * vertices[i].y;
+    }
+    const sub = area / 2;
+    if (sub > 0) {
+      // 逆时针
+      return true;
+    } else {
+      // 顺时针
+      return false;
+    }
+  }
+
+  reverse(points) {
+    const _points = [];
+    for (let i = points.length - 1; i > -1; --i) {
+      _points.push(points[i]);
+    }
+    return _points;
+  }
+
+  //两条线的交点
+  getIntersectionPoint(parameter1, parameter2) {
+    if (this.isParallel(parameter1, parameter2)) {
+      return null;
+    }
+    if (
+      typeof parameter1.a == "undefined" &&
+      typeof parameter2.a != "undefined"
+    ) {
+      if (parameter1.x) {
+        return {
+          x: parameter1.x,
+          y: parameter2.a * parameter1.x + parameter2.b,
+        };
+      } else if (parameter1.y) {
+        return {
+          x: (parameter1.y - parameter2.b) / parameter2.a,
+          y: parameter1.y,
+        };
+      }
+    } else if (
+      typeof parameter2.a == "undefined" &&
+      typeof parameter1.a != "undefined"
+    ) {
+      if (parameter2.x) {
+        return {
+          x: parameter2.x,
+          y: parameter1.a * parameter2.x + parameter1.b,
+        };
+      } else if (parameter2.y) {
+        return {
+          x: (parameter2.y - parameter1.b) / parameter1.a,
+          y: parameter2.y,
+        };
+      }
+    } else if (
+      typeof parameter2.a == "undefined" &&
+      typeof parameter1.a == "undefined"
+    ) {
+      if (parameter1.hasOwnProperty("x") && parameter2.hasOwnProperty("y")) {
+        return { x: parameter1.x, y: parameter2.y };
+      } else if (
+        parameter1.hasOwnProperty("y") &&
+        parameter2.hasOwnProperty("x")
+      ) {
+        return { x: parameter2.x, y: parameter1.y };
+      } else {
+        return null;
+      }
+    }
+
+    if (parameter1.a == parameter2.a) {
+      return null;
+    }
+
+    let joinpointx =
+      (parameter2.b - parameter1.b) / (parameter1.a - parameter2.a);
+    let joinpointy =
+      (parameter1.a * parameter2.b - parameter2.a * parameter1.b) /
+      (parameter1.a - parameter2.a);
+
+    let point = { x: joinpointx, y: joinpointy };
+    return point;
+  }
+
+  // 直线的交点
+  getIntersectionPoint2(a, b, c, d) {
+    /** 1 解线性方程组, 求线段交点. **/
+
+    // 如果分母为0 则平行或共线, 不相交
+    const denominator = (b.y - a.y) * (d.x - c.x) - (a.x - b.x) * (c.y - d.y);
+    if (denominator == 0) {
+      return null;
+    }
+
+    // 线段所在直线的交点坐标 (x , y)
+    const x =
+      ((b.x - a.x) * (d.x - c.x) * (c.y - a.y) +
+        (b.y - a.y) * (d.x - c.x) * a.x -
+        (d.y - c.y) * (b.x - a.x) * c.x) /
+      denominator;
+    const y =
+      -(
+        (b.y - a.y) * (d.y - c.y) * (c.x - a.x) +
+        (b.x - a.x) * (d.y - c.y) * a.y -
+        (d.x - c.x) * (b.y - a.y) * c.y
+      ) / denominator;
+
+    return { x: x, y: y };
+  }
+
+  //两条线段交点
+  getIntersectionPoint3(a, b, c, d) {
+    const join = this.getIntersectionPoint2(a, b, c, d);
+    if (join) {
+      const x = join.x;
+      const y = join.y; // 交点在线段1上 且交点也在线段2上
+      /** 2 判断交点是否在两条线段上 **/
+      if (
+        (x - a.x) * (x - b.x) <= 0.001 &&
+        (y - a.y) * (y - b.y) <= 0.001 &&
+        (x - c.x) * (x - d.x) <= 0.001 &&
+        (y - c.y) * (y - d.y) <= 0.001
+      ) {
+        // 返回交点p
+        return {
+          x: x,
+          y: y,
+        };
+      }
+      return null;
+    }
+    return null;
+  }
+
+  // 线段和直线是否相交
+  getIntersectionPoint4(point1, point2, line) {
+    const line1 = this.createLine1(point1, point2);
+    const join = this.getIntersectionPoint(line1, line);
+    if (join == null) {
+      return null;
+    }
+    if (this.PointInSegment(join, point1, point2)) {
+      return join; // 相交
+    } else {
+      return null;
+    }
+  }
+
+  //返回true表示平行
+  isParallel(line1, line2) {
+    if (typeof line1.a == "undefined" && typeof line2.a == "undefined") {
+      if (line1.hasOwnProperty("x") && line2.hasOwnProperty("x")) {
+        return true;
+      } else if (line1.hasOwnProperty("y") && line2.hasOwnProperty("y")) {
+        return true;
+      } else {
+        return false;
+      }
+    } else if (typeof line1.a == "undefined" || typeof line2.a == "undefined") {
+      return false;
+    } else if (this.getFixed(line1.a) == this.getFixed(line2.a)) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  //两条相交的线段的夹角,永远小于180度
+  Angle(o, s, e) {
+    let cosfi = 0,
+      fi = 0,
+      norm = 0;
+    let dsx = s.x - o.x;
+    let dsy = s.y - o.y;
+    let dex = e.x - o.x;
+    let dey = e.y - o.y;
+
+    cosfi = dsx * dex + dsy * dey;
+    norm = (dsx * dsx + dsy * dsy) * (dex * dex + dey * dey);
+    cosfi /= Math.sqrt(norm);
+
+    if (cosfi >= 1.0) return 0;
+    //if (cosfi <= -1.0) return Math.PI;
+    if (cosfi <= -1.0) return Math.PI;
+    fi = Math.acos(cosfi);
+
+    if ((180 * fi) / Math.PI < 180) {
+      //return 180 * fi / Math.PI;
+      return fi;
+    } else {
+      //return 360 - 180 * fi / Math.PI;
+      return 2 * Math.PI - fi;
+    }
+  }
+
+  //经过point且与line垂直的线
+  getLineForPoint(line, point) {
+    let parameter = {};
+    if (line.a == 0 || typeof line.a == "undefined") {
+      if (line.hasOwnProperty("x")) {
+        parameter.y = point.y;
+      } else if (line.hasOwnProperty("y")) {
+        parameter.x = point.x;
+      }
+    } else {
+      parameter.a = -1 / line.a;
+      parameter.b = point.y - point.x * parameter.a;
+    }
+    return parameter;
+  }
+
+  // 经过point且与line垂直的直线,该直线与line的交点
+  getJoinLinePoint(point, line) {
+    const verticalLine = this.getVerticalLine(line, point);
+    const join = this.getIntersectionPoint(line, verticalLine);
+    return join;
+  }
+
+  // 点到直线的距离
+  getDisForPoinLine(point, line) {
+    const join = this.getJoinLinePoint(point, line);
+    return this.getDistance(point, join);
+  }
+
+  // 垂直线
+  getVerticalLine(line, point) {
+    if (typeof line.a === "undefined") {
+      if (line.hasOwnProperty("x")) {
+        return { y: point.y };
+      } else if (line.hasOwnProperty("y")) {
+        return { x: point.x };
+      } else {
+        return null;
+      }
+    } else if (line.a == 0) {
+      return { x: point.x };
+    } else {
+      const tl = {};
+      tl.a = -1 / line.a;
+      const result = this.createLine3(tl, point);
+      return result;
+    }
+  }
+
+  //point在直线上,只是不确定是否在线段上
+  //方法:point到startPoint和endPoint的距离之和与startPoint和endPoint之间的距离对比
+  isContainForSegment(point, startPoint, endPoint, minDis) {
+    if (!minDis) {
+      minDis = Constant.minAdsorb;
+    }
+    let dis1 =
+      this.getDistance(startPoint, point) + this.getDistance(endPoint, point);
+    let dis2 = this.getDistance(startPoint, endPoint);
+    if (Math.abs(dis1 - dis2) < minDis) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /*
+    //minDis
+    isPointInPoly(point, points, minDis) {
+        if (!minDis) {
+            minDis = Constant.minRealDis
+        }
+        const x = point.x
+        const y = point.y
+
+        let inside = false
+
+        // 是否在顶点附近
+        for (let i = 0; i < points.length; ++i) {
+            let distance = this.getDistance(point, points[i])
+            if (distance < minDis) {
+                return true
+            }
+        }
+
+        // 是否在边沿
+        for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
+            let pt1 = points[i]
+            let pt2 = points[j]
+            const flag = this.isContainForSegment(point, pt1, pt2, minDis)
+            if (flag) {
+                return true
+            }
+        }
+
+        for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
+            let pt1 = points[i]
+            let pt2 = points[j]
+            const xi = pt1.x
+            const yi = pt1.y
+            const xj = pt2.x
+            const yj = pt2.y
+
+            const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
+            if (intersect) inside = !inside
+        }
+
+        return inside
+    }
+    */
+
+  isPointInPoly(point, points) {
+    const x = point.x;
+    const y = point.y;
+
+    let inside = false;
+
+    for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
+      let pt1 = points[i];
+      let pt2 = points[j];
+      const xi = pt1.x;
+      const yi = pt1.y;
+      const xj = pt2.x;
+      const yj = pt2.y;
+
+      const intersect =
+        yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
+      if (intersect) inside = !inside;
+    }
+
+    return inside;
+  }
+
+  // 点到线段的距离
+  // 在minDistance范围内,会吸附到point1/point2上
+  // 返回值:type是1表示吸附在point1,是2表示吸附在point2,是0表示在线段point1-point2上;
+  getDisForPoinSegment(point, point1, point2, minDistance) {
+    const line = this.createLine1(point1, point2);
+    const join = this.getJoinLinePoint(point, line);
+    const dis = this.getDistance(point1, point2);
+    const dis1 = this.getDistance(join, point1);
+    const dis2 = this.getDistance(join, point2);
+    if (
+      this.getDistance(join, point1) > dis ||
+      this.getDistance(join, point2) > dis
+    ) {
+      // 在线段外
+      if (dis1 < dis2 && dis1 < minDistance) {
+        return { type: 1, join: point1 };
+      } else if (dis2 < dis1 && dis2 < minDistance) {
+        return { type: 2, join: point2 };
+      } else {
+        return null;
+      }
+    } else {
+      if (dis1 < minDistance) {
+        return { type: 1, join: point1 };
+      } else if (dis2 < minDistance) {
+        return { type: 2, join: point2 };
+      } else if (this.getDistance(point, join) < minDistance) {
+        return { type: 0, join: join };
+      }
+    }
+  }
+
+  PointInSegment(Q, pi, pj, minDis) {
+    if (
+      this.getDistance(Q, pi) < Constant.minAdsorb ||
+      this.getDistance(Q, pj) < Constant.minAdsorb
+    ) {
+      return true;
+    }
+
+    if (!minDis) {
+      minDis = 0.1;
+    }
+    minDis = minDis / 2;
+
+    const offset1 = (Q.x - pi.x) * (pj.y - pi.y) - (pj.x - pi.x) * (Q.y - pi.y);
+    const offset2 = Math.min(pi.x, pj.x) - Q.x;
+    const offset3 = Q.x - Math.max(pi.x, pj.x);
+    const offset4 = Math.min(pi.y, pj.y) - Q.y;
+    const offset5 = Q.y - Math.max(pi.y, pj.y);
+
+    if (
+      Math.abs(offset1) < minDis &&
+      (offset2 <= 0 || Math.abs(offset2) < minDis) &&
+      (offset3 <= 0 || Math.abs(offset3) < minDis) &&
+      (offset4 <= 0 || Math.abs(offset4) < minDis) &&
+      (offset5 <= 0 || Math.abs(offset5) < minDis)
+    ) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  //点p是否在线段AB上
+  isPointOnSegment(p, A, B) {
+    // 计算向量 AP 和 BP
+    const AP = {
+      x: p.x - A.x,
+      y: p.y - A.y,
+    };
+    const BP = {
+      x: p.x - B.x,
+      y: p.y - B.y,
+    };
+
+    // 计算向量 AB 的长度和方向
+    const AB = {
+      x: B.x - A.x,
+      y: B.y - A.y,
+    };
+    const AB_length = this.getDistance(A, B);
+    const AB_direction = {
+      x: AB.x / AB_length,
+      y: AB.y / AB_length,
+    };
+
+    // 检查 AP 和 BP 的方向是否与 AB 相同,并检查它们的长度是否小于等于 AB 的长度
+    const dot_product_AP = AP.x * AB_direction.x + AP.y * AB_direction.y;
+    const dot_product_BP = BP.x * AB_direction.x + BP.y * AB_direction.y;
+    //return dot_product_AP >= 0 && dot_product_BP <= 0 && Math.abs(AP.x * BP.y - AP.y * BP.x) <= AB_length * Number.EPSILON;
+    return (
+      dot_product_AP >= 0 &&
+      dot_product_BP <= 0 &&
+      Math.abs(AP.x * BP.y - AP.y * BP.x) <= 0.001
+    );
+  }
+
+  clonePoint(p1, p2) {
+    p1.x = p2.x;
+    p1.y = p2.y;
+  }
+
+  equalPoint(p1, p2) {
+    if (p1.x == p2.x && p1.y == p2.y) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  crossTwoLines(point1, point2, point3, point4, dis) {
+    if (typeof dis == "undefined") {
+      dis = Constant.minAdsorb;
+    }
+    const join = this.getIntersectionPoint2(point1, point2, point3, point4);
+    if (join != null) {
+      if (
+        this.getDistance(point1, join) > dis &&
+        this.getDistance(point2, join) > dis &&
+        this.getDistance(point3, join) > dis &&
+        this.getDistance(point4, join) > dis
+      ) {
+        if (
+          this.getDistance(point1, join) < this.getDistance(point1, point2) &&
+          this.getDistance(point2, join) < this.getDistance(point1, point2) &&
+          this.getDistance(point3, join) < this.getDistance(point3, point4) &&
+          this.getDistance(point4, join) < this.getDistance(point3, point4)
+        ) {
+          return true;
+        } else {
+          return false;
+        }
+      }
+    } else {
+      if (
+        this.PointInSegment(point1, point3, point4) ||
+        this.PointInSegment(point2, point3, point4)
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  getDisPointsLine(line, point, distance1, distance2) {
+    const newpoint1 = {};
+    const newpoint2 = {};
+    const result = {};
+    if (line.hasOwnProperty("x")) {
+      newpoint1.x = line.x;
+      newpoint1.y = point.y - distance1;
+      newpoint2.x = line.x;
+      newpoint2.y = point.y + distance2;
+    } else if (line.hasOwnProperty("y")) {
+      newpoint1.y = line.y;
+      newpoint1.x = point.x - distance1;
+      newpoint2.y = line.y;
+      newpoint2.x = point.x + distance2;
+    } else {
+      const a = Math.atan(line.a);
+      const t_line = { a: -1 / line.a };
+      const line_ab2 = this.createLine3(t_line, point);
+      const join = this.getIntersectionPoint(line, line_ab2);
+
+      newpoint1.x = join.x - distance1 * Math.cos(a);
+      newpoint1.y = join.y - distance1 * Math.sin(a);
+
+      newpoint2.x = join.x + distance2 * Math.cos(a);
+      newpoint2.y = join.y + distance2 * Math.sin(a);
+    }
+    result.newpoint1 = newpoint1;
+    result.newpoint2 = newpoint2;
+    return result;
+  }
+
+  getBoundingBox(points) {
+    let minX = points[0].x;
+    let maxX = points[0].x;
+    let minY = points[0].y;
+    let maxY = points[0].y;
+
+    for (let i = 1; i < points.length; ++i) {
+      const point = points[i];
+      if (minX > point.x) {
+        minX = point.x;
+      }
+      if (minY > point.y) {
+        minY = point.y;
+      }
+      if (maxX < point.x) {
+        maxX = point.x;
+      }
+      if (maxY < point.y) {
+        maxY = point.y;
+      }
+    }
+
+    const box = {};
+    box.minX = minX;
+    box.minY = minY;
+    box.maxX = maxX;
+    box.maxY = maxY;
+
+    return box;
+  }
+
+  getBoundingBox2(points) {
+    let minX = null;
+    let maxX = null;
+    let minY = null;
+    let maxY = null;
+
+    for (let key in points) {
+      const point = points[key];
+      if (minX == null || minX > point.x) {
+        minX = point.x;
+      }
+      if (minY == null || minY > point.y) {
+        minY = point.y;
+      }
+      if (maxX == null || maxX < point.x) {
+        maxX = point.x;
+      }
+      if (maxY == null || maxY < point.y) {
+        maxY = point.y;
+      }
+    }
+
+    const box = {};
+    box.minX = minX;
+    box.minY = minY;
+    box.maxX = maxX;
+    box.maxY = maxY;
+
+    return box;
+  }
+
+  ComputePolygonArea(points) {
+    const point_num = points.length;
+    if (point_num < 3) {
+      return 0;
+    }
+    let s = points[0].y * (points[point_num - 1].x - points[1].x);
+    for (let i = 1; i < point_num; ++i)
+      s += points[i].y * (points[i - 1].x - points[(i + 1) % point_num].x);
+    return Math.abs(s / 2.0);
+  }
+
+  // 获取多边形重心
+  getPolygonCore(points) {
+    function Area(p0, p1, p2) {
+      let area = 0.0;
+      area =
+        p0.x * p1.y +
+        p1.x * p2.y +
+        p2.x * p0.y -
+        p1.x * p0.y -
+        p2.x * p1.y -
+        p0.x * p2.y;
+      return area / 2;
+    }
+
+    let sum_x = 0;
+    let sum_y = 0;
+    let sum_area = 0;
+    let p1 = points[1];
+    for (let i = 2; i < points.length; i++) {
+      const p2 = points[i];
+      const area = Area(points[0], p1, p2);
+      sum_area += area;
+      sum_x += (points[0].x + p1.x + p2.x) * area;
+      sum_y += (points[0].y + p1.y + p2.y) * area;
+      p1 = p2;
+    }
+    const xx = sum_x / sum_area / 3;
+    const yy = sum_y / sum_area / 3;
+    return {
+      x: xx,
+      y: yy,
+    };
+  }
+
+  // points1是否在points2里
+  isPolyInPoly(points1, points2, minDis) {
+    for (let i = 0; i < points1.length; ++i) {
+      let flag = false;
+      for (let j = 0; j < points2.length; ++j) {
+        if (this.equalPoint(points1[i], points2[j])) {
+          flag = true;
+          break;
+        }
+      }
+      if (!flag) {
+        if (!this.isPointInPoly(points1[i], points2, minDis)) {
+          return false;
+        }
+      } else {
+        const nextIndex = i == points1.length - 1 ? 0 : i + 1;
+        const mid = {
+          x: (points1[i].x + points1[nextIndex].x) / 2,
+          y: (points1[i].y + points1[nextIndex].y) / 2,
+        };
+        if (!this.isPointInPoly(mid, points2, minDis)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  dotPoints(pt1, pt2, point1, point2) {
+    let vt1 = {};
+    let vt2 = {};
+    vt1.start = {};
+    vt1.end = {};
+    vt1.start.x = 0;
+    vt1.start.y = 0;
+    vt1.end.x = pt2.x - pt1.x;
+    vt1.end.y = pt2.y - pt1.y;
+
+    vt2.start = {};
+    vt2.end = {};
+    vt2.start.x = 0;
+    vt2.start.y = 0;
+    vt2.end.x = point2.x - point1.x;
+    vt2.end.y = point2.y - point1.y;
+
+    let result = vt1.end.x * vt2.end.x + vt1.end.y * vt2.end.y;
+    return result;
+  }
+
+  //start是起点,target是朝着目标移动,distance是移动的距离
+  translate(start, target, point, distance) {
+    let dx = target.x - start.x;
+    let dy = target.y - start.y;
+    let dis = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
+
+    let result = {
+      x: point.x + (dx * distance) / dis,
+      y: point.y + (dy * distance) / dis,
+    };
+
+    return result;
+  }
+
+  //射线与线段相交
+  // intersection(rayStart, rayEnd, segmentStart, segmentEnd) {
+  //     // 计算射线和线段的方向向量
+  //     const rayDirection = {
+  //         x:rayEnd.x - rayStart.x,
+  //         y:rayEnd.y - rayStart.y,
+  //     };
+  //     const segmentDirection = {
+  //         x:segmentEnd.x - segmentStart.x,
+  //         y:segmentEnd.y - segmentStart.y,
+  //     };
+
+  //     // 计算射线和线段的起点之间的向量
+  //     const startPointVector = {
+  //         x:rayStart.x - segmentStart.x,
+  //         y:rayStart.y - segmentStart.y,
+  //     };
+
+  //     // 计算射线和线段的叉积
+  //     const crossProduct = rayDirection.x * segmentDirection.y - rayDirection.y * segmentDirection.x;
+
+  //     // 如果叉积为0,则表示射线和线段平行
+  //     if (crossProduct === 0) {
+  //       return null;
+  //     }
+
+  //     // 计算线段起点到射线的交点的向量
+  //     const t = (startPointVector.x * segmentDirection.y - startPointVector.y * segmentDirection.x) / crossProduct;
+
+  //     // 如果t的值小于0,则交点在射线的起点之后
+  //     if (t < 0) {
+  //       return null;
+  //     }
+
+  //     // 计算交点的坐标
+  //     const intersectionX = rayStart.x + t * rayDirection.x;
+  //     const intersectionY = rayStart.y + t * rayDirection.y;
+
+  //     // 如果交点在线段的范围内,则返回交点坐标
+  //     if ((intersectionX >= Math.min(segmentStart.x, segmentEnd.x)) &&
+  //         (intersectionX <= Math.max(segmentStart.x, segmentEnd.x)) &&
+  //         (intersectionY >= Math.min(segmentStart.y, segmentEnd.y)) &&
+  //         (intersectionY <= Math.max(segmentStart.y, segmentEnd.y))) {
+  //       //return [intersectionX, intersectionY];
+  //       return {
+  //         x:intersectionX,
+  //         y:intersectionY,
+  //       };
+  //     }
+
+  //     // 否则返回null
+  //     return null;
+  //   }
+  // raySegmentIntersection(rayOrigin, rayDirection, segmentStart, segmentEnd) {
+  //     // 计算射线和线段的交点
+  //     const x1 = rayOrigin.x
+  //     const y1 = rayOrigin.y
+  //     const x2 = segmentStart.x
+  //     const y2 = segmentStart.y
+  //     const dx1 = rayDirection.x
+  //     const dy1 = rayDirection.y
+  //     const dx2 = segmentEnd.x - x2
+  //     const dy2 = segmentEnd.y - y2
+
+  //     const crossProduct = dx1 * dy2 - dx2 * dy1
+  //     if (Math.abs(crossProduct) < 1e-8) {
+  //         // 射线和线段平行或共线
+  //         return null
+  //     }
+
+  //     const t1 = (dx2 * (y1 - y2) - dy2 * (x1 - x2)) / crossProduct
+  //     const t2 = (dx1 * (y1 - y2) - dy1 * (x1 - x2)) / crossProduct
+
+  //     if (t1 >= 0 && t2 >= 0 && t2 <= 1) {
+  //         // 有交点,计算交点坐标
+  //         const intersectionX = x1 + t1 * dx1
+  //         const intersectionY = y1 + t1 * dy1
+  //         return {
+  //             x: intersectionX,
+  //             y: intersectionY,
+  //         }
+  //     } else {
+  //         // 没有交点
+  //         return null
+  //     }
+  // }
+  raySegmentIntersection(rayOrigin, rayDirection, segmentStart, segmentEnd) {
+    const end = {
+      x: rayOrigin.x + rayDirection.x,
+      y: rayOrigin.y - rayDirection.z,
+    };
+    const line = this.createLine1(rayOrigin, end);
+    const join = this.getIntersectionPoint4(segmentStart, segmentEnd, line);
+    if (join == null) {
+      return null;
+    } else {
+      const dis = this.getDistance(end, join);
+      const dis1 = this.getDistance(rayOrigin, join);
+      const dis2 = this.getDistance(rayOrigin, end);
+      if (dis - (dis1 + dis2) + 0.01 > 0) {
+        return null;
+      } else {
+        return join;
+      }
+    }
+  }
+}
+
+const mathUtil = new MathUtil();
+export { mathUtil };

+ 13 - 0
src/graphic/enum/ElementEvents.js

@@ -0,0 +1,13 @@
+const ElementEvents = {
+    StartAddWall: 'StartAddWall',
+    NewWall: 'NewWall',
+    StartSymbolPoints: 'StartSymbolPoints',
+    EndSymbolPoints: 'EndSymbolPoints',
+    CheckLinesX: 'CheckLinesX',
+    CheckLinesY: 'CheckLinesY',
+    VCheckLinesX: 'vCheckLinesX',
+    VCheckLinesY: 'vCheckLinesY',
+    SignLine1: 'SignLine1',
+    SignLine2: 'SignLine2',
+}
+export default ElementEvents

+ 28 - 0
src/graphic/enum/HistoryEvents.js

@@ -0,0 +1,28 @@
+const HistoryEvents = {
+  AddPoint: "addPoint",
+  DeletePoint: "deletePoint",
+  ModifyPoint: "modifyPoint",
+
+  AddRoad: "addRoad",
+  DeleteWall: "deleteWall",
+  ModifyWall: "modifyWall",
+
+  AddSymbol: "addSymbol",
+  DeleteSymbol: "deleteSymbol",
+  ModifySymbol: "modifySymbol",
+
+  AddComponent: "addComponent",
+  DeleteComponent: "deleteComponent",
+  ModifyComponent: "modifyComponent",
+
+  AddTag: "addTag",
+  DeleteTag: "deleteTag",
+  ModifyTag: "modifyTag",
+
+  AddFurniture: "addFurniture",
+  DeleteFurniture: "deleteFurniture",
+  ModifyFurniture: "modifyFurniture",
+
+  ModifyAngle: "modifyAngle",
+};
+export default HistoryEvents;

+ 11 - 0
src/graphic/enum/LayerEvents.js

@@ -0,0 +1,11 @@
+const LayerEvents = {
+  PanBackGround: "panBackGround", //拖拽背景
+  AddRoad: "addRoad", //开始添加墙
+  AddingRoad: "addingRoad", //添加墙进行中
+  MoveRoad: "moveRoad", //拖拽墙面
+  MoveWallRoad: "moveWallRoad", //拖拽墙角
+
+  AddTag: "addTag",
+  MoveTag: "moveTag",
+};
+export default LayerEvents;

+ 25 - 0
src/graphic/enum/SelectState.js

@@ -0,0 +1,25 @@
+const SelectState = {
+    All: 'all',
+    Start: 'start',
+    End: 'end',
+    Select: 'select',
+    /*,
+    focus: "focus",
+    selectArrow: "selectArrow", // beam,flue  focus后,选中下面的箭头
+    dragging: "dragging",
+    error: "error",
+    full: "full",
+    building: "building",
+    rotate: "rotate",
+    resize: "resize",
+    num1: 1,
+    num2: 2,
+    num3: 3,
+    num4: 4,
+    num5: 5,
+    num6: 6,
+    num7: 7,
+    num8: 8,
+    */
+}
+export default SelectState

+ 5 - 0
src/graphic/enum/SymbolEvents.js

@@ -0,0 +1,5 @@
+const SymbolEvents = {
+    Left: 'LEFT',
+    Right: 'RIGHT',
+}
+export default SymbolEvents

+ 30 - 0
src/graphic/enum/UIEvents.js

@@ -0,0 +1,30 @@
+const UIEvents = {
+  Road: "Road",
+  Tag: "Tag", //这个是标注,暂时这样
+};
+
+// export const Furnitures = {
+//   TV: UIEvents.TV, //电视
+//   CombinationSofa: UIEvents.CombinationSofa, //组合沙发
+//   SingleSofa: UIEvents.SingleSofa, //单人沙发
+//   TeaTable: UIEvents.TeaTable, //茶几
+//   Carpet: UIEvents.Carpet, //地毯
+//   Plant: UIEvents.Plant, //植物
+//   DiningTable: UIEvents.DiningTable, //餐桌
+//   DoubleBed: UIEvents.DoubleBed, //双人床
+//   SingleBed: UIEvents.SingleBed, //单人床
+//   Wardrobe: UIEvents.Wardrobe, //衣柜
+//   Dresser: UIEvents.Dresser, //梳妆台
+//   BedsideCupboard: UIEvents.BedsideCupboard, //床头柜
+//   Pillow: UIEvents.Pillow, //抱枕
+//   GasStove: UIEvents.GasStove, //燃气灶
+//   Cupboard: UIEvents.Cupboard, //橱柜
+//   Bathtub: UIEvents.Bathtub, //浴缸
+//   Closestool: UIEvents.Closestool, //马桶
+//   Washstand: UIEvents.Washstand, //洗漱台
+//   Desk: UIEvents.Desk, //书桌
+//   BalconyChair: UIEvents.BalconyChair, //阳台椅
+//   Elevator: UIEvents.Elevator, //电梯
+// };
+
+export default UIEvents;

+ 46 - 0
src/graphic/enum/VectorType.js

@@ -0,0 +1,46 @@
+const VectorType = {
+    Point: 'Point',
+    WallCorner: 'WallCorner',
+    Wall: 'Wall',
+    SingleDoor: 'SingleDoor',
+    DoubleDoor: 'DoubleDoor',
+    SlideDoor: 'SlideDoor',
+
+    SingleWindow: 'SingleWindow',
+    BayWindow: 'BayWindow',
+    FrenchWindow: 'FrenchWindow',
+    Pass: 'Pass',
+
+    Beam: 'Beam',
+    Flue: 'Flue',
+    Corridor: 'Corridor',
+    Line: 'Line',
+
+    Tag: 'Tag',
+
+    TV: 'TV', //电视
+    CombinationSofa: 'CombinationSofa', //组合沙发
+    SingleSofa: 'SingleSofa', //单人沙发
+    TeaTable: 'TeaTable', //茶几
+    Carpet: 'Carpet', //地毯
+    Plant: 'Plant', //植物
+    DiningTable: 'DiningTable', //餐桌
+
+    DoubleBed: 'DoubleBed', //双人床
+    SingleBed: 'SingleBed', //单人床
+    Wardrobe: 'Wardrobe', //衣柜
+    Dresser: 'Dresser', //梳妆台
+    BedsideCupboard: 'BedsideCupboard', //床头柜
+    Pillow: 'Pillow', //抱枕
+
+    GasStove: 'GasStove', //燃气灶
+    Cupboard: 'Cupboard', //橱柜
+    Bathtub: 'Bathtub', //浴缸
+    Closestool: 'Closestool', //马桶
+    Washstand: 'Washstand', //洗漱台
+
+    Desk: 'Desk', //书桌
+    BalconyChair: 'BalconyChair', //阳台椅
+    Elevator: 'Elevator', //电梯
+}
+export default VectorType