Draw.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. import { dataService } from "../Service/DataService.js";
  2. import { stateService } from "../Service/StateService.js";
  3. import { coordinate } from "../Coordinate.js";
  4. import Style from "@/graphic/CanvasStyle/index.js";
  5. import VectorType from "../enum/VectorType.js";
  6. import { mathUtil } from "../Util/MathUtil.js";
  7. import ElementEvents from "../enum/ElementEvents.js";
  8. import { elementService } from "../Service/ElementService.js";
  9. import UIEvents from "@/graphic/enum/UIEvents.js";
  10. const imgCache = {};
  11. const help = {
  12. getVectorStyle(vector, geoType = vector.geoType) {
  13. const geoId = vector?.vectorId;
  14. if (!geoId) {
  15. return [Style[geoType], undefined];
  16. }
  17. const itemsEntry = [
  18. [stateService.getSelectItem(), "Select"],
  19. [stateService.getDraggingItem(), "Dragging"],
  20. [stateService.getFocusItem(), "Focus"],
  21. ];
  22. let currentAttr;
  23. return [
  24. itemsEntry.reduce((prev, [item, attr]) => {
  25. if (
  26. item &&
  27. // item.type === VectorType[geoType] &&
  28. geoId === item.vectorId
  29. ) {
  30. if (Style[attr] && Style[attr][geoType]) {
  31. currentAttr = attr;
  32. return Style[attr][geoType];
  33. }
  34. }
  35. return prev;
  36. }, Style[geoType]),
  37. currentAttr,
  38. ];
  39. },
  40. setVectorStyle(ctx, vector, geoType = vector.geoType) {
  41. const [styles, attr] = help.getVectorStyle(vector, geoType);
  42. for (const style in styles) {
  43. if (typeof styles[style] === "function") {
  44. styles[style](ctx, vector);
  45. } else {
  46. ctx[style] = styles[style];
  47. }
  48. }
  49. return [styles, attr];
  50. },
  51. transformCoves(lines) {
  52. return lines.map((line) =>
  53. line.map((line) => ({
  54. start: coordinate.getScreenXY(line.start),
  55. end: coordinate.getScreenXY(line.end),
  56. controls: line.controls.map(coordinate.getScreenXY.bind(coordinate)),
  57. }))
  58. );
  59. },
  60. drawCoves(ctx, coves) {
  61. for (const curve of coves) {
  62. ctx.beginPath();
  63. ctx.moveTo(curve.start.x, curve.start.y);
  64. if (curve.controls.length === 1) {
  65. ctx.quadraticCurveTo(
  66. curve.controls[0].x,
  67. curve.controls[0].y,
  68. curve.end.x,
  69. curve.end.y
  70. );
  71. } else {
  72. ctx.bezierCurveTo(
  73. curve.controls[0].x,
  74. curve.controls[0].y,
  75. curve.controls[1].x,
  76. curve.controls[1].y,
  77. curve.end.x,
  78. curve.end.y
  79. );
  80. }
  81. ctx.stroke();
  82. }
  83. },
  84. getReal(data) {
  85. return (data * coordinate.ratio * coordinate.zoom) / coordinate.defaultZoom;
  86. },
  87. getImage(src) {
  88. if (imgCache[src]) {
  89. return imgCache[src];
  90. }
  91. const img = new Image();
  92. img.src = src;
  93. return (imgCache[src] = new Promise((resolve) => {
  94. img.onload = () => {
  95. resolve(img);
  96. };
  97. }));
  98. },
  99. };
  100. export default class Draw {
  101. constructor() {
  102. this.canvas = null;
  103. this.context = null;
  104. }
  105. initContext(canvas) {
  106. if (canvas) {
  107. this.canvas = canvas;
  108. this.context = canvas.getContext("2d");
  109. } else {
  110. this.context = null;
  111. this.canvas = null;
  112. }
  113. }
  114. clear() {
  115. this.context.clearRect(
  116. 0,
  117. 0,
  118. this.context.canvas.width,
  119. this.context.canvas.height
  120. );
  121. }
  122. drawBackGroundImg(vector) {
  123. const img = vector.imageData;
  124. const width = help.getReal(img.width);
  125. const height = help.getReal(img.height);
  126. const center = coordinate.getScreenXY(vector.center);
  127. this.context.save();
  128. this.context.drawImage(
  129. img,
  130. center.x - width / 2,
  131. center.y - height / 2,
  132. width,
  133. height
  134. );
  135. this.context.restore();
  136. }
  137. drawGrid(startX, startY, w, h, step1, step2) {
  138. this.context.save();
  139. this.context.beginPath();
  140. for (var x = startX; x <= w; x += step1) {
  141. this.context.moveTo(x, 0);
  142. this.context.lineTo(x, h);
  143. }
  144. for (var y = startY; y <= h; y += step1) {
  145. this.context.moveTo(0, y);
  146. this.context.lineTo(w, y);
  147. }
  148. this.context.strokeStyle = "rgba(0,0,0,0.1)";
  149. this.context.lineWidth = 0.5;
  150. this.context.stroke();
  151. this.context.beginPath();
  152. for (var x = startX; x <= w; x += step2) {
  153. this.context.moveTo(x, 0);
  154. this.context.lineTo(x, h);
  155. }
  156. for (var y = startY; y <= h; y += step2) {
  157. this.context.moveTo(0, y);
  158. this.context.lineTo(w, y);
  159. }
  160. this.context.strokeStyle = "rgba(0,0,0,0.2)";
  161. this.context.lineWidth = 1;
  162. this.context.stroke();
  163. this.context.restore();
  164. }
  165. drawRoad(vector, isTemp) {
  166. if (!isTemp && vector.display && vector.way !== "oneWay") {
  167. const ctx = this.context;
  168. const draw = (midDivide) => {
  169. const startScreen = coordinate.getScreenXY(midDivide.start);
  170. const endScreen = coordinate.getScreenXY(midDivide.end);
  171. ctx.beginPath();
  172. ctx.moveTo(startScreen.x, startScreen.y);
  173. ctx.lineTo(endScreen.x, endScreen.y);
  174. ctx.stroke();
  175. };
  176. ctx.save();
  177. help.setVectorStyle(ctx, vector);
  178. vector.midDivide.leftMidDivide && draw(vector.midDivide.leftMidDivide);
  179. vector.midDivide.rightMidDivide && draw(vector.midDivide.rightMidDivide);
  180. ctx.restore();
  181. }
  182. if (import.meta.env.DEV && !isTemp) {
  183. const startReal = isTemp
  184. ? vector.start
  185. : dataService.getRoadPoint(vector.startId);
  186. const endReal = isTemp
  187. ? vector.end
  188. : dataService.getRoadPoint(vector.endId);
  189. this.drawTextByInfo(
  190. { x: (startReal.x + endReal.x) / 2, y: (startReal.y + endReal.y) / 2 },
  191. vector.vectorId
  192. );
  193. }
  194. this.drawRoadEdge(vector, isTemp);
  195. vector.leftLanes && vector.leftLanes.forEach(this.drawLan.bind(this));
  196. vector.rightLanes && vector.rightLanes.forEach(this.drawLan.bind(this));
  197. }
  198. drawLan(lan) {
  199. const ctx = this.context;
  200. const start = coordinate.getScreenXY(lan.start);
  201. const end = coordinate.getScreenXY(lan.end);
  202. ctx.save();
  203. ctx.beginPath();
  204. help.setVectorStyle(ctx, null, "Lane");
  205. ctx.setLineDash(Style.Lane.dash);
  206. ctx.moveTo(start.x, start.y);
  207. ctx.lineTo(end.x, end.y);
  208. ctx.stroke();
  209. ctx.restore();
  210. if (import.meta.env.DEV) {
  211. this.drawPoint(lan.start);
  212. this.drawPoint(lan.end);
  213. }
  214. }
  215. drawRoadEdge(vector, isTemp) {
  216. //判断是否与road方向一致。角度足够小,路足够宽,有可能向量方向不一致
  217. const start = isTemp
  218. ? vector.start
  219. : dataService.getRoadPoint(vector.startId);
  220. const end = isTemp ? vector.end : dataService.getRoadPoint(vector.endId);
  221. const drawRoadEdgeChild = (edgeVector) => {
  222. const flag = mathUtil.isSameDirForVector(
  223. start,
  224. end,
  225. edgeVector.start,
  226. edgeVector.end
  227. );
  228. if (flag) {
  229. ctx.beginPath();
  230. const point1 = coordinate.getScreenXY(edgeVector.start);
  231. const point2 = coordinate.getScreenXY(edgeVector.end);
  232. ctx.moveTo(point1.x, point1.y);
  233. ctx.lineTo(point2.x, point2.y);
  234. ctx.stroke();
  235. }
  236. this.drawTextByInfo(
  237. {
  238. x: (edgeVector.start.x + edgeVector.end.x) / 2,
  239. y: (edgeVector.start.y + edgeVector.end.y) / 2,
  240. },
  241. edgeVector.vectorId
  242. );
  243. };
  244. const leftEdge = isTemp
  245. ? vector.leftEdge
  246. : dataService.getRoadEdge(vector.leftEdgeId);
  247. const rightEdge = isTemp
  248. ? vector.rightEdge
  249. : dataService.getRoadEdge(vector.rightEdgeId);
  250. const ctx = this.context;
  251. ctx.save();
  252. isTemp && (ctx.globalAlpha = 0.3);
  253. help.setVectorStyle(ctx, leftEdge);
  254. drawRoadEdgeChild(leftEdge);
  255. help.setVectorStyle(ctx, rightEdge);
  256. drawRoadEdgeChild(rightEdge);
  257. ctx.restore();
  258. if (import.meta.env.DEV) {
  259. this.drawPoint(leftEdge.start);
  260. this.drawPoint(leftEdge.end);
  261. this.drawPoint(rightEdge.start);
  262. this.drawPoint(rightEdge.end);
  263. }
  264. }
  265. drawCrossPoint(vector) {
  266. const start = coordinate.getScreenXY(
  267. dataService
  268. .getRoadEdge(vector.edgeInfo1.id)
  269. .getPosition(vector.edgeInfo1.dir)
  270. );
  271. const end = coordinate.getScreenXY(
  272. dataService
  273. .getRoadEdge(vector.edgeInfo2.id)
  274. .getPosition(vector.edgeInfo2.dir)
  275. );
  276. const pt2 = mathUtil.twoOrderBezier(
  277. 0.5,
  278. start,
  279. coordinate.getScreenXY({ x: vector.x, y: vector.y }),
  280. end
  281. );
  282. const pt = mathUtil.twoOrderBezier2(0.5, start, pt2, end);
  283. const extremePoint = coordinate.getScreenXY(vector.extremePoint);
  284. const ctx = this.context;
  285. ctx.save();
  286. ctx.strokeStyle = "red";
  287. ctx.beginPath();
  288. ctx.arc(
  289. // pt.x,
  290. // pt.y,
  291. extremePoint.x,
  292. extremePoint.y,
  293. Style.CrossPoint.radius * coordinate.ratio,
  294. 0,
  295. Math.PI * 2,
  296. true
  297. );
  298. ctx.stroke();
  299. ctx.fill();
  300. ctx.restore();
  301. ctx.save();
  302. ctx.beginPath();
  303. help.setVectorStyle(ctx, null, "RoadEdge");
  304. //曲线
  305. // ctx.moveTo(start.x, start.y);
  306. // ctx.quadraticCurveTo(pt.x, pt.y, end.x, end.y);
  307. const [coves] = help.transformCoves([vector.curves]);
  308. help.drawCoves(ctx, coves);
  309. ctx.restore();
  310. }
  311. drawCurveRoad(vector) {
  312. if (vector.display && vector.midDivide) {
  313. const covesArray = help.transformCoves([
  314. vector.midDivide.leftMidDivideCurves,
  315. vector.midDivide.rightMidDivideCurves,
  316. ]);
  317. const ctx = this.context;
  318. ctx.save();
  319. help.setVectorStyle(ctx, vector);
  320. for (let coves of covesArray) {
  321. help.drawCoves(ctx, coves);
  322. }
  323. ctx.restore();
  324. }
  325. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.rightEdgeId));
  326. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.leftEdgeId));
  327. vector.leftLanesCurves &&
  328. vector.leftLanesCurves.forEach(this.drawCurveLan.bind(this));
  329. vector.rightLanesCurves &&
  330. vector.rightLanesCurves.forEach(this.drawCurveLan.bind(this));
  331. if (import.meta.env.DEV) {
  332. vector.points.forEach(this.drawPoint.bind(this));
  333. }
  334. }
  335. drawCurveRoadEdge(vector, isTemp) {
  336. const [coves] = help.transformCoves([vector.curves]);
  337. const ctx = this.context;
  338. ctx.save();
  339. help.setVectorStyle(ctx, vector);
  340. help.drawCoves(ctx, coves);
  341. ctx.restore();
  342. if (import.meta.env.DEV) {
  343. vector.points.forEach(this.drawPoint.bind(this));
  344. }
  345. }
  346. drawCurveLan(lines) {
  347. const [coves] = help.transformCoves([lines]);
  348. const ctx = this.context;
  349. ctx.save();
  350. help.setVectorStyle(ctx, null, "CurveLan");
  351. ctx.setLineDash(Style.Lane.dash);
  352. help.drawCoves(ctx, coves);
  353. ctx.restore();
  354. if (import.meta.env.DEV) {
  355. lines.map((line) => {
  356. this.drawPoint(line.start);
  357. this.drawPoint(line.end);
  358. });
  359. }
  360. }
  361. drawRoadPoint(vector) {
  362. this.drawPoint(vector);
  363. }
  364. drawArrow(vector) {
  365. const startReal = dataService.getPoint(vector.startId);
  366. const start = coordinate.getScreenXY(startReal);
  367. const endReal = dataService.getPoint(vector.endId);
  368. const end = coordinate.getScreenXY(endReal);
  369. const ctx = this.context;
  370. ctx.save();
  371. const [style] = help.setVectorStyle(this.context, vector, "Arrow");
  372. if (vector.arrowColor) {
  373. ctx.strokeStyle = vector.arrowColor;
  374. }
  375. ctx.beginPath();
  376. ctx.moveTo(start.x, start.y);
  377. ctx.lineTo(end.x, end.y);
  378. const dires =
  379. vector.category === UIEvents.MeasureLine
  380. ? [
  381. [start, end],
  382. [end, start],
  383. ]
  384. : [[start, end]];
  385. for (let [start, end] of dires) {
  386. const lines = mathUtil.getArrow(start, end);
  387. ctx.moveTo(lines[0].x, lines[0].y);
  388. ctx.lineTo(lines[1].x, lines[1].y);
  389. ctx.lineTo(lines[2].x, lines[2].y);
  390. }
  391. ctx.stroke();
  392. ctx.restore();
  393. if ([Style.Focus.Arrow, Style.Select.Arrow].includes(style)) {
  394. this.drawPoint(startReal);
  395. this.drawPoint(endReal);
  396. }
  397. }
  398. drawMagnifier(vector) {
  399. const ctx = this.context;
  400. this.drawPoint({
  401. ...vector,
  402. ...vector.position,
  403. radius: Style.Magnifier.radius,
  404. });
  405. const pt = coordinate.getScreenXY(vector.position);
  406. const target = coordinate.getScreenXY(vector.popPosition);
  407. const [style] = help.setVectorStyle(ctx, vector);
  408. const radius = help.getReal(vector.radius || style.radius);
  409. const offset = radius / 2;
  410. const targetPts =
  411. style === Style.Focus.Magnifier
  412. ? [mathUtil.translate(pt, target, pt, radius), target]
  413. : null;
  414. ctx.save();
  415. ctx.beginPath();
  416. ctx.moveTo(pt.x - offset, pt.y);
  417. ctx.lineTo(pt.x + offset, pt.y);
  418. ctx.stroke();
  419. ctx.beginPath();
  420. ctx.moveTo(pt.x, pt.y - offset);
  421. ctx.lineTo(pt.x, pt.y + offset);
  422. ctx.stroke();
  423. if (targetPts) {
  424. ctx.beginPath();
  425. ctx.moveTo(targetPts[0].x, targetPts[0].y);
  426. ctx.lineTo(targetPts[1].x, targetPts[1].y);
  427. ctx.stroke();
  428. let img, imgBound;
  429. if (vector.photoImage) {
  430. img = vector.photoImage;
  431. imgBound = [0, 0, img.width, img.height];
  432. } else {
  433. const size = help.getReal(style.target.realRadius);
  434. const backImg = dataService.getBackgroundImg();
  435. img = backImg.imageData;
  436. const imgCenter = coordinate.getScreenXY(backImg.center);
  437. const start = {
  438. x: imgCenter.x - help.getReal(img.width) / 2,
  439. y: imgCenter.y - help.getReal(img.height) / 2,
  440. };
  441. const ro = img.width / help.getReal(img.width);
  442. imgBound = [
  443. (pt.x - start.x - size) * ro,
  444. (pt.y - start.y - size) * ro,
  445. size * 2 * ro,
  446. size * 2 * ro,
  447. ];
  448. }
  449. const size = help.getReal(style.target.radius);
  450. ctx.beginPath();
  451. ctx.arc(target.x, target.y, size, 0, 2 * Math.PI);
  452. ctx.clip();
  453. ctx.drawImage(
  454. img,
  455. ...imgBound,
  456. target.x - size,
  457. target.y - size,
  458. size * 2,
  459. size * 2
  460. );
  461. ctx.strokeStyle = style.target.strokeStyle;
  462. ctx.lineWidth = style.target.lineWidth;
  463. ctx.stroke();
  464. }
  465. ctx.restore();
  466. }
  467. drawCircle(element) {
  468. this.drawPoint({
  469. ...element,
  470. geoType: "Circle",
  471. ...element.center,
  472. });
  473. element.points.forEach((point) => this.drawPoint(point));
  474. }
  475. drawPoint(vector) {
  476. const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
  477. const ctx = this.context;
  478. const [style] = help.setVectorStyle(ctx, vector, vector.geoType || "Point");
  479. if (vector.color) {
  480. ctx.strokeStyle = vector.color;
  481. }
  482. const radius = help.getReal(vector.radius || style.radius);
  483. ctx.save();
  484. ctx.beginPath();
  485. ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2, true);
  486. ctx.stroke();
  487. ctx.fill();
  488. ctx.restore();
  489. if (import.meta.env.DEV) {
  490. if (vector.vectorId) {
  491. this.drawTextByInfo(vector, vector.vectorId);
  492. }
  493. }
  494. }
  495. drawTextByInfo(position, txt, angle, setStyle = true) {
  496. const ctx = this.context;
  497. ctx.save();
  498. setStyle && help.setVectorStyle(ctx, null, "Text");
  499. const pt = coordinate.getScreenXY(position);
  500. const text = ctx.measureText(txt);
  501. pt.x -= text.width / 2;
  502. pt.y += (text.actualBoundingBoxAscent + text.actualBoundingBoxDescent) / 2;
  503. if (angle) {
  504. ctx.translate(pt.x, pt.y);
  505. ctx.rotate(angle);
  506. ctx.fillText(txt, 0, 0);
  507. } else {
  508. ctx.fillText(txt, pt.x, pt.y);
  509. }
  510. ctx.restore();
  511. }
  512. // 文字
  513. drawText(vector) {
  514. help.setVectorStyle(this.context, vector);
  515. this.context.fillStyle = vector.color;
  516. const oldFont = this.context.font;
  517. this.context.font = `${vector.fontSize}px Microsoft YaHei`;
  518. this.drawTextByInfo(vector.center, vector.value, 0, false);
  519. const ctx = this.context;
  520. const pt = coordinate.getScreenXY(vector.center);
  521. const text = ctx.measureText(vector.value);
  522. pt.x -= text.width / 2;
  523. pt.y += (text.actualBoundingBoxAscent + text.actualBoundingBoxDescent) / 2;
  524. this.context.font = oldFont;
  525. }
  526. drawLine(vector) {
  527. if ([UIEvents.Arrow, UIEvents.MeasureLine].includes(vector.category)) {
  528. return this.drawArrow(vector);
  529. }
  530. const startReal = dataService.getPoint(vector.startId);
  531. const start = coordinate.getScreenXY(startReal);
  532. const endReal = dataService.getPoint(vector.endId);
  533. const end = coordinate.getScreenXY(endReal);
  534. this.context.save();
  535. const [style, attr] = help.setVectorStyle(
  536. this.context,
  537. vector,
  538. vector.category || vector.geoType
  539. );
  540. if (style.dash) {
  541. this.context.setLineDash(style.dash);
  542. }
  543. this.context.beginPath();
  544. this.context.moveTo(start.x, start.y);
  545. this.context.lineTo(end.x, end.y);
  546. this.context.stroke();
  547. this.context.restore();
  548. // if (attr) {
  549. // this.drawPoint(startReal)
  550. // this.drawPoint(endReal)
  551. // }
  552. }
  553. drawElementLine(element) {
  554. let start = elementService.getPoint(element.startId);
  555. start = coordinate.getScreenXY(start);
  556. let end = elementService.getPoint(element.endId);
  557. end = coordinate.getScreenXY(end);
  558. this.context.save();
  559. const [style] = help.setVectorStyle(
  560. this.context,
  561. element,
  562. element.category || element.geoType
  563. );
  564. if (style.dash) {
  565. this.context.setLineDash(style.dash);
  566. }
  567. this.context.beginPath();
  568. this.context.moveTo(start.x, start.y);
  569. this.context.lineTo(end.x, end.y);
  570. this.context.stroke();
  571. this.context.restore();
  572. }
  573. }
  574. const draw = new Draw();
  575. export { draw };