import { dataService } from "../Service/DataService.js"; import { stateService } from "../Service/StateService.js"; import { coordinate } from "../Coordinate.js"; import Style from "@/graphic/CanvasStyle/index.js"; import { mathUtil } from "../Util/MathUtil.js"; import { elementService } from "../Service/ElementService.js"; import UIEvents from "@/graphic/enum/UIEvents.js"; import VectorCategory from "@/graphic/enum/VectorCategory.js"; import Settings from "@/graphic/Settings.js"; import SVGIcons from "../CanvasStyle/ImageLabels/SVGIcons"; import VectorStyle from "@/graphic/enum/VectorStyle.js"; import VectorWeight from "@/graphic/enum/VectorWeight.js"; const imgCache = {}; export const help = { getVectorStyle(vector, geoType = vector.geoType) { const geoId = vector?.vectorId; if (!geoId || Settings.screenMode) { return [Style[geoType], undefined]; } const itemsEntry = [ [stateService.getSelectItem(), "Select"], [stateService.getDraggingItem(), "Dragging"], [stateService.getFocusItem(), "Focus"], ]; console.log(itemsEntry); let currentAttr; //console.log(itemsEntry) return [ itemsEntry.reduce((prev, [item, attr]) => { if (!item) return prev; const selected = geoId === item.vectorId || (item.parent && Object.keys(item.parent).some((id) => id === geoId)); if (selected && Style[attr]) { const style = Style[attr][geoType] || Style[attr][vector.category]; if (style) { currentAttr = attr; return style; } } return prev; }, Style[geoType]), currentAttr, ]; }, setStyle(ctx, styles) { for (const style in styles) { if (typeof styles[style] === "function") { styles[style](ctx, vector); } else { ctx[style] = styles[style]; } } }, setVectorStyle(ctx, vector, geoType = vector.geoType) { let styles, attr; if (Array.isArray(geoType)) { for (const type of geoType) { [styles, attr] = help.getVectorStyle(vector, type); if (styles) { break; } } } else { [styles, attr] = help.getVectorStyle(vector, geoType); } help.setStyle(ctx, styles); return [styles, attr]; }, transformCoves(lines) { return lines.map((line) => line.map((line) => ({ start: coordinate.getScreenXY(line.start), end: coordinate.getScreenXY(line.end), controls: line.controls.map(coordinate.getScreenXY.bind(coordinate)), })) ); }, drawCove(ctx, curve) { if (curve.controls.length === 1) { ctx.quadraticCurveTo( curve.controls[0].x, curve.controls[0].y, curve.end.x, curve.end.y ); } else { ctx.bezierCurveTo( curve.controls[0].x, curve.controls[0].y, curve.controls[1].x, curve.controls[1].y, curve.end.x, curve.end.y ); } }, drawCoves(ctx, coves) { for (const curve of coves) { ctx.beginPath(); ctx.moveTo(curve.start.x, curve.start.y); help.drawCove(ctx, curve); ctx.stroke(); } }, getReal(data) { return (data * coordinate.ratio * coordinate.zoom) / coordinate.defaultZoom; }, getImage(src) { if (imgCache[src]) { return imgCache[src]; } const img = new Image(); img.src = src; return (imgCache[src] = new Promise((resolve) => { img.onload = () => { resolve(img); }; })); }, getTextCenter(ctx, txt) { const text = ctx.measureText(txt); const height = text.actualBoundingBoxAscent + text.actualBoundingBoxDescent; return { width: text.width, height, x: text.width / 2, y: -height / 2, }; }, // 绘制圆角矩形 roundRect(ctx, x, y, width, height, radius) { ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); }, getRealDistance(p1, p2) { return ( Math.round( (mathUtil.getDistance(p1, p2) * coordinate.res * 100) / coordinate.ratio ) / 100 ); }, getPerpendicularPoint(p1, p2, p3, d) { if (p1.x === p2.x) { return { x: p3.x + d, y: p3.y }; } else if (p1.y === p2.y) { return { x: p3.x, y: p3.y + d }; } // 计算通过 p1 和 p2 的直线的斜率和截距 const slope = (p2.y - p1.y) / (p2.x - p1.x); const intercept = p1.y - slope * p1.x; // 计算垂直线的斜率和截距 const perpendicularSlope = -1 / slope; const perpendicularIntercept = p3.y - perpendicularSlope * p3.x; // 计算垂足点 p0 const x = (perpendicularIntercept - intercept) / (slope - perpendicularSlope); const y = slope * x + intercept; const p0 = { x, y }; // 计算点 p4 const distance = d; // 指定距离 const dx = distance / Math.sqrt(1 + perpendicularSlope ** 2); const dy = perpendicularSlope * dx; return { x: p0.x + dx, y: p0.y + dy }; }, drawLineText(ctx, start, end, text, style) { if (start.x > end.x) { [start, end] = [end, start]; } const angle = (Math.atan2(end.y - start.y, end.x - start.x) * 180) / Math.PI; const center = mathUtil.lineCenter(start, end); ctx.save(); ctx.translate(center.x, center.y); ctx.rotate((angle * Math.PI) / 180); ctx.font = `${(style.fontSize || 10) * coordinate.ratio}px Microsoft YaHei`; const textCenter = help.getTextCenter(ctx, text); const padding = style.padding; help.roundRect( ctx, -textCenter.x - padding, textCenter.y - padding, textCenter.width + 2 * padding, textCenter.height + 2 * padding, textCenter.height / 2 + padding ); ctx.fillStyle = style.backColor; ctx.fill(); ctx.fillStyle = style.fillColor; ctx.fillText(text, -textCenter.x, -textCenter.y); ctx.restore(); }, isTriangleClockwise(p1, p2, p3) { const crossProduct = (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y); return crossProduct < 0; }, drawStyleLine( ctx, line, style = VectorStyle.SingleSolidLine, weight = VectorStyle.Thinning ) { ctx.save(); style = style || VectorStyle.SingleSolidLine; ctx.beginPath(); const lineWidth = Settings.lineWidth * (ctx.lineWidth || 1) * (weight === VectorWeight.Bold ? 2 : 1); const funStyle = [ VectorStyle.PointDrawLine, VectorStyle.SingleDashedLine, VectorStyle.SingleSolidLine, ]; if (typeof line === "function" && !funStyle.includes(style)) { style = VectorStyle.SingleSolidLine; } switch (style) { case VectorStyle.PointDrawLine: case VectorStyle.SingleDashedLine: case VectorStyle.SingleSolidLine: ctx.lineWidth = lineWidth; if (style === VectorStyle.SingleDashedLine) { ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]); } else if (style === VectorStyle.PointDrawLine) { ctx.setLineDash([ 6 * coordinate.ratio, 6 * coordinate.ratio, 2 * coordinate.ratio, ]); } if (typeof line === "function") { line(); } else { ctx.moveTo(line[0].x, line[0].y); ctx.lineTo(line[1].x, line[1].y); } break; // 单实线 case VectorStyle.DoubleDashedLine: case VectorStyle.DoubleSolidLine: ctx.lineWidth = lineWidth; if (style === VectorStyle.DoubleDashedLine) { ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]); } const pd1 = help.getPerpendicularPoint( line[0], line[1], line[0], 4 * coordinate.ratio ); const pd2 = help.getPerpendicularPoint( line[0], line[1], line[1], 4 * coordinate.ratio ); const pd3 = help.getPerpendicularPoint( line[0], line[1], line[0], -4 * coordinate.ratio ); const pd4 = help.getPerpendicularPoint( line[0], line[1], line[1], -4 * coordinate.ratio ); ctx.moveTo(pd1.x, pd1.y); ctx.lineTo(pd2.x, pd2.y); ctx.stroke(); ctx.moveTo(pd3.x, pd3.y); ctx.lineTo(pd4.x, pd4.y); break; case VectorStyle.BrokenLine: const ldis = 5 * coordinate.ratio; if (mathUtil.getDistance(...line) < ldis * 2) { ctx.moveTo(line[0].x, line[0].y); ctx.lineTo(line[1].x, line[1].y); } else { const start = mathUtil.translate(line[0], line[1], line[0], ldis); const end = mathUtil.translate(line[0], line[1], line[1], -ldis); const lineDis = mathUtil.getDistance(start, end); const len = Math.ceil(lineDis / (6 * coordinate.ratio)); const split = lineDis / len; const points = [start]; let temp = start; for (let i = 0; i < len; i++) { temp = mathUtil.translate(temp, line[1], temp, split); points.push(temp); } ctx.moveTo(line[0].x, line[0].y); ctx.lineTo(start.x, start.y); for (let i = 0; i < points.length - 1; i++) { const vTop = help.getPerpendicularPoint( points[i], points[i + 1], mathUtil.lineCenter(points[i], points[i + 1]), (split * (i % 2 ? -1 : 1)) / 2 ); ctx.lineTo(vTop.x, vTop.y); } ctx.lineTo(end.x, end.y); ctx.lineTo(line[1].x, line[1].y); } ctx.lineWidth = lineWidth; break; case VectorStyle.Greenbelt: const dis = 4 * coordinate.ratio; const size = 8 * coordinate.ratio; const p1 = help.getPerpendicularPoint(line[0], line[1], line[0], dis); const p2 = help.getPerpendicularPoint(line[0], line[1], line[1], dis); const p3 = help.getPerpendicularPoint(p1, p2, p2, size); const p4 = help.getPerpendicularPoint(p1, p2, p1, size); ctx.beginPath(); ctx.lineWidth = lineWidth; ctx.moveTo(line[0].x, line[0].y); ctx.lineTo(line[1].x, line[1].y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(p4.x, p4.y); ctx.lineTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.lineTo(p3.x, p3.y); ctx.stroke(); const rdis = 6 * coordinate.ratio; const lineDis = mathUtil.getDistance(p3, p4); const len = Math.ceil(lineDis / rdis); const split = lineDis / len; const points = [p3]; const geo = [p4, { ...p4, x: 999 }, p3]; let angle = (mathUtil.Angle1(...geo) / 180) * Math.PI; const isClock = help.isTriangleClockwise(...geo) || angle === 0; angle = isClock ? -angle : angle; let temp = p3; for (let i = 0; i < len; i++) { temp = mathUtil.translate(temp, p4, temp, split); points.push(temp); } for (let i = 0; i < points.length - 1; i++) { const center = mathUtil.lineCenter(points[i], points[i + 1]); ctx.beginPath(); ctx.arc( center.x, center.y, split / 2, angle, angle + Math.PI, !isClock ); ctx.stroke(); } ctx.lineWidth = lineWidth; break; } ctx.stroke(); ctx.restore(); }, }; export default class Draw { constructor() { this.canvas = null; this.context = null; } initContext(canvas) { if (canvas) { this.canvas = canvas; this.context = canvas.getContext("2d"); const saveRaw = this.context.save.bind(this.context); const restoreRaw = this.context.restore.bind(this.context); let index = 0; this.context.save = saveRaw; // this.context.save = () => { // index++; // console.error("save", index); // saveRaw(); // }; this.context.restore = restoreRaw; // this.context.restore = () => { // index--; // console.log("restore", index); // restoreRaw(); // }; } else { this.context = null; this.canvas = null; } } clear() { this.context.clearRect( 0, 0, this.context.canvas.width, this.context.canvas.height ); } drawBackGroundImg(vector) { if (!vector.display) { return; } const img = vector.imageData; const width = help.getReal(img.width); const height = help.getReal(img.height); const center = coordinate.getScreenXY(vector.center); this.context.save(); if (vector.scale) { this.context.translate(center.x, center.y); this.context.scale(vector.scale, vector.scale); this.context.translate(-center.x, -center.y); } this.context.drawImage( img, center.x - width / 2, center.y - height / 2, width, height ); this.context.restore(); } drawGrid(startX, startY, w, h, step1, step2) { this.context.save(); this.context.beginPath(); for (var x = startX; x <= w; x += step1) { this.context.moveTo(x, 0); this.context.lineTo(x, h); } for (var y = startY; y <= h; y += step1) { this.context.moveTo(0, y); this.context.lineTo(w, y); } this.context.strokeStyle = "rgba(0,0,0,0.1)"; this.context.lineWidth = 0.5 * coordinate.ratio; this.context.stroke(); this.context.beginPath(); for (var x = startX; x <= w; x += step2) { this.context.moveTo(x, 0); this.context.lineTo(x, h); } for (var y = startY; y <= h; y += step2) { this.context.moveTo(0, y); this.context.lineTo(w, y); } this.context.strokeStyle = "rgba(0,0,0,0.2)"; this.context.lineWidth = 1 * coordinate.ratio; this.context.stroke(); this.context.restore(); } drawRoad(vector, isTemp) { const [styles, label] = help.getVectorStyle(vector); if (!isTemp && vector.display && vector.way !== "oneWay") { const ctx = this.context; const draw = (midDivide) => { const startScreen = coordinate.getScreenXY(midDivide.start); const endScreen = coordinate.getScreenXY(midDivide.end); ctx.beginPath(); if (label) { help.setStyle(ctx, Style.Focus.Road); } ctx.moveTo(startScreen.x, startScreen.y); ctx.lineTo(endScreen.x, endScreen.y); ctx.stroke(); }; ctx.save(); help.setStyle(ctx, styles); vector.midDivide.leftMidDivide && draw(vector.midDivide.leftMidDivide); vector.midDivide.rightMidDivide && draw(vector.midDivide.rightMidDivide); ctx.restore(); } if (import.meta.env.DEV && !isTemp) { const startReal = isTemp ? vector.start : dataService.getRoadPoint(vector.startId); const endReal = isTemp ? vector.end : dataService.getRoadPoint(vector.endId); this.drawTextByInfo( { x: (startReal.x + endReal.x) / 2, y: (startReal.y + endReal.y) / 2 }, vector.vectorId ); } this.drawRoadEdge(vector, isTemp); if (vector.way === "oneWay") { vector.singleLanes && vector.singleLanes.forEach((g) => this.drawLan(g, !!label)); } else { vector.leftLanes && vector.leftLanes.forEach((g) => this.drawLan(g, !!label)); vector.rightLanes && vector.rightLanes.forEach((g) => this.drawLan(g, !!label)); } } drawLan(lan, focus) { const ctx = this.context; const start = coordinate.getScreenXY(lan.start); const end = coordinate.getScreenXY(lan.end); ctx.save(); ctx.beginPath(); help.setVectorStyle(ctx, null, "Lane"); if (focus) { ctx.strokeStyle = "rgba(255, 143, 40, 1)"; } ctx.lineWidth *= Settings.lineWidth; ctx.setLineDash(Style.Lane.dash); ctx.moveTo(start.x, start.y); ctx.lineTo(end.x, end.y); ctx.stroke(); ctx.restore(); if (import.meta.env.DEV) { // this.drawPoint(lan.start); // this.drawPoint(lan.end); } } drawRoadEdge(vector, isTemp) { //判断是否与road方向一致。角度足够小,路足够宽,有可能向量方向不一致 const start = isTemp ? vector.start : dataService.getRoadPoint(vector.startId); const end = isTemp ? vector.end : dataService.getRoadPoint(vector.endId); const drawRoadEdgeChild = (edgeVector) => { const flag = mathUtil.isSameDirForVector( start, end, edgeVector.start, edgeVector.end ); if (flag) { const point1 = coordinate.getScreenXY(edgeVector.start); const point2 = coordinate.getScreenXY(edgeVector.end); help.drawStyleLine( ctx, [point1, point2], edgeVector.style, edgeVector.weight ); } if (import.meta.env.DEV) { this.drawTextByInfo( { x: (edgeVector.start.x + edgeVector.end.x) / 2, y: (edgeVector.start.y + edgeVector.end.y) / 2, }, edgeVector.vectorId ); } }; const leftEdge = isTemp ? vector.leftEdge : dataService.getRoadEdge(vector.leftEdgeId); const rightEdge = isTemp ? vector.rightEdge : dataService.getRoadEdge(vector.rightEdgeId); const ctx = this.context; ctx.save(); isTemp && (ctx.globalAlpha = 0.3); help.setVectorStyle(ctx, leftEdge); let [style, fo] = help.getVectorStyle(vector); fo && help.setStyle(ctx, style); drawRoadEdgeChild(leftEdge); help.setVectorStyle(ctx, rightEdge); fo && help.setStyle(ctx, style); drawRoadEdgeChild(rightEdge); ctx.restore(); if (fo) { ctx.save(); const p1 = coordinate.getScreenXY(leftEdge.start); const p2 = coordinate.getScreenXY(rightEdge.start); const p3 = coordinate.getScreenXY(leftEdge.end); const p4 = coordinate.getScreenXY(rightEdge.end); ctx.lineWidth = 1 * coordinate.ratio; ctx.setLineDash([5 * coordinate.ratio, 5 * coordinate.ratio]); ctx.strokeStyle = Style.Road.strokeStyle; // ctx.beginPath(); // ctx.moveTo(p1.x, p1.y); // ctx.lineTo(p2.x, p2.y); // ctx.stroke(); // ctx.beginPath(); // ctx.moveTo(p3.x, p3.y); // ctx.lineTo(p4.x, p4.y); // ctx.stroke(); ctx.fillStyle = "rgba(255, 153, 0, 0.30)"; ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.lineTo(p4.x, p4.y); ctx.lineTo(p3.x, p3.y); ctx.fill(); ctx.restore(); } if (import.meta.env.DEV) { // this.drawPoint(leftEdge.start); // this.drawPoint(leftEdge.end); // this.drawPoint(rightEdge.start); // this.drawPoint(rightEdge.end); } } drawCrossPoint(vector) { const start = coordinate.getScreenXY( dataService .getRoadEdge(vector.edgeInfo1.id) .getPosition(vector.edgeInfo1.dir) ); const end = coordinate.getScreenXY( dataService .getRoadEdge(vector.edgeInfo2.id) .getPosition(vector.edgeInfo2.dir) ); const pt2 = mathUtil.twoOrderBezier( 0.5, start, coordinate.getScreenXY({ x: vector.x, y: vector.y }), end ); const pt = mathUtil.twoOrderBezier2(0.5, start, pt2, end); const extremePoint = coordinate.getScreenXY(vector.extremePoint); const ctx = this.context; ctx.save(); help.setVectorStyle(ctx, vector); ctx.beginPath(); if (!Settings.screenMode) { ctx.arc( extremePoint.x, extremePoint.y, Style.CrossPoint.radius * coordinate.ratio, 0, Math.PI * 2, true ); } ctx.stroke(); ctx.fill(); ctx.restore(); ctx.save(); ctx.beginPath(); help.setVectorStyle(ctx, null, "RoadEdge"); //曲线 // ctx.moveTo(start.x, start.y); // ctx.quadraticCurveTo(pt.x, pt.y, end.x, end.y); help.drawStyleLine( ctx, () => { const [coves] = help.transformCoves([vector.curves]); help.drawCoves(ctx, coves); }, vector.style, vector.weight ); ctx.restore(); } drawCurveRoad(vector) { const ctx = this.context; ctx.save(); let midCovesArray; const [_, foo] = help.setVectorStyle(ctx, vector); if (vector.display && vector.midDivide) { midCovesArray = help.transformCoves([ vector.midDivide.leftMidDivideCurves, // vector.midDivide.rightMidDivideCurves, ]); ctx.setLineDash([8 * coordinate.ratio, 8 * coordinate.ratio]); ctx.lineWidth *= Settings.lineWidth; for (let coves of midCovesArray) { help.drawCoves(ctx, coves); } // midCovesArray = help.transformCoves([ // vector.curves, // ]); // ctx.strokeStyle = 'red' // for (let coves of midCovesArray) { // help.drawCoves(ctx, coves); // } } ctx.restore(); this.drawCurveRoadEdge( dataService.getCurveRoadEdge(vector.rightEdgeId), vector ); this.drawCurveRoadEdge( dataService.getCurveRoadEdge(vector.leftEdgeId), vector ); vector.leftLanesCurves && vector.leftLanesCurves.forEach((data) => this.drawCurveLan(data, foo)); vector.rightLanesCurves && vector.rightLanesCurves.forEach((data) => this.drawCurveLan(data, foo)); if (foo) { const leftEdge = dataService.getCurveRoadEdge(vector.leftEdgeId); const rightEdge = dataService.getCurveRoadEdge(vector.rightEdgeId); // const p1 = coordinate.getScreenXY(leftEdge.start); // const p2 = coordinate.getScreenXY(rightEdge.start); // const p3 = coordinate.getScreenXY(leftEdge.end); // const p4 = coordinate.getScreenXY(rightEdge.end); // ctx.save(); // ctx.setLineDash([5 * coordinate.ratio, 5 * coordinate.ratio]); // ctx.lineWidth = 1 * coordinate.ratio; // ctx.strokeStyle = Style.Lane.strokeStyle; // ctx.beginPath(); // ctx.moveTo(p1.x, p1.y); // ctx.lineTo(p2.x, p2.y); // ctx.stroke(); // ctx.beginPath(); // ctx.moveTo(p3.x, p3.y); // ctx.lineTo(p4.x, p4.y); // ctx.stroke(); if (midCovesArray) { const edgeCurves = help.transformCoves([ leftEdge.curves, rightEdge.curves, ]); edgeCurves[1] = edgeCurves[1].reverse().map((curve) => ({ start: curve.end, end: curve.start, controls: curve.controls.reverse(), })); ctx.beginPath(); ctx.setLineDash([]); ctx.moveTo(edgeCurves[0][0].start.x, edgeCurves[0][0].start.y); edgeCurves[0].forEach((cuve) => help.drawCove(ctx, cuve)); ctx.lineTo(edgeCurves[1][0].start.x, edgeCurves[1][0].start.y); edgeCurves[1].forEach((cuve) => help.drawCove(ctx, cuve)); ctx.closePath(); ctx.fillStyle = "rgba(255, 153, 0, 0.30)"; ctx.fill(); } ctx.restore(); } // if (import.meta.env.DEV) { vector.points.forEach(this.drawPoint.bind(this)); // } } drawCurveRoadEdge(vector, roadVector) { const [coves] = help.transformCoves([vector.curves]); const ctx = this.context; const [style, select] = help.getVectorStyle(roadVector); ctx.save(); help.setVectorStyle(ctx, vector); select && help.setStyle(ctx, style); // ctx.lineWidth *= Settings.lineWidth; help.drawStyleLine( this.context, () => { help.drawCoves(ctx, coves); }, vector.style, vector.weight ); // help.drawCoves(ctx, coves); ctx.restore(); if (import.meta.env.DEV) { // vector.points.forEach(this.drawPoint.bind(this)); } } drawCurveLan(lines, focus) { const [coves] = help.transformCoves([lines]); const ctx = this.context; ctx.save(); help.setVectorStyle(ctx, null, "CurveLan"); ctx.lineWidth *= Settings.lineWidth; if (focus) { ctx.strokeStyle = "rgba(255, 153, 0, 1)"; ctx.lineWidth *= 2; } ctx.setLineDash(Style.Lane.dash); help.drawCoves(ctx, coves); ctx.restore(); // if (import.meta.env.DEV) { // lines.map((line) => { // this.drawPoint(line.start); // this.drawPoint(line.end); // }); // } } drawRoadPoint(vector) { this.drawPoint(vector); } drawLineArrow(line, doubleArrow = false) { const ctx = this.context; const [start, end] = line; const dires = doubleArrow ? [ [start, end], [end, start], ] : [[start, end]]; ctx.save(); for (let [start, end] of dires) { const lines = mathUtil.getArrow(start, end); ctx.moveTo(lines[0].x, lines[0].y); ctx.lineTo(lines[1].x, lines[1].y); ctx.lineTo(lines[2].x, lines[2].y); } ctx.stroke(); ctx.restore(); } drawArrow(vector) { const startReal = dataService.getPoint(vector.startId); const start = coordinate.getScreenXY(startReal); const endReal = dataService.getPoint(vector.endId); const end = coordinate.getScreenXY(endReal); const ctx = this.context; ctx.save(); help.setVectorStyle(this.context, vector); if (vector.color) { ctx.strokeStyle = vector.color; } this.drawLineArrow([start, end], vector.category === UIEvents.DoubleArrow); } drawMagnifier(vector) { const ctx = this.context; ctx.save(); const [style] = help.setVectorStyle(ctx, vector); const radius = vector.radius || style.radius; this.drawPoint({ ...vector, ...vector.position, radius, }); console.log(vector); const pt = coordinate.getScreenXY(vector.position); // vector.setPopPosition(); const target = { x: vector.popPosition.x, y: vector.popPosition.y, }; const offset = radius / 2; const targetPts = [mathUtil.translate(pt, target, pt, radius), target]; ctx.beginPath(); ctx.moveTo(pt.x - offset, pt.y); ctx.lineTo(pt.x + offset, pt.y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(pt.x, pt.y - offset); ctx.lineTo(pt.x, pt.y + offset); ctx.stroke(); if (targetPts) { ctx.beginPath(); ctx.moveTo(targetPts[0].x, targetPts[0].y); ctx.lineTo(targetPts[1].x, targetPts[1].y); ctx.stroke(); let img, imgBound; if (vector.photoImage) { img = vector.photoImage; let top = 0, left = 0, size = 0; if (img.width > img.height) { size = img.height; left = (img.width - size) / 2; } else { size = img.width; top = (img.height - size) / 2; } imgBound = [left, top, size, size]; } else { const backImg = dataService.getBackgroundImg(); const size = help.getReal(style.target.realRadius); img = backImg.imageData; const imgCenter = coordinate.getScreenXY(backImg.center); const width = img.width * backImg.scale; const height = img.height * backImg.scale; const start = { x: imgCenter.x - help.getReal(width) / 2, y: imgCenter.y - help.getReal(height) / 2, }; const pts = pt; const ro = width / help.getReal(width); imgBound = [ ((pts.x - start.x - size) * ro) / backImg.scale, ((pts.y - start.y - size) * ro) / backImg.scale, (size * 2 * ro) / backImg.scale, (size * 2 * ro) / backImg.scale, ]; } const size = style.target.radius; ctx.beginPath(); ctx.arc(target.x, target.y, size, 0, 2 * Math.PI); ctx.clip(); ctx.drawImage( img, ...imgBound, target.x - size, target.y - size, size * 2, size * 2 ); ctx.strokeStyle = style.target.strokeStyle; ctx.lineWidth = style.target.lineWidth; ctx.stroke(); } ctx.restore(); } drawElliptic(element, radiusX = element.radiusX, radiusY = element.radiusY) { function drawEllipse(context, x, y, a, b) { const step = a > b ? 1 / a : 1 / b; context.beginPath(); context.moveTo(x + a, y); for (let i = 0; i < 2 * Math.PI; i += step) { context.lineTo(x + a * Math.cos(i), y + b * Math.sin(i)); } context.closePath(); } const pt = coordinate.getScreenXY({ x: element.center.x, y: element.center.y, }); const ctx = this.context; ctx.save(); const [_, label] = help.setVectorStyle(ctx, element); ctx.strokeStyle = element.color; drawEllipse( ctx, pt.x, pt.y, (radiusX * coordinate.zoom) / coordinate.defaultZoom, (radiusY * coordinate.zoom) / coordinate.defaultZoom ); ctx.stroke(); ctx.fill(); ctx.restore(); } drawCircle(element) { this.context.save(); const geo = [ element.center, element.points[1], { ...element.center, x: 999 }, ]; let angle = mathUtil.Angle(...geo); angle = help.isTriangleClockwise(...geo) ? -angle : angle; const center = coordinate.getScreenXY(element.center); this.context.translate(center.x, center.y); this.context.rotate((angle / 180) * Math.PI); this.context.translate(-center.x, -center.y); this.drawElliptic(element, element.radiusX, element.radiusY); this.context.restore(); const [_, label] = help.getVectorStyle(element); label && element.points.forEach((point) => this.drawPoint(point)); } drawPoint(vector, screenSave) { const screenNotDrawTypes = [VectorCategory.Point.NormalPoint]; if (!screenSave) { if ( (Settings.screenMode && (!vector.category || screenNotDrawTypes.includes(vector.category))) || vector.category === VectorCategory.Point.TestBasePoint ) { return; } } const pt = coordinate.getScreenXY({ x: vector.x, y: vector.y }); const ctx = this.context; ctx.save(); let [style, attr] = help.setVectorStyle(ctx, vector, [ vector.category, vector.geoType, "Point", ]); if (vector.category === VectorCategory.Point.NormalPoint) { const lineid = Object.keys(vector.parent)[0]; let line; if (!(lineid && (line = dataService.getLine(lineid)))) { ctx.restore(); return; } const [stylea, attr] = help.getVectorStyle(line, line.category); style = { ...style, ...stylea, fillStyle: attr ? "#fff" : stylea.strokeStyle, }; } else if (vector.category === VectorCategory.Point.FixPoint) { const text = dataService.getText(vector?.linkedTextId); if (text) { style = { ...style, fillStyle: text.color, strokeStyle: text.color, }; } } if (vector.color) { ctx.strokeStyle = vector.color; style = { ...style, strokeStyle: vector.color, }; } if (vector.fillColor) { style = { ...style, fillStyle: vector.fillColor, }; } const draw = (style) => { const radius = vector.radius || style.radius; ctx.save(); ctx.beginPath(); ctx.arc(pt.x, pt.y, radius, 0, Math.PI * 2, true); help.setStyle(ctx, style); ctx.stroke(); ctx.fill(); ctx.restore(); }; if (Settings.selectBasePointId === vector.vectorId) { style = { ...style, fillStyle: "rgba(255, 143, 40, 1)", strokeStyle: "rgba(255,255,255,1)", out: style.out && { ...style.out, strokeStyle: "rgba(255, 143, 40, 1)", }, }; } //console.log(vector, style, Settings.selectBasePointId) draw(style); if (style.out) { draw(style.out); } if (vector.category === "BasePoint") { ctx.font = `${12 * coordinate.ratio}px Microsoft YaHei`; const bound = help.getTextCenter(ctx, "基准点"); const screen = coordinate.getScreenXY(vector); const textPt = coordinate.getXYFromScreenNotRatio({ y: screen.y + bound.height + style.radius + 4 * coordinate.ratio, x: screen.x - bound.width / 2, }); ctx.fillStyle = style.fillStyle; this.drawTextByInfo(textPt, "基准点", 0, false); } else { if (import.meta.env.DEV) { if (vector.vectorId) { // this.drawTextByInfo(vector, vector.vectorId); } } } ctx.restore(); } drawTextByInfo(position, txt, angle, setStyle = true) { const ctx = this.context; ctx.save(); setStyle && help.setVectorStyle(ctx, null, "Text"); const pt = coordinate.getScreenXY(position); const textCenter = help.getTextCenter(ctx, txt); // pt.x -= textCenter.x; // pt.y -= textCenter.y; if (angle) { ctx.translate(pt.x, pt.y); ctx.rotate(angle); ctx.translate(-textCenter.x, -textCenter.y); ctx.fillText(txt, 0, 0); } else { ctx.fillText(txt, pt.x, pt.y); } ctx.restore(); } // 文字 drawText(vector) { this.context.save(); const [_, foo] = help.setVectorStyle(this.context, vector); this.context.fillStyle = vector.color; this.context.textBaseline = "bottom"; this.context.font = `${ vector.fontSize * coordinate.ratio }px Microsoft YaHei`; const bound = vector .getBound(this.context) .map(coordinate.getScreenXY.bind(coordinate)); this.context.fillText(vector.value, bound[3].x, bound[3].y); let select = false; const geo = stateService.getFocusItem(); if (geo) { const realVector = dataService.getGeo(geo.type, geo.vectorId); select = realVector && realVector.linkedTextId === vector.vectorId; } if (select || foo) { this.context.beginPath(); const padding = 2 * coordinate.ratio; this.context.moveTo(bound[0].x - padding, bound[0].y - padding); this.context.lineTo(bound[1].x + padding, bound[1].y - padding); this.context.lineTo(bound[2].x + padding, bound[2].y + padding); this.context.lineTo(bound[3].x - padding, bound[3].y + padding); this.context.strokeStyle = "rgba(255, 153, 0, 1)"; this.context.fillStyle = "rgba(255, 153, 0, 0.30)"; this.context.lineWidth = 2 * coordinate.ratio; this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]); this.context.closePath(); this.context.stroke(); this.context.fill(); } this.context.restore(); vector.displayPoint && this.drawPoint( { ...vector.center, color: vector.color, fillColor: vector.color }, true ); // const bound = help.getTextCenter(this.context, vector.value); // // console.log(vector) // const screen = coordinate.getScreenXY(vector.center); // this.drawTextByInfo( // // vector.center, // coordinate.getXYFromScreenNotRatio({ // // y: screen.y + (bound.height + Style.Point.radius), // y: screen.y + (bound.height + Style.Point.radius), // x: screen.x - bound.width / 2, // }), // vector.value, // -(vector.angle || 0), // false // ); // this.context.restore(); // vector.displayPoint && // this.drawPoint({ ...vector.center, color: vector.color }, true); // vector.getBound(this.context).forEach(this.drawPoint.bind(this)); } drawSVG(vector) { const points = vector.points.map(coordinate.getScreenXY.bind(coordinate)); const svgWidth = 64; const svgHidth = 64; const width = mathUtil.getDistance(points[0], points[1]); const height = mathUtil.getDistance(points[0], points[3]); const dires = [points[0], { ...points[0], x: 10000 }, points[1]]; let angle = mathUtil.Angle(...dires) * (Math.PI / 180); angle = mathUtil.isClockwise(dires) ? angle : -angle; this.context.save(); this.context.translate(points[0].x, points[0].y); this.context.rotate(angle); this.context.scale(width / svgWidth, height / svgHidth); const [style, label] = help.getVectorStyle(vector); this.context.lineWidth = style.lineWidth / (width / svgWidth); this.context.fillStyle = "rgba(0,0,0,0)"; this.context.strokeStyle = "rgba(0,0,0,0)"; SVGIcons[vector.type].draw( this.context, style.fillStyle || "rgba(0,0,0,0)", style.strokeStyle || "rgba(0,0,0,0)" ); this.context.restore(); if (label) { this.context.save(); this.context.beginPath(); this.context.moveTo(points[0].x, points[0].y); this.context.lineTo(points[1].x, points[1].y); this.context.lineTo(points[2].x, points[2].y); this.context.lineTo(points[3].x, points[3].y); this.context.strokeStyle = style.strokeStyle; this.context.fillStyle = "rgba(255, 153, 0, 0.30)"; this.context.lineWidth = 2 * coordinate.ratio; this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]); this.context.closePath(); this.context.stroke(); this.context.fill(); this.context.restore(); vector.points.forEach((point) => this.drawPoint({ ...point, fillColor: "#fff", color: style.strokeStyle, radius: 5, }) ); } } drawLineText(vector, style) { const startReal = dataService.getPoint(vector.startId); const endReal = dataService.getPoint(vector.endId); help.drawLineText( this.context, coordinate.getScreenXY(startReal), coordinate.getScreenXY(endReal), (vector.value ? Math.round(vector.value * 100) / 100 : help.getRealDistance(startReal, endReal)) + "m", style ); } drawBaseLineLabel(vector) { const startReal = dataService.getPoint(vector.startId); const start = coordinate.getScreenXY(startReal); const endReal = dataService.getPoint(vector.endId); const end = coordinate.getScreenXY(endReal); const point = mathUtil.translate( end, start, end, mathUtil.getDistance(start, end) / 3 ); const p4 = help.getPerpendicularPoint( start, end, point, 30 * coordinate.ratio ); const ctx = this.context; ctx.save(); ctx.beginPath(); const [style] = help.setVectorStyle( this.context, vector, vector.category || vector.geoType ); ctx.moveTo(point.x, point.y); ctx.lineTo(p4.x, p4.y); ctx.stroke(); const p5 = help.getPerpendicularPoint( start, end, point, 35 * coordinate.ratio ); this.context.font = `${12 * coordinate.ratio}px Microsoft YaHei`; help.drawLineText( this.context, help.getPerpendicularPoint(point, p5, p5, 10 * coordinate.ratio), help.getPerpendicularPoint(point, p5, p5, -10 * coordinate.ratio), "基准线", { padding: 6 * coordinate.ratio, backColor: "rgba(0,0,0,0)", fillColor: style.strokeStyle, } ); ctx.restore(); } drawCurveLine(vector) { // points CurveLine const ctx = this.context; ctx.save(); help.setVectorStyle(ctx, vector); help.drawStyleLine( this.context, () => { help.transformCoves([vector.curves]).forEach((coves) => { help.drawCoves(ctx, coves); }); }, vector.style, vector.weight ); ctx.restore(); // if (import.meta.env.DEV) { vector.points.forEach(this.drawPoint.bind(this)); // } } drawLine(vector) { const startReal = dataService.getPoint(vector.startId); const start = coordinate.getScreenXY(startReal); const endReal = dataService.getPoint(vector.endId); const end = coordinate.getScreenXY(endReal); this.context.save(); const [style, attr] = help.setVectorStyle(this.context, vector, [ vector.category, vector.geoType, "BaseLine", ]); if (style.dash) { this.context.setLineDash(style.dash); } help.drawStyleLine(this.context, [start, end], vector.style, vector.weight); // vector.category = VectorCategory.Line.LocationLineByFixPoint; switch (vector.category) { case VectorCategory.Line.SingleArrowLine: this.drawArrow(vector); break; case VectorCategory.Line.DoubleArrowLine: this.drawArrow(vector); break; case VectorCategory.Line.BaseLine: this.drawBaseLineLabel(vector); break; case VectorCategory.Line.LocationLineByFixPoint: case VectorCategory.Line.LocationLineByBasePoint: case VectorCategory.Line.FreeMeasureLine: case VectorCategory.Line.MeasureLine: case VectorCategory.Line.PositionLine: this.drawLineText(vector, style.text); if ( [ VectorCategory.Line.LocationLineByFixPoint, VectorCategory.Line.LocationLineByBasePoint, ].includes(vector.category) ) { this.drawLineArrow([start, end], true); } break; } this.context.restore(); } drawElementLine(element) { let start = elementService.getPoint(element.startId); start = coordinate.getScreenXY(start); let end = elementService.getPoint(element.endId); end = coordinate.getScreenXY(end); this.context.save(); const [style] = help.setVectorStyle( this.context, element, element.category || element.geoType ); if (style.dash) { this.context.setLineDash(style.dash); } this.context.beginPath(); this.context.moveTo(start.x, start.y); this.context.lineTo(end.x, end.y); this.context.stroke(); this.context.restore(); } } const draw = new Draw(); export { draw };