DivideFloorScene.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. import { SMouseEvent } from "@persagy-web/base/lib";
  2. import { SMathUtil } from "@persagy-web/big/lib/utils/SMathUtil";
  3. import { SPoint, SRect } from "@persagy-web/draw/lib";
  4. import { FloorScene } from "./FloorScene";
  5. import { HighlightItem } from "./HighlightItem";
  6. import { ShadeItem } from "./ShadeItem";
  7. // @ts-ignore
  8. import { intersect, polygon, segments, combine, selectIntersect, selectUnion, selectDifference, selectDifferenceRev, difference } from "polybooljs";
  9. import { DrawZoneItem } from "./DrawZoneItem";
  10. import { SItemStatus, unzip } from "@persagy-web/big/lib";
  11. import { Wall } from "@persagy-web/big/lib/types/floor/Wall";
  12. import { CustomWall } from "./CustomWall";
  13. import { SGraphItem, SGraphStyleItem } from "@persagy-web/graph/lib";
  14. import { SWallItem } from "@persagy-web/big/lib/items/floor/SWallItem";
  15. // @ts-ignore
  16. import pako from "pako";
  17. export class DivideFloorScene extends FloorScene {
  18. /** 划分item */
  19. cutItem: ShadeItem | null = null;
  20. /** 绘制的分区item */
  21. drawItem: DrawZoneItem | null = null;
  22. /** 当前绘制命令 cut-交集区域绘制 zoneDraw-绘制业务空间 wallDraw-画墙 */
  23. // 2021.3.4需求不算交集,直接绘制业务空间
  24. drawCmd: string = '';
  25. /** 是否开启吸附 */
  26. private _isAbsorbing: boolean = false;
  27. get isAbsorbing(): boolean {
  28. return this._isAbsorbing;
  29. } // Get isAbsorbing
  30. set isAbsorbing(v: boolean) {
  31. this._isAbsorbing = v;
  32. } // Set isAbsorbing
  33. /** 高亮item */
  34. highLight: HighlightItem | null = null;
  35. /** 删除日志 */
  36. deleteWallLog: Wall[] = []
  37. /** 用户自己增加的墙item */
  38. customWall: CustomWall[] = []
  39. /**
  40. * 清除划分区域
  41. */
  42. clearCut(): void {
  43. if (this.cutItem && this.drawCmd == 'cut') {
  44. this.grabItem = null;
  45. this.removeItem(this.cutItem);
  46. this.cutItem = null;
  47. this.view && this.view.update();
  48. }
  49. if (this.drawItem && this.drawCmd == 'zoneDraw') {
  50. this.grabItem = null;
  51. this.removeItem(this.drawItem);
  52. this.drawItem = null;
  53. this.view && this.view.update();
  54. }
  55. this.drawCmd = '';
  56. } // Function clearCut()
  57. /**
  58. * 清除绘制的墙
  59. */
  60. clearWalls() {
  61. if (this.customWall.length) {
  62. this.customWall.forEach(t => {
  63. this.removeItem(t)
  64. })
  65. this.customWall = []
  66. this.grabItem = null;
  67. }
  68. }
  69. /**
  70. * 删除墙功能
  71. */
  72. delWall() {
  73. if (this.grabItem && this.grabItem instanceof CustomWall && this.grabItem.status == SItemStatus.Create) {
  74. this.deleteItem(this.grabItem);
  75. this.grabItem = null;
  76. } else if (this.selectContainer.count > 0) {
  77. const item = this.selectContainer.itemList[0];
  78. if (item instanceof SWallItem || item instanceof CustomWall) {
  79. this.deleteItem(item);
  80. this.selectContainer.itemList.shift();
  81. }
  82. }
  83. }
  84. /**
  85. * 删除墙
  86. */
  87. deleteItem(item: SGraphItem) {
  88. if (item instanceof CustomWall) {
  89. this.removeItem(item);
  90. for (let i = 0; i < this.customWall.length; i++) {
  91. if (this.customWall[i] == item) {
  92. this.customWall.splice(i, 1)
  93. break
  94. }
  95. }
  96. this.view?.update()
  97. } else if (item instanceof SWallItem) {
  98. this.removeItem(item);
  99. this.deleteWallLog.push(item.data)
  100. this.view?.update()
  101. }
  102. }
  103. /**
  104. * 鼠标按下事件
  105. *
  106. * @param event 保存事件参数
  107. * @return boolean
  108. */
  109. onMouseDown(event: SMouseEvent): boolean {
  110. // 判断是否开启吸附,并且有吸附的点
  111. if (
  112. this.isAbsorbing &&
  113. this.highLight &&
  114. this.highLight.visible
  115. ) {
  116. event.x = this.highLight.point.x;
  117. event.y = this.highLight.point.y;
  118. }
  119. if (this.grabItem) {
  120. return this.grabItem.onMouseDown(event);
  121. }
  122. if (event.buttons == 1) {
  123. if (this.drawCmd == 'cut') {
  124. if (!this.cutItem) {
  125. let point = new SPoint(event.x, event.y);
  126. let item = new ShadeItem(null, point);
  127. this.addItem(item);
  128. this.cutItem = item;
  129. this.grabItem = item;
  130. return true;
  131. }
  132. } else if (this.drawCmd == 'zoneDraw') {
  133. if (!this.drawItem) {
  134. let point = new SPoint(event.x, event.y);
  135. let item = new DrawZoneItem(null, point);
  136. item.status = SItemStatus.Create;
  137. this.addItem(item);
  138. this.drawItem = item;
  139. this.grabItem = item;
  140. return true;
  141. }
  142. } else if (this.drawCmd == "wallDraw") {
  143. let point = new SPoint(event.x, event.y);
  144. let item = new CustomWall(null, point)
  145. item.status = SItemStatus.Create;
  146. this.addItem(item);
  147. this.customWall.push(item)
  148. item.connect("finishCreated", this, this.finishCreated)
  149. this.grabItem = item;
  150. return true;
  151. }
  152. }
  153. return super.onMouseDown(event)
  154. }
  155. /**
  156. * 吸附空间
  157. *
  158. * @param event 鼠标事件对象
  159. */
  160. onMouseMove(event: SMouseEvent): boolean {
  161. super.onMouseMove(event);
  162. if (this.isAbsorbing) {
  163. if (!this.highLight) {
  164. this.highLight = new HighlightItem(null);
  165. this.addItem(this.highLight);
  166. }
  167. this.highLight.visible = false;
  168. // if (!this.absorbShade(event)) {
  169. this.absorbSpace(event);
  170. // }
  171. }
  172. return false;
  173. } // Function onMouseMove()
  174. /**
  175. * 吸附空间
  176. *
  177. * @param event 鼠标事件对象
  178. * @return boolean 是否找到吸附的对象
  179. */
  180. absorbSpace(event: SMouseEvent): boolean {
  181. if (!this.highLight) {
  182. return false;
  183. }
  184. let absorbLen = 1000;
  185. if (this.view) {
  186. absorbLen = 10 / this.view.scale;
  187. }
  188. let P = this.absorbSpacePoint(event, absorbLen);
  189. if (P.Point) {
  190. this.highLight.distance = P.MinDis;
  191. this.highLight.point = new SPoint(P.Point.X, -P.Point.Y);
  192. this.highLight.visible = true;
  193. return true;
  194. } else {
  195. let L = this.absorbSpaceLine(event, absorbLen);
  196. if (L.Line && L.Point) {
  197. this.highLight.distance = L.MinDis;
  198. this.highLight.point = L.Point;
  199. this.highLight.line = L.Line;
  200. this.highLight.visible = true;
  201. return true;
  202. }
  203. return false;
  204. }
  205. } // Function absorbSpace()
  206. /**
  207. * 吸附空间点
  208. *
  209. * @param event 鼠标事件对象
  210. * @param absorbLen 吸附距离
  211. * @return MinDis 吸附的点
  212. */
  213. absorbSpacePoint(event: SMouseEvent, absorbLen: number): any {
  214. let minPointDis = Number.MAX_SAFE_INTEGER;
  215. let Point;
  216. this.spaceList.map((space): void => {
  217. if (
  218. DivideFloorScene.isPointInAbsorbArea(
  219. new SPoint(event.x, event.y),
  220. space.minX,
  221. space.maxX,
  222. space.minY,
  223. space.maxY
  224. )
  225. ) {
  226. space.data.OutLine.forEach((item): void => {
  227. let minDis = SMathUtil.getMinDisPoint(
  228. new SPoint(event.x, event.y),
  229. item
  230. );
  231. if (
  232. minDis &&
  233. minDis.MinDis < absorbLen &&
  234. minDis.MinDis < minPointDis
  235. ) {
  236. minPointDis = minDis.MinDis;
  237. Point = minDis.Point;
  238. }
  239. });
  240. }
  241. });
  242. return {
  243. MinDis: minPointDis,
  244. Point: Point
  245. };
  246. } // Function absorbSpacePoint()
  247. /**
  248. * 点是否在吸附区域内
  249. *
  250. * @param p 要判断的点
  251. * @param minX 空间区域
  252. * @param minY 空间区域
  253. * @param maxX 空间区域
  254. * @param maxY 空间区域
  255. */
  256. static isPointInAbsorbArea(
  257. p: SPoint,
  258. minX: number,
  259. maxX: number,
  260. minY: number,
  261. maxY: number
  262. ): boolean {
  263. let rect = new SRect(
  264. minX - 1000,
  265. minY - 1000,
  266. maxX - minX + 2000,
  267. maxY - minY + 2000
  268. );
  269. return rect.contains(p.x, p.y);
  270. } // Function isPointInAbsorbArea()
  271. /**
  272. * 吸附空间线
  273. *
  274. * @param event 鼠标事件对象
  275. * @param absorbLen 吸附距离
  276. * @return PointToLine 吸附的线
  277. */
  278. absorbSpaceLine(event: SMouseEvent, absorbLen: number): any {
  279. let minPointDis = Number.MAX_SAFE_INTEGER;
  280. let Point, Line;
  281. this.spaceList.forEach((space): void => {
  282. if (
  283. DivideFloorScene.isPointInAbsorbArea(
  284. new SPoint(event.x, event.y),
  285. space.minX,
  286. space.maxX,
  287. space.minY,
  288. space.maxY
  289. )
  290. ) {
  291. space.data.OutLine.forEach((item): void => {
  292. let minDisLine = SMathUtil.getMinDisLine(
  293. new SPoint(event.x, event.y),
  294. item
  295. );
  296. if (
  297. minDisLine &&
  298. minDisLine.MinDis < absorbLen &&
  299. minDisLine.MinDis < minPointDis
  300. ) {
  301. minPointDis = minDisLine.MinDis;
  302. Point = minDisLine.Point;
  303. Line = minDisLine.Line;
  304. }
  305. });
  306. }
  307. });
  308. return {
  309. MinDis: minPointDis,
  310. Point: Point,
  311. Line: Line
  312. };
  313. } // Function absorbSpaceLine()
  314. /**
  315. * 计算选中的空间与业务空间的差集
  316. */
  317. getSpaceZoneIntersect(): SPoint[][] | undefined {
  318. // 未选中空间- 不计算
  319. if (!this.selectContainer.itemList.length) {
  320. return
  321. }
  322. // 没有业务空间不计算
  323. if (!this.zoneList.length) {
  324. return
  325. }
  326. // 生成选中的空间计算时用的格式
  327. const space = {
  328. regions: [],
  329. inverted: false
  330. }
  331. const sourceIdToDiff = {};
  332. try {
  333. let poly1, poly2;
  334. const start = +new Date();
  335. let rect1: SRect, list = [];
  336. // 计算外接矩阵与选中空间相交的业务空间的所有轮廓
  337. this.selectContainer.itemList.forEach(item => {
  338. rect1 = item.boundingRect();
  339. this.zoneList.forEach(t => {
  340. if (t.visible) {
  341. let rect2 = t.boundingRect();
  342. // 外接矩阵是否相交,缩小计算范围
  343. if (SMathUtil.rectIntersection(rect1, rect2)) {
  344. t.pointList.forEach((zoneLine): void => {
  345. let polygons = {
  346. regions: [],
  347. inverted: false
  348. };
  349. zoneLine.forEach((po): void => {
  350. let point = SMathUtil.transferToArray(po);
  351. polygons.regions.push(point);
  352. });
  353. list.push(polygons);
  354. })
  355. }
  356. }
  357. })
  358. })
  359. //
  360. if (list.length) {
  361. let seg1 = segments(list[0]),
  362. seg2,
  363. comb;
  364. for (let i = 1; i < list.length; i++) {
  365. seg2 = segments(list[i]);
  366. comb = combine(seg1, seg2);
  367. seg1 = selectUnion(comb);
  368. }
  369. poly1 = seg1;
  370. this.selectContainer.itemList.forEach(t => {
  371. let key = t.data.SourceId;
  372. poly2 = {
  373. regions: [],
  374. inverted: false
  375. }
  376. poly2.regions = t.pointList[0].map((item): number[][] => {
  377. return SMathUtil.transferToArray(item);
  378. });
  379. const comb = combine(segments(poly2), poly1)
  380. const diffObj = selectDifference(comb);
  381. const diffPoly = polygon(diffObj)
  382. // 理论上只要选择了空间 length就不会为0
  383. if (diffPoly.regions.length) {
  384. let outlineList = SMathUtil.getIntersectInArray(
  385. diffPoly.regions
  386. );
  387. console.log(outlineList);
  388. // @ts-ignore
  389. sourceIdToDiff[key] = outlineList.map(t => {
  390. let arr = [t.Outer];
  391. t.Inner.forEach((inner): void => {
  392. arr.push(inner);
  393. });
  394. return arr;
  395. });
  396. }
  397. });
  398. const end = +new Date()
  399. console.log(sourceIdToDiff);
  400. console.log(end - start, 'comb-diff');
  401. return sourceIdToDiff
  402. }
  403. } catch (err) {
  404. console.log(err);
  405. }
  406. }
  407. /**
  408. * 计算选中的空间与绘制的区域交集
  409. */
  410. getSpaceCutIntersect(seg?: any): SPoint[][] | undefined {
  411. // 未选中空间- 不计算
  412. if (!this.selectContainer.itemList.length) {
  413. return
  414. }
  415. // 没有绘制不计算
  416. if (!this.cutItem) {
  417. return
  418. }
  419. const sourceIdToIntersect = {};
  420. try {
  421. const start = +new Date();
  422. let cutPoly;
  423. if (seg) {
  424. cutPoly = seg
  425. } else {
  426. const poly = {
  427. regions: [SMathUtil.transferToArray(this.cutItem.pointList)],
  428. inverted: false
  429. }
  430. cutPoly = segments(poly)
  431. }
  432. this.selectContainer.itemList.forEach(item => {
  433. const arr = item.pointList[0].map(t => {
  434. return SMathUtil.transferToArray(t);
  435. })
  436. const poly2 = {
  437. regions: arr,
  438. inverted: false
  439. }
  440. const seg = segments(poly2)
  441. const comb = combine(cutPoly, seg)
  442. if (item.data.SourceId) {
  443. const spoly = polygon(selectIntersect(comb));
  444. // 绘制切割区域可能与空间没有交集
  445. if (spoly.regions.length) {
  446. let outlineList = SMathUtil.getIntersectInArray(
  447. spoly.regions
  448. );
  449. console.log(outlineList);
  450. // @ts-ignore
  451. sourceIdToIntersect[item.data.SourceId] = outlineList.map(t => {
  452. let arr = [t.Outer];
  453. t.Inner.forEach((inner): void => {
  454. arr.push(inner);
  455. });
  456. return arr;
  457. });
  458. // 得到的结果
  459. // sourceIdToIntersect[item.data.SourceId] = polygon(selectIntersect(comb))
  460. } else {
  461. // 没有交集
  462. }
  463. }
  464. })
  465. const end = +new Date()
  466. console.log(end - start, 'comb-intersect', sourceIdToIntersect);
  467. return sourceIdToIntersect
  468. } catch (err) {
  469. console.log(err);
  470. }
  471. return
  472. }
  473. /**
  474. * 如果场景中既有绘制的区域也有创建好的业务空间,则在区域中将已创建的业务空间减去
  475. */
  476. getCurInZone() {
  477. if (!this.cutItem) {
  478. return
  479. }
  480. // 绘制区域外接矩阵
  481. const rect2 = this.cutItem.boundingRect()
  482. // 绘制区域的多边形 - 也是每次减去业务空间后剩下的多边形
  483. let poly = segments({
  484. regions: [SMathUtil.transferToArray(this.cutItem.pointList)],
  485. inverted: false
  486. })
  487. this.zoneList.forEach(item => {
  488. if (item.visible) {
  489. const rect1 = item.boundingRect();
  490. if (SMathUtil.rectIntersection(rect1, rect2)) {
  491. item.pointList.forEach((zoneLine): void => {
  492. let polygons = {
  493. regions: [],
  494. inverted: false
  495. }
  496. zoneLine.forEach((po): void => {
  497. let point = SMathUtil.transferToArray(po);
  498. polygons.regions.push(point);
  499. });
  500. const segZone = segments(polygons);
  501. const comb = combine(poly, segZone);
  502. poly = selectDifference(comb)
  503. })
  504. }
  505. }
  506. })
  507. console.log(poly);
  508. // poly为segments类型
  509. return poly;
  510. }
  511. /**
  512. * 读取场景中的底图对象并生成相应json
  513. */
  514. getMapObject(): string {
  515. try {
  516. if (this.json) {
  517. let map = JSON.parse(this.json);
  518. const tempWall = map.EntityList[0].Elements.Walls
  519. console.log(map.EntityList[0].Elements.Walls.length);
  520. const logArr = this.deleteWallLog.map(t => {
  521. return t.SourceId
  522. })
  523. if (this.deleteWallLog.length) {
  524. for (let i = 0; i < tempWall.length; i++) {
  525. if (logArr.includes(tempWall[i].SourceId)) {
  526. tempWall.splice(i, 1);
  527. i--;
  528. }
  529. }
  530. }
  531. if (this.customWall.length) {
  532. this.customWall.forEach(t => {
  533. const data = t.toData();
  534. if (data.OutLine.length) {
  535. tempWall.push(data)
  536. }
  537. })
  538. }
  539. console.log(map.EntityList[0].Elements.Walls.length);
  540. return JSON.stringify(map);
  541. }
  542. return ''
  543. } catch (err) {
  544. console.log(err)
  545. return ''
  546. }
  547. }
  548. /**
  549. * item绘制完成
  550. */
  551. finishCreated(item: SGraphStyleItem) {
  552. this.grabItem = null;
  553. // @ts-ignore
  554. item.status = SItemStatus.Normal;
  555. this.selectContainer.clear();
  556. this.selectContainer.toggleItem(item);
  557. this.clearCmdStatus()
  558. }
  559. /**
  560. * 清除命令
  561. */
  562. clearCmdStatus() {
  563. this.drawCmd = ''
  564. }
  565. /**
  566. * 生成txt文件流
  567. *
  568. * @param fName 文件名
  569. */
  570. generateFile(fName: string, _fn: Function) {
  571. if (this.json) {
  572. // const zip = new JSZip();
  573. // // // // // // 名称-文件内容
  574. // zip.file(fName, this.json)
  575. // // console.log(zip);
  576. // zip.generateAsync({ type: "blob", compression: "DEFLATE" }).then(blob => {
  577. // // console.log(blob);
  578. // unzip(blob);
  579. // // this.upload('testmap4.jsonz', blob)
  580. // // let url = URL.createObjectURL(blob);
  581. // // SNetUtil.downLoad('12312312', url);
  582. // let xxx = new JSZip()
  583. // xxx.loadAsync(blob).then(res => {
  584. // // console.log(res.files)
  585. // // for(let i in res.files) {
  586. // // console.log(res.files[i]);
  587. // // res.files[i].async('nodebuffer').then(con => {
  588. // // console.log(con);
  589. // // })
  590. // // }
  591. // xxx.file('1').async("string").then(resp => {
  592. // // console.log(resp)
  593. // })
  594. // });
  595. // }, err => {
  596. // console.log(err)
  597. // });
  598. //
  599. const json = this.getMapObject()
  600. // 生成压缩的字符串
  601. const bl = this.zip(json);
  602. // const blob = new File([bl], '1')
  603. // 生成blob对象
  604. const blob = new Blob([bl], { type: 'application/octet-stream' })
  605. // let url = URL.createObjectURL(new Blob([bl]))
  606. // SNetUtil.downLoad('12312312', url)
  607. // 测试能否正常解压
  608. // unzip(blob);
  609. // console.log(blob)
  610. // const file = new File([bl], '111')
  611. this.upload(fName, blob, _fn)
  612. }
  613. }
  614. /**
  615. * 上传
  616. *
  617. * @param key 当前显示的楼层平面图的key
  618. * @param blob 上传的blob对象
  619. */
  620. upload(key: string, blob: Blob, _fn: Function) {
  621. let reader = new FileReader();
  622. reader.onloadstart = function () {
  623. // 这个事件在读取开始时触发
  624. console.log('start');
  625. };
  626. reader.onprogress = function (p) {
  627. // 这个事件在读取进行中定时触发
  628. console.log('onprogress--------', p);
  629. };
  630. reader.onload = function () {
  631. // 这个事件在读取成功结束后触发
  632. console.log('onload');
  633. };
  634. reader.onloadend = function () {
  635. var xhr = new XMLHttpRequest();
  636. xhr.open(
  637. "POST",
  638. `/image-service/common/file_upload?systemId=revit&secret=63afbef6906c342b&overwrite=true&key=${key}`
  639. );
  640. xhr.send(reader.result);
  641. xhr.onreadystatechange = function () {
  642. if (xhr.readyState == 4) {
  643. if (xhr.status == 200) {
  644. _fn()
  645. } else {
  646. }
  647. }
  648. };
  649. }
  650. reader.readAsArrayBuffer(blob);
  651. }
  652. /**
  653. * 压缩文件
  654. *
  655. * @param str 要压缩的字符串
  656. */
  657. zip(str: string): string {
  658. var binaryString = pako.deflate(str)
  659. // var binaryString = pako.gzip(escape(str), { to: 'string' });
  660. return binaryString;
  661. }
  662. }