resource-swkk.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. import { genCache, validNum } from "@/utils/shared";
  2. import {
  3. BorderTaggingInfo,
  4. CoverLine,
  5. getSceneApi,
  6. ResourceArgs,
  7. Scene,
  8. SceneResource,
  9. TaggingInfo,
  10. WallTaggingInfo,
  11. } from "./platform-resource";
  12. import { lineLen, Pos, zeroEq } from "@/utils/math";
  13. import {
  14. aiIconMap,
  15. getIconItem,
  16. styleIconMap,
  17. traceIconMap,
  18. } from "../constant";
  19. import {
  20. Euler,
  21. MathUtils,
  22. Matrix4,
  23. Matrix4Tuple,
  24. Object3D,
  25. Quaternion,
  26. Vector3,
  27. } from "three";
  28. import { extractConnectedSegments } from "@/utils/polygon";
  29. const fetchResource = genCache(
  30. (scene: Scene) => scene.m,
  31. async (scene: Scene) => {
  32. const prev = `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${
  33. scene.m
  34. }`;
  35. let version = Date.now();
  36. const get = (url: string, def?: any) =>
  37. getSceneApi("oss", `${prev}${url}?_=${version}`)
  38. .then((url) => fetch(url))
  39. .then((res) => res.json())
  40. .catch(() => def);
  41. const config = await get(`/data/scene.json`, {
  42. version: 0,
  43. billboards: 0,
  44. tags: 0,
  45. });
  46. version = config.version;
  47. const [
  48. userFloorpan,
  49. floorplan,
  50. hots,
  51. billboards,
  52. ais,
  53. cadInfo,
  54. cad,
  55. traces,
  56. detects,
  57. ] = await Promise.all([
  58. get("/user/floorplan.json", {}),
  59. get("/data/floorplan.json", { floors: [] }),
  60. get("/user/hot.json", []),
  61. get("/user/billboards.json", []),
  62. get("/data/floorplan/ai.json", []),
  63. get("/data/floorplan/info.json", { floors: [] }),
  64. get("/data/floorplan_cad.json", { floors: [] }),
  65. get("/user/evidence.json", []),
  66. get("/images/ai/detect/detect-ai.json", []),
  67. ]);
  68. return {
  69. userFloorpan,
  70. hots,
  71. billboards,
  72. ais,
  73. floorplan,
  74. cad,
  75. cadInfo,
  76. config,
  77. traces,
  78. detects,
  79. };
  80. },
  81. 150000
  82. );
  83. export const getFloors = async (scene: Scene) => {
  84. const { floorplan } = await fetchResource(scene);
  85. return floorplan.floors;
  86. };
  87. export const getCoverLine = async (
  88. scene: Scene,
  89. subgroup: any,
  90. scale: number
  91. ) => {
  92. const { cadInfo, cad } = await fetchResource(scene);
  93. const floors = cad.floors;
  94. let info: CoverLine;
  95. const reqs: Promise<any>[] = [];
  96. for (let i = 0; i < floors.length; i++) {
  97. const floor = floors[i];
  98. if (floor.subgroup !== subgroup) continue;
  99. const floorInfo = cadInfo.floors.find(
  100. (item: any) => item.subgroup === floor.subgroup
  101. );
  102. const geos = extractConnectedSegments(floor.segment).map((geo) => {
  103. return geo.map((id) => {
  104. const p = floor["vertex-xy"].find((item: any) => item.id === id);
  105. return { x: p.x * scale, y: -p.y * scale, key: id.toString() };
  106. });
  107. });
  108. let bound = {
  109. ...(floor.cadInfo.cadBoundingBox || {}),
  110. ...floorInfo?.bound,
  111. };
  112. if (!bound || !("x_max" in bound)) {
  113. const xs = geos.flatMap((item) => item.map((p) => p.x));
  114. const ys = geos.flatMap((item) => item.map((p) => p.y));
  115. bound = {
  116. x_min: Math.min(...xs),
  117. x_max: Math.max(...xs),
  118. y_min: Math.min(...ys),
  119. y_max: Math.max(...ys),
  120. };
  121. } else {
  122. bound = {
  123. x_min: bound.x_min * scale,
  124. x_max: bound.x_max * scale,
  125. y_min: bound.y_min * scale,
  126. y_max: bound.y_max * scale,
  127. };
  128. }
  129. const item = {
  130. name: floor.name,
  131. subgroup: Number(floor.subgroup),
  132. thumb: "",
  133. bound,
  134. geos,
  135. };
  136. info = item;
  137. reqs.push(
  138. getSceneApi(
  139. "oss",
  140. `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${
  141. scene.m
  142. }/data/floorplan/floor_${floor.subgroup}.png`
  143. )
  144. .then((url) => (item.thumb = url))
  145. .catch(() => {})
  146. );
  147. }
  148. await Promise.all(reqs);
  149. return info!;
  150. };
  151. export const getCompass = async (scene: Scene) => {
  152. const { config, userFloorpan } = await fetchResource(scene);
  153. return "compass" in userFloorpan
  154. ? userFloorpan?.compass
  155. : MathUtils.radToDeg(Number(config.orientation || 0));
  156. };
  157. export const getHotTaggingInfos = async (scene: Scene, scale: number) => {
  158. const { hots } = await fetchResource(scene);
  159. const infos: TaggingInfo[] = [];
  160. const reqs: Promise<any>[] = [];
  161. for (const hot of hots) {
  162. if (!validNum(hot.position.x) || !validNum(hot.position.y)) continue;
  163. reqs.push(
  164. getSceneApi(
  165. "oss",
  166. `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${
  167. scene.m
  168. }/user/${hot.icon}`
  169. )
  170. .then((url) =>
  171. infos.push({
  172. position: { x: hot.position.x * scale, y: hot.position.y * scale },
  173. url,
  174. })
  175. )
  176. .catch(() => {})
  177. );
  178. }
  179. await Promise.all(reqs);
  180. return infos;
  181. };
  182. const getTraceAttri = async (icon: string, trace: any) => {
  183. const size = {
  184. width: trace.visiSetting.scale,
  185. height: trace.visiSetting.scale,
  186. };
  187. const attrib: any = {
  188. url: icon,
  189. size,
  190. angle: trace.visiSetting.angle,
  191. };
  192. if (trace.iconType === 2 || !trace.tag3d?.object) {
  193. return attrib;
  194. }
  195. const getPath = (children: any, name: string): any[] | undefined => {
  196. for (const item of children) {
  197. if (item.name === name) {
  198. return [item];
  199. } else if (item.children) {
  200. const childPath = getPath(item.children, name);
  201. if (childPath) {
  202. return [item, ...childPath];
  203. }
  204. }
  205. }
  206. };
  207. const path = getPath([trace.tag3d.object], "sprite");
  208. if (!path) return attrib;
  209. const mat = new Matrix4();
  210. for (const node of path) {
  211. mat.multiply(new Matrix4(...(node.matrix as Matrix4Tuple)));
  212. }
  213. const position = new Vector3();
  214. const quat = new Quaternion();
  215. const scale = new Vector3();
  216. mat.decompose(position, quat, scale);
  217. const euler = new Euler().setFromQuaternion(quat, "XYZ");
  218. return {
  219. ...attrib,
  220. angle: MathUtils.radToDeg(euler.y),
  221. };
  222. };
  223. // 痕迹物证
  224. export const getTraceTaggingInfos = async (
  225. scene: Scene,
  226. scale: number,
  227. floorIndex: number
  228. ) => {
  229. const { traces } = await fetchResource(scene);
  230. const infos: TaggingInfo[] = [];
  231. const reqs: Promise<any>[] = [];
  232. for (const trace of traces) {
  233. if (
  234. !validNum(trace.position?.x) ||
  235. !validNum(trace.position?.z) ||
  236. ("floorIndex" in trace && trace.floorIndex !== floorIndex) ||
  237. trace.iconType === 0
  238. )
  239. continue;
  240. const isSys = trace.icon.indexOf("/") > -1;
  241. const icon = isSys
  242. ? trace.icon.substring(trace.icon.lastIndexOf("/") + 1)
  243. : trace.icon;
  244. const styleMap = (traceIconMap as any)[icon];
  245. if (!icon) continue;
  246. const getIcon = isSys
  247. ? styleMap
  248. ? Promise.resolve(`./icons/${styleMap}.svg`)
  249. : getSceneApi("./", `./traces/${icon}.svg`)
  250. : getSceneApi(
  251. "oss",
  252. `${scene.mapping ? "/" + scene.mapping : ""}/scene_edit_data/${
  253. scene.m
  254. }/user/${icon}`
  255. );
  256. const name = (styleMap && getIconItem(styleMap)?.name) || "";
  257. const getAttr = getIcon.then(async (url) => getTraceAttri(url, trace));
  258. reqs.push(
  259. getAttr
  260. .then((attr) => ({
  261. url: attr.url,
  262. name: trace.title || name,
  263. position: {
  264. x: trace.position.x * scale,
  265. y: trace.position.z * scale,
  266. z:
  267. trace.position.y < 0
  268. ? Math.ceil(trace.position.y * scale * 10) / 10
  269. : Math.floor(trace.position.y * scale * 10) / 10,
  270. },
  271. key: "trace",
  272. rotate: attr.angle,
  273. size: {
  274. width: attr.size.width * scale,
  275. height: attr.size.height * scale,
  276. },
  277. }))
  278. .then((info) => infos.push(info))
  279. .catch(() => {})
  280. );
  281. }
  282. await Promise.all(reqs);
  283. return infos;
  284. };
  285. const getBillYaw = (bill: any) => {
  286. function isLieDown(quaternion: Quaternion) {
  287. let direction = new Vector3(0, 0, -1).applyQuaternion(quaternion);
  288. return Math.abs(direction.y) > 0.9;
  289. }
  290. let billboard = new Object3D();
  291. let plane = new Object3D();
  292. billboard.add(plane);
  293. billboard.quaternion
  294. .copy({ x: bill.qua[0], y: bill.qua[1], z: bill.qua[2], w: bill.qua[3] })
  295. .normalize(); //qua数据里的
  296. plane.quaternion.setFromAxisAngle(
  297. new Vector3(0, 0, 1),
  298. MathUtils.degToRad(-bill.faceAngle)
  299. );
  300. const up = new Vector3(0, 1, 0); //plane的上方指示着方向
  301. const right = new Vector3(1, 0, 0); //令躺倒时的旋转轴
  302. let qua = plane.getWorldQuaternion(new Quaternion());
  303. const ld = isLieDown(billboard.quaternion);
  304. if (!ld) {
  305. //使朝其后方躺倒后再求angle
  306. let rotAxis = right.clone().applyQuaternion(qua); //旋转轴
  307. (rotAxis.y = 0), rotAxis.normalize();
  308. let rot = new Quaternion().setFromAxisAngle(rotAxis, Math.PI / 2);
  309. qua.premultiply(rot);
  310. }
  311. let dir = up.clone().applyQuaternion(qua); //在墙面朝x时正反得到的一样,很奇怪,所以得到的会反向
  312. let yaw = Math.atan2(-dir.z, dir.x) - Math.PI / 2;
  313. return -yaw;
  314. };
  315. export const getBillTaggingInfos = async (
  316. scene: Scene,
  317. scale: number,
  318. subgroup: number
  319. ) => {
  320. const { billboards } = await fetchResource(scene);
  321. const infos: TaggingInfo[] = [];
  322. const reqs: Promise<any>[] = [];
  323. for (const bill of billboards) {
  324. if (
  325. !validNum(bill.pos[0]) ||
  326. !validNum(bill.pos[2]) ||
  327. ("subgroup" in bill && bill.subgroup !== subgroup)
  328. )
  329. continue;
  330. const styleMap = (styleIconMap as any)[bill.icon];
  331. const getIcon =
  332. bill.icon.indexOf("style-") === 0
  333. ? styleMap
  334. ? Promise.resolve(`./icons/${styleMap}.svg`)
  335. : getSceneApi("./", `./styles/${bill.icon}.svg`).catch((e) => {
  336. return getSceneApi(
  337. "ossRoot",
  338. `/sdk/images/billboard/${bill.icon}.png`
  339. );
  340. })
  341. : getSceneApi(
  342. "oss",
  343. `${scene.mapping ? "/" + scene.mapping : ""}/scene_view_data/${
  344. scene.m
  345. }/user/${bill.icon}`
  346. );
  347. const yRotate = getBillYaw(bill);
  348. const name = (styleMap && getIconItem(styleMap)?.name) || "";
  349. reqs.push(
  350. getIcon
  351. .then((url) => ({
  352. url,
  353. name,
  354. position: {
  355. x: bill.pos[0] * scale,
  356. y: bill.pos[2] * scale,
  357. z:
  358. bill.pos[1] < 0
  359. ? Math.ceil(bill.pos[1] * scale * 10) / 10
  360. : Math.floor(bill.pos[1] * scale * 10) / 10,
  361. },
  362. rotate: yRotate,
  363. size: {
  364. width: bill.width * scale,
  365. height: bill.height * scale,
  366. },
  367. }))
  368. .then((info) => infos.push(info))
  369. .catch(() => {})
  370. );
  371. }
  372. await Promise.all(reqs);
  373. return infos;
  374. };
  375. export const getAITaggingInfos = async (
  376. scene: Scene,
  377. subgroup: any,
  378. bound: Record<string, number>
  379. ) => {
  380. const { ais } = await fetchResource(scene);
  381. const infos: TaggingInfo[] = [];
  382. const drawBound = {
  383. x: bound.x_min,
  384. y: bound.y_min,
  385. w: bound.x_max - bound.x_min,
  386. h: bound.y_max - bound.y_min,
  387. };
  388. for (const data of ais) {
  389. if (data.imagePath) {
  390. const reg = data.imagePath.match(/floor_(\d)\.png/);
  391. const curSubgroup = reg ? Number(reg[1]) : undefined;
  392. if (curSubgroup !== subgroup) continue;
  393. }
  394. for (const shape of data.shapes) {
  395. const icon =
  396. shape.category in aiIconMap
  397. ? (aiIconMap as any)[shape.category]
  398. : shape.category;
  399. const itemIcon = getIconItem(icon);
  400. const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
  401. if (isWall) continue;
  402. const name = itemIcon?.name || "";
  403. const isTag = icon === "Tag";
  404. if (!name && !isTag) continue;
  405. const pixelCenter = {
  406. x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth,
  407. y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight,
  408. };
  409. const center = {
  410. x: pixelCenter.x * drawBound.w + drawBound.x,
  411. y: pixelCenter.y * drawBound.h + drawBound.y,
  412. };
  413. const size = {
  414. width:
  415. ((shape.bbox[2] - shape.bbox[0]) / data.imageWidth) * drawBound.w,
  416. height:
  417. ((shape.bbox[3] - shape.bbox[1]) / data.imageHeight) * drawBound.h,
  418. };
  419. infos.push({
  420. isText: isTag,
  421. position: center,
  422. rotate: 0,
  423. url: isTag ? shape.name : `./icons/${icon ? icon : "circle"}.svg`,
  424. name,
  425. size,
  426. });
  427. }
  428. }
  429. return infos;
  430. };
  431. export const getAIBorderTaggingInfos = async (
  432. scene: Scene,
  433. subgroup: any,
  434. bound: Record<string, number>,
  435. scale: number
  436. ) => {
  437. const { detects } = await fetchResource(scene);
  438. const infos: BorderTaggingInfo[] = [];
  439. const drawBound = {
  440. x: bound.x_min,
  441. y: bound.y_min,
  442. w: bound.x_max - bound.x_min,
  443. h: bound.y_max - bound.y_min,
  444. };
  445. // console.log(drawBound);
  446. for (const shape of detects) {
  447. // if (data.imagePath) {
  448. // const reg = data.imagePath.match(/floor_(\d)\.png/);
  449. // const curSubgroup = reg ? Number(reg[1]) : undefined;
  450. // if (curSubgroup !== subgroup) continue;
  451. // }
  452. const min = { x: shape.bbox[0][0], y: shape.bbox[0][2] };
  453. const max = { x: shape.bbox[0][0], y: shape.bbox[0][2] };
  454. for (const box of shape.bbox) {
  455. min.x = Math.min(box[0], min.x);
  456. min.y = Math.min(box[2], min.y);
  457. max.x = Math.max(box[0], max.x);
  458. max.y = Math.max(box[2], max.y);
  459. }
  460. min.x *= scale;
  461. min.y *= scale;
  462. max.x *= scale;
  463. max.y *= scale;
  464. const center = {
  465. x: (min.x + max.x) / 2,
  466. y: (min.y + max.y) / 2,
  467. };
  468. const size = {
  469. width: max.x - min.x,
  470. height: max.y - min.y,
  471. };
  472. infos.push({
  473. text: shape.name,
  474. points: [
  475. { x: center.x - size.width / 2, y: center.y - size.height / 2 },
  476. { x: center.x + size.width / 2, y: center.y - size.height / 2 },
  477. { x: center.x + size.width / 2, y: center.y + size.height / 2 },
  478. { x: center.x - size.width / 2, y: center.y + size.height / 2 },
  479. ],
  480. center,
  481. });
  482. }
  483. return infos;
  484. };
  485. export const getWallAITaggingInfos = async (
  486. scene: Scene,
  487. subgroup: any,
  488. scale: number,
  489. cover: CoverLine
  490. ) => {
  491. const { floorplan } = await fetchResource(scene);
  492. const infos: WallTaggingInfo[] = [];
  493. const getCoverLine = (wall: Pos[]) => {
  494. type Info = { p: Pos & { key: string }; len: number };
  495. const infos: { start: Info; end: Info; inv: boolean }[] = [];
  496. for (let i = 0; i < cover.geos.length; i++) {
  497. const geo = cover.geos[i];
  498. for (let j = 0; j < geo.length - 1; j++) {
  499. const len00 = lineLen(geo[j], wall[0]);
  500. const len10 = lineLen(geo[j + 1], wall[0]);
  501. const len01 = lineLen(geo[j], wall[1]);
  502. const len11 = lineLen(geo[j + 1], wall[1]);
  503. let start: Info, end: Info, inv: boolean;
  504. if (len00 + len11 < len10 + len01) {
  505. inv = false;
  506. start = { p: geo[j], len: len00 };
  507. end = { p: geo[j + 1], len: len11 };
  508. } else {
  509. inv = true;
  510. start = { p: geo[j + 1], len: len10 };
  511. end = { p: geo[j], len: len01 };
  512. }
  513. if (zeroEq(start.len) && zeroEq(end.len)) {
  514. return { points: [start.p, end.p], inv };
  515. }
  516. infos.push({ start, end, inv });
  517. }
  518. }
  519. const sortInfos = [...infos].sort(
  520. (a, b) => a.start.len + a.end.len - (b.start.len + b.end.len)
  521. );
  522. const target = sortInfos[0];
  523. if (target.start.len + target.end.len < 10) {
  524. return { points: [target.start.p, target.end.p], inv: target.inv };
  525. }
  526. };
  527. for (const floor of floorplan.floors) {
  528. if (floor.subgroup !== subgroup) continue;
  529. for (const key in floor.symbols) {
  530. const item = floor.symbols[key];
  531. const icon =
  532. item.geoType in aiIconMap
  533. ? (aiIconMap as any)[item.geoType]
  534. : item.geoType;
  535. const itemIcon = getIconItem(icon);
  536. let name = itemIcon?.name;
  537. const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
  538. if (!isWall) continue;
  539. const cadWall = [
  540. floor.points[floor.walls[item.parent].start],
  541. floor.points[floor.walls[item.parent].end],
  542. ].map((p) => ({
  543. x: p.x * scale,
  544. y: -p.y * scale,
  545. }));
  546. const wall = getCoverLine(cadWall);
  547. const line = [item.startPoint, item.endPoint].map((p) => ({
  548. x: p.x * scale,
  549. y: -p.y * scale,
  550. }));
  551. if (!wall) {
  552. continue;
  553. }
  554. const points = wall.inv ? wall.points.reverse() : wall.points;
  555. infos.push({
  556. name: name,
  557. url: `./icons/${icon ? icon : "circle"}.svg`,
  558. startLen: lineLen(points[0], line[0]),
  559. endLen: lineLen(points[0], line[1]),
  560. openSide: item.openSide,
  561. height: itemIcon?.parse?.height,
  562. type: itemIcon?.parse?.type || "full",
  563. pointIds: points.map((item) => item.key),
  564. });
  565. }
  566. }
  567. return infos;
  568. };
  569. export const getResource = async ({
  570. scene,
  571. floorName,
  572. syncs,
  573. scale,
  574. }: ResourceArgs) => {
  575. const data = await fetchResource(scene);
  576. const floorIndex = data.floorplan.floors.findIndex(
  577. (item: any) => item.name === floorName
  578. );
  579. const floor = data.floorplan.floors[floorIndex];
  580. const key = floor.subgroup;
  581. const taggings: TaggingInfo[] = [];
  582. const wallTaggings: WallTaggingInfo[] = [];
  583. const borderTaggings: BorderTaggingInfo[] = [];
  584. let coverLine: CoverLine;
  585. const compass = await getCompass(scene);
  586. console.log(compass);
  587. const reqs: Promise<any>[] = [
  588. getCompass(scene),
  589. getCoverLine(scene, key, scale)
  590. .then((lines) => (coverLine = lines))
  591. .then((cover) =>
  592. Promise.all([
  593. getWallAITaggingInfos(scene, key, scale, cover).then((ts) =>
  594. wallTaggings.push(...ts)
  595. ),
  596. getAITaggingInfos(scene, key, cover.bound).then((ts) =>
  597. taggings.push(...ts)
  598. ),
  599. getAIBorderTaggingInfos(scene, key, cover.bound, scale).then((ts) =>
  600. borderTaggings.push(...ts)
  601. ),
  602. ])
  603. ),
  604. ];
  605. if (syncs.includes("hot")) {
  606. reqs.push(
  607. getHotTaggingInfos(scene, scale).then((ts) => taggings.push(...ts))
  608. );
  609. }
  610. if (syncs.includes("signage")) {
  611. reqs.push(
  612. getBillTaggingInfos(scene, scale, key).then((ts) => taggings.push(...ts))
  613. );
  614. }
  615. if (syncs.includes("traces")) {
  616. reqs.push(
  617. getTraceTaggingInfos(scene, scale, floorIndex).then((ts) =>
  618. taggings.push(...ts)
  619. )
  620. );
  621. }
  622. await Promise.all(reqs);
  623. return {
  624. taggings: taggings,
  625. borderTaggings,
  626. wallTaggings,
  627. cover: coverLine!,
  628. compass: compass,
  629. } as SceneResource;
  630. };