WebSocket地圖實時更新位置
問題描述
今天在公司的平臺業務有一個功能需求:要求實時顯示裝置的地理位置在地圖上,裝置可能是一臺汽車,而我們使用的地圖是百度地圖API,這樣難免會涉及到實時更新位置的問題,又由於是web,http不是一個持續型的協議,所以功能上難免有點和桌面應用不一樣
公司技術選型
首先是技術選型的問題,公司平臺本身是javaWeb,所以不用說基本上都是SSM和SSH這系列的框架,公司使用javaWeb同時支援RPC 和 Restfull跨平臺呼叫,由於我們底層還要一個視訊流的控制是由C++實現的,又加上整個平臺操作資料庫的只有JAVA,所以不可避免的會有跨語言的業務產生。
既然我們的JavaWeb支援了這麼多,直接使用servlet是不現實的,所以我們的業務必須和spring完美縫合,這樣才能保證不會出現其它坑
為什麼選擇WebSocket?
首先:當前的平臺並非是大眾平臺,而且企業內部使用的資訊化平臺,所以無需考慮瀏覽器的相容性,只要告訴客戶公司,你只用這些瀏覽器就好了,所以這為使用websocket奠定了基礎
其次:是關於伺服器資源的問題,如果使用大量的Ajax輪詢,必然會導致一段時間內產生大量的Http請求(要知道其實Http請求的開銷也挺大的)
再者:對方公司希望地圖的資料顯示儘量同步,如果使用Ajax輪詢難免會造成延遲,如果我們把Ajax輪詢時間間期調短,必然會使Http請求大量增加,如果調長會導致資料延遲性較大,影響使用者體驗
最後:spring是能完全支援webSocket的,這樣也使得使用WebSocket接入平臺不會挖坑
WebSocket接入過程
1、首先我先介紹一下springmvc接入WebSocket需要準備什麼
maven依賴(我主要使用的是這個,當然也有其它工具包可以使用)
其次我們需要編寫兩個類
一個是:WebSocketConfig
另一個是:SystemWebSocketHandler
WebSocketConfig:
package com.zsl.WebSocket.oth;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
//webSocket註冊
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(systemWebSocketHandler(),"/webSocketServer");
}
@Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
}
SystemWebSocketHandler:
package com.zsl.WebSocket.oth;
import org.springframework.web.socket.*;
import java.util.ArrayList;
import java.util.Random;
public class SystemWebSocketHandler implements WebSocketHandler {
private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();;
//連結成功
@Override
public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
System.out.println("ConnectionEstablished");
users.add(webSocketSession);
webSocketSession.sendMessage(new TextMessage("連結成功!"));
}
//收到訊息
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
System.out.println("handleMessage:" + webSocketMessage.toString());
int max=175;
int min=10;
Random random = new Random();
while(true) {
int x = random.nextInt(max) % (max - min + 1) + min;
int y = random.nextInt(max) % (max - min + 1) + min;
System.out.println(x + "," + y);
TextMessage msBack = new TextMessage(x + "," + y);
webSocketSession.sendMessage(msBack);
Thread.sleep(1000);
}
}
//異常錯誤
@Override
public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
if(webSocketSession.isOpen()){
webSocketSession.close();
}
users.remove(webSocketSession);
}
//斷開連結
@Override
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
users.remove(webSocketSession);
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
從程式碼中可以看出所有的websocket的請求路徑都是在WebSocketConfig 中配置的,這個類就是我們整合Websocket和spring的配置類,我們也在這個類裡面註冊相應的webSocket的請求路徑和對應的處理Handler,其中我們使用的註解的方式整合
在SystemWebSocketHandler中,重寫的方法就是對應與websocket的處理
我這裡使用Java後臺生成隨機座標然後前臺通過WebSocket響應
百度地圖頁面(樣例)
程式碼:
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>百度地圖API顯示多個標註點帶提示的程式碼</title>
<!--css-->
<style type="text/css">
body, html, #allmap
{
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
}
#l-map
{
height: 100%;
width: 78%;
float: left;
border-right: 2px solid #bcbcbc;
}
#r-result
{
height: 100%;
width: 20%;
float: left;
}
</style>
<!--javascript-->
<script src="http://www.w3school.com.cn/jquery/jquery.js" type="text/javascript"></script>
</head>
<body>
<div id="allmap">
</div>
<script type="text/javascript">
var websocket = new WebSocket('ws://localhost:8080/webSocketServer');
websocket.onopen = function (evnt) {
console.log('ws clint:open websocket');
//傳送訊息
var msg = '這個是來自瀏覽器的訊息';
console.log('ws clint:send msg:'+msg);
websocket.send(msg);
};
websocket.onmessage = function (evnt) {
console.log('ws client:get message ');
var bp0 = evnt.data.split(",")[0];
var bp1 = evnt.data.split(",")[1];
makers[0].setPosition(new window.BMap.Point(bp0, bp1));
/* makers[0].getPosition().lng = bp0;
makers[0].getPosition().lat = bp1;*/
};
websocket.onerror = function (evnt) {
console.log('ws client:error '+evnt)
};
websocket.onclose = function (evnt) {
console.log('ws clent:close ')
}
var makers = new Array();
var tempPoint = null;
var markerArr = [
{ id: "1", title: "名稱:123", point: "113.264531,23.157003", address: "123456", tel: "12306" },
];
var map; //Map例項
function map_init() {
map = new BMap.Map("allmap");
//第1步:設定地圖中心點
var point = new BMap.Point(113.312213, 23.147267);
//第2步:初始化地圖,設定中心點座標和地圖級別。
map.centerAndZoom(point, 1);
//第3步:啟用滾輪放大縮小
map.enableScrollWheelZoom(true);
//第4步:向地圖中新增縮放控制元件
var ctrlNav = new window.BMap.NavigationControl({
anchor: BMAP_ANCHOR_TOP_LEFT,
type: BMAP_NAVIGATION_CONTROL_LARGE
});
map.addControl(ctrlNav);
//第5步:向地圖中新增縮圖控制元件
var ctrlOve = new window.BMap.OverviewMapControl({
anchor: BMAP_ANCHOR_BOTTOM_RIGHT,
isOpen: 1
});
map.addControl(ctrlOve);
//第6步:向地圖中新增比例尺控制元件
var ctrlSca = new window.BMap.ScaleControl({
anchor: BMAP_ANCHOR_BOTTOM_LEFT
});
map.addControl(ctrlSca);
//點選事件
map.addEventListener("click", showInfo);
function showInfo(e) {//滑鼠點選顯示經緯度
//alert(e.point.lng + ", " + e.point.lat);
alert("websocket觸發");
if(confirm("是否傳送websocket?")==true){
websocket.send("瀏覽器傳送過來的訊息");
}
tempPoint = e;
}
//第7步:繪製點
for (var i = 0; i < markerArr.length; i++) {
var p0 = markerArr[i].point.split(",")[0];
var p1 = markerArr[i].point.split(",")[1];
var maker = addMarker(new window.BMap.Point(p0, p1), i);
makers[i] = maker;
addInfoWindow(maker, markerArr[i], i);
}
}
var makers = new Array();
// 新增標註
function addMarker(point, index) {
var myIcon = new BMap.Icon("http://api.map.baidu.com/img/markers.png",
new BMap.Size(23, 25), {
offset: new BMap.Size(10, 25),
imageOffset: new BMap.Size(0, 0 - index * 25)
});
var marker = new BMap.Marker(point, { icon: myIcon });
marker.enableDragging();
map.addOverlay(marker);
return marker;
}
function getActPoint(id){
var marketpoint =makers[id].getPosition();
alert(tempPoint.point.lng+" ,"+tempPoint.point.lat);
}
// 新增資訊視窗
function addInfoWindow(marker, poi,e) {
//pop彈窗標題
var title = '<p style="font-weight:bold;color:#CE5521;font-size:14px">' + poi.title + '</p>';
//pop彈窗資訊
var html = [];
html.push('<table cellspacing="0" style="table-layout:fixed;width:100%;font:12px arial,simsun,sans-serif"><tbody>');
html.push('<tr>');
html.push('<td style="vertical-align:top;line-height:16px;width:38px;white-space:nowrap;word-break:keep-all">地址:</td>');
html.push('<td style="vertical-align:top;line-height:16px">' + poi.address + ' </td>');
html.push('</tr>');
html.push('</tbody></table>');
html.push('<div>');
html.push('<button onclick=\"getActPoint('+poi.id+');\">顯示當前位置</button>');
html.push('</div>');
var infoWindow = new BMap.InfoWindow(html.join("*"), { title: title, width: 200 });
var openInfoWinFun = function () {
marker.openInfoWindow(infoWindow);
};
marker.addEventListener("click", openInfoWinFun);
marker.setAnimation(BMAP_ANIMATION_BOUNCE); //跳動的動畫
return openInfoWinFun;
}
//非同步呼叫百度js
function map_load() {
var load = document.createElement("script");
load.src = "http://api.map.baidu.com/api?v=1.4&callback=map_init";
document.body.appendChild(load);
}
window.onload = map_load;
</script>
</body>
</html>
最後效果:
歡迎大家指出問題