leaflet.pm绘制多边形判断marker在绘制范围内问题

135 阅读1分钟

问题:使用leaflet库中自带的contains方法来判断点是否在绘制范围内时,不够准确,会导致判断错误

一、使用leaflet库中自带的contains方法

this.map.on('pm:create', (e) => {
        console.log(e, '绘制完成时调用')
        // 获取被选中的设备
        const bounds = e.layer.getBounds()
        const fillMarket = this.deviceListData.filter((item) => bounds.contains(item.latlng))
        console.log('图形包含选中设备:', fillMarket)
        if (fillMarket.length) this.handleSelected(fillMarket)
        // 循环markersLayer图层上所有marker
        this.markersLayer.eachLayer((marker) => {
          // 判断marker在绘制范围内
          if (bounds.contains(marker.getLatLng())) {
            const { alt } = marker.options
            marker.setIcon(
              this.$leaflet.icon({
                iconUrl: this.transIcons({ devType: Number(alt.split('-')[1]), status: 99 }),
                iconSize: [40, 40], // 图标大小,单位(px)
                popupAnchor: [-20, 0], // popup相对于锚点中心的坐标
                tooltipAnchor: [0, -20] // tooltip相对于锚点中心的坐标
              })
            )
          }
        })
        this.cancelDraw()
      })

二、自定义判断方法

leaflet-pm-create.js,其中有判断点是否在矩形内、判断点是否多边形内、判断点是否在圆的内部

export default {
  methods: {
    /**
     * 判断点是否在矩形内
     * @param {Point} point 点对象
     * @param {Bounds} bounds 矩形边界对象
     * @returns {Boolean} 点在矩形内返回true,否则返回false
     */
    isPointInRect(point, bounds) {
      const sw = bounds.getSouthWest() // 西南脚点
      const ne = bounds.getNorthEast() // 东北脚点
      return (
        point.lng >= sw.lng && point.lng <= ne.lng && point.lat >= sw.lat && point.lat <= ne.lat
      )
    },
    /**
     * 判断点是否多边形内
     * @param {Point} point 点对象
     * @param {Polyline} polygon 多边形对象
     * @returns {Boolean} 点在多边形内返回true,否则返回false
     */
    isPointInPolygon(point, polygon) {
      // 首先判断点是否在多边形的外包矩形内,如果在,则进一步判断,否则返回false
      const polygonBounds = polygon.getBounds()
      if (!this.isPointInRect(point, polygonBounds)) {
        return false
      }

      const pts = polygon.getLatLngs()[0] // 获取多边形点

      // 下述代码来源:http://paulbourke.net/geometry/insidepoly/,进行了部分修改
      // 基本思想是利用射线法,计算射线与多边形各边的交点,如果是偶数,则点在多边形外,否则
      // 在多边形内。还会考虑一些特殊情况,如点在多边形顶点上,点在多边形边上等特殊情况。
      const N = pts.length
      const boundOrVertex = true // 如果点位于多边形的顶点或边上,也算做点在多边形内,直接返回true
      let intersectCount = 0 // cross points count of x
      const precision = 2e-10 // 浮点类型计算时候与0比较时候的容差
      let p1 = pts[0] // left vertex
      let p2 = null // neighbour bound vertices
      const p = point // 测试点

      for (let i = 1; i <= N; i += 1) {
        // check all rays
        if (p.equals(p1)) {
          return boundOrVertex // p is an vertex
        }

        p2 = pts[i % N] // right vertex
        if (p.lat < Math.min(p1.lat, p2.lat) || p.lat > Math.max(p1.lat, p2.lat)) {
          // ray is outside of our interests
          p1 = p2
        }

        if (p.lat > Math.min(p1.lat, p2.lat) && p.lat < Math.max(p1.lat, p2.lat)) {
          // ray is crossing over by the algorithm (common part of)
          if (p.lng <= Math.max(p1.lng, p2.lng)) {
            // x is before of ray
            if (p1.lat === p2.lat && p.lng >= Math.min(p1.lng, p2.lng)) {
              // overlies on a horizontal ray
              return boundOrVertex
            }

            if (p1.lng === p2.lng) {
              // ray is vertical
              if (p1.lng === p.lng) {
                // overlies on a vertical ray
                return boundOrVertex
              }
              intersectCount += 1
            } else {
              // cross point on the left side
              const xinters = ((p.lat - p1.lat) * (p2.lng - p1.lng)) / (p2.lat - p1.lat) + p1.lng // cross point of lng
              if (Math.abs(p.lng - xinters) < precision) {
                // overlies on a ray
                return boundOrVertex
              }

              if (p.lng < xinters) {
                // before ray
                intersectCount += 1
              }
            }
          }
        } else if (p.lat === p2.lat && p.lng <= p2.lng) {
          // special case when ray is crossing through the vertex
          //  {
          // p crossing over p2
          const p3 = pts[(i + 1) % N] // next vertex
          if (p.lat >= Math.min(p1.lat, p3.lat) && p.lat <= Math.max(p1.lat, p3.lat)) {
            // p.lat lies between p1.lat & p3.lat
            intersectCount += 1
          } else {
            intersectCount += 2
          }
          // }
        }
        p1 = p2 // next ray left point
      }

      if (intersectCount % 2 === 0) {
        console.log('false', intersectCount % 2)
        // 偶数在多边形外
        return false
      }
      // 奇数在多边形内
      console.log('true=====', intersectCount % 2)
      return true
    },
    /**
      判断一个点是否在圆的内部
      @param point 测试点坐标
      @param circle 圆心坐标
      @param r 圆半径
      返回true为真,false为假
    */
    pointInsideCircle(point, circle, r) {
      if (r === 0) return false
      const dx = circle[0] - point[0]
      const dy = circle[1] - point[1]
      return dx * dx + dy * dy <= r * r
    },
    IsPtInPoly(ALon, ALat, APoints) {
      let iSum = 0
      let iCount = 0
      let dLon1 = null
      let dLon2 = null
      let dLat1 = null
      let dLat2 = null
      let dLon = null
      if (APoints.length < 3) return false
      iCount = APoints.length
      for (let i = 0; i < iCount; i += 1) {
        if (i === iCount - 1) {
          dLon1 = APoints[i].lng
          dLat1 = APoints[i].lat
          dLon2 = APoints[0].lng
          dLat2 = APoints[0].lat
        } else {
          dLon1 = APoints[i].lng
          dLat1 = APoints[i].lat
          dLon2 = APoints[i + 1].lng
          dLat2 = APoints[i + 1].lat
        }
        // 以下语句判断A点是否在边的两端点的水平平行线之间,在则可能有交点,开始判断交点是否在左射线上
        if ((ALat >= dLat1 && ALat < dLat2) || (ALat >= dLat2 && ALat < dLat1)) {
          if (Math.abs(dLat1 - dLat2) > 0) {
            // 得到 A点向左射线与边的交点的x坐标:
            dLon = dLon1 - ((dLon1 - dLon2) * (dLat1 - ALat)) / (dLat1 - dLat2)
            if (dLon < ALon) iSum += 1
          }
        }
      }
      if (iSum % 2 !== 0) return true
      return false
    }
  }
}

应用:

this.map.on('pm:create', (e) => {
        console.log(e, '绘制完成时调用')
        // 获取被选中的设备
        const polygon = e.layer
        const bounds = polygon.getBounds()
        let fillMarket = this.deviceListData.filter((item) => bounds.contains(item.latlng))
        console.log('图形包含选中设备:', fillMarket)
        const isPolygon = polygon instanceof this.$leaflet.Polygon
        if (fillMarket.length) {
          console.log('isPolygon===', isPolygon)
          if (isPolygon) {
            fillMarket = []
            this.deviceListData.forEach((item) => {
              if (this.isPointInPolygon(this.$leaflet.latLng(item.latlng), polygon)) {
                fillMarket.push(item)
              }
            })
            console.log('fillMarket====', fillMarket)
          }
          if (fillMarket?.length > 0) this.handleSelected(fillMarket)
        }
        // 循环markersLayer图层上所有marker
        this.markersLayer.eachLayer((marker) => {
          // 判断marker在绘制范围内
          if (bounds.contains(marker.getLatLng())) {
            if (isPolygon && !this.isPointInPolygon(marker.getLatLng(), polygon)) return
            const { alt } = marker.options
            marker.setIcon(
              this.$leaflet.icon({
                iconUrl: this.transIcons({ devType: Number(alt.split('-')[1]), status: 99 }),
                iconSize: [40, 40], // 图标大小,单位(px)
                popupAnchor: [-20, 0], // popup相对于锚点中心的坐标
                tooltipAnchor: [0, -20] // tooltip相对于锚点中心的坐标
              })
            )
          }
        })
        this.cancelDraw()
      })