Draw.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960
  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 { mathUtil } from "../Util/MathUtil.js";
  6. import { elementService } from "../Service/ElementService.js";
  7. import UIEvents from "@/graphic/enum/UIEvents.js";
  8. import VectorCategory from "@/graphic/enum/VectorCategory.js";
  9. import Settings from "@/graphic/Settings.js";
  10. import SVGIcons from "../CanvasStyle/ImageLabels/SVGIcons";
  11. const imgCache = {};
  12. const help = {
  13. getVectorStyle(vector, geoType = vector.geoType) {
  14. const geoId = vector?.vectorId;
  15. if (!geoId) {
  16. return [Style[geoType], undefined];
  17. }
  18. const itemsEntry = [
  19. [stateService.getSelectItem(), "Select"],
  20. [stateService.getDraggingItem(), "Dragging"],
  21. [stateService.getFocusItem(), "Focus"],
  22. ];
  23. let currentAttr;
  24. return [
  25. itemsEntry.reduce((prev, [item, attr]) => {
  26. if (!item) return prev;
  27. const selected =
  28. geoId === item.vectorId ||
  29. (item.parent && Object.keys(item.parent).some((id) => id === geoId));
  30. if (selected && Style[attr]) {
  31. const style = Style[attr][geoType] || Style[attr][vector.category];
  32. if (style) {
  33. currentAttr = attr;
  34. return style;
  35. }
  36. }
  37. return prev;
  38. }, Style[geoType]),
  39. currentAttr,
  40. ];
  41. },
  42. setStyle(ctx, styles) {
  43. for (const style in styles) {
  44. if (typeof styles[style] === "function") {
  45. styles[style](ctx, vector);
  46. } else {
  47. ctx[style] = styles[style];
  48. }
  49. }
  50. },
  51. setVectorStyle(ctx, vector, geoType = vector.geoType) {
  52. let styles, attr;
  53. if (Array.isArray(geoType)) {
  54. for (const type of geoType) {
  55. [styles, attr] = help.getVectorStyle(vector, type);
  56. if (styles) {
  57. break;
  58. }
  59. }
  60. } else {
  61. [styles, attr] = help.getVectorStyle(vector, geoType);
  62. }
  63. help.setStyle(ctx, styles);
  64. return [styles, attr];
  65. },
  66. transformCoves(lines) {
  67. return lines.map((line) =>
  68. line.map((line) => ({
  69. start: coordinate.getScreenXY(line.start),
  70. end: coordinate.getScreenXY(line.end),
  71. controls: line.controls.map(coordinate.getScreenXY.bind(coordinate)),
  72. }))
  73. );
  74. },
  75. drawCoves(ctx, coves) {
  76. for (const curve of coves) {
  77. ctx.beginPath();
  78. ctx.moveTo(curve.start.x, curve.start.y);
  79. if (curve.controls.length === 1) {
  80. ctx.quadraticCurveTo(
  81. curve.controls[0].x,
  82. curve.controls[0].y,
  83. curve.end.x,
  84. curve.end.y
  85. );
  86. } else {
  87. ctx.bezierCurveTo(
  88. curve.controls[0].x,
  89. curve.controls[0].y,
  90. curve.controls[1].x,
  91. curve.controls[1].y,
  92. curve.end.x,
  93. curve.end.y
  94. );
  95. }
  96. ctx.stroke();
  97. }
  98. },
  99. getReal(data) {
  100. return (data * coordinate.ratio * coordinate.zoom) / coordinate.defaultZoom;
  101. },
  102. getImage(src) {
  103. if (imgCache[src]) {
  104. return imgCache[src];
  105. }
  106. const img = new Image();
  107. img.src = src;
  108. return (imgCache[src] = new Promise((resolve) => {
  109. img.onload = () => {
  110. resolve(img);
  111. };
  112. }));
  113. },
  114. getTextCenter(ctx, txt) {
  115. const text = ctx.measureText(txt);
  116. const height = text.actualBoundingBoxAscent + text.actualBoundingBoxDescent;
  117. return {
  118. width: text.width,
  119. height,
  120. x: text.width / 2,
  121. y: -height / 2,
  122. };
  123. },
  124. // 绘制圆角矩形
  125. roundRect(ctx, x, y, width, height, radius) {
  126. ctx.beginPath();
  127. ctx.moveTo(x + radius, y);
  128. ctx.lineTo(x + width - radius, y);
  129. ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  130. ctx.lineTo(x + width, y + height - radius);
  131. ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  132. ctx.lineTo(x + radius, y + height);
  133. ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  134. ctx.lineTo(x, y + radius);
  135. ctx.quadraticCurveTo(x, y, x + radius, y);
  136. ctx.closePath();
  137. },
  138. getRealDistance(p1, p2) {
  139. return (
  140. Math.round(mathUtil.getDistance(p1, p2) * coordinate.res * 100) / 100
  141. );
  142. },
  143. getPerpendicularPoint(p1, p2, p3, d) {
  144. if (p1.x === p2.x) {
  145. return { x: p3.x + d, y: p3.y };
  146. } else if (p1.y === p2.y) {
  147. return { x: p3.x, y: p3.y + d };
  148. }
  149. // 计算通过 p1 和 p2 的直线的斜率和截距
  150. const slope = (p2.y - p1.y) / (p2.x - p1.x);
  151. const intercept = p1.y - slope * p1.x;
  152. // 计算垂直线的斜率和截距
  153. const perpendicularSlope = -1 / slope;
  154. const perpendicularIntercept = p3.y - perpendicularSlope * p3.x;
  155. // 计算垂足点 p0
  156. const x =
  157. (perpendicularIntercept - intercept) / (slope - perpendicularSlope);
  158. const y = slope * x + intercept;
  159. const p0 = { x, y };
  160. // 计算点 p4
  161. const distance = d; // 指定距离
  162. const dx = distance / Math.sqrt(1 + perpendicularSlope ** 2);
  163. const dy = perpendicularSlope * dx;
  164. return { x: p0.x + dx, y: p0.y + dy };
  165. },
  166. drawLineText(ctx, start, end, text, style) {
  167. if (start.x > end.x) {
  168. [start, end] = [end, start];
  169. }
  170. const angle =
  171. (Math.atan2(end.y - start.y, end.x - start.x) * 180) / Math.PI;
  172. const center = mathUtil.lineCenter(start, end);
  173. ctx.save();
  174. ctx.translate(center.x, center.y);
  175. ctx.rotate((angle * Math.PI) / 180);
  176. ctx.font = `${(style.fontSize || 10) * coordinate.ratio}px Microsoft YaHei`;
  177. const textCenter = help.getTextCenter(ctx, text);
  178. const padding = style.padding;
  179. help.roundRect(
  180. ctx,
  181. -textCenter.x - padding,
  182. textCenter.y - padding,
  183. textCenter.width + 2 * padding,
  184. textCenter.height + 2 * padding,
  185. textCenter.height / 2 + padding
  186. );
  187. ctx.fillStyle = style.backColor;
  188. ctx.fill();
  189. ctx.fillStyle = style.fillColor;
  190. ctx.fillText(text, -textCenter.x, -textCenter.y);
  191. ctx.restore();
  192. },
  193. };
  194. export default class Draw {
  195. constructor() {
  196. this.canvas = null;
  197. this.context = null;
  198. }
  199. initContext(canvas) {
  200. if (canvas) {
  201. this.canvas = canvas;
  202. this.context = canvas.getContext("2d");
  203. } else {
  204. this.context = null;
  205. this.canvas = null;
  206. }
  207. }
  208. clear() {
  209. this.context.clearRect(
  210. 0,
  211. 0,
  212. this.context.canvas.width,
  213. this.context.canvas.height
  214. );
  215. }
  216. drawBackGroundImg(vector) {
  217. if (!vector.display) {
  218. return;
  219. }
  220. const img = vector.imageData;
  221. const width = help.getReal(img.width);
  222. const height = help.getReal(img.height);
  223. const center = coordinate.getScreenXY(vector.center);
  224. this.context.save();
  225. this.context.drawImage(
  226. img,
  227. center.x - width / 2,
  228. center.y - height / 2,
  229. width,
  230. height
  231. );
  232. this.context.restore();
  233. }
  234. drawGrid(startX, startY, w, h, step1, step2) {
  235. this.context.save();
  236. this.context.beginPath();
  237. for (var x = startX; x <= w; x += step1) {
  238. this.context.moveTo(x, 0);
  239. this.context.lineTo(x, h);
  240. }
  241. for (var y = startY; y <= h; y += step1) {
  242. this.context.moveTo(0, y);
  243. this.context.lineTo(w, y);
  244. }
  245. this.context.strokeStyle = "rgba(0,0,0,0.1)";
  246. this.context.lineWidth = 0.5 * coordinate.ratio;
  247. this.context.stroke();
  248. this.context.beginPath();
  249. for (var x = startX; x <= w; x += step2) {
  250. this.context.moveTo(x, 0);
  251. this.context.lineTo(x, h);
  252. }
  253. for (var y = startY; y <= h; y += step2) {
  254. this.context.moveTo(0, y);
  255. this.context.lineTo(w, y);
  256. }
  257. this.context.strokeStyle = "rgba(0,0,0,0.2)";
  258. this.context.lineWidth = 1 * coordinate.ratio;
  259. this.context.stroke();
  260. this.context.restore();
  261. }
  262. drawRoad(vector, isTemp) {
  263. if (!isTemp && vector.display && vector.way !== "oneWay") {
  264. const ctx = this.context;
  265. const draw = (midDivide) => {
  266. const startScreen = coordinate.getScreenXY(midDivide.start);
  267. const endScreen = coordinate.getScreenXY(midDivide.end);
  268. ctx.beginPath();
  269. if (label) {
  270. help.setStyle(ctx, Style.Focus.Road)
  271. }
  272. ctx.moveTo(startScreen.x, startScreen.y);
  273. ctx.lineTo(endScreen.x, endScreen.y);
  274. ctx.stroke();
  275. };
  276. ctx.save();
  277. const [_, label] = help.setVectorStyle(ctx, vector);
  278. vector.midDivide.leftMidDivide && draw(vector.midDivide.leftMidDivide);
  279. vector.midDivide.rightMidDivide && draw(vector.midDivide.rightMidDivide);
  280. ctx.restore();
  281. }
  282. if (import.meta.env.DEV && !isTemp) {
  283. const startReal = isTemp
  284. ? vector.start
  285. : dataService.getRoadPoint(vector.startId);
  286. const endReal = isTemp
  287. ? vector.end
  288. : dataService.getRoadPoint(vector.endId);
  289. this.drawTextByInfo(
  290. { x: (startReal.x + endReal.x) / 2, y: (startReal.y + endReal.y) / 2 },
  291. vector.vectorId
  292. );
  293. }
  294. this.drawRoadEdge(vector, isTemp);
  295. vector.leftLanes && vector.leftLanes.forEach(this.drawLan.bind(this));
  296. vector.rightLanes && vector.rightLanes.forEach(this.drawLan.bind(this));
  297. vector.singleLanes && vector.singleLanes.forEach(this.drawLan.bind(this));
  298. }
  299. drawLan(lan) {
  300. const ctx = this.context;
  301. const start = coordinate.getScreenXY(lan.start);
  302. const end = coordinate.getScreenXY(lan.end);
  303. ctx.save();
  304. ctx.beginPath();
  305. help.setVectorStyle(ctx, null, "Lane");
  306. ctx.setLineDash(Style.Lane.dash);
  307. ctx.moveTo(start.x, start.y);
  308. ctx.lineTo(end.x, end.y);
  309. ctx.stroke();
  310. ctx.restore();
  311. if (import.meta.env.DEV) {
  312. this.drawPoint(lan.start);
  313. this.drawPoint(lan.end);
  314. }
  315. }
  316. drawRoadEdge(vector, isTemp) {
  317. //判断是否与road方向一致。角度足够小,路足够宽,有可能向量方向不一致
  318. const start = isTemp
  319. ? vector.start
  320. : dataService.getRoadPoint(vector.startId);
  321. const end = isTemp ? vector.end : dataService.getRoadPoint(vector.endId);
  322. const drawRoadEdgeChild = (edgeVector) => {
  323. const flag = mathUtil.isSameDirForVector(
  324. start,
  325. end,
  326. edgeVector.start,
  327. edgeVector.end
  328. );
  329. if (flag) {
  330. ctx.beginPath();
  331. const point1 = coordinate.getScreenXY(edgeVector.start);
  332. const point2 = coordinate.getScreenXY(edgeVector.end);
  333. ctx.moveTo(point1.x, point1.y);
  334. ctx.lineTo(point2.x, point2.y);
  335. ctx.stroke();
  336. }
  337. if (import.meta.env.DEV) {
  338. this.drawTextByInfo(
  339. {
  340. x: (edgeVector.start.x + edgeVector.end.x) / 2,
  341. y: (edgeVector.start.y + edgeVector.end.y) / 2,
  342. },
  343. edgeVector.vectorId
  344. );
  345. }
  346. };
  347. const leftEdge = isTemp
  348. ? vector.leftEdge
  349. : dataService.getRoadEdge(vector.leftEdgeId);
  350. const rightEdge = isTemp
  351. ? vector.rightEdge
  352. : dataService.getRoadEdge(vector.rightEdgeId);
  353. const ctx = this.context;
  354. ctx.save();
  355. isTemp && (ctx.globalAlpha = 0.3);
  356. help.setVectorStyle(ctx, leftEdge);
  357. drawRoadEdgeChild(leftEdge);
  358. help.setVectorStyle(ctx, rightEdge);
  359. drawRoadEdgeChild(rightEdge);
  360. ctx.restore();
  361. if (import.meta.env.DEV) {
  362. this.drawPoint(leftEdge.start);
  363. this.drawPoint(leftEdge.end);
  364. this.drawPoint(rightEdge.start);
  365. this.drawPoint(rightEdge.end);
  366. }
  367. }
  368. drawCrossPoint(vector) {
  369. const start = coordinate.getScreenXY(
  370. dataService
  371. .getRoadEdge(vector.edgeInfo1.id)
  372. .getPosition(vector.edgeInfo1.dir)
  373. );
  374. const end = coordinate.getScreenXY(
  375. dataService
  376. .getRoadEdge(vector.edgeInfo2.id)
  377. .getPosition(vector.edgeInfo2.dir)
  378. );
  379. const pt2 = mathUtil.twoOrderBezier(
  380. 0.5,
  381. start,
  382. coordinate.getScreenXY({ x: vector.x, y: vector.y }),
  383. end
  384. );
  385. const pt = mathUtil.twoOrderBezier2(0.5, start, pt2, end);
  386. const extremePoint = coordinate.getScreenXY(vector.extremePoint);
  387. const ctx = this.context;
  388. ctx.save();
  389. help.setVectorStyle(ctx, vector)
  390. ctx.beginPath();
  391. ctx.arc(
  392. extremePoint.x,
  393. extremePoint.y,
  394. Style.CrossPoint.radius * coordinate.ratio,
  395. 0,
  396. Math.PI * 2,
  397. true
  398. );
  399. ctx.stroke();
  400. ctx.fill();
  401. ctx.restore();
  402. ctx.save();
  403. ctx.beginPath();
  404. help.setVectorStyle(ctx, null, "RoadEdge");
  405. //曲线
  406. // ctx.moveTo(start.x, start.y);
  407. // ctx.quadraticCurveTo(pt.x, pt.y, end.x, end.y);
  408. const [coves] = help.transformCoves([vector.curves]);
  409. help.drawCoves(ctx, coves);
  410. ctx.restore();
  411. }
  412. drawCurveRoad(vector) {
  413. if (vector.display && vector.midDivide) {
  414. const covesArray = help.transformCoves([
  415. vector.midDivide.leftMidDivideCurves,
  416. vector.midDivide.rightMidDivideCurves,
  417. ]);
  418. const ctx = this.context;
  419. ctx.save();
  420. help.setVectorStyle(ctx, vector);
  421. for (let coves of covesArray) {
  422. help.drawCoves(ctx, coves);
  423. }
  424. ctx.restore();
  425. }
  426. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.rightEdgeId));
  427. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.leftEdgeId));
  428. vector.leftLanesCurves &&
  429. vector.leftLanesCurves.forEach(this.drawCurveLan.bind(this));
  430. vector.rightLanesCurves &&
  431. vector.rightLanesCurves.forEach(this.drawCurveLan.bind(this));
  432. // if (import.meta.env.DEV) {
  433. vector.points.forEach(this.drawPoint.bind(this));
  434. // }
  435. }
  436. drawCurveRoadEdge(vector, isTemp) {
  437. const [coves] = help.transformCoves([vector.curves]);
  438. const ctx = this.context;
  439. ctx.save();
  440. help.setVectorStyle(ctx, vector);
  441. help.drawCoves(ctx, coves);
  442. ctx.restore();
  443. if (import.meta.env.DEV) {
  444. vector.points.forEach(this.drawPoint.bind(this));
  445. }
  446. }
  447. drawCurveLan(lines) {
  448. const [coves] = help.transformCoves([lines]);
  449. const ctx = this.context;
  450. ctx.save();
  451. help.setVectorStyle(ctx, null, "CurveLan");
  452. ctx.setLineDash(Style.Lane.dash);
  453. help.drawCoves(ctx, coves);
  454. ctx.restore();
  455. // if (import.meta.env.DEV) {
  456. lines.map((line) => {
  457. this.drawPoint(line.start);
  458. this.drawPoint(line.end);
  459. });
  460. // }
  461. }
  462. drawRoadPoint(vector) {
  463. this.drawPoint(vector);
  464. }
  465. drawArrow(vector) {
  466. const startReal = dataService.getPoint(vector.startId);
  467. const start = coordinate.getScreenXY(startReal);
  468. const endReal = dataService.getPoint(vector.endId);
  469. const end = coordinate.getScreenXY(endReal);
  470. const ctx = this.context;
  471. ctx.save();
  472. const [style] = help.setVectorStyle(this.context, vector);
  473. if (vector.color) {
  474. ctx.strokeStyle = vector.color;
  475. }
  476. const dires =
  477. vector.category === UIEvents.DoubleArrow
  478. ? [
  479. [start, end],
  480. [end, start],
  481. ]
  482. : [[start, end]];
  483. for (let [start, end] of dires) {
  484. const lines = mathUtil.getArrow(start, end);
  485. ctx.moveTo(lines[0].x, lines[0].y);
  486. ctx.lineTo(lines[1].x, lines[1].y);
  487. ctx.lineTo(lines[2].x, lines[2].y);
  488. }
  489. ctx.stroke();
  490. ctx.restore();
  491. }
  492. drawMagnifier(vector) {
  493. const ctx = this.context;
  494. const [style] = help.setVectorStyle(ctx, vector);
  495. const radius = vector.radius || style.radius;
  496. this.drawPoint({
  497. ...vector,
  498. ...vector.position,
  499. radius,
  500. });
  501. const pt = coordinate.getScreenXY(vector.position);
  502. vector.setPopPosition();
  503. const target = {
  504. x: vector.popPosition.x,
  505. y: vector.popPosition.y,
  506. };
  507. const offset = radius / 2;
  508. const targetPts =
  509. style === Style.Focus.Magnifier
  510. ? [mathUtil.translate(pt, target, pt, radius), target]
  511. : null;
  512. ctx.save();
  513. ctx.beginPath();
  514. ctx.moveTo(pt.x - offset, pt.y);
  515. ctx.lineTo(pt.x + offset, pt.y);
  516. ctx.stroke();
  517. ctx.beginPath();
  518. ctx.moveTo(pt.x, pt.y - offset);
  519. ctx.lineTo(pt.x, pt.y + offset);
  520. ctx.stroke();
  521. if (targetPts) {
  522. ctx.beginPath();
  523. ctx.moveTo(targetPts[0].x, targetPts[0].y);
  524. ctx.lineTo(targetPts[1].x, targetPts[1].y);
  525. ctx.stroke();
  526. let img, imgBound;
  527. if (vector.photoImage) {
  528. img = vector.photoImage;
  529. imgBound = [0, 0, img.width, img.height];
  530. } else {
  531. const size = help.getReal(style.target.realRadius);
  532. const backImg = dataService.getBackgroundImg();
  533. img = backImg.imageData;
  534. const imgCenter = coordinate.getScreenXY(backImg.center);
  535. const start = {
  536. x: imgCenter.x - help.getReal(img.width) / 2,
  537. y: imgCenter.y - help.getReal(img.height) / 2,
  538. };
  539. const ro = img.width / help.getReal(img.width);
  540. imgBound = [
  541. (pt.x - start.x - size) * ro,
  542. (pt.y - start.y - size) * ro,
  543. size * 2 * ro,
  544. size * 2 * ro,
  545. ];
  546. }
  547. const size = style.target.radius;
  548. ctx.beginPath();
  549. ctx.arc(target.x, target.y, size, 0, 2 * Math.PI);
  550. ctx.clip();
  551. ctx.drawImage(
  552. img,
  553. ...imgBound,
  554. target.x - size,
  555. target.y - size,
  556. size * 2,
  557. size * 2
  558. );
  559. ctx.strokeStyle = style.target.strokeStyle;
  560. ctx.lineWidth = style.target.lineWidth;
  561. ctx.stroke();
  562. }
  563. ctx.restore();
  564. }
  565. drawElliptic(element, radiusX = element.radiusX, radiusY = element.radiusY) {
  566. function drawEllipse(context, x, y, a, b) {
  567. const step = (a > b) ? 1 / a : 1 / b;
  568. context.beginPath();
  569. context.moveTo(x + a, y);
  570. for (let i = 0; i < 2 * Math.PI; i += step) {
  571. context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i));
  572. }
  573. context.closePath();
  574. }
  575. const pt = coordinate.getScreenXY({ x: element.center.x, y: element.center.y });
  576. const ctx = this.context;
  577. const [_, label] = help.setVectorStyle(ctx, element);
  578. ctx.strokeStyle = element.color
  579. ctx.save();
  580. drawEllipse(
  581. ctx, pt.x, pt.y,
  582. (radiusX * coordinate.zoom) / coordinate.defaultZoom,
  583. (radiusY * coordinate.zoom) / coordinate.defaultZoom
  584. )
  585. ctx.stroke();
  586. ctx.fill();
  587. ctx.restore();
  588. label && element.points.forEach((point) => this.drawPoint(point));
  589. }
  590. drawCircle(element) {
  591. this.drawElliptic(element, element.radius, element.radius)
  592. const [_, label] = help.getVectorStyle(element);
  593. label && element.points.forEach((point) => this.drawPoint(point));
  594. }
  595. drawPoint(vector) {
  596. if (vector.category === VectorCategory.Point.TestBasePoint) {
  597. return;
  598. }
  599. const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
  600. const ctx = this.context;
  601. let [style, attr] = help.setVectorStyle(ctx, vector, [
  602. vector.category,
  603. vector.geoType,
  604. "Point",
  605. ]);
  606. if (vector.category === VectorCategory.Point.NormalPoint) {
  607. const lineid = Object.keys(vector.parent)[0];
  608. let line;
  609. if (!(lineid && (line = dataService.getLine(lineid)))) {
  610. return;
  611. }
  612. const [stylea, attr] = help.getVectorStyle(line, line.category);
  613. style = {
  614. ...style,
  615. ...stylea
  616. }
  617. // if (!attr) {
  618. // return;
  619. // }
  620. }
  621. if (vector.color) {
  622. ctx.strokeStyle = vector.color;
  623. style = {
  624. ...style,
  625. strokeStyle: vector.color
  626. };
  627. }
  628. const draw = (style) => {
  629. const radius = vector.radius || style.radius;
  630. ctx.save();
  631. ctx.beginPath();
  632. ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2, true);
  633. help.setStyle(ctx, style);
  634. ctx.stroke();
  635. ctx.fill();
  636. ctx.restore();
  637. };
  638. if (Settings.selectBasePointId === vector.vectorId) {
  639. style = {
  640. ...style,
  641. fillStyle: "red",
  642. out: style.out && {
  643. ...style.out,
  644. strokeStyle: "red",
  645. },
  646. };
  647. }
  648. draw(style);
  649. if (style.out) {
  650. draw(style.out);
  651. }
  652. if (vector.category === "BasePoint") {
  653. const bound = help.getTextCenter(ctx, "基准点")
  654. const textPt = {
  655. y: pt.y - bound.height,
  656. x: pt.x - (bound.width / 2)
  657. }
  658. ctx.fillStyle = style.fillStyle
  659. this.drawTextByInfo(coordinate.getXYFromScreen(textPt), "基准点", 0, false);
  660. } else {
  661. if (import.meta.env.DEV) {
  662. if (vector.vectorId) {
  663. // this.drawTextByInfo(vector, vector.vectorId);
  664. }
  665. }
  666. }
  667. }
  668. drawTextByInfo(position, txt, angle, setStyle = true) {
  669. const ctx = this.context;
  670. ctx.save();
  671. setStyle && help.setVectorStyle(ctx, null, "Text");
  672. const pt = coordinate.getScreenXY(position);
  673. const textCenter = help.getTextCenter(ctx, txt);
  674. // pt.x -= textCenter.x;
  675. // pt.y -= textCenter.y;
  676. if (angle) {
  677. ctx.translate(pt.x, pt.y);
  678. ctx.rotate(angle);
  679. ctx.translate(-textCenter.x, -textCenter.y);
  680. ctx.fillText(txt, 0, 0);
  681. } else {
  682. ctx.fillText(txt, pt.x, pt.y);
  683. }
  684. ctx.restore();
  685. }
  686. // 文字
  687. drawText(vector) {
  688. help.setVectorStyle(this.context, vector);
  689. this.context.fillStyle = vector.color;
  690. const oldFont = this.context.font;
  691. this.context.font = `${
  692. vector.fontSize * coordinate.ratio
  693. }px Microsoft YaHei`;
  694. this.drawTextByInfo(
  695. vector.center,
  696. vector.value,
  697. -(vector.angle || 0),
  698. false
  699. );
  700. const ctx = this.context;
  701. const pt = coordinate.getScreenXY(vector.center);
  702. const text = ctx.measureText(vector.value);
  703. pt.x -= text.width / 2;
  704. pt.y += (text.actualBoundingBoxAscent + text.actualBoundingBoxDescent) / 2;
  705. this.context.font = oldFont;
  706. }
  707. drawSVG(vector) {
  708. const points = vector.points.map(coordinate.getScreenXY.bind(coordinate));
  709. const svgWidth = 64;
  710. const svgHidth = 64;
  711. const width = mathUtil.getDistance(points[0], points[1]);
  712. const height = mathUtil.getDistance(points[0], points[3]);
  713. const dires = [points[0], { ...points[0], x: 10000 }, points[1]];
  714. let angle = mathUtil.Angle(...dires) * (Math.PI / 180);
  715. angle = mathUtil.isClockwise(dires) ? angle : -angle;
  716. this.context.save();
  717. this.context.translate(points[0].x, points[0].y);
  718. this.context.rotate(angle);
  719. this.context.scale(width / svgWidth, height / svgHidth);
  720. const [style, label] = help.setVectorStyle(this.context, vector);
  721. this.context.lineWidth = style.lineWidth / (width / svgWidth);
  722. SVGIcons[vector.type].draw(this.context);
  723. this.context.restore();
  724. if (label) {
  725. this.context.save();
  726. this.context.beginPath();
  727. this.context.moveTo(points[0].x, points[0].y);
  728. this.context.lineTo(points[1].x, points[1].y);
  729. this.context.lineTo(points[2].x, points[2].y);
  730. this.context.lineTo(points[3].x, points[3].y);
  731. this.context.strokeStyle = "red"
  732. this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]);
  733. this.context.closePath();
  734. this.context.stroke();
  735. this.context.restore();
  736. vector.points.forEach(point => this.drawPoint({...point, color: 'red', radius: 5 }))
  737. }
  738. }
  739. drawLineText(vector, style) {
  740. const startReal = dataService.getPoint(vector.startId);
  741. const endReal = dataService.getPoint(vector.endId);
  742. help.drawLineText(
  743. this.context,
  744. coordinate.getScreenXY(startReal),
  745. coordinate.getScreenXY(endReal),
  746. (vector.value
  747. ? Math.round(vector.value * 100) / 100
  748. : help.getRealDistance(startReal, endReal)) + "m",
  749. style
  750. );
  751. }
  752. drawBaseLineLabel(vector) {
  753. const startReal = dataService.getPoint(vector.startId);
  754. const start = coordinate.getScreenXY(startReal);
  755. const endReal = dataService.getPoint(vector.endId);
  756. const end = coordinate.getScreenXY(endReal);
  757. const point = mathUtil.translate(
  758. end,
  759. start,
  760. end,
  761. mathUtil.getDistance(start, end) / 3
  762. );
  763. const p4 = help.getPerpendicularPoint(
  764. start,
  765. end,
  766. point,
  767. 30 * coordinate.ratio
  768. );
  769. const ctx = this.context;
  770. ctx.beginPath();
  771. const [style] = help.setVectorStyle(
  772. this.context,
  773. vector,
  774. vector.category || vector.geoType
  775. );
  776. ctx.moveTo(point.x, point.y);
  777. ctx.lineTo(p4.x, p4.y);
  778. ctx.stroke();
  779. const p5 = help.getPerpendicularPoint(
  780. start,
  781. end,
  782. point,
  783. 35 * coordinate.ratio
  784. );
  785. this.context.font = `${12 * coordinate.ratio}px Microsoft YaHei`;
  786. help.drawLineText(
  787. this.context,
  788. help.getPerpendicularPoint(point, p5, p5, 10 * coordinate.ratio),
  789. help.getPerpendicularPoint(point, p5, p5, -10 * coordinate.ratio),
  790. "基准线",
  791. {
  792. padding: 6 * coordinate.ratio,
  793. backColor: "rgba(0,0,0,0)",
  794. fillColor: style.strokeStyle,
  795. }
  796. );
  797. }
  798. drawCurveLine(vector) {
  799. // points CurveLine
  800. const ctx = this.context;
  801. ctx.save();
  802. help.setVectorStyle(ctx, vector);
  803. help.drawCoves(ctx, help.transformCoves([vector.points]));
  804. ctx.restore();
  805. if (import.meta.env.DEV) {
  806. vector.points.forEach(this.drawPoint.bind(this));
  807. }
  808. }
  809. drawLine(vector) {
  810. const startReal = dataService.getPoint(vector.startId);
  811. const start = coordinate.getScreenXY(startReal);
  812. const endReal = dataService.getPoint(vector.endId);
  813. const end = coordinate.getScreenXY(endReal);
  814. this.context.save();
  815. const [style, attr] = help.setVectorStyle(this.context, vector, [
  816. vector.category,
  817. vector.geoType,
  818. "BaseLine",
  819. ]);
  820. if (style.dash) {
  821. this.context.setLineDash(style.dash);
  822. }
  823. this.context.beginPath();
  824. this.context.moveTo(start.x, start.y);
  825. this.context.lineTo(end.x, end.y);
  826. this.context.stroke();
  827. this.context.restore();
  828. const drawPoints = () => {
  829. // if (attr) {
  830. // this.drawPoint(dataService.getPoint(vector.startId))
  831. // this.drawPoint(dataService.getPoint(vector.endId))
  832. // }
  833. };
  834. switch (vector.category) {
  835. case VectorCategory.Line.SingleArrowLine:
  836. this.drawArrow(vector);
  837. drawPoints();
  838. break;
  839. case VectorCategory.Line.DoubleArrowLine:
  840. this.drawArrow(vector);
  841. drawPoints();
  842. break;
  843. case VectorCategory.Line.BaseLine:
  844. this.drawBaseLineLabel(vector);
  845. break;
  846. case VectorCategory.Line.MeasureLine:
  847. case VectorCategory.Line.PositionLine:
  848. this.drawLineText(vector, style.text);
  849. break;
  850. }
  851. }
  852. drawElementLine(element) {
  853. let start = elementService.getPoint(element.startId);
  854. start = coordinate.getScreenXY(start);
  855. let end = elementService.getPoint(element.endId);
  856. end = coordinate.getScreenXY(end);
  857. this.context.save();
  858. const [style] = help.setVectorStyle(
  859. this.context,
  860. element,
  861. element.category || element.geoType
  862. );
  863. if (style.dash) {
  864. this.context.setLineDash(style.dash);
  865. }
  866. this.context.beginPath();
  867. this.context.moveTo(start.x, start.y);
  868. this.context.lineTo(end.x, end.y);
  869. this.context.stroke();
  870. this.context.restore();
  871. }
  872. }
  873. const draw = new Draw();
  874. export { draw };