关于mapbox 中拖拽point,line ,polygon实现原理

最近闲来无事开始对mapbox-gl-draw.js工具做了一些剖析,在绘制完一个line或者polygon 和point 鼠标按下移动鼠标可以改变图层位置。当时我就在想,他是怎样实现的。所以了解源码之后我的小小结论就出来。
怎样让一个line图层或者polygon图层跟着你的鼠标移动呢?这里point就不作探讨了,mapbox官网有相关案例。一看就明白那种,所以我们就重点讨论怎样实现line或者polygon的拖动。
首先要理解line 和polygon的拖动,最基本要理解数学二维直角坐标系和三角形相关的知识和概念。在直角坐标系中,知道一点(x,y)和夹角 和一条边的长度就可以计算出未知的目标点,如图所示
在这里插入图片描述
所以在mapbox中移动图层就是运用这样的数学逻辑,那怎样去拖拽line或polygon改变其位置呢。所以你就要理解line或者polygon在概念和数据组织上是一个个point组成的 ,所以通过已知图层数据到鼠标点击或者鼠标按下的坐标点,就可以计算出该鼠标点到各个点间的距离和夹角。那么是不是再进行鼠标移动过程中,就可以通过原始各个点的距离和夹角来计算出移动后的各个点的坐标呢。所以梳理原理之后,那么就开始实现,以下是在vue2中对线的实现代码

    /**
     * 
     * 拖动原理的实现
     * ① 首先实现需要三个个鼠标事件  mousedown   , mousemove ,mouseup,还有turf.js(that.$turf)工具(辅助计算长度和距离),其实这里我有一个大胆想法不用turf直接把放在二维直角坐标系中使用三角公式一样可以做
     *    1:mousedown 用来计算初始layer 各个点到鼠标点的距离和夹角,这是在后续拖动图层可以计算新的各个点的坐标
     *    2:mousemove 使用拖动的坐标点来计算各个点的坐标
     *    3: mouseup   结束拖动  , 关闭鼠标事件  
     * 
     */
    drewDrag(key){
      let that = this;
      that.index = key;
      let layerId = 'drew_line'
      //添加鼠标按下事件
      that.map.on('mousedown',layerId, mousedownEvent);
      //添加触控启动事件
      that.map.on('touchstart', layerId,touchstartEvent)
    
      function mousemoveEvent(e) {
        let coords =[e.lngLat.lng,e.lngLat.lat] //当前坐标 

        //根据点,距离和角度计算目标点
        that.json_line.features.forEach((_e)=>{
          var s_destination = that.$turf.destination(
          that.$turf.point([e.lngLat.lng,e.lngLat.lat]), 
          _e.properties.distance[0], 
          _e.properties.angle[0], {units: 'degrees'});
          console.log(s_destination)
          var e_destination = that.$turf.destination(
          that.$turf.point([e.lngLat.lng,e.lngLat.lat]), 
          _e.properties.distance[1], 
          _e.properties.angle[1], {units: 'degrees'});
          console.log(e_destination)
          _e.geometry.coordinates=[s_destination.geometry.coordinates,e_destination.geometry.coordinates]
        })
        that.map.getSource('drew_line').setData(that.json_line)
        

        console.log(e+coords);
        
      }

      function onUp(e) {
        that.map.off('mousemove', mousemoveEvent);
        that.map.off('touchmove', mousemoveEvent);
        that.map.off('mousedown', mousedownEvent);
        that.map.off('touchstart', touchstartEvent);
        that.map.off('touchend', onUp);
        console.log(e)
      }

      function mousedownEvent(e) {        
        e.preventDefault();//防止默认地图拖动行为。
        //使用turf 计算各个坐标点的长度 和角度
        let data = that.map.getSource('drew_line')._data.features
        console.log(data)
        that.json_line_point.features = []
        that.json_line.features = []
        data.forEach((_e,index,row)=>{
          console.log(index+row)
          //计算距离
          var start_distance = that.$turf.rhumbDistance(
            that.$turf.point([e.lngLat.lng,e.lngLat.lat]), 
            that.$turf.point(_e.properties.start), {units: 'degrees'}); 
          var end_distance = that.$turf.rhumbDistance(
            that.$turf.point([e.lngLat.lng,e.lngLat.lat]), 
            that.$turf.point(_e.properties.end), {units: 'degrees'}); 
          console.log(start_distance+"--"+end_distance)
          _e.properties.distance[0] = start_distance  
          _e.properties.distance[1] = end_distance

          // 计算两点间的夹角
          var s_bearing = that.$turf.bearing(that.$turf.point([e.lngLat.lng,e.lngLat.lat]), that.$turf.point(_e.properties.start));
          var e_bearing = that.$turf.bearing(that.$turf.point([e.lngLat.lng,e.lngLat.lat]), _e.properties.end);
          _e.properties.angle[0]=s_bearing
          _e.properties.angle[1]=e_bearing
          that.json_line.features.push({
            type: _e.type,
            geometry:_e.geometry,
            properties:_e.properties
          });     
        })


        var feature = e.features[0];
        that.map.setFilter('pointlayerhighlight', ['in', 'id', feature.properties.id]);
        //鼠标移动
        that.map.on('mousemove',mousemoveEvent);
        //鼠标抬起
        that.map.once('mouseup', onUp);
        console.log(e);
      }

      function touchstartEvent(e){
        //防止默认地图拖动行为
        e.preventDefault();
        //触控移动
        that.map.on('touchmove', mousemoveEvent);
        //触控端起
        that.map.once('touchend', onUp);
        console.log(e)
      }
    }

在这里插入图片描述

以上是实现line的拖拽,polygon的拖拽原理差不多,当然可以直接使用turf.js 提供的工具实现这样的效果。想看完整案例,请移步手术台 案例

<think>嗯,用户的问题是关于如何将后端返回的正北方向顺时针角度(范围0-360)转换为Turf.js使用的角度,尤其是当开始角度大于结束角度的时候。我需要先理解用户的具体需求和他们可能遇到的困难。 首先,用户提到后端返回的角度是以正北为0度,顺时针方向增加到360度。而Turf.js的方向系统可能不同,比如可能使用数学上的极坐标系,即东为0度,逆时针增加。所以用户需要转换这两个系统之间的角度。 然后,用户提到开始角度有时候大于结束角度。这可能是因为在顺时针系统中,如果跨越了北方向(比如开始角度350度,结束角度10度),这时候直接计算会导致问题,比如弧线方向相反或者计算错误。用户需要处理这种情况,确保转换后的角度在Turf中能正确表示。 接下来,我需要回忆Turf.js的角度定义。Turf中的方位角(bearing)通常是以北为基准,顺时针方向,但某些函数可能使用不同的标准,比如线段的计算可能基于数学坐标系。需要确认Turf的具体标准,比如是否东为0度,逆时针增加,或者是否有其他定义。 可能的转换步骤包括: 1. 将后端返回的顺时针角度转换为逆时针角度,可能需要用360减去原角度,再取模。 2. 处理开始角度大于结束角度的情况,可能需要调整方向,或者将角度扩展到超过360度来处理跨圈的情况。 3. 确保转换后的角度在Turf中能正确表示线段的方向,比如在计算线段之间的夹角或生成弧线时。 此外,用户可能需要处理跨0度的情况,比如开始350度,结束10度,这时候实际覆盖了20度的范围,但直接相减会得到-340度,需要转换为正确的20度。因此,转换过程中要考虑角度的差值的正确计算,可能需要使用(end - start + 360) % 360来得到正确的正角度差。 还要考虑Turf.js中是否有特定的函数或方法来处理角度转换,或者是否需要用户自行编写转换函数。例如,Turf的bearing模块可能已经考虑到了不同方向的问题,但需要验证是否符合用户的后端数据格式。 用户可能还希望确保转换后的角度在Turf中生成的地理要素(如线段、多边形)能正确对应实际的地理方向,例如正北方向的线段在转换后仍然指向正确的方位。 可能的错误包括方向转换错误导致的角度偏差,或者处理跨0度时没有正确计算角度差,导致生成的图形方向相反或形状错误。因此,测试不同情况下的转换结果,尤其是开始角度大于结束角度的情况,是必要的。 总结来说,解决方案应包括: - 角度方向的转换:将顺时针转为逆时针,或者调整基准方向。 - 处理开始角度大于结束角度的情况,可能需要调整差值计算方式。 - 确保转换后的角度适用于Turf.js的函数,如计算线段夹角或生成弧线。 最后,需要给出具体的代码示例,展示如何将后端的角度转换为Turf可用的格式,并处理开始大于结束的情况,比如使用函数进行调整,并计算正确的夹角。</think>在后端返回的**正北方向顺时针角度**(范围 `0-360°`)需要转换为 Turf.js 使用的**数学坐标系角度**(东为 `0°`,**逆时针**增加)时,若遇到**开始角度 > 结束角度**的情况,需按以下逻辑处理: --- ### **1. 角度系统的差异** - **后端角度系统**: - **基准方向**:正北(`0°`) - **方向**:顺时针增加(东为 `90°`,南为 `180°`,西为 `270°`) - **范围**:`0-360°` - **Turf.js 角度系统**: - **基准方向**:正东(`0°`) - **方向**:逆时针增加(北为 `90°`,西为 `180°`,南为 `270°`) - **典型用途**:计算线段方向、多边形顶顺序等。 --- ### **2. 转换核心步骤** #### **(1) 将后端角度转换为 Turf 的数学坐标系角度** ```javascript // 后端角度 -> Turf 角度 function backendToTurfAngle(backendAngle) { // 顺时针转逆时针:360 - angle // 正北转正东:基准方向偏移 90° return (360 - backendAngle + 90) % 360; } // 示例: const turfStart = backendToTurfAngle(backendStart); // 后端角度转 Turf const turfEnd = backendToTurfAngle(backendEnd); ``` #### **(2) 处理开始角度 > 结束角度** 若转换后的 `turfStart > turfEnd`,通常表示**跨坐标系分界线**(如跨越正东方向 `0°`)。需明确业务意图: - **场景1**:直接绘制从 `turfStart` 到 `turfEnd` 的**逆时针弧**(默认行为)。 - **场景2**:强制表示**顺时针弧**,需交换顺序并调整范围: ```javascript if (turfStart > turfEnd) { // 方法1:保持逆时针,允许跨 0° // (Turf 的线段计算通常自动处理) // 方法2:转换为顺时针弧(需扩展角度范围) turfEnd += 360; // 例如:turfStart=350°, turfEnd=10° → turfEnd=370° } ``` --- ### **3. 完整代码示例** ```javascript /** * 将后端角度转换为 Turf 兼容的线段方向 * @param {number} backendStart - 后端开始角度(正北顺时针 0-360°) * @param {number} backendEnd - 后端结束角度(正北顺时针 0-360°) * @returns {[number, number]} - Turf 坐标系的角度对 [start, end] */ function convertToTurfAngles(backendStart, backendEnd) { // 转换为 Turf 角度 let turfStart = (360 - backendStart + 90) % 360; let turfEnd = (360 - backendEnd + 90) % 360; // 处理跨 0° 情况(例如:turfStart=350°, turfEnd=10°) if (turfStart > turfEnd) { // 根据需求选择处理方式: // 方式1:保持逆时针,直接使用(Turf 自动处理) // 方式2:强制顺时针,扩展结束角度(turfEnd += 360) turfEnd += 360; } return [turfStart, turfEnd]; } // 使用示例 const [turfStart, turfEnd] = convertToTurfAngles(350, 10); console.log(turfStart, turfEnd); // 输出:100, 370(可传递给 Turf 函数) ``` --- ### **4. 应用场景验证** #### **案例1:绘制圆弧** ```javascript const backendStart = 350; // 正北顺时针 350° const backendEnd = 10; // 正北顺时针 10° // 转换后得到 Turf 角度 [100°, 370°] const [turfStart, turfEnd] = convertToTurfAngles(backendStart, backendEnd); // 使用 Turf 绘制圆弧(自动处理 370° 为跨圈) const arc = turf.lineArc(center, radius, turfStart, turfEnd); ``` #### **案例2:计算线段方向** ```javascript const line = turf.lineString([[0, 0], [1, 1]]); const bearing = turf.bearing( line ); // 返回 Turf 坐标系角度(东为 0°) ``` --- ### **5. 注意事项** - **方向语义一致性**:确保前后端对“开始角度”和“结束角度”的方向定义一致(如是否表示圆弧的绘制方向)。 - **跨圈处理**:Turf 的 `lineArc` 等方法会自动处理角度值超过 `360°` 的情况,无需额外取模。 - **性能优化**:若频繁处理角度转换,可将计算逻辑移至后端或使用 WebAssembly 加速。 通过以上方法,可确保后端角度与 Turf.js 的数学坐标系无缝兼容,并正确处理开始角度大于结束角度的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值