postgres+socket.io+nodejs實(shí)時(shí)地圖應(yīng)用實(shí)踐

nodejs一直以異步io著稱,其語言特性尤其擅長于在realtime應(yīng)用中潘明,如聊天室等。在進(jìn)行實(shí)時(shí)應(yīng)用開發(fā)時(shí)秕噪,必不可少的需要用到 socket.io庫钳降,可以說,nodejs+socket.io在實(shí)時(shí)應(yīng)用中具有較好的表現(xiàn)能力腌巾。
??本文既然選擇以實(shí)時(shí)地圖應(yīng)用做個(gè)小例子遂填,那么選擇經(jīng)典的PostgreSQL/PostGIS作為地圖的數(shù)據(jù)庫。希望實(shí)現(xiàn)的是模擬數(shù)據(jù)庫數(shù)據(jù)插入了新的GPS坐標(biāo)澈蝙,而一旦數(shù)據(jù)發(fā)生改變吓坚,立刻將插入的GPS坐標(biāo)廣播到服務(wù)端,服務(wù)端廣播到所有的客戶端地圖上灯荧,進(jìn)行定位展示礁击。早期作者使用的是redis的廣播/訂閱機(jī)制,最近發(fā)現(xiàn)Pg數(shù)據(jù)庫的listen/notify也具備這種消息傳遞機(jī)制逗载。
??本文主要的socke.io廣播/訂閱參考官網(wǎng)哆窿,Pg的listen/notify自行谷歌,作者僅簡述一下自己如何考慮應(yīng)用的厉斟。

一 服務(wù)器端

        var fs = require('fs');
    var http = require('http');
    var socket = require('socket.io');
    var pg = require('pg');
    var util=require('util');
    
    var constr=util.format('%s://%s:%s@%s:%s/%s', 'postgres','postgres','123456','192.168.43.125',5432,'Test');
    var server = http.createServer(function(req, res) {
        res.writeHead(200, { 'Content-type': 'text/html'});
        res.end(fs.readFileSync(__dirname + '/index.html'));
    }).listen(8081, function() {
        console.log('Listening at: http://localhost:8081');
    });

    var pgClient = new pg.Client(constr);//數(shù)據(jù)庫連接
    var socketio=socket.listen(server);//socketio
    socketio.on('connection', function (socketclient) {
        console.log('已連接socket:');
        //socketclient.broadcast.emit('GPSCoor', data.payload);//廣播給別人
        //socketclient.emit('GPSCoor', data.payload);//廣播給自己
    
    });
    var sql = 'LISTEN gps'; //監(jiān)聽數(shù)據(jù)庫的gps消息
    var query = pgClient.query(sql);//開始數(shù)據(jù)庫消息監(jiān)聽
        //數(shù)據(jù)庫一旦獲取通知挚躯,將通知消息通過socket.io發(fā)送到各個(gè)客戶端展示。
    pgClient.on('notification', function (data) {
        console.log(data.payload);
        //socketio.sockets.emit('GPSCoor', data.payload); //與下面的等價(jià)
        socketio.emit('GPSCoor', data.payload);//廣播給所有的客戶端
            
    });
    pgClient.connect();
    

二 數(shù)據(jù)庫端

建立一個(gè)測試表如下:

create table t_gps(
          id serial not null,
          geom geometry(Point,4326),
          constraint t_gps_pkey primary key (id)
);
--建立索引
create index t_gps_geom_idx on t_gps using gist(geom);

對表的增刪改建立一個(gè)觸發(fā)器捏膨,觸發(fā)器中發(fā)送變化數(shù)據(jù)出去:

CREATE OR REPLACE FUNCTION process_t_gps() RETURNS TRIGGER AS $body$
    DECLARE
        rec record;
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            --插入的GPS都是4326的經(jīng)緯度,我們將在3857的谷歌底圖上顯示數(shù)據(jù)食侮,發(fā)送轉(zhuǎn)換后的3857出去
            select TG_OP TG_OP,OLD.id,ST_AsText(ST_Transform(OLD.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN OLD;
        ELSIF (TG_OP = 'UPDATE') THEN 
            select TG_OP TG_OP,NEW.id,ST_AsText(ST_Transform(NEW.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            select TG_OP TG_OP,NEW.id,ST_AsText(ST_Transform(NEW.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN NEW;
        END IF;
        RETURN NULL;
    END;
$body$ LANGUAGE plpgsql;

CREATE TRIGGER T_GPS_TRIGGER
AFTER INSERT OR UPDATE OR DELETE ON T_GPS
    FOR EACH ROW EXECUTE PROCEDURE process_t_gps();

三 客戶端

<html>
<head>
    <meta charset='utf-8'>
    <title>實(shí)時(shí)地圖應(yīng)用</title>
    <link rel="stylesheet"  type="text/css">
    <script src="http://openlayers.org/en/v3.18.2/build/ol.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script>
            var wktform=new ol.format.WKT();//wkt解析
            var gpsSource=new ol.source.Vector();
            function init(){
                var gpsLayer=new ol.layer.Vector({
                    source:gpsSource,
                    style:new ol.style.Style({
                        image: new ol.style.Icon(({
                            anchor: [0.5, 1],
                            src: 'http://openlayers.org/en/v3.18.2/examples/data/icon.png'
                        }))
                    })
                });
                var map = new ol.Map({
                    layers : [
                        new ol.layer.Tile({
                            title : '街道圖',
                            visible : true,
                            source : new ol.source.XYZ({
                                url : 'http://www.google.cn/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i342009817!3m9!2szh-CN!3sCN!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!4e0&token=32965'
                            })
                        }),
                        gpsLayer
                    ],
                    target : 'map',
                    controls : ol.control.defaults({
                        attributionOptions : 
                        ({
                            collapsible : false
                        })
                    }),
                    view : new ol.View({
                        center : [0, 0],
                        zoom : 2
                    })
                });
                var iosocket = io.connect();
                //接受服務(wù)端消息
                iosocket.on('GPSCoor', function(data) {
                    data=JSON.parse(data);
                    switch(data.tg_op){
                        case 'INSERT':
                            var feature=new ol.Feature({
                                geometry:wktform.readGeometry(data.geom)
                            });
                            feature.setId(data.id);
                            gpsSource.addFeature(feature);//地圖新增點(diǎn)
                            break;
                        case 'UPDATE':
                            var geom=wktform.readGeometry(data.geom);
                            var feature=gpsSource.getFeatureById(data.id);
                            if(feature)
                                feature.setGeometry(geom);//修改已有點(diǎn)
                            break;
                        case 'DELETE':
                            var feature=gpsSource.getFeatureById(data.id);
                            if(feature)
                                gpsSource.removeFeature(feature);//刪除點(diǎn)
                            break;
                    }
                });
            }
            
           
    </script>
</head>
<body onload="init()">
    <div id="map"></div>
</body>
</html>

客戶端接收到消息后号涯,改變當(dāng)前地圖上的圖標(biāo)gps坐標(biāo)位置。

四 測試與結(jié)果

連開三個(gè)客戶端連接如下:


初始化三個(gè)客戶端.png
服務(wù)器端socket連接成功.png

4.1 數(shù)據(jù)庫新增GPS坐標(biāo)

insert into t_gps(geom) values (st_geomfromtext('Point(0 0)',4326));
insert into t_gps(geom) values (st_geomfromtext('Point(118 32)',4326));
insert into t_gps(geom) values (st_geomfromtext('Point(-118 -32)',4326));

頁面自動(dòng)響應(yīng)效果如下:

服務(wù)器端監(jiān)聽到的數(shù)據(jù)庫消息.png
服務(wù)器端socket到客戶端的效果.png

4.2 數(shù)據(jù)庫修改GPS坐標(biāo)

查看下當(dāng)前的數(shù)據(jù)如下:

Test=# select id,st_astext(geom) from t_gps;
 id |    st_astext    
----+-----------------
 24 | POINT(0 0)
 25 | POINT(118 32)
 26 | POINT(-118 -32)
(3 rows)

將id=25的坐標(biāo)改成 150,40:

Test=# update t_gps set geom=st_geomfromtext('Point(150 40)',4326) where id=25;
UPDATE 1

服務(wù)器端打印如下:

顯示一條更新語句.png
更新效果.png

4.3 數(shù)據(jù)庫刪除GPS坐標(biāo)

Test=# delete from t_gps where id=25;
DELETE 1
顯示一條刪除.png
刪除效果.png

所有以上操作锯七,只是數(shù)據(jù)的增刪改指令链快,服務(wù)器和客戶端都是自動(dòng)響應(yīng)的。

結(jié)論:本文實(shí)現(xiàn)了眉尸,數(shù)據(jù)庫一旦廣播了消息域蜗,服務(wù)器端監(jiān)聽,并繼續(xù)以sockeio廣播到客戶端噪猾。全部過程霉祸,只是數(shù)據(jù)庫發(fā)送了一個(gè)坐標(biāo)消息無任何其他操作。pg的notify和listen消息機(jī)制钓株,真實(shí)應(yīng)用一般比如寫在觸發(fā)器中颤枪,觸發(fā)器監(jiān)聽是否有數(shù)據(jù)采集終端將新坐標(biāo)寫入或者更新汉操,然后在觸發(fā)器中notify消息余素,這樣奔穿,前端實(shí)時(shí)響應(yīng)镜沽。可以做到將終端應(yīng)用位置無任何操作的一波流發(fā)送到全部客戶端實(shí)時(shí)展示贱田。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缅茉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子男摧,更是在濱河造成了極大的恐慌蔬墩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彩倚,死亡現(xiàn)場離奇詭異筹我,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)帆离,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蔬蕊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哥谷,你說我怎么就攤上這事岸夯。” “怎么了们妥?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵猜扮,是天一觀的道長。 經(jīng)常有香客問我监婶,道長旅赢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任惑惶,我火速辦了婚禮煮盼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘带污。我一直安慰自己僵控,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布鱼冀。 她就那樣靜靜地躺著报破,像睡著了一般。 火紅的嫁衣襯著肌膚如雪千绪。 梳的紋絲不亂的頭發(fā)上充易,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機(jī)與錄音荸型,去河邊找鬼蔽氨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹉究。 我是一名探鬼主播宇立,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼自赔!你這毒婦竟也來了妈嘹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤绍妨,失蹤者是張志新(化名)和其女友劉穎润脸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體他去,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毙驯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灾测。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爆价。...
    茶點(diǎn)故事閱讀 39,739評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖媳搪,靈堂內(nèi)的尸體忽然破棺而出铭段,到底是詐尸還是另有隱情,我是刑警寧澤秦爆,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布序愚,位于F島的核電站,受9級特大地震影響等限,放射性物質(zhì)發(fā)生泄漏爸吮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一望门、第九天 我趴在偏房一處隱蔽的房頂上張望形娇。 院中可真熱鬧,春花似錦怒允、人聲如沸埂软。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至所灸,卻和暖如春丽惶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爬立。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工钾唬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓抡秆,卻偏偏與公主長得像奕巍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子儒士,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理的止,服務(wù)發(fā)現(xiàn),斷路器着撩,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,081評論 25 707
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 10,962評論 6 13
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫诅福、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,095評論 4 62
  • 程序設(shè)計(jì)本身就是工程實(shí)踐拖叙。 上篇先介紹摩托車組成氓润,中篇以開發(fā)一個(gè)管理系統(tǒng)為例來講解程序設(shè)計(jì),下篇從工程實(shí)踐來統(tǒng)一分...
    結(jié)構(gòu)學(xué)AI閱讀 373評論 0 0