Draw.js 36 KB

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