当前位置: 首页 > news >正文

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

请添加图片描述

目录

  • 射线法
    • 原理
    • 简要逻辑代码
  • 小程序代码
    • 调试基础库
    • 小程序配置
    • 地图数据
    • 地图多边形
    • 点与多边形关系

射线法

原理

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

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

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

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

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

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

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

在这里插入图片描述

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

① 点的两边都在射线同一侧
② 点的两边在射线不同侧
③ 与多边形的一条边共线

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

简要逻辑代码

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

小程序代码

项目结构树

.
├── 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

{"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

/* 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

// pages/polygons/polygons.js
import map_data from '@data/map_data'Page({/*** 页面的初始数据*/data: {Marker2_Activated: "https://3gimg.qq.com/lightmap/xcx/demoCenter/images/Marker2_Activated@3x.png",Marker3_Activated: "https://3gimg.qq.com/lightmap/xcx/demoCenter/images/Marker3_Activated@3x.png",// 地图中心点坐标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.pointsif (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-FFstrokeColor: "#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.pointsthis.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.lengthmarkers.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-FFstrokeColor: "#789cff", // 描边颜色:较深的淡蓝色strokeWidth: 2, // 描边宽度}]this.setData({polygons,})}},/*** 清除 上一个标记点*/clearPrevious() {let polygons = this.data.polygonslet markers = this.data.markerslet points = this.data.pointsmarkers.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.pointsif (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.textif (choose == "复制") {this.copy()}},/*** 复制 参数信息*/copy() {wx.setClipboardData({data: this.data.points_data,})},/*** 显示/隐藏标记点 按钮*/show() {let isShow = this.data.isShowlet markers = this.data.markersmarkers.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

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

polygons.wxml

<!--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

/* 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

// 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-FFstrokeColor: "#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.pointslet 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

{"usingComponents": {},"navigationBarTitleText": "点与多边形关系"
}

range.wxml

<!--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

/* 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辅助生成

http://www.xdnf.cn/news/508249.html

相关文章:

  • C语言内存函数与数据在内存中的存储
  • ctr查看镜像
  • 掌握版本控制从本地到分布式
  • flat_map, flat_set, flat_multimap, flat_multimap
  • 深入理解位图(Bit - set):概念、实现与应用
  • python中http.cookiejar和http.cookie的区别
  • 深入解析Spring Boot与Kafka集成:构建高性能消息驱动应用
  • 【技海登峰】Kafka漫谈系列(十一)SpringBoot整合Kafka之消费者Consumer
  • 【云原生架构反模式】常见误区与解决方案
  • WPS多级标题编号以及样式控制
  • ES(ES2023/ES14)最新更新内容,及如何减少内耗
  • 大模型微调:从基础模型到专用模型的演进之路
  • IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink
  • 为新装的Linux系统配置国内yum源(阿里源)
  • 19. 结合Selenium和YAML对页面实例化PO对象改造
  • 大数据场景下数据导出的架构演进与EasyExcel实战方案
  • 理想AI Talk第二季-重点信息总结
  • 【架构美学】Java 访问者模式:解构数据与操作的双重分发哲学
  • 基于单片机路灯自动控制仪仿真设计
  • 包装设备跨系统兼容:Profinet转Modbus TCP的热收缩包装机改造方案
  • 出现 Uncaught ReferenceError: process is not defined 错误
  • 【NLP 75、如何通过API调用智谱大模型】
  • Spring Web MVC————入门(3)
  • ngx_http_rewrite_module 技术指南
  • 2025年、2024年最新版IntelliJ IDEA下载安装过程(含Java环境搭建+Maven下载及配置)
  • 操作系统之EXT文件系统
  • windows笔记本连接RKNN3588网络配置解析
  • Go 与 Gin 搭建简易 Postman:实现基础 HTTP 拨测的详细指南
  • golang选项设计模式
  • Linux518 YUM源仓库回顾(需查)ssh 服务配置回顾 特定任务配置回顾