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提升计算性能在实际项目中我们还需要考虑用户位置的实时更新、商家营业状态等因素这些都会影响最终的筛选结果。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2440153.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!