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
]
}
}
}