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 = {}; 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"], ]; 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); 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]); } ctx.moveTo(line[0].x, line[0].y); ctx.lineTo(line[1].x, line[1].y); break // 单实线 case VectorStyle.DoubleDashedLine: case VectorStyle.DoubleSolidLine: 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"); } 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(); 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) { 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(); const [_, label] = help.setVectorStyle(ctx, vector); 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); vector.leftLanes && vector.leftLanes.forEach(this.drawLan.bind(this)); vector.rightLanes && vector.rightLanes.forEach(this.drawLan.bind(this)); vector.singleLanes && vector.singleLanes.forEach(this.drawLan.bind(this)); } drawLan(lan) { 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"); 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(23, 121, 237, 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(); 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); const [coves] = help.transformCoves([vector.curves]); help.drawCoves(ctx, coves); 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); } } ctx.restore(); this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.rightEdgeId), vector); this.drawCurveRoadEdge(dataService.getCurveRoadEdge(vector.leftEdgeId), vector); vector.leftLanesCurves && vector.leftLanesCurves.forEach(this.drawCurveLan.bind(this)); vector.rightLanesCurves && vector.rightLanesCurves.forEach(this.drawCurveLan.bind(this)); 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(23, 121, 237, 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.drawCoves(ctx, coves); ctx.restore(); if (import.meta.env.DEV) { // vector.points.forEach(this.drawPoint.bind(this)); } } drawCurveLan(lines) { const [coves] = help.transformCoves([lines]); const ctx = this.context; ctx.save(); help.setVectorStyle(ctx, null, "CurveLan"); ctx.lineWidth *= Settings.lineWidth 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); } 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(); const [style] = help.setVectorStyle(this.context, vector); if (vector.color) { ctx.strokeStyle = vector.color; } const dires = vector.category === UIEvents.DoubleArrow ? [ [start, end], [end, start], ] : [[start, end]]; 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(); } 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, }); 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 size = help.getReal(style.target.realRadius); const backImg = dataService.getBackgroundImg(); img = backImg.imageData; const imgCenter = coordinate.getScreenXY(backImg.center); const start = { x: imgCenter.x - help.getReal(img.width) / 2, y: imgCenter.y - help.getReal(img.height) / 2, }; const ro = img.width / help.getReal(img.width); imgBound = [ (pt.x - start.x - size) * ro, (pt.y - start.y - size) * ro, size * 2 * ro, size * 2 * ro, ]; } 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)))) { return; } const [stylea, attr] = help.getVectorStyle(line, line.category); style = { ...style, ...stylea } } if (vector.color) { ctx.strokeStyle = vector.color; style = { ...style, strokeStyle: vector.color }; } 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, strokeStyle: "rgba(255,255,255,1)", out: style.out && { ...style.out, strokeStyle: "red", }, }; } 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, 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(); help.setVectorStyle(this.context, vector); this.context.fillStyle = vector.color; this.context.font = `${ vector.fontSize * coordinate.ratio }px Microsoft YaHei`; 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) } 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.setVectorStyle(this.context, vector); this.context.lineWidth = style.lineWidth / (width / svgWidth); SVGIcons[vector.type].draw(this.context, style.fillStyle, style.strokeStyle); 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.lineWidth = 2 * coordinate.ratio this.context.setLineDash([6 * coordinate.ratio, 2 * coordinate.ratio]); this.context.closePath(); this.context.stroke(); this.context.restore(); vector.points.forEach(point => this.drawPoint({...point, 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.transformCoves([vector.curves]).forEach(coves => { help.drawCoves(ctx, coves); }) 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) 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.FreeMeasureLine: case VectorCategory.Line.MeasureLine: case VectorCategory.Line.PositionLine: this.drawLineText(vector, style.text); 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 };