Qt Qml Map-地图绘制点与圆的切线

时间:2024-10-13 07:56:51
import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.3 import QtLocation 5.3 import QtPositioning 5.3 Window { id: root visible: true height: 700 width: 1000 property var center01: QtPositioning.coordinate(25.00856, 102.68702) property real radius01: 20 property var center02: QtPositioning.coordinate(25.00956, 102.68902) property real radius02: 30 /** * @brief 根据两个圆的圆心坐标、半径、顺时针或逆时针方向绘制内外公切线。 * * 此函数通过计算两个圆的圆心距离和相对方向,判断是绘制内公切线还是外公切线,并在地图上绘制对应的切线。切线分为内切线和外切线,方向可以是顺时针或逆时针。 * * @param c1 圆1的圆心坐标(QtPositioning.coordinate类型) * @param r1 圆1的半径 * @param direction1 圆1的方向,true表示顺时针,false表示逆时针 * @param c2 圆2的圆心坐标(QtPositioning.coordinate类型) * @param r2 圆2的半径 * @param direction2 圆2的方向,true表示顺时针,false表示逆时针 * * @details * 1. 首先比较两个圆的半径,确定哪个圆较大并作为 largeCircle,较小的圆作为 smallCircle。 * 2. 计算两个圆心之间的方位角和距离 (centerDist)。 * 3. 如果圆心距离小于或等于两圆半径之差,则不会绘制切线,提示 "Error: 圆心距离小于半径。",并清除所有切线。 * 4. 判断两个圆的方向是否一致 (directionMatch): * - 如果方向一致(顺时针-顺时针或逆时针-逆时针),则绘制外公切线。 * - 如果方向不一致(顺时针-逆时针或逆时针-顺时针),则绘制内公切线。 * 5. 对于外切线:计算两个圆的切点并绘制对应的切线路径。 * 6. 对于内切线:计算两个圆的切点并绘制对应的切线路径。 */ function drawTangentCircleToCircle(c1, r1, direction1, c2, r2, direction2) { // 确定较大和较小的圆 var largeCircle, smallCircle, largeRadius, smallRadius, largeDirection, smallDirection; if (r1 >= r2) { largeCircle = c1; smallCircle = c2; largeRadius = r1; smallRadius = r2; largeDirection = direction1; smallDirection = direction2; } else { largeCircle = c2; smallCircle = c1; largeRadius = r2; smallRadius = r1; largeDirection = direction2; smallDirection = direction1; } // 计算两圆心之间的方位角和距离 var doubleCircle_azimuth = largeCircle.azimuthTo(smallCircle); var centerDist = largeCircle.distanceTo(smallCircle); // 确保圆心距离大于半径之差 if (centerDist <= Math.abs(largeRadius - smallRadius)) { console.log("Error: 圆心距离小于半径."); // 清空外切线 crosspointLineItem_ccw.path = []; crosspointLineItem_cw.path = []; // 清空内切线 innerTangentLineItem1.path = []; innerTangentLineItem2.path = []; return; } // 判断圆1和圆2的顺逆方向是否一致 var directionMatch = (largeDirection === smallDirection); // --------------------- 外切线 --------------------- if (directionMatch) { console.log("[绘制外切线]", direction1, direction2) var angleRadians = Math.acos(Math.abs(largeRadius - smallRadius) / centerDist); var angleDegrees = angleRadians * (180 / Math.PI); // 第二个圆为顺时针 if(direction2) { var _LeftCrosspoint_cw = largeCircle.atDistanceAndAzimuth(largeRadius, angleDegrees + doubleCircle_azimuth); var _RightCrosspoint_cw = smallCircle.atDistanceAndAzimuth(smallRadius, angleDegrees + doubleCircle_azimuth); // 绘制外公切线 crosspointLineItem_cw.path = [_LeftCrosspoint_cw, _RightCrosspoint_cw] } // 第二个圆为逆时针 else { var _leftCrosspoint = largeCircle.atDistanceAndAzimuth(largeRadius, 90 - angleDegrees + (doubleCircle_azimuth - 90) ) var _RightCrosspoint = smallCircle.atDistanceAndAzimuth(smallRadius, (180 - angleDegrees - 90) + (doubleCircle_azimuth - 90) ) // 绘制外公切线 crosspointLineItem_ccw.path = [_leftCrosspoint, _RightCrosspoint] } // 清空内切线 innerTangentLineItem1.path = []; innerTangentLineItem2.path = []; } // --------------------- 内切线 --------------------- else { console.log("[绘制内切线]", direction1, direction2) var innerAngleRadians = Math.acos((largeRadius + smallRadius) / centerDist); var innerAngleDegrees = innerAngleRadians * (180 / Math.PI); // 内公切线角度 var innerAngle1 = doubleCircle_azimuth + innerAngleDegrees; var innerAngle2 = doubleCircle_azimuth - innerAngleDegrees; // 第二个圆为顺时针 if(direction2) { var innerTangentPoint1Large = largeCircle.atDistanceAndAzimuth(largeRadius, innerAngle1); var innerTangentPoint1Small = smallCircle.atDistanceAndAzimuth(smallRadius, innerAngle1 + 180); // 绘制内公切线 innerTangentLineItem1.path = [innerTangentPoint1Large, innerTangentPoint1Small]; } // 第二个圆为逆时针 else { var innerTangentPoint2Large = largeCircle.atDistanceAndAzimuth(largeRadius, innerAngle2); var innerTangentPoint2Small = smallCircle.atDistanceAndAzimuth(smallRadius, innerAngle2 + 180); // 绘制内公切线 innerTangentLineItem2.path = [innerTangentPoint2Large, innerTangentPoint2Small]; } // 清空外切线 crosspointLineItem_ccw.path = []; crosspointLineItem_cw.path = []; } } /** * @brief 计算并绘制给定坐标点与圆的两条切线 * @param point 坐标点 * @param circleCenter 圆心坐标 * @param radius 圆半径 */ function drawTangentPointToCircle(point, circleCenter, radius) { // 计算点到圆心的距离 var distance = point.distanceTo(circleCenter); // 确保点在圆的外部,才能计算切线 if (distance <= radius) { console.log("错误:坐标点位于圆内或圆上,无法计算切线。"); tangentLineItem1.path = []; tangentLineItem2.path = []; return; } // 余弦定理计算角度 α(弧度) var alpha = Math.acos(radius / distance); // 将 α 转换为角度 var alphaDegrees = alpha * (180 / Math.PI); // 计算从点到圆心的初始方位角(度) var bearingPO = point.azimuthTo(circleCenter); // 单位为度 // 计算从圆心到切点的方位角 var bearingOT1 = (bearingPO + 180 - alphaDegrees + 360) % 360; var bearingOT2 = (bearingPO + 180 + alphaDegrees) % 360; // 计算切点的坐标 var tangentPoint1 = circleCenter.atDistanceAndAzimuth(radius, bearingOT1); var tangentPoint2 = circleCenter.atDistanceAndAzimuth(radius, bearingOT2); // 绘制从点到切点的两条切线 tangentLineItem1.path = [point, tangentPoint1]; tangentLineItem2.path = [point, tangentPoint2]; } Map { id: _map anchors.fill: parent zoomLevel: 15 center: QtPositioning.coordinate(25.00856, 102.68702); gesture.flickDeceleration: 3000 plugin: Plugin { name: "Gaode" } Component.onCompleted: { drawTangentCircleToCircle(center01, radius01, true, center02, radius02, true) } MouseArea { id: mouseArea_measure anchors.fill: parent onClicked: { var _coordinate = _map.toCoordinate(Qt.point(mouse.x, mouse.y)) console.log("更新第二圆坐标", _coordinate.latitude, _coordinate.longitude) center02 = _coordinate //drawTangentCircleToCircle(center01, radius01, true, center02, radius02, true) drawTangentPointToCircle(center01, center02, radius02, true) } } // *********** 外切线 *********** // (逆) MapPolyline { id: crosspointLineItem_ccw line.width: 2 line.color: 'pink' visible: false } // (顺) MapPolyline { id: crosspointLineItem_cw line.width: 2 line.color: 'pink' visible: false } // *********** 内切线 *********** // 第一条内切线 MapPolyline { id: innerTangentLineItem1 line.width: 3 line.color: 'blue' } // 第二条内切线 MapPolyline { id: innerTangentLineItem2 line.width: 3 line.color: 'blue' } // *********** 点与圆的切线 *********** // 第一条内切线 MapPolyline { id: tangentLineItem1 line.width: 3 line.color: 'blue' } // 第二条内切线 MapPolyline { id: tangentLineItem2 line.width: 3 line.color: 'blue' } Row { anchors.bottom: parent.bottom anchors.bottomMargin: 10 anchors.horizontalCenter: parent.horizontalCenter Text { text: "圆1(红色)半径:" color: "white" anchors.verticalCenter: parent.verticalCenter } TextField { id: radius01Item text: radius01 } Item { width: 20 height: 10 } Text { text: "圆2(黄色)半径:" color: "white" anchors.verticalCenter: parent.verticalCenter } TextField { id: radius02Item text: radius02 } Item { width: 20 height: 10 } Button { text: "更新" onClicked: { radius01 = radius01Item.text radius02 = radius02Item.text drawTangentCircleToCircle(center01, radius01, true, center02, radius02, true) } } Button { text: "顺-顺" onClicked: { drawTangentCircleToCircle(center01, radius01, true, center02, radius02, true) } } Button { text: "顺-逆" onClicked: { drawTangentCircleToCircle(center01, radius01, true, center02, radius02, false) } } Button { text: "逆-逆" onClicked: { drawTangentCircleToCircle(center01, radius01, false, center02, radius02, false) } } Button { text: "逆-顺" onClicked: { drawTangentCircleToCircle(center01, radius01, false, center02, radius02, true) } } } // 圆1(红色) MapCircle { center: center01 radius: radius01 color: "transparent" border.width: 3 border.color: "red" visible: false } // 圆2(黄色),使用atDistanceAndAzimuth计算其位置 MapCircle { center: center02 radius: radius02 color: "transparent" border.width: 3 border.color: 'yellow' } // 两圆心连线 MapPolyline { line.width: 1 line.color: 'red' visible: false path: [ center01, center02 ] } } }