news 2026/6/11 17:49:30

JavaScript 实战:用Haversine公式计算附近5公里内的商家(附完整代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 实战:用Haversine公式计算附近5公里内的商家(附完整代码)

JavaScript 实战:用Haversine公式计算附近5公里内的商家(附完整代码)

当你在开发一个本地生活服务应用时,如何快速找到用户当前位置5公里范围内的商家?这个问题看似简单,但背后涉及到地理空间计算的精妙算法。今天我们就来深入探讨如何用JavaScript实现这一功能。

1. 理解Haversine公式的核心原理

Haversine公式是计算球面上两点之间距离的经典算法,特别适用于地球表面的距离计算。与简单的平面距离计算不同,它考虑了地球的曲率,因此计算结果更加准确。

公式的核心思想是将经纬度转换为弧度,然后利用三角函数的性质计算两点之间的中心角,最后乘以地球半径得到实际距离。具体来说:

a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2) c = 2 ⋅ atan2(√a, √(1−a)) d = R ⋅ c

其中:

  • φ1, φ2:两点的纬度(弧度)
  • Δφ:纬度差(弧度)
  • Δλ:经度差(弧度)
  • R:地球半径(平均值约为6371公里)

提示:地球并非完美球体,Haversine公式假设地球为完美球体,实际应用中误差通常在0.5%以内,对于大多数商业应用已经足够精确。

2. JavaScript实现基础距离计算

让我们先实现一个基础版的Haversine公式计算函数:

/** * 计算两个经纬度之间的球面距离(单位:米) * @param {number} lat1 - 第一个点的纬度(十进制度) * @param {number} lon1 - 第一个点的经度(十进制度) * @param {number} lat2 - 第二个点的纬度(十进制度) * @param {number} lon2 - 第二个点的经度(十进制度) * @returns {number} 距离(米) */ function getDistance(lat1, lon1, lat2, lon2) { const toRad = angle => angle * Math.PI / 180; const R = 6371000; // 地球半径,单位:米 const φ1 = toRad(lat1); const φ2 = toRad(lat2); const Δφ = toRad(lat2 - lat1); const Δλ = toRad(lon2 - lon1); const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; }

使用示例:

// 计算北京天安门和上海外滩的距离 const distance = getDistance(39.9087, 116.3975, 31.2400, 121.4900); console.log(`两地距离约为 ${(distance/1000).toFixed(2)} 公里`); // 输出:两地距离约为 1068.01 公里

3. 优化性能:批量计算附近商家

在实际应用中,我们通常需要从大量商家中筛选出附近5公里内的商家。直接逐个计算距离显然效率不高。我们可以采用以下优化策略:

3.1 预筛选优化

在应用Haversine公式前,先进行粗略筛选:

function findNearbyBusinesses(userLat, userLon, businesses, maxDistance = 5000) { // 粗略筛选:纬度1度约111公里,经度1度在赤道约111公里 const latRange = maxDistance / 111000; const lonRange = maxDistance / (111000 * Math.cos(userLat * Math.PI / 180)); return businesses.filter(business => { // 先进行粗略筛选 if (Math.abs(business.lat - userLat) > latRange || Math.abs(business.lon - userLon) > lonRange) { return false; } // 精确计算 const distance = getDistance(userLat, userLon, business.lat, business.lon); return distance <= maxDistance; }); }

3.2 使用空间索引

对于更大规模的数据,可以考虑使用空间索引技术:

索引类型适用场景JavaScript实现
网格索引中小规模数据简单二维数组划分
R树大规模数据使用rbush等库
GeoHash分布式系统使用geohash-js
// 使用rbush实现R树索引 import RBush from 'rbush'; const tree = new RBush(); businesses.forEach(business => { tree.insert({ minX: business.lon, minY: business.lat, maxX: business.lon, maxY: business.lat, business: business }); }); // 查询附近商家 const nearby = tree.search({ minX: userLon - 0.1, // 适当扩大范围 minY: userLat - 0.1, maxX: userLon + 0.1, maxY: userLat + 0.1 }).map(item => item.business);

4. 完整实战:外卖配送场景实现

让我们实现一个完整的外卖配送场景解决方案:

class NearbyService { constructor(businesses = []) { this.businesses = businesses; this.tree = this._buildIndex(businesses); } _buildIndex(businesses) { const tree = new RBush(); businesses.forEach(b => { tree.insert({ minX: b.lon - 0.01, minY: b.lat - 0.01, maxX: b.lon + 0.01, maxY: b.lat + 0.01, business: b }); }); return tree; } getNearby(userLat, userLon, radius = 5000) { // 1. 粗略筛选 const latRange = radius / 111000; const lonRange = radius / (111000 * Math.cos(userLat * Math.PI / 180)); const candidates = this.tree.search({ minX: userLon - lonRange, minY: userLat - latRange, maxX: userLon + lonRange, maxY: userLat + latRange }).map(item => item.business); // 2. 精确计算 return candidates.filter(business => { const distance = getDistance(userLat, userLon, business.lat, business.lon); business.distance = distance; // 添加距离信息 return distance <= radius; }).sort((a, b) => a.distance - b.distance); // 按距离排序 } } // 使用示例 const businesses = [ {id: 1, name: "餐厅A", lat: 39.9087, lon: 116.3975}, {id: 2, name: "餐厅B", lat: 39.9187, lon: 116.4075}, // 更多商家数据... ]; const service = new NearbyService(businesses); const nearby = service.getNearby(39.9087, 116.3975, 5000); console.log(nearby);

5. 高级优化与注意事项

5.1 缓存计算结果

对于静态商家数据,可以预先计算并缓存距离:

const cache = new Map(); function getCachedDistance(lat1, lon1, lat2, lon2) { const key = `${lat1},${lon1},${lat2},${lon2}`; if (!cache.has(key)) { cache.set(key, getDistance(lat1, lon1, lat2, lon2)); } return cache.get(key); }

5.2 Web Worker并行计算

对于大量计算,可以使用Web Worker避免阻塞主线程:

// worker.js self.onmessage = function(e) { const {userLat, userLon, businesses} = e.data; const results = businesses.map(b => ({ ...b, distance: getDistance(userLat, userLon, b.lat, b.lon) })).filter(b => b.distance <= 5000); self.postMessage(results); }; // 主线程 const worker = new Worker('worker.js'); worker.postMessage({userLat, userLon, businesses}); worker.onmessage = e => { console.log('附近商家:', e.data); };

5.3 常见问题与解决方案

  • 精度问题:Haversine公式的误差通常在0.5%以内,对于更高精度需求,可以考虑Vincenty公式
  • 性能瓶颈:对于超过10万条数据,建议使用专业的地理空间数据库如PostGIS
  • 移动端优化:考虑使用原生模块或WebAssembly提升计算性能

在实际项目中,我们还需要考虑用户位置的实时更新、商家营业状态等因素,这些都会影响最终的筛选结果。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/18 22:47:45

基于SUMP协议的Arduino逻辑分析仪固件实现

1. LogicAnalyzer固件&#xff1a;基于SUMP协议的Arduino逻辑分析仪实现 1.1 项目定位与工程价值 LogicAnalyzer是一个面向嵌入式开发者的轻量级、可部署于标准Arduino硬件的逻辑分析仪固件。其核心价值不在于替代专业仪器&#xff0c;而在于为硬件调试提供“随手可得”的信号…

作者头像 李华
网站建设 2026/5/18 22:47:45

智能音频处理实战:OpenLRC一站式字幕生成解决方案

智能音频处理实战&#xff1a;OpenLRC一站式字幕生成解决方案 【免费下载链接】openlrc Transcribe and translate voice into LRC file using Whisper and LLMs (GPT, Claude, et,al). 使用whisper和LLM(GPT&#xff0c;Claude等)来转录、翻译你的音频为字幕文件。 项目地址…

作者头像 李华
网站建设 2026/5/18 22:47:44

灰色预测模型(GM) 神经网络 指数平滑 多项式拟合 线性回归 各种预测模型dai做,写完整预测报告

灰色预测模型(GM) 神经网络 指数平滑 多项式拟合 线性回归 各种预测模型dai做&#xff0c;写完整预测报告&#xff0c;可提供图表与代码预测模型的世界里&#xff0c;工具比超市货架上的泡面种类还多。今天咱们扒开几个常用模型的底裤&#xff0c;看看到底怎么玩转这些数学魔术…

作者头像 李华
网站建设 2026/5/18 22:47:46

UABEA:Unity资源处理的技术革新与效率革命

UABEA&#xff1a;Unity资源处理的技术革新与效率革命 【免费下载链接】UABEA UABEA: 这是一个用于新版本Unity的C# Asset Bundle Extractor&#xff08;资源包提取器&#xff09;&#xff0c;用于提取游戏中的资源。 项目地址: https://gitcode.com/gh_mirrors/ua/UABEA …

作者头像 李华
网站建设 2026/5/18 22:47:49

寻音捉影·侠客行高算力优化:FunASR模型量化压缩,内存占用降低65%

寻音捉影侠客行高算力优化&#xff1a;FunASR模型量化压缩&#xff0c;内存占用降低65% 在音频处理领域&#xff0c;实时语音识别和关键词检索一直是个计算密集型任务。传统的语音识别模型往往需要大量的内存和计算资源&#xff0c;这让很多普通设备难以承受。今天我们要介绍的…

作者头像 李华
网站建设 2026/5/18 22:48:02

ANIMATEDIFF PRO快速部署:RTX显卡一键启动,告别环境配置烦恼

ANIMATEDIFF PRO快速部署&#xff1a;RTX显卡一键启动&#xff0c;告别环境配置烦恼 1. 为什么选择ANIMATEDIFF PRO 在AI视频生成领域&#xff0c;大多数工具要么需要复杂的配置过程&#xff0c;要么生成质量难以满足专业需求。ANIMATEDIFF PRO的出现改变了这一现状&#xff…

作者头像 李华