基于百度地图API实现的一个车辆模拟器

需要实现一个车辆模拟器,在地图上按照一条线路来驾驶,模拟器需要通过MQTT与服务器进行通讯,发送车辆当前的GPS坐标,速度等信息。服务器根据车辆的位置,以及当前地图区域上的网络状况,提前预测车辆将要到达区域的网络状况,并采取相应的行动(例如调整网络参数等)并发送消息通知模拟器。

模拟器的实现

模拟器的界面是基于百度地图来实现的一个前端页面,这里需要实现几个功能:

1. 与服务器的通讯

前端页面与一个MQTT服务器通信,实时上报车辆的信息到一个主题,并且订阅另外一个主题来获取服务器的通知消息。

MQTT服务器采用的是ActiveMQ的artemis,具体的设置可以参见我以前的博客https://blog.csdn.net/gzroy/article/details/104013738, 为简便起见,这里就不用设置SSL和颁发证书了。

2. 模拟车辆在地图上行驶

采用百度的开放地图平台来搭建这个模拟器。通过DrivingRoute函数来设置一条行驶路线,获取路线的坐标点,然后定义一个小车图片的Marker,按照坐标点来进行位置更新,在更新位置的同时,计算当前行驶方向的方位角(即当前坐标与上一个坐标之间的连线与正北方向的夹角),调整小车图片的旋转角度,使得车头始终对准行驶方向。在行驶过程中,每次更新坐标时都往MQTT服务器发送位置消息。此外在行驶路线上还增加了几个Marker,表示不同位置的网络质量的状态。

代码如下:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
	<style type="text/css">
		body, html,#allmap {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
	</style>
    <script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=XXXX"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
	<title>车辆模拟器</title>
</head>
<body>
	<div id="allmap"></div>
    <script type="text/javascript">
        client = new Paho.MQTT.Client("localhost", 1883, "clientId");
        client.connect({userName: "abc", password: "123456", onSuccess:onConnect, useSSL:false});
        function onConnect() {
        // Once a connection has been made, make a subscription and send a message.
            console.log("onConnect");
            client.subscribe("World");
            message = new Paho.MQTT.Message("Hello");
            message.destinationName = "World";
            client.send(message);
        }
        // 百度地图API功能
        var map = new BMap.Map("allmap");
        map.centerAndZoom(new BMap.Point(116.404, 39.915), 15);
        map.enableScrollWheelZoom(true); 
        var myP1 = new BMap.Point(116.381084,39.913178);    //起点
        var myP2 = new BMap.Point(116.404305,39.90635);    //终点
        var myIcon = new BMap.Icon("car.png", new BMap.Size(70, 37), {    //小车图片
            imageSize: new BMap.Size(70, 37),
            imageOffset: new BMap.Size(0, 0)    //图片的偏移量。为了是图片底部中心对准坐标点。
        });
        var driving2 = new BMap.DrivingRoute(map, {renderOptions:{map: map, autoViewport: true}});    //驾车实例
        driving2.search(myP1, myP2);    //显示一条公交线路
        var toRadian = Math.PI/180;
        var toDegree = 180/Math.PI;
        
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.onreadystatechange=state_Change;
        xmlhttp.open("GET",'http://localhost/measurements.txt',true);
        xmlhttp.send(null);
        function state_Change(){
            if (xmlhttp.readyState==4){// 4 = "loaded"
                if (xmlhttp.status==200){
                    measurements_txt = xmlhttp.responseText;
                    console.log(measurements_txt)
                    records = measurements_txt.split("\n");
                    for(i=0;i<records.length;i++){
                        record = records[i].split(",");
                        var marker = new BMap.Marker(new BMap.Point(parseFloat(record[0]), parseFloat(record[1])));
                        marker.setTitle(record[2]);
                        map.addOverlay(marker);
                    }
                }
                else{
                    alert("Problem retrieving measurement data");
                }
            }
        }

        window.run = function (){
            var driving = new BMap.DrivingRoute(map);    //驾车实例
            driving.search(myP1, myP2);
            driving.setSearchCompleteCallback(function(){
                var pts = driving.getResults().getPlan(0).getRoute(0).getPath();    //通过驾车实例,获得一系列点的数组
                var paths = pts.length;    //获得有几个点
                for (i=0;i<paths;i++){
                    console.log(pts[i])
                }
                var radian_conv = Math.PI/180;
                var carMk = new BMap.Marker(pts[0],{icon:myIcon});
                map.addOverlay(carMk);
                i=0;
                function calculateRotate(lon1, lat1, lon2, lat2){
                    lon1 = lon1*toRadian;
                    lat1 = lat1*toRadian;
                    lon2 = lon2*toRadian;
                    lat2 = lat2*toRadian;
                    deltaFI = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
                    deltaLON = Math.abs(lon1-lon2)%180;
                    theta = Math.atan2(deltaFI, deltaLON)*toDegree;
                    return -theta;
                }
                function resetMkPoint(i){
                    carMk.setPosition(pts[i]);
                    if(i>0 && i<paths){
                        rotate = calculateRotate(pts[i-1].lng, pts[i-1].lat, pts[i].lng, pts[i].lat);
                        carMk.setRotation(rotate);
                        message = new Paho.MQTT.Message(pts[i].lng.toString()+","+pts[i].lat.toString());
                        message.destinationName = "vehicle/location/vin123";
                        client.send(message);
                    }
                    if(i < paths){
                        setTimeout(function(){
                            i++;
                            resetMkPoint(i);
                        },500);
                    }
                }
                setTimeout(function(){
                    resetMkPoint(5);
                },500)

            });
        }

        setTimeout(function(){
            run();
        },500);
        </script>
</body>
</html>

服务器端的实现

服务器订阅车辆上报的MQTT消息,获取车辆的位置,判断车辆行进方向上离哪个测量点最近,根据那个测量点的网络状况来预测车辆的网络变化,从而及早进行网络参数的调整。这里用到了Google的S2库来进行地理坐标的距离和范围的计算,具体使用S2的方法可以见我以前的博客:https://blog.csdn.net/gzroy/article/details/104054507

这里我只做一个简单的示例,即在车辆驾驶途中,服务器判断车辆将接近哪个测量点,并打印出来。代码如下:

import paho.mqtt.client as mqtt
from cacheout import Cache
import re
import time
import pywraps2 as s2
import math

re_location = r'.*\/location\/(.*)'
radius = 500  #500 meters
radius_of_earth = 40076020/2
degree_to_distance = (radius_of_earth)/180
radius_to_angle = s2.S1Angle.Degrees(radius/radius_of_earth*180)
geofence_cell_level = 12
geofence_cell_level_bits = geofence_cell_level*2+3
remaining_bits = 64-geofence_cell_level_bits
cell_level_bits = int(math.pow(2, geofence_cell_level_bits+1)-1)<<remaining_bits

measurement_point_cache = Cache(maxsize=512, ttl=120)
measurement_infor_cache = Cache(maxsize=512, ttl=120)
measurement_cellid_cache = Cache(maxsize=512, ttl=120)
vehicle_location_cache = Cache(maxsize=1024, ttl=1)
region_cover = s2.S2RegionCoverer()
region_cover.set_fixed_level(geofence_cell_level)

measurements = []
with open('measurements.txt', 'r') as f:
    lines = f.readlines()

for l in lines:
    longitude, latitude, infor = l.split(',')
    point = s2.S2LatLng_FromDegrees(float(latitude), float(longitude)).ToPoint()
    cellid = s2.S2CellId(point).id()
    key = hash(l)
    measurement_point_cache.set(key, point)
    measurement_infor_cache.set(key, infor)
    measurement_cellid_cache.set(cellid, key)

def predict_network_quality(vin):
    location = vehicle_location_cache.get(vin)
    current_point = s2.S2LatLng_FromDegrees(location[0][0], location[0][1]).ToPoint()
    last_point = s2.S2LatLng_FromDegrees(location[1][0], location[1][1]).ToPoint()
    geofence = s2.S2Cap(current_point, radius_to_angle)
    cells = region_cover.GetCovering(geofence)
    keys = measurement_cellid_cache.keys()
    selected_keys = []
    for cell in cells:
        cellid = cell.id()
        cell_begin = cellid & cell_level_bits
        cell_end = cell_begin + int(math.pow(2, remaining_bits))
        for k in keys:
            if k>cell_begin and k<cell_end:
                selected_keys.append(k)
    selected_measuments_keys = measurement_cellid_cache.get_many(selected_keys).values()
    min_distance = radius
    nearest_point_key = None
    distance = s2.S1Angle(current_point, last_point).degrees()*degree_to_distance
    for k in selected_measuments_keys:
        point = measurement_point_cache.get(k)
        d1 = s2.S1Angle(point, current_point).degrees()*degree_to_distance
        d2 = s2.S1Angle(point, last_point).degrees()*degree_to_distance
        angle = math.acos((d1**2+distance**2-d2**2)/(2*d1*distance))
        if angle>math.pi/2 and angle<math.pi:
            if d1<min_distance:
                nearest_point_key = k
                min_distance = d1
    if nearest_point_key is not None:
        network_infor = measurement_infor_cache.get(nearest_point_key)
        print('find nearest point:'+network_infor)
        return True
    else:
        return False

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

def on_message(client, userdata, msg):
    location_topic = re.search(re_location, msg.topic)
    if location_topic:
        vin = location_topic.group(1)
        longitude, latitude = str(msg.payload, 'utf-8').split(',')
        longitude = float(longitude)
        latitude = float(latitude)
        if vehicle_location_cache.has(vin):
            existing_location = vehicle_location_cache.get(vin)
            vehicle_location_cache.set(vin, [(latitude, longitude), existing_location[0]])
            predict_network_quality(vin)
        else:
            vehicle_location_cache.set(vin, [(latitude, longitude)])

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set('abc', '123456')

client.connect("localhost", 1883, 60)

client.subscribe('vehicle/#', 0)
client.loop_start()

start = time.time()
while True:
    time.sleep(1)
    end = time.time()
    if int(end - start) > 60:
        client.loop_stop()
        break
client.disconnect()

 

猜你喜欢

转载自blog.csdn.net/gzroy/article/details/108490903