微信小程序 地图 使用 射线法 判断目标点是否在多边形内部(可用于判断当前位置是否在某个区域内部)

目录

射线法

原理

使用射线法来判断,目标点是否在多边形内部

这里简单说下,具体细节可以看这篇文章

平面几何:判断点是否在多边形内(射线法)

原理很简单,从点引出一条射线,计算射线和多边形的交点数量。

交点数如果是 奇数 ,说明点 多边形内;如果是 偶数 ,则点 不在 多边形内。

射线方向没有要求,通常选择水平或垂直方向的射线,能够有效减少计算量。这里选择 向右的射线

然后就是遍历多边形的所有边,判断边线段和射线是否有交点,有交点就给相交数加 1。

还需要处理一些特殊情况,就是 射线刚好穿过多边形的顶点的情况

① 点的两边都在射线同一侧

② 点的两边在射线不同侧

③ 与多边形的一条边共线

此时可以使用 叉积 判断 目标点是否在边的左侧/右侧

简要逻辑代码

bash 复制代码
const isPointInPolygon = (polygon, pt) => {
  let count = 0;
  for (let i = 0; i < polygon.length; i++) {
    let a = polygon[i];
    let b = polygon[(i + 1) % polygon.length];

    if (a.y > b.y) {
      [a, b] = [b, a];
    }

    if (a.y <= pt.y && b.y > pt.y) {
      const crossProduct = 
        (pt.x - a.x) * (b.y - a.y) - (b.x - a.x) * (pt.y - a.y);
      if (crossProduct === 0) {
        return true;
      }
      if (crossProduct > 0) {
        count++;
      }
    }
  }

  return count % 2 === 1;
};

小程序代码

项目结构树

bash 复制代码
.
├── app.js
├── app.json
├── app.wxss
├── data
│   └── map_data.js
├── pages
│   ├── polygons
│   │   ├── polygons.js
│   │   ├── polygons.json
│   │   ├── polygons.wxml
│   │   └── polygons.wxss
│   └── range
│       ├── range.js
│       ├── range.json
│       ├── range.wxml
│       └── range.wxss
├── project.config.json
├── project.private.config.json
└── sitemap.json

调试基础库

3.8.0

小程序配置

app.json

bash 复制代码
{
  "pages": [
      "pages/polygons/polygons",
      "pages/range/range"
  ],
  "window": {
      "backgroundTextStyle": "light",
      "navigationBarBackgroundColor": "#ffffff",
      "navigationBarTitleText": "点与多边形",
      "navigationBarTextStyle": "black"
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json",
  "lazyCodeLoading": "requiredComponents",
  "useExtendedLib": {
      "weui": true
  },
  "resolveAlias": {
    "@data/*": "data/*"
  }
}

地图数据

data/map_data.js

bash 复制代码
/* data/map_data.js */
// 地图相关
module.exports = {
  // 地图部分参数

  // 学校中心点坐标
  longitude: 110.27672,
  latitude: 25.0937,

  // 是否展示 POI 点
  enablepoi: true,
  // 是否显示带有方向的当前定位点
  showLocation: true,
  // 缩放级别
  scale: 16,

  // 闭合多边形
  points: [
    {
      "latitude": 25.098567,
      "longitude": 110.280995
    },
    {
      "latitude": 25.097711,
      "longitude": 110.281167
    },
    {
      "latitude": 25.096842,
      "longitude": 110.281137
    },
    {
      "latitude": 25.095527,
      "longitude": 110.280778
    },
    {
      "latitude": 25.092988,
      "longitude": 110.279991
    },
    {
      "latitude": 25.090947,
      "longitude": 110.279089
    },
    {
      "latitude": 25.088824,
      "longitude": 110.277994
    },
    {
      "latitude": 25.088921,
      "longitude": 110.277342
    },
    {
      "latitude": 25.088633,
      "longitude": 110.277057
    },
    {
      "latitude": 25.089162,
      "longitude": 110.275070
    },
    {
      "latitude": 25.090355,
      "longitude": 110.272274
    },
    {
      "latitude": 25.094758,
      "longitude": 110.274553
    },
    {
      "latitude": 25.095421,
      "longitude": 110.275129
    },
    {
      "latitude": 25.096114,
      "longitude": 110.275212
    },
    {
      "latitude": 25.097485,
      "longitude": 110.277418
    },
    {
      "latitude": 25.098553,
      "longitude": 110.279565
    },
    {
      "latitude": 25.098779,
      "longitude": 110.280243
    }
  ],
}

地图多边形

polygons.js

bash 复制代码
// pages/polygons/polygons.js
import map_data from '@data/map_data'

Page({
  /**
   * 页面的初始数据
   */
  data: {
    Marker2_Activated: "https://3gimg.qq.com/lightmap/xcx/demoCenter/images/[email protected]",
    Marker3_Activated: "https://3gimg.qq.com/lightmap/xcx/demoCenter/images/[email protected]",

    // 地图中心点坐标
    longitude: map_data.longitude,
    latitude: map_data.latitude,
    // 缩放级别
    scale: map_data.scale,
    // 标记点
    markers: [],
    // 多边形
    polygons: null,
    // 经纬度数组
    points: map_data.points ?? [],
    // 经纬度数据
    points_data: null,

    // 显示/隐藏 标记点
    isShow: true,
    // 显示/隐藏 对话框
    dialogShow: false,
    // 对话框按钮组
    buttons: [{
      text: '关闭'
    }, {
      text: '复制'
    }],
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad() {
    let points = this.data.points
    if (points.length > 0) {
      points.forEach(item => {
        item.latitude = Number(item.latitude)
        item.longitude = Number(item.longitude)
      })
      let markers = points.map((item, index) => ({
        id: index,
        latitude: item.latitude,
        longitude: item.longitude,
        iconPath: this.data.Marker3_Activated,
        width: 25,
        height: 25
      }));
      this.setData({
        markers,
        points,
      })

      // 判断如果标记的点数大于2就给polygons赋值
      if (points.length > 2) {
        let polygons = [{
          points,
          fillColor: "#d5dff233", // 填充颜色:淡蓝色,7-8位为十六进制透明度00-FF
          strokeColor: "#789cff", // 描边颜色:较深的淡蓝色
          strokeWidth: 2, // 描边宽度
        }]
        this.setData({
          polygons,
        })

        // let maxLo = 0
        // let minLo = 180
        // let maxLa = 0
        // let minLa = 90
        // points.forEach(item => {
        //   maxLo = Math.max(maxLo, item.longitude)
        //   minLo = Math.min(minLo, item.longitude)
        //   maxLa = Math.max(maxLa, item.latitude)
        //   minLa = Math.min(minLa, item.latitude)
        // })
        // console.log("地图中心点经度:", (maxLo + minLo) / 2);
        // console.log("地图中心点纬度:", (maxLa + minLa) / 2);
      }
      this.includePoints()
    }
  },

  /**
   * 缩放视野以包含所有给定的坐标点
   */
  includePoints() {
    let points = this.data.points
    this.mapCtx = wx.createMapContext('map')
    this.mapCtx.includePoints({
      padding: [10, 10, 10, 10],
      points,
    })
  },

  /**
   * 绑定地图点击事件
   */
  bindMap(e) {
    let points = this.data.points // 不直接使用polygons[0].points是因为如果不够三个点会报错
    let markers = this.data.markers // 点击地图添加一个标记点
    let latitude = e.detail.latitude.toFixed(6)
    let longitude = e.detail.longitude.toFixed(6)

    points.push({
      latitude,
      longitude
    })
    let length = markers.length
    markers.push({
      id: length,
      latitude,
      longitude,
      iconPath: this.data.Marker3_Activated,
      width: 25,
      height: 25
    })

    this.setData({
      markers,
      points
    })

    // 判断如果标记的点数大于2就给polygons赋值
    if (points.length > 2) {
      let polygons = [{
        points,
        fillColor: "#d5dff233", // 填充颜色:淡蓝色,7-8位为十六进制透明度00-FF
        strokeColor: "#789cff", // 描边颜色:较深的淡蓝色
        strokeWidth: 2, // 描边宽度
      }]
      this.setData({
        polygons,
      })
    }
  },

  /**
   * 清除 上一个标记点
   */
  clearPrevious() {
    let polygons = this.data.polygons
    let markers = this.data.markers
    let points = this.data.points

    markers.pop()
    points.pop()
    if (markers.length < 3) {
      polygons = null
    } else {
      polygons[0].points = points
    }

    this.setData({
      polygons,
      markers,
      points
    })
  },

  /**
   * 清除 标记点和多边形
   */
  clearGon() {
    this.setData({
      polygons: null,
      markers: [],
      points: []
    })
  },

  /**
   * 生成数据 按钮
   */
  generate() {
    let points = this.data.points
    if (points.length > 2) {
      let points_data = "points: " + JSON.stringify(points)
      this.setData({
        dialogShow: true,
        points_data
      })
    } else {
      wx.showToast({
        title: '不能少于三个点',
        icon: 'error'
      })
    }
  },

  /**
   * 对话框 按钮
   */
  dialogButton(e) {
    this.setData({
      dialogShow: false,
    })

    let choose = e.detail.item.text
    if (choose == "复制") {
      this.copy()
    }
  },

  /**
   * 复制 参数信息
   */
  copy() {
    wx.setClipboardData({
      data: this.data.points_data,
    })
  },

  /**
   * 显示/隐藏标记点 按钮
   */
  show() {
    let isShow = this.data.isShow
    let markers = this.data.markers
    markers.forEach(item => {
      // 标注的透明度	范围 0 ~ 1,对应 0% ~ 100%
      item.alpha = isShow ? 0 : 1
    })
    this.setData({
      isShow: !isShow,
      markers
    })
    wx.showToast({
      title: !isShow ? '显示标记点' : '隐藏标记点',
      icon: 'none'
    })
  },

  /**
   * 跳转到 点与多边形 页面
   */
  torange() {
    let points = JSON.stringify(this.data.points)
    wx.navigateTo({
      url: '../range/range?points=' + points,
    })
  },
})

polygons.json

bash 复制代码
{
    "usingComponents": {
      "mp-dialog": "weui-miniprogram/dialog/dialog"
    },
    "navigationBarTitleText": "地图多边形绘制"
}

polygons.wxml

bash 复制代码
<!--pages/polygons/polygons.wxml-->
<map id="map" latitude="{{latitude}}" longitude="{{longitude}}" scale="{{scale}}" markers='{{markers}}' polygons="{{polygons}}" bindtap="bindMap">
  <view class="control-btn" wx:if="{{points.length > 2}}">
    <image src="{{Marker2_Activated}}" class="img" bindtap="torange"></image>
    <view class="text">点与多边形</view>
  </view>
</map>

<view style="display: flex; margin: 10px 0;">
  <button type="default" bindtap="clearPrevious">清除上一个点</button>
  <button type="warn" bindtap="clearGon">清除全部点面</button>
</view>

<view style="display: flex; margin: 10px 0;">
  <button type="{{!isShow ? 'default' : 'warn' }}" bindtap="show">{{!isShow ? '显示' : '隐藏' }} 标记点</button>
  <button style="height: 45px;" type="primary" bindtap="generate">生成数据</button>
</view>

<mp-dialog title="经纬度数组" show="{{dialogShow}}" bindbuttontap="dialogButton" buttons="{{buttons}}">
  <view class="title">
    <scroll-view scroll-y="true" class="scroll">
      <text user-select="true">{{points_data}}</text>
    </scroll-view>
  </view>
</mp-dialog>

polygons.wxss

bash 复制代码
/* pages/polygons/polygons.wxss */
page {
  height: 100%;
}

map {
  width: 100%;
  height: calc(100% - 125px);
}

.title {
  text-align: left;
  margin-top: 10px;
}

.scroll {
  height: 300px;
  width: 100%;
  margin-bottom: 15px;
}

.weui-dialog__btn_primary {
  background-color: #07c160;
  color: #fff;
}

.control-btn {
  position: absolute;
  width: 45px;
  z-index: 99;
  background: #FFF;
  text-align: center;
  border-radius: 4px;
  right: 10px;
  bottom: 40px;
}

.img {
  height: 40px;
  width: 40px;
}

.text {
  font-size: small;
}

点与多边形关系

range.js

bash 复制代码
// pages/range/range.js
import map_data from '@data/map_data'

Page({
  /**
   * 页面的初始数据
   */
  data: {
    // 地图中心点坐标
    longitude: map_data.longitude,
    latitude: map_data.latitude,
    // 缩放级别
    scale: map_data.scale,
    // 标记点
    markers: [],
    // 多边形
    polygons: null,
    // 经纬度数组
    points: [],
    // 是否在多边形内部
    isAtPolygons: false
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    let points = JSON.parse(options.points)
    let polygons = [{
      points,
      fillColor: "#d5dff233", // 填充颜色:淡蓝色,7-8位为十六进制透明度00-FF
      strokeColor: "#789cff", // 描边颜色:淡蓝色
      strokeWidth: 2, // 描边宽度
    }]
    this.setData({
      polygons,
      points,
    })
    this.includePoints()
  },

  /**
   * 缩放视野以包含所有给定的坐标点
   */
  includePoints() {
    this.mapCtx = wx.createMapContext('map')
    this.mapCtx.includePoints({
      padding: [10, 10, 10, 10],
      points: this.data.points,
    })
  },

  /**
   * 绑定地图点击事件
   */
  bindMap(e) {
    let latitude = e.detail.latitude.toFixed(6)
    let longitude = e.detail.longitude.toFixed(6)
    let markers= [{
      id: 1,
      latitude,
      longitude,
      width: 25, // 默认图标的宽度
      height: 34 // 默认图标的高度
    }]
    this.setData({
      markers: markers,
    })

    let testPoint = {
      latitude: latitude,
      longitude: longitude
    }
    let polygon = this.data.points
    let bool = this.isPointInPolygon(testPoint, polygon)

    this.setData({
      isAtPolygons: bool
    })
  },

  /**
   * 判断点是否在多边形内部
   * @param {{longitude: number, latitude: number}} point - 待检测点
   * @param {{longitude: number, latitude: number}[]} polygon - 多边形顶点数组
   */
  isPointInPolygon(point, polygon) {
    // 预处理:全部转为数值
    const toNum = ({ longitude, latitude }) => ({
      longitude: +longitude,
      latitude: +latitude
    });
    point = toNum(point);
    polygon = polygon.map(v => toNum(v));

    // 检查顶点
    for (const v of polygon) {
      if (Math.abs(v.longitude - point.longitude) < 1e-9 
      && Math.abs(v.latitude - point.latitude) < 1e-9) {
        return true;
      }
    }

    // 检查边
    const n = polygon.length;
    for (let i = 0; i < n; i++) {
      const a = polygon[i];
      const b = polygon[(i + 1) % n];
      if (this.isPointOnSegment(point, a, b)) return true;
    }

    // 射线法核心逻辑
    let crossings = 0;
    for (let i = 0; i < n; i++) {
      const a = polygon[i];
      const b = polygon[(i + 1) % n];
      const [aLat, bLat] = [a.latitude, b.latitude];
      const pLat = point.latitude;

      // 边跨越射线时才处理
      if ((aLat >= pLat) === (bLat >= pLat)) continue;

      // 排除水平边
      if (aLat === bLat) continue;

      // 计算交点经度
      const t = (pLat - aLat) / (bLat - aLat);
      const intersectLon = a.longitude + t * (b.longitude - a.longitude);

      // 交点在射线右侧(经度更大)
      if (intersectLon > point.longitude + 1e-9) {
        crossings++;
      }
    }

    return crossings % 2 === 1;
  },

  /**
   * 判断点是否在多边形边上
   * @param {{longitude: number, latitude: number}} p - 待检测点
   * @param {{longitude: number, latitude: number}} a - 线段起点
   * @param {{longitude: number, latitude: number}} b - 线段终点
   */
  isPointOnSegment(p, a, b) {
    // 强制转换为数值
    const toNum = obj => ({
      longitude: +obj.longitude,
      latitude: +obj.latitude
    });
    p = toNum(p);
    a = toNum(a);
    b = toNum(b);

    // 叉积判共线
    const cross = (p.longitude - a.longitude) * (b.latitude - a.latitude) 
              - (p.latitude - a.latitude) * (b.longitude - a.longitude);
    if (Math.abs(cross) > 1e-9) return false;

    // 包围盒检查
    const minLon = Math.min(a.longitude, b.longitude);
    const maxLon = Math.max(a.longitude, b.longitude);
    const minLat = Math.min(a.latitude, b.latitude);
    const maxLat = Math.max(a.latitude, b.latitude);

    return p.longitude >= minLon - 1e-9 
        && p.longitude <= maxLon + 1e-9 
        && p.latitude >= minLat - 1e-9 
        && p.latitude <= maxLat + 1e-9;
  },
})

range.json

bash 复制代码
{
    "usingComponents": {},
    "navigationBarTitleText": "点与多边形关系"
}

range.wxml

bash 复制代码
<!--pages/range/range.wxml-->
<map id="map" latitude="{{latitude}}" longitude="{{longitude}}" scale="{{scale}}" markers='{{markers}}' polygons="{{polygons}}" bindtap="bindMap" />

<view class="text">
  点
  <span style="color: {{isAtPolygons ? 'black' : 'red'}}; margin: 0 5px;">
    {{isAtPolygons ? '在' : '不在'}}
  </span>
  多边形内
</view>

range.wxss

bash 复制代码
/* pages/range/range.wxss */
page{
  height: 100%;
}

map {
  width: 100%;
  height: calc(100% - 100px);
}

.text {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: x-large;
  margin: 5px;
}

射线法逻辑的代码使用deepseek辅助生成

相关推荐
龙泉寺天下行走5 小时前
Python 翻译词典小程序
python·oracle·小程序
老华带你飞7 小时前
实习记录小程序|基于SSM+Vue的实习记录小程序设计与实现(源码+数据库+文档)
java·数据库·spring boot·小程序·论文·毕设·实习记录小程序
小新11018 小时前
微信小程序之按钮短时间内被多次点击问题
微信小程序·小程序
说私域18 小时前
O2O电商变现:线上线下相互导流——基于定制开发开源AI智能名片S2B2C商城小程序的研究
人工智能·小程序·开源·零售
说私域21 小时前
基于开源AI智能名片链动2+1模式S2B2C商城小程序源码的去中心化商业扩散研究
人工智能·小程序·开源·去中心化·零售
不爱吃饭爱吃菜1 天前
uniapp微信小程序一键授权登录
前端·javascript·vue.js·微信小程序·uni-app
mon_star°1 天前
微信答题小程序支持latex公式显示解决方案
微信·小程序
MaCa .BaKa2 天前
38-日语学习小程序
java·vue.js·spring boot·学习·mysql·小程序·maven
HouGISer2 天前
副业小程序YUERGS,从开发到变现
前端·小程序