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

 基于此源码替换 main.qml 文件
https://download.csdn.net/download/qq_38159549/89860109icon-default.png?t=O83Ahttps://download.csdn.net/download/qq_38159549/89860109

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值