沿道路曲线旋转位置标记并存储旋转角度到Firebase Realtime DB的实现咨询
实现方案
核心逻辑
- 优先调用设备原生传感器返回的
heading值获取前进方向角度,无传感器支持时通过前后两次定位坐标计算方位角作为降级方案 - 基于Leaflet原生DivIcon实现图标旋转,无需引入额外第三方依赖
- 旋转角度同步写入Firebase Realtime Database持久化
- 增加低速/静止过滤逻辑,避免角度跳变
完整修改代码
<html> <head> <title>Rider Module</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"/> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script> <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script> <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script> <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-database.js"></script> <script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-analytics.js"></script> <style> html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="map" style="height:100%; width:100%; background-color:#eeeeee;"></div> <script> mbUrl1 = 'https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'; mbUrl2 = 'https://{s}.google.com/vt/lyrs=s,h,p&x={x}&y={y}&z={z}'; mbUrl3 = 'https://{s}.google.com/vt/lyrs=m@221097413,traffic&x={x}&y={y}&z={z}'; const streets = L.tileLayer(mbUrl1, {maxZoom: 18, subdomains:['mt0','mt1','mt2','mt3']}), hybrid = L.tileLayer(mbUrl2, {maxZoom: 18, subdomains:['mt0','mt1','mt2','mt3']}), traffic = L.tileLayer(mbUrl3, {maxZoom: 18, subdomains:['mt0','mt1','mt2','mt3']}); const map = L.map("map", { center: [8.4819, 77.0361], zoom: 18, maxZoom: 18, minZoom: 18, layers: [streets] }); var baseLayers = { "Streets": streets, "Hybrid": hybrid, "Traffic": traffic }; L.control.layers(baseLayers).addTo(map); var firebaseConfig = { apiKey: "XXXXXXXXXXXXXXXX", databaseURL: "XXXXXXXXXXXXXXXX", projectId: "XXXXXXXXXXXXXXXX", appId: "XXXXXXXXXXXXXXXX" }; firebase.initializeApp(firebaseConfig); firebase.analytics(); this.database = firebase.database(); var marker; var ridername="Rider 1"; var username="rider1@abcde.com"; var i=0; var pointA=[]; var lastRotation = 0; // 存储上次生效的旋转角度 // 两点经纬度计算方位角函数(正北为0,顺时针0-360度) function calculateBearing(lat1, lng1, lat2, lng2) { const radLat1 = lat1 * Math.PI / 180; const radLat2 = lat2 * Math.PI / 180; const diffLng = (lng2 - lng1) * Math.PI / 180; const y = Math.sin(diffLng) * Math.cos(radLat2); const x = Math.cos(radLat1) * Math.sin(radLat2) - Math.sin(radLat1) * Math.cos(radLat2) * Math.cos(diffLng); let bearing = Math.atan2(y, x) * 180 / Math.PI; return (bearing + 360) % 360; } function onLocationFound(e) { var lat= e.latlng.lat; var lng= e.latlng.lng; var accuracy= e.accuracy/2; var rotation = lastRotation; // 计算旋转角度逻辑 if (e.heading !== null && e.speed > 1) { // 优先用设备返回的heading,速度大于1m/s时生效 rotation = e.heading; } else if (i > 0) { // 无heading时用前后两点计算,移动距离大于5米时更新角度 const prevLat = pointA[i-1].lat; const prevLng = pointA[i-1].lng; const distance = e.latlng.distanceTo(pointA[i-1]); if (distance > 5) { rotation = calculateBearing(prevLat, prevLng, lat, lng); } } lastRotation = rotation; if (marker) { map.removeLayer(marker); } // 用DivIcon实现旋转图标 var markerIcon = new L.DivIcon({ html: `<img src="markericon.png" style="transform: rotate(${rotation}deg); transform-origin: center bottom;" width="30" height="45">`, iconSize: [30, 45], iconAnchor: [15, 47], popupAnchor: [0, -50], className: '' // 清空默认类避免额外样式干扰 }); marker= L.marker(e.latlng,{icon: markerIcon}).addTo(map); marker.bindPopup("<b>Rider Name:</b> " + ridername + "<br><b>Latitude:</b> " + lat + "<br><b>Longitude:</b> " + lng + "<br><b>Accuracy:</b> " + accuracy.toFixed(2) + "M Radius<br><b>Rotation:</b> " + rotation.toFixed(2) + "°").addTo(map).openPopup(); firebase.database().ref('locations/users/'+username).set( { ridername: ridername, lat: lat, lng : lng, accuracy : accuracy, rotation: rotation // 存储旋转角度到Firebase }); if (i==0) { pointA[i] = new L.LatLng(lat, lng); } ++i; pointA[i]= new L.LatLng(lat, lng); var pointList = [pointA[i-1], pointA[i]]; new L.Polyline(pointList, {color: 'red'}).addTo(map); } function onLocationError(e) { alert(e.message); var user = firebase.database().ref('locations/users/'); user.child(username).remove(); } map.on('locationfound', onLocationFound); map.on('locationerror', onLocationError); map.locate({setView: true, watch: true, enableHighAccuracy: true, maxZoom: 18}); </script> </body> </html>
注意事项
- 首次定位时无前序坐标,会使用默认0度朝向,移动后自动更新为正确角度
- 设备需授予位置权限和传感器权限才能获取heading值,部分无陀螺仪/指南针的设备会自动降级为坐标计算模式
- 可根据实际需求调整速度阈值和距离阈值,适配不同精度的定位场景
内容的提问来源于stack exchange,提问作者Mahesh A R
相关产品推荐
相关产品推荐

