Draw.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222
  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. import VectorStyle from "@/graphic/enum/VectorStyle.js";
  12. import VectorWeight from "@/graphic/enum/VectorWeight.js";
  13. const imgCache = {};
  14. const help = {
  15. getVectorStyle(vector, geoType = vector.geoType) {
  16. const geoId = vector?.vectorId;
  17. if (!geoId || Settings.screenMode) {
  18. return [Style[geoType], undefined];
  19. }
  20. const itemsEntry = [
  21. [stateService.getSelectItem(), "Select"],
  22. [stateService.getDraggingItem(), "Dragging"],
  23. [stateService.getFocusItem(), "Focus"],
  24. ];
  25. let currentAttr;
  26. console.log(itemsEntry)
  27. return [
  28. itemsEntry.reduce((prev, [item, attr]) => {
  29. if (!item) return prev;
  30. const selected =
  31. geoId === item.vectorId ||
  32. (item.parent && Object.keys(item.parent).some((id) => id === geoId));
  33. if (selected && Style[attr]) {
  34. const style = Style[attr][geoType] || Style[attr][vector.category];
  35. if (style) {
  36. currentAttr = attr;
  37. return style;
  38. }
  39. }
  40. return prev;
  41. }, Style[geoType]),
  42. currentAttr,
  43. ];
  44. },
  45. setStyle(ctx, styles) {
  46. for (const style in styles) {
  47. if (typeof styles[style] === "function") {
  48. styles[style](ctx, vector);
  49. } else {
  50. ctx[style] = styles[style];
  51. }
  52. }
  53. },
  54. setVectorStyle(ctx, vector, geoType = vector.geoType) {
  55. let styles, attr;
  56. if (Array.isArray(geoType)) {
  57. for (const type of geoType) {
  58. [styles, attr] = help.getVectorStyle(vector, type);
  59. if (styles) {
  60. break;
  61. }
  62. }
  63. } else {
  64. [styles, attr] = help.getVectorStyle(vector, geoType);
  65. }
  66. help.setStyle(ctx, styles);
  67. return [styles, attr];
  68. },
  69. transformCoves(lines) {
  70. return lines.map((line) =>
  71. line.map((line) => ({
  72. start: coordinate.getScreenXY(line.start),
  73. end: coordinate.getScreenXY(line.end),
  74. controls: line.controls.map(coordinate.getScreenXY.bind(coordinate)),
  75. }))
  76. );
  77. },
  78. drawCove(ctx, curve) {
  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. },
  97. drawCoves(ctx, coves) {
  98. for (const curve of coves) {
  99. ctx.beginPath();
  100. ctx.moveTo(curve.start.x, curve.start.y);
  101. help.drawCove(ctx, curve)
  102. ctx.stroke();
  103. }
  104. },
  105. getReal(data) {
  106. return (data * coordinate.ratio * coordinate.zoom) / coordinate.defaultZoom;
  107. },
  108. getImage(src) {
  109. if (imgCache[src]) {
  110. return imgCache[src];
  111. }
  112. const img = new Image();
  113. img.src = src;
  114. return (imgCache[src] = new Promise((resolve) => {
  115. img.onload = () => {
  116. resolve(img);
  117. };
  118. }));
  119. },
  120. getTextCenter(ctx, txt) {
  121. const text = ctx.measureText(txt);
  122. const height = text.actualBoundingBoxAscent + text.actualBoundingBoxDescent;
  123. return {
  124. width: text.width,
  125. height,
  126. x: text.width / 2,
  127. y: -height / 2,
  128. };
  129. },
  130. // 绘制圆角矩形
  131. roundRect(ctx, x, y, width, height, radius) {
  132. ctx.beginPath();
  133. ctx.moveTo(x + radius, y);
  134. ctx.lineTo(x + width - radius, y);
  135. ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  136. ctx.lineTo(x + width, y + height - radius);
  137. ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  138. ctx.lineTo(x + radius, y + height);
  139. ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  140. ctx.lineTo(x, y + radius);
  141. ctx.quadraticCurveTo(x, y, x + radius, y);
  142. ctx.closePath();
  143. },
  144. getRealDistance(p1, p2) {
  145. return (
  146. Math.round((mathUtil.getDistance(p1, p2) * coordinate.res * 100) / coordinate.ratio) / 100
  147. );
  148. },
  149. getPerpendicularPoint(p1, p2, p3, d) {
  150. if (p1.x === p2.x) {
  151. return { x: p3.x + d, y: p3.y };
  152. } else if (p1.y === p2.y) {
  153. return { x: p3.x, y: p3.y + d };
  154. }
  155. // 计算通过 p1 和 p2 的直线的斜率和截距
  156. const slope = (p2.y - p1.y) / (p2.x - p1.x);
  157. const intercept = p1.y - slope * p1.x;
  158. // 计算垂直线的斜率和截距
  159. const perpendicularSlope = -1 / slope;
  160. const perpendicularIntercept = p3.y - perpendicularSlope * p3.x;
  161. // 计算垂足点 p0
  162. const x =
  163. (perpendicularIntercept - intercept) / (slope - perpendicularSlope);
  164. const y = slope * x + intercept;
  165. const p0 = { x, y };
  166. // 计算点 p4
  167. const distance = d; // 指定距离
  168. const dx = distance / Math.sqrt(1 + perpendicularSlope ** 2);
  169. const dy = perpendicularSlope * dx;
  170. return { x: p0.x + dx, y: p0.y + dy };
  171. },
  172. drawLineText(ctx, start, end, text, style) {
  173. if (start.x > end.x) {
  174. [start, end] = [end, start];
  175. }
  176. const angle =
  177. (Math.atan2(end.y - start.y, end.x - start.x) * 180) / Math.PI;
  178. const center = mathUtil.lineCenter(start, end);
  179. ctx.save();
  180. ctx.translate(center.x, center.y);
  181. ctx.rotate((angle * Math.PI) / 180);
  182. ctx.font = `${(style.fontSize || 10) * coordinate.ratio}px Microsoft YaHei`;
  183. const textCenter = help.getTextCenter(ctx, text);
  184. const padding = style.padding;
  185. help.roundRect(
  186. ctx,
  187. -textCenter.x - padding,
  188. textCenter.y - padding,
  189. textCenter.width + 2 * padding,
  190. textCenter.height + 2 * padding,
  191. textCenter.height / 2 + padding
  192. );
  193. ctx.fillStyle = style.backColor;
  194. ctx.fill();
  195. ctx.fillStyle = style.fillColor;
  196. ctx.fillText(text, -textCenter.x, -textCenter.y);
  197. ctx.restore();
  198. },
  199. isTriangleClockwise(p1, p2, p3) {
  200. const crossProduct = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y);
  201. return crossProduct < 0;
  202. },
  203. drawStyleLine(ctx, line, style = VectorStyle.SingleSolidLine, weight = VectorStyle.Thinning) {
  204. ctx.save();
  205. style = style || VectorStyle.SingleSolidLine
  206. ctx.beginPath();
  207. const lineWidth = Settings.lineWidth * (ctx.lineWidth || 1) * (weight === VectorWeight.Bold ? 2 : 1);
  208. switch (style) {
  209. case VectorStyle.PointDrawLine:
  210. case VectorStyle.SingleDashedLine:
  211. case VectorStyle.SingleSolidLine:
  212. ctx.lineWidth = lineWidth
  213. if (style === VectorStyle.SingleDashedLine) {
  214. ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]);
  215. } else if (style === VectorStyle.PointDrawLine) {
  216. ctx.setLineDash([6 * coordinate.ratio, 6* coordinate.ratio, 2 * coordinate.ratio]);
  217. }
  218. ctx.moveTo(line[0].x, line[0].y);
  219. ctx.lineTo(line[1].x, line[1].y);
  220. break
  221. // 单实线
  222. case VectorStyle.DoubleDashedLine:
  223. case VectorStyle.DoubleSolidLine:
  224. if (style === VectorStyle.DoubleDashedLine) {
  225. ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]);
  226. }
  227. const pd1 = help.getPerpendicularPoint(
  228. line[0], line[1], line[0], 4 * coordinate.ratio,
  229. )
  230. const pd2 = help.getPerpendicularPoint(
  231. line[0], line[1], line[1], 4 * coordinate.ratio,
  232. )
  233. const pd3 = help.getPerpendicularPoint(
  234. line[0], line[1], line[0], -4 * coordinate.ratio,
  235. )
  236. const pd4 = help.getPerpendicularPoint(
  237. line[0], line[1], line[1], -4 * coordinate.ratio,
  238. )
  239. ctx.moveTo(pd1.x, pd1.y);
  240. ctx.lineTo(pd2.x, pd2.y);
  241. ctx.stroke();
  242. ctx.moveTo(pd3.x, pd3.y);
  243. ctx.lineTo(pd4.x, pd4.y);
  244. break
  245. case VectorStyle.BrokenLine:
  246. const ldis = 5 * coordinate.ratio
  247. if (mathUtil.getDistance(...line) < ldis * 2) {
  248. ctx.moveTo(line[0].x, line[0].y);
  249. ctx.lineTo(line[1].x, line[1].y);
  250. } else {
  251. const start = mathUtil.translate(line[0], line[1], line[0], ldis)
  252. const end = mathUtil.translate(line[0], line[1], line[1], -ldis)
  253. const lineDis = mathUtil.getDistance(start, end)
  254. const len = Math.ceil(lineDis / (6 * coordinate.ratio))
  255. const split = lineDis / len
  256. const points = [start]
  257. let temp = start
  258. for (let i = 0; i < len ; i++) {
  259. temp = mathUtil.translate(temp, line[1], temp, split)
  260. points.push(temp)
  261. }
  262. ctx.moveTo(line[0].x, line[0].y);
  263. ctx.lineTo(start.x, start.y);
  264. for (let i = 0; i < points.length - 1; i++) {
  265. const vTop = help.getPerpendicularPoint(
  266. points[i],
  267. points[i + 1],
  268. mathUtil.lineCenter(points[i], points[i + 1]),
  269. (split * ((i%2) ? -1 : 1)) / 2
  270. )
  271. ctx.lineTo(vTop.x, vTop.y);
  272. }
  273. ctx.lineTo(end.x, end.y);
  274. ctx.lineTo(line[1].x, line[1].y);
  275. }
  276. ctx.lineWidth = lineWidth
  277. break
  278. case VectorStyle.Greenbelt:
  279. const dis = 4 * coordinate.ratio
  280. const size = 8 * coordinate.ratio
  281. const p1 = help.getPerpendicularPoint(
  282. line[0], line[1], line[0], dis
  283. )
  284. const p2 = help.getPerpendicularPoint(
  285. line[0], line[1], line[1], dis
  286. )
  287. const p3 = help.getPerpendicularPoint(
  288. p1, p2, p2, size
  289. )
  290. const p4 = help.getPerpendicularPoint(
  291. p1, p2, p1, size
  292. )
  293. ctx.beginPath()
  294. ctx.lineWidth = lineWidth
  295. ctx.moveTo(line[0].x, line[0].y);
  296. ctx.lineTo(line[1].x, line[1].y);
  297. ctx.stroke();
  298. ctx.beginPath()
  299. ctx.moveTo(p4.x, p4.y);
  300. ctx.lineTo(p1.x, p1.y);
  301. ctx.lineTo(p2.x, p2.y);
  302. ctx.lineTo(p3.x, p3.y);
  303. ctx.stroke();
  304. const rdis = 6 * coordinate.ratio
  305. const lineDis = mathUtil.getDistance(p3, p4)
  306. const len = Math.ceil(lineDis / rdis)
  307. const split = lineDis / len
  308. const points = [p3]
  309. const geo = [p4, {...p4, x: 999}, p3]
  310. let angle = (mathUtil.Angle1(...geo) / 180) * Math.PI
  311. const isClock = help.isTriangleClockwise(...geo) || angle === 0
  312. angle = isClock ? -angle : angle
  313. let temp = p3
  314. for (let i = 0; i < len; i++) {
  315. temp = mathUtil.translate(temp, p4, temp, split)
  316. points.push(temp)
  317. }
  318. for (let i = 0; i < points.length - 1; i++) {
  319. const center = mathUtil.lineCenter(points[i], points[i+1])
  320. ctx.beginPath()
  321. ctx.arc(center.x, center.y, split / 2, angle, angle + Math.PI, !isClock)
  322. ctx.stroke();
  323. }
  324. ctx.lineWidth = lineWidth
  325. break
  326. }
  327. ctx.stroke();
  328. ctx.restore();
  329. },
  330. };
  331. export default class Draw {
  332. constructor() {
  333. this.canvas = null;
  334. this.context = null;
  335. }
  336. initContext(canvas) {
  337. if (canvas) {
  338. this.canvas = canvas;
  339. this.context = canvas.getContext("2d");
  340. } else {
  341. this.context = null;
  342. this.canvas = null;
  343. }
  344. }
  345. clear() {
  346. this.context.clearRect(
  347. 0,
  348. 0,
  349. this.context.canvas.width,
  350. this.context.canvas.height
  351. );
  352. }
  353. drawBackGroundImg(vector) {
  354. if (!vector.display) {
  355. return;
  356. }
  357. const img = vector.imageData;
  358. const width = help.getReal(img.width);
  359. const height = help.getReal(img.height);
  360. const center = coordinate.getScreenXY(vector.center);
  361. this.context.save();
  362. this.context.drawImage(
  363. img,
  364. center.x - width / 2,
  365. center.y - height / 2,
  366. width,
  367. height
  368. );
  369. this.context.restore();
  370. }
  371. drawGrid(startX, startY, w, h, step1, step2) {
  372. this.context.save();
  373. this.context.beginPath();
  374. for (var x = startX; x <= w; x += step1) {
  375. this.context.moveTo(x, 0);
  376. this.context.lineTo(x, h);
  377. }
  378. for (var y = startY; y <= h; y += step1) {
  379. this.context.moveTo(0, y);
  380. this.context.lineTo(w, y);
  381. }
  382. this.context.strokeStyle = "rgba(0,0,0,0.1)";
  383. this.context.lineWidth = 0.5 * coordinate.ratio;
  384. this.context.stroke();
  385. this.context.beginPath();
  386. for (var x = startX; x <= w; x += step2) {
  387. this.context.moveTo(x, 0);
  388. this.context.lineTo(x, h);
  389. }
  390. for (var y = startY; y <= h; y += step2) {
  391. this.context.moveTo(0, y);
  392. this.context.lineTo(w, y);
  393. }
  394. this.context.strokeStyle = "rgba(0,0,0,0.2)";
  395. this.context.lineWidth = 1 * coordinate.ratio;
  396. this.context.stroke();
  397. this.context.restore();
  398. }
  399. drawRoad(vector, isTemp) {
  400. if (!isTemp && vector.display && vector.way !== "oneWay") {
  401. const ctx = this.context;
  402. const draw = (midDivide) => {
  403. const startScreen = coordinate.getScreenXY(midDivide.start);
  404. const endScreen = coordinate.getScreenXY(midDivide.end);
  405. ctx.beginPath();
  406. if (label) {
  407. help.setStyle(ctx, Style.Focus.Road)
  408. }
  409. ctx.moveTo(startScreen.x, startScreen.y);
  410. ctx.lineTo(endScreen.x, endScreen.y);
  411. ctx.stroke();
  412. };
  413. ctx.save();
  414. const [_, label] = help.setVectorStyle(ctx, vector);
  415. vector.midDivide.leftMidDivide && draw(vector.midDivide.leftMidDivide);
  416. vector.midDivide.rightMidDivide && draw(vector.midDivide.rightMidDivide);
  417. ctx.restore();
  418. }
  419. if (import.meta.env.DEV && !isTemp) {
  420. const startReal = isTemp
  421. ? vector.start
  422. : dataService.getRoadPoint(vector.startId);
  423. const endReal = isTemp
  424. ? vector.end
  425. : dataService.getRoadPoint(vector.endId);
  426. this.drawTextByInfo(
  427. { x: (startReal.x + endReal.x) / 2, y: (startReal.y + endReal.y) / 2 },
  428. vector.vectorId
  429. );
  430. }
  431. this.drawRoadEdge(vector, isTemp);
  432. vector.leftLanes && vector.leftLanes.forEach(this.drawLan.bind(this));
  433. vector.rightLanes && vector.rightLanes.forEach(this.drawLan.bind(this));
  434. vector.singleLanes && vector.singleLanes.forEach(this.drawLan.bind(this));
  435. }
  436. drawLan(lan) {
  437. const ctx = this.context;
  438. const start = coordinate.getScreenXY(lan.start);
  439. const end = coordinate.getScreenXY(lan.end);
  440. ctx.save();
  441. ctx.beginPath();
  442. help.setVectorStyle(ctx, null, "Lane");
  443. ctx.lineWidth *= Settings.lineWidth
  444. ctx.setLineDash(Style.Lane.dash);
  445. ctx.moveTo(start.x, start.y);
  446. ctx.lineTo(end.x, end.y);
  447. ctx.stroke();
  448. ctx.restore();
  449. if (import.meta.env.DEV) {
  450. // this.drawPoint(lan.start);
  451. // this.drawPoint(lan.end);
  452. }
  453. }
  454. drawRoadEdge(vector, isTemp) {
  455. //判断是否与road方向一致。角度足够小,路足够宽,有可能向量方向不一致
  456. const start = isTemp
  457. ? vector.start
  458. : dataService.getRoadPoint(vector.startId);
  459. const end = isTemp ? vector.end : dataService.getRoadPoint(vector.endId);
  460. const drawRoadEdgeChild = (edgeVector) => {
  461. const flag = mathUtil.isSameDirForVector(
  462. start,
  463. end,
  464. edgeVector.start,
  465. edgeVector.end
  466. );
  467. if (flag) {
  468. const point1 = coordinate.getScreenXY(edgeVector.start);
  469. const point2 = coordinate.getScreenXY(edgeVector.end);
  470. help.drawStyleLine(ctx, [point1, point2], edgeVector.style, edgeVector.weight)
  471. }
  472. if (import.meta.env.DEV) {
  473. this.drawTextByInfo(
  474. {
  475. x: (edgeVector.start.x + edgeVector.end.x) / 2,
  476. y: (edgeVector.start.y + edgeVector.end.y) / 2,
  477. },
  478. edgeVector.vectorId
  479. );
  480. }
  481. };
  482. const leftEdge = isTemp
  483. ? vector.leftEdge
  484. : dataService.getRoadEdge(vector.leftEdgeId);
  485. const rightEdge = isTemp
  486. ? vector.rightEdge
  487. : dataService.getRoadEdge(vector.rightEdgeId);
  488. const ctx = this.context;
  489. ctx.save();
  490. isTemp && (ctx.globalAlpha = 0.3);
  491. help.setVectorStyle(ctx, leftEdge);
  492. let [style, fo] = help.getVectorStyle(vector)
  493. fo && help.setStyle(ctx, style)
  494. drawRoadEdgeChild(leftEdge);
  495. help.setVectorStyle(ctx, rightEdge);
  496. fo && help.setStyle(ctx, style)
  497. drawRoadEdgeChild(rightEdge);
  498. ctx.restore();
  499. if (fo) {
  500. ctx.save()
  501. const p1 = coordinate.getScreenXY(leftEdge.start);
  502. const p2 = coordinate.getScreenXY(rightEdge.start);
  503. const p3 = coordinate.getScreenXY(leftEdge.end);
  504. const p4 = coordinate.getScreenXY(rightEdge.end);
  505. ctx.lineWidth = 1 * coordinate.ratio
  506. ctx.setLineDash([5 * coordinate.ratio, 5 * coordinate.ratio ]);
  507. ctx.strokeStyle = Style.Road.strokeStyle
  508. ctx.beginPath()
  509. ctx.moveTo(p1.x, p1.y)
  510. ctx.lineTo(p2.x, p2.y)
  511. ctx.stroke()
  512. ctx.beginPath()
  513. ctx.moveTo(p3.x, p3.y)
  514. ctx.lineTo(p4.x, p4.y)
  515. ctx.stroke()
  516. ctx.fillStyle = 'rgba(23, 121, 237, 0.30)'
  517. ctx.moveTo(p1.x, p1.y)
  518. ctx.lineTo(p2.x, p2.y)
  519. ctx.lineTo(p4.x, p4.y)
  520. ctx.lineTo(p3.x, p3.y)
  521. ctx.fill()
  522. ctx.restore()
  523. }
  524. if (import.meta.env.DEV) {
  525. // this.drawPoint(leftEdge.start);
  526. // this.drawPoint(leftEdge.end);
  527. // this.drawPoint(rightEdge.start);
  528. // this.drawPoint(rightEdge.end);
  529. }
  530. }
  531. drawCrossPoint(vector) {
  532. const start = coordinate.getScreenXY(
  533. dataService
  534. .getRoadEdge(vector.edgeInfo1.id)
  535. .getPosition(vector.edgeInfo1.dir)
  536. );
  537. const end = coordinate.getScreenXY(
  538. dataService
  539. .getRoadEdge(vector.edgeInfo2.id)
  540. .getPosition(vector.edgeInfo2.dir)
  541. );
  542. const pt2 = mathUtil.twoOrderBezier(
  543. 0.5,
  544. start,
  545. coordinate.getScreenXY({ x: vector.x, y: vector.y }),
  546. end
  547. );
  548. const pt = mathUtil.twoOrderBezier2(0.5, start, pt2, end);
  549. const extremePoint = coordinate.getScreenXY(vector.extremePoint);
  550. const ctx = this.context;
  551. ctx.save();
  552. help.setVectorStyle(ctx, vector)
  553. ctx.beginPath();
  554. ctx.arc(
  555. extremePoint.x,
  556. extremePoint.y,
  557. Style.CrossPoint.radius * coordinate.ratio,
  558. 0,
  559. Math.PI * 2,
  560. true
  561. );
  562. ctx.stroke();
  563. ctx.fill();
  564. ctx.restore();
  565. ctx.save();
  566. ctx.beginPath();
  567. help.setVectorStyle(ctx, null, "RoadEdge");
  568. //曲线
  569. // ctx.moveTo(start.x, start.y);
  570. // ctx.quadraticCurveTo(pt.x, pt.y, end.x, end.y);
  571. const [coves] = help.transformCoves([vector.curves]);
  572. help.drawCoves(ctx, coves);
  573. ctx.restore();
  574. }
  575. drawCurveRoad(vector) {
  576. const ctx = this.context;
  577. ctx.save();
  578. let midCovesArray
  579. const [_, foo] = help.setVectorStyle(ctx, vector);
  580. if (vector.display && vector.midDivide) {
  581. midCovesArray = help.transformCoves([
  582. vector.midDivide.leftMidDivideCurves,
  583. // vector.midDivide.rightMidDivideCurves,
  584. ]);
  585. ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio])
  586. ctx.lineWidth *= Settings.lineWidth
  587. for (let coves of midCovesArray) {
  588. help.drawCoves(ctx, coves);
  589. }
  590. }
  591. ctx.restore();
  592. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.rightEdgeId), vector);
  593. this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.leftEdgeId), vector);
  594. vector.leftLanesCurves &&
  595. vector.leftLanesCurves.forEach(this.drawCurveLan.bind(this));
  596. vector.rightLanesCurves &&
  597. vector.rightLanesCurves.forEach(this.drawCurveLan.bind(this));
  598. if (foo) {
  599. const leftEdge = dataService.getCurveRoadEdge(vector.leftEdgeId)
  600. const rightEdge = dataService.getCurveRoadEdge(vector.rightEdgeId)
  601. const p1 = coordinate.getScreenXY(leftEdge.start);
  602. const p2 = coordinate.getScreenXY(rightEdge.start);
  603. const p3 = coordinate.getScreenXY(leftEdge.end);
  604. const p4 = coordinate.getScreenXY(rightEdge.end);
  605. ctx.save();
  606. ctx.setLineDash([5 * coordinate.ratio, 5 * coordinate.ratio ]);
  607. ctx.lineWidth = 1 * coordinate.ratio
  608. ctx.strokeStyle = Style.Lane.strokeStyle
  609. ctx.beginPath()
  610. ctx.moveTo(p1.x, p1.y)
  611. ctx.lineTo(p2.x, p2.y)
  612. ctx.stroke()
  613. ctx.beginPath()
  614. ctx.moveTo(p3.x, p3.y)
  615. ctx.lineTo(p4.x, p4.y)
  616. ctx.stroke()
  617. if (midCovesArray) {
  618. const edgeCurves = help.transformCoves([
  619. leftEdge.curves,
  620. rightEdge.curves
  621. ]);
  622. edgeCurves[1] = edgeCurves[1].reverse().map(curve => ({
  623. start: curve.end,
  624. end: curve.start,
  625. controls: curve.controls.reverse()
  626. }))
  627. ctx.beginPath();
  628. ctx.setLineDash([])
  629. ctx.moveTo(edgeCurves[0][0].start.x, edgeCurves[0][0].start.y);
  630. edgeCurves[0].forEach(cuve => help.drawCove(ctx, cuve))
  631. ctx.lineTo(edgeCurves[1][0].start.x, edgeCurves[1][0].start.y)
  632. edgeCurves[1].forEach(cuve => help.drawCove(ctx, cuve))
  633. ctx.closePath()
  634. ctx.fillStyle = 'rgba(23, 121, 237, 0.30)'
  635. ctx.fill()
  636. }
  637. ctx.restore();
  638. }
  639. // if (import.meta.env.DEV) {
  640. vector.points.forEach(this.drawPoint.bind(this));
  641. // }
  642. }
  643. drawCurveRoadEdge(vector, roadVector) {
  644. const [coves] = help.transformCoves([vector.curves]);
  645. const ctx = this.context;
  646. const [style, select] = help.getVectorStyle(roadVector)
  647. ctx.save();
  648. help.setVectorStyle(ctx, vector);
  649. select && help.setStyle(ctx, style)
  650. ctx.lineWidth *= Settings.lineWidth
  651. help.drawCoves(ctx, coves);
  652. ctx.restore();
  653. if (import.meta.env.DEV) {
  654. // vector.points.forEach(this.drawPoint.bind(this));
  655. }
  656. }
  657. drawCurveLan(lines) {
  658. const [coves] = help.transformCoves([lines]);
  659. const ctx = this.context;
  660. ctx.save();
  661. help.setVectorStyle(ctx, null, "CurveLan");
  662. ctx.lineWidth *= Settings.lineWidth
  663. ctx.setLineDash(Style.Lane.dash);
  664. help.drawCoves(ctx, coves);
  665. ctx.restore();
  666. // if (import.meta.env.DEV) {
  667. lines.map((line) => {
  668. this.drawPoint(line.start);
  669. this.drawPoint(line.end);
  670. });
  671. // }
  672. }
  673. drawRoadPoint(vector) {
  674. this.drawPoint(vector);
  675. }
  676. drawArrow(vector) {
  677. const startReal = dataService.getPoint(vector.startId);
  678. const start = coordinate.getScreenXY(startReal);
  679. const endReal = dataService.getPoint(vector.endId);
  680. const end = coordinate.getScreenXY(endReal);
  681. const ctx = this.context;
  682. ctx.save();
  683. const [style] = help.setVectorStyle(this.context, vector);
  684. if (vector.color) {
  685. ctx.strokeStyle = vector.color;
  686. }
  687. const dires =
  688. vector.category === UIEvents.DoubleArrow
  689. ? [
  690. [start, end],
  691. [end, start],
  692. ]
  693. : [[start, end]];
  694. for (let [start, end] of dires) {
  695. const lines = mathUtil.getArrow(start, end);
  696. ctx.moveTo(lines[0].x, lines[0].y);
  697. ctx.lineTo(lines[1].x, lines[1].y);
  698. ctx.lineTo(lines[2].x, lines[2].y);
  699. }
  700. ctx.stroke();
  701. ctx.restore();
  702. }
  703. drawMagnifier(vector) {
  704. const ctx = this.context;
  705. ctx.save();
  706. const [style] = help.setVectorStyle(ctx, vector);
  707. const radius = vector.radius || style.radius;
  708. this.drawPoint({
  709. ...vector,
  710. ...vector.position,
  711. radius,
  712. });
  713. const pt = coordinate.getScreenXY(vector.position);
  714. // vector.setPopPosition();
  715. const target = {
  716. x: vector.popPosition.x,
  717. y: vector.popPosition.y,
  718. };
  719. const offset = radius / 2;
  720. const targetPts =[mathUtil.translate(pt, target, pt, radius), target];
  721. ctx.beginPath();
  722. ctx.moveTo(pt.x - offset, pt.y);
  723. ctx.lineTo(pt.x + offset, pt.y);
  724. ctx.stroke();
  725. ctx.beginPath();
  726. ctx.moveTo(pt.x, pt.y - offset);
  727. ctx.lineTo(pt.x, pt.y + offset);
  728. ctx.stroke();
  729. if (targetPts) {
  730. ctx.beginPath();
  731. ctx.moveTo(targetPts[0].x, targetPts[0].y);
  732. ctx.lineTo(targetPts[1].x, targetPts[1].y);
  733. ctx.stroke();
  734. let img, imgBound;
  735. if (vector.photoImage) {
  736. img = vector.photoImage;
  737. let top = 0, left = 0, size = 0
  738. if (img.width > img.height) {
  739. size = img.height
  740. left = (img.width - size) / 2
  741. } else {
  742. size = img.width
  743. top = (img.height - size) / 2
  744. }
  745. imgBound = [left, top, size, size];
  746. } else {
  747. const size = help.getReal(style.target.realRadius);
  748. const backImg = dataService.getBackgroundImg();
  749. img = backImg.imageData;
  750. const imgCenter = coordinate.getScreenXY(backImg.center);
  751. const start = {
  752. x: imgCenter.x - help.getReal(img.width) / 2,
  753. y: imgCenter.y - help.getReal(img.height) / 2,
  754. };
  755. const ro = img.width / help.getReal(img.width);
  756. imgBound = [
  757. (pt.x - start.x - size) * ro,
  758. (pt.y - start.y - size) * ro,
  759. size * 2 * ro,
  760. size * 2 * ro,
  761. ];
  762. }
  763. const size = style.target.radius;
  764. ctx.beginPath();
  765. ctx.arc(target.x, target.y, size, 0, 2 * Math.PI);
  766. ctx.clip();
  767. ctx.drawImage(
  768. img,
  769. ...imgBound,
  770. target.x - size,
  771. target.y - size,
  772. size * 2,
  773. size * 2
  774. );
  775. ctx.strokeStyle = style.target.strokeStyle;
  776. ctx.lineWidth = style.target.lineWidth;
  777. ctx.stroke();
  778. }
  779. ctx.restore();
  780. }
  781. drawElliptic(element, radiusX = element.radiusX, radiusY = element.radiusY) {
  782. function drawEllipse(context, x, y, a, b) {
  783. const step = (a > b) ? 1 / a : 1 / b;
  784. context.beginPath();
  785. context.moveTo(x + a, y);
  786. for (let i = 0; i < 2 * Math.PI; i += step) {
  787. context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i));
  788. }
  789. context.closePath();
  790. }
  791. const pt = coordinate.getScreenXY({ x: element.center.x, y: element.center.y });
  792. const ctx = this.context;
  793. ctx.save();
  794. const [_, label] = help.setVectorStyle(ctx, element);
  795. ctx.strokeStyle = element.color
  796. drawEllipse(
  797. ctx, pt.x, pt.y,
  798. (radiusX * coordinate.zoom) / coordinate.defaultZoom,
  799. (radiusY * coordinate.zoom) / coordinate.defaultZoom
  800. )
  801. ctx.stroke();
  802. ctx.fill();
  803. ctx.restore();
  804. }
  805. drawCircle(element) {
  806. this.context.save()
  807. const geo = [element.center, element.points[1], {...element.center, x: 999}]
  808. let angle = mathUtil.Angle(...geo)
  809. angle = help.isTriangleClockwise(...geo) ? -angle : angle
  810. const center = coordinate.getScreenXY(element.center)
  811. this.context.translate(center.x, center.y)
  812. this.context.rotate((angle / 180) * Math.PI)
  813. this.context.translate(-center.x, -center.y)
  814. this.drawElliptic(element, element.radiusX, element.radiusY)
  815. this.context.restore()
  816. const [_, label] = help.getVectorStyle(element);
  817. label && element.points.forEach((point) => this.drawPoint(point));
  818. }
  819. drawPoint(vector, screenSave) {
  820. const screenNotDrawTypes = [
  821. VectorCategory.Point.NormalPoint,
  822. ]
  823. if (!screenSave) {
  824. if ((Settings.screenMode && (!vector.category || screenNotDrawTypes.includes(vector.category))) ||
  825. (vector.category === VectorCategory.Point.TestBasePoint)) {
  826. return;
  827. }
  828. }
  829. const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y });
  830. const ctx = this.context;
  831. ctx.save();
  832. let [style, attr] = help.setVectorStyle(ctx, vector, [
  833. vector.category,
  834. vector.geoType,
  835. "Point",
  836. ]);
  837. if (vector.category === VectorCategory.Point.NormalPoint) {
  838. const lineid = Object.keys(vector.parent)[0];
  839. let line;
  840. if (!(lineid && (line = dataService.getLine(lineid)))) {
  841. return;
  842. }
  843. const [stylea, attr] = help.getVectorStyle(line, line.category);
  844. style = {
  845. ...style,
  846. ...stylea
  847. }
  848. }
  849. if (vector.color) {
  850. ctx.strokeStyle = vector.color;
  851. style = {
  852. ...style,
  853. strokeStyle: vector.color
  854. };
  855. }
  856. const draw = (style) => {
  857. const radius = vector.radius || style.radius;
  858. ctx.save();
  859. ctx.beginPath();
  860. ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2, true);
  861. help.setStyle(ctx, style);
  862. ctx.stroke();
  863. ctx.fill();
  864. ctx.restore();
  865. };
  866. if (Settings.selectBasePointId === vector.vectorId ) {
  867. style = {
  868. ...style,
  869. strokeStyle: "rgba(255,255,255,1)",
  870. out: style.out && {
  871. ...style.out,
  872. strokeStyle: "red",
  873. },
  874. };
  875. }
  876. console.log(vector, style, Settings.selectBasePointId)
  877. draw(style);
  878. if (style.out) {
  879. draw(style.out);
  880. }
  881. if (vector.category === "BasePoint") {
  882. ctx.font = `${12 * coordinate.ratio}px Microsoft YaHei`
  883. const bound = help.getTextCenter(ctx, "基准点")
  884. const screen = coordinate.getScreenXY(vector)
  885. const textPt = coordinate.getXYFromScreenNotRatio({
  886. y: screen.y + bound.height + style.radius,
  887. x: screen.x - (bound.width / 2)
  888. })
  889. ctx.fillStyle = style.fillStyle
  890. this.drawTextByInfo(textPt, "基准点", 0, false);
  891. } else {
  892. if (import.meta.env.DEV) {
  893. if (vector.vectorId) {
  894. // this.drawTextByInfo(vector, vector.vectorId);
  895. }
  896. }
  897. }
  898. ctx.restore();
  899. }
  900. drawTextByInfo(position, txt, angle, setStyle = true) {
  901. const ctx = this.context;
  902. ctx.save();
  903. setStyle && help.setVectorStyle(ctx, null, "Text");
  904. const pt = coordinate.getScreenXY(position);
  905. const textCenter = help.getTextCenter(ctx, txt);
  906. // pt.x -= textCenter.x;
  907. // pt.y -= textCenter.y;
  908. if (angle) {
  909. ctx.translate(pt.x, pt.y);
  910. ctx.rotate(angle);
  911. ctx.translate(-textCenter.x, -textCenter.y);
  912. ctx.fillText(txt, 0, 0);
  913. } else {
  914. ctx.fillText(txt, pt.x, pt.y);
  915. }
  916. ctx.restore();
  917. }
  918. // 文字
  919. drawText(vector) {
  920. this.context.save();
  921. help.setVectorStyle(this.context, vector);
  922. this.context.fillStyle = vector.color;
  923. this.context.font = `${
  924. vector.fontSize * coordinate.ratio
  925. }px Microsoft YaHei`;
  926. const bound = help.getTextCenter(this.context, vector.value)
  927. // console.log(vector)
  928. const screen = coordinate.getScreenXY(vector.center)
  929. this.drawTextByInfo(
  930. // vector.center,
  931. coordinate.getXYFromScreenNotRatio({
  932. // y: screen.y + (bound.height + Style.Point.radius),
  933. y: screen.y + (bound.height + Style.Point.radius ),
  934. x: screen.x - (bound.width / 2)
  935. }),
  936. vector.value,
  937. -(vector.angle || 0),
  938. false
  939. );
  940. this.context.restore();
  941. vector.displayPoint && this.drawPoint({...vector.center, color: vector.color}, true)
  942. }
  943. drawSVG(vector) {
  944. const points = vector.points.map(coordinate.getScreenXY.bind(coordinate));
  945. const svgWidth = 64;
  946. const svgHidth = 64;
  947. const width = mathUtil.getDistance(points[0], points[1]);
  948. const height = mathUtil.getDistance(points[0], points[3]);
  949. const dires = [points[0], { ...points[0], x: 10000 }, points[1]];
  950. let angle = mathUtil.Angle(...dires) * (Math.PI / 180);
  951. angle = mathUtil.isClockwise(dires) ? angle : -angle;
  952. this.context.save();
  953. this.context.translate(points[0].x, points[0].y);
  954. this.context.rotate(angle);
  955. this.context.scale(width / svgWidth, height / svgHidth);
  956. const [style, label] = help.setVectorStyle(this.context, vector);
  957. this.context.lineWidth = style.lineWidth / (width / svgWidth);
  958. SVGIcons[vector.type].draw(this.context, style.fillStyle, style.strokeStyle);
  959. this.context.restore();
  960. if (label) {
  961. this.context.save();
  962. this.context.beginPath();
  963. this.context.moveTo(points[0].x, points[0].y);
  964. this.context.lineTo(points[1].x, points[1].y);
  965. this.context.lineTo(points[2].x, points[2].y);
  966. this.context.lineTo(points[3].x, points[3].y);
  967. this.context.strokeStyle = style.strokeStyle
  968. this.context.lineWidth = 2 * coordinate.ratio
  969. this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]);
  970. this.context.closePath();
  971. this.context.stroke();
  972. this.context.restore();
  973. vector.points.forEach(point => this.drawPoint({...point, color: style.strokeStyle, radius: 5 }))
  974. }
  975. }
  976. drawLineText(vector, style) {
  977. const startReal = dataService.getPoint(vector.startId);
  978. const endReal = dataService.getPoint(vector.endId);
  979. help.drawLineText(
  980. this.context,
  981. coordinate.getScreenXY(startReal),
  982. coordinate.getScreenXY(endReal),
  983. (vector.value
  984. ? Math.round(vector.value * 100) / 100
  985. : help.getRealDistance(startReal, endReal)) + "m",
  986. style
  987. );
  988. }
  989. drawBaseLineLabel(vector) {
  990. const startReal = dataService.getPoint(vector.startId);
  991. const start = coordinate.getScreenXY(startReal);
  992. const endReal = dataService.getPoint(vector.endId);
  993. const end = coordinate.getScreenXY(endReal);
  994. const point = mathUtil.translate(
  995. end,
  996. start,
  997. end,
  998. mathUtil.getDistance(start, end) / 3
  999. );
  1000. const p4 = help.getPerpendicularPoint(
  1001. start,
  1002. end,
  1003. point,
  1004. 30 * coordinate.ratio
  1005. );
  1006. const ctx = this.context;
  1007. ctx.save()
  1008. ctx.beginPath();
  1009. const [style] = help.setVectorStyle(
  1010. this.context,
  1011. vector,
  1012. vector.category || vector.geoType
  1013. );
  1014. ctx.moveTo(point.x, point.y);
  1015. ctx.lineTo(p4.x, p4.y);
  1016. ctx.stroke();
  1017. const p5 = help.getPerpendicularPoint(
  1018. start,
  1019. end,
  1020. point,
  1021. 35 * coordinate.ratio
  1022. );
  1023. this.context.font = `${12 * coordinate.ratio}px Microsoft YaHei`;
  1024. help.drawLineText(
  1025. this.context,
  1026. help.getPerpendicularPoint(point, p5, p5, 10 * coordinate.ratio),
  1027. help.getPerpendicularPoint(point, p5, p5, -10 * coordinate.ratio),
  1028. "基准线",
  1029. {
  1030. padding: 6 * coordinate.ratio,
  1031. backColor: "rgba(0,0,0,0)",
  1032. fillColor: style.strokeStyle,
  1033. }
  1034. );
  1035. ctx.restore()
  1036. }
  1037. drawCurveLine(vector) {
  1038. // points CurveLine
  1039. const ctx = this.context;
  1040. ctx.save();
  1041. help.setVectorStyle(ctx, vector);
  1042. help.transformCoves([vector.curves]).forEach(coves => {
  1043. help.drawCoves(ctx, coves);
  1044. })
  1045. ctx.restore();
  1046. if (import.meta.env.DEV) {
  1047. vector.points.forEach(this.drawPoint.bind(this));
  1048. }
  1049. }
  1050. drawLine(vector) {
  1051. const startReal = dataService.getPoint(vector.startId);
  1052. const start = coordinate.getScreenXY(startReal);
  1053. const endReal = dataService.getPoint(vector.endId);
  1054. const end = coordinate.getScreenXY(endReal);
  1055. this.context.save();
  1056. const [style, attr] = help.setVectorStyle(this.context, vector, [
  1057. vector.category,
  1058. vector.geoType,
  1059. "BaseLine",
  1060. ]);
  1061. if (style.dash) {
  1062. this.context.setLineDash(style.dash);
  1063. }
  1064. help.drawStyleLine(this.context, [start, end], vector.style, vector.weight)
  1065. switch (vector.category) {
  1066. case VectorCategory.Line.SingleArrowLine:
  1067. this.drawArrow(vector);
  1068. break;
  1069. case VectorCategory.Line.DoubleArrowLine:
  1070. this.drawArrow(vector);
  1071. break;
  1072. case VectorCategory.Line.BaseLine:
  1073. this.drawBaseLineLabel(vector);
  1074. break;
  1075. case VectorCategory.Line.FreeMeasureLine:
  1076. case VectorCategory.Line.MeasureLine:
  1077. case VectorCategory.Line.PositionLine:
  1078. this.drawLineText(vector, style.text);
  1079. break;
  1080. }
  1081. this.context.restore();
  1082. }
  1083. drawElementLine(element) {
  1084. let start = elementService.getPoint(element.startId);
  1085. start = coordinate.getScreenXY(start);
  1086. let end = elementService.getPoint(element.endId);
  1087. end = coordinate.getScreenXY(end);
  1088. this.context.save();
  1089. const [style] = help.setVectorStyle(
  1090. this.context,
  1091. element,
  1092. element.category || element.geoType
  1093. );
  1094. if (style.dash) {
  1095. this.context.setLineDash(style.dash);
  1096. }
  1097. this.context.beginPath();
  1098. this.context.moveTo(start.x, start.y);
  1099. this.context.lineTo(end.x, end.y);
  1100. this.context.stroke();
  1101. this.context.restore();
  1102. }
  1103. }
  1104. const draw = new Draw();
  1105. export { draw };