/首页
/开源
/关于
持续搞【附近的人】---长连接坐标流和“地理围栏”(五)
发表@2019-09-19 08:53:53
更新@2023-01-21 22:47:40
最近的事情告诉我三件事: - 谨言慎行,言多必失 - 非常欢迎接受批评指针和讨论。千万不要反击【无脑】怼杠你的人,要在第一时间及时向他们认错,承认他们的正确,海纳百川 - 这年月扮猪吃大象的人太多,自己一定要低调。真的忍不住想装个叉什么的,切记要反向装叉,
反向装叉进可攻退可守
总之就一句话: ![](https://ti-node.com/static/upload/6580248960147914752) 回到正文,我们经过【附近】系列的二、三、四篇章后,已经基本了解了市面上用于解决LBS问题的几种常见方案和做法,当然除了PostGre外... ...那个有兴趣的哥们可以考虑补一篇PostGre版本直接投稿。实际上前面的思路是很简单的,算是循序渐进类型的,从MySQL到MongoDB再到ES,大概就是从GeoHASH到Google S2再到R树们。我没有在文章里显式地说这些但是背后就是这些,往深处地挖掘全靠诸位自己了~ 今天这篇可以彻底摆脱这些了,说句实话我自己都快恶心地想吐了,今儿个咱整点儿稍微不一样的: ![](https://ti-node.com/static/upload/6580249075105398784) 所以今天主要问题就两个: - 多边形围栏 - 长连接坐标流 众所周知,我们在使用下面这款著名租车软件的时候,总是会弹出下面的运营范围提示,我贴一张图你们感受一下: ![](https://ti-node.com/static/upload/6580249209935495168) ![](https://ti-node.com/static/upload/6580249266344689664) 一般是我们开完车后停车的时候,会提示我们:你停的这个地方尚在我们运营范围之外,如果你非要这里停车,我们会象征性收取你5块钱运营费之类云云。那么,一般我们此时该怎么办?那就是掉头往回骑一直到APP提醒你在运营范围之内即可... 除此之外,在【次著名】已下架的陌生社交APP --- 探探的卡牌界面上你有时候会看到如下提示:某某某在史各庄与曾您擦肩而过(由于本人从来没有安装过此类软件,所以并不能提供截图了)。 此处的一个关键技术点就是多边形~我们在数据库里添加一坨坐标,画成一个闭合的多边形。在使用APP的时候,APP与服务器建立一个长连接,不断地上报自己的坐标,一旦上报的坐标位于划好的多边形内部的时候,就算命中了某多边形,根据这个结果就可以分别做我们自己的业务逻辑了。 长连接这种鬼东西,直接用四层的TCP是不可能的。一来是咱自己能力有限,悟性较低,始终无法【精通】这门学问;二来是咱不会写APP demo,只能靠浏览器临时客串当客户端。综上所属,最佳选择就是Websocket协议。
下面的环节是我们最爱的CV环节!
------------ ####
第一步:搞好数据库
事到如今,我们让是得辛苦MongoDB出来临时客串顶一下帮我们存储地理多边形。我们创建一个Mongodb 2dsphere索引,其次插入一个地理多边形。我们的数据库momo,数据表是geo: ```php // 选择momo数据库 use momo; // 在fence字段上建立2dsphere索引 db.geo.ensureIndex( { fence: "2dsphere" } ); // 查看一下momo.geo中的索引 db.geo.getIndexes(); // 如果不出问题的话,下面表示索引已经OK > db.geo.getIndexes(); [{ "v": 2, "key": { "_id": 1 }, "name": "_id_", "ns": "momo.geo" }, { "v": 2, "key": { "fence": "2dsphere" }, "name": "fence_2dsphere", "ns": "momo.geo", "2dsphereIndexVersion": 3 }] ``` 我们在地图上选择四个点来封闭一下老李和巨蛀暂住的著名小区,入下图所示,用黄褐色线条框起来的封闭四边形: ![](https://ti-node.com/static/upload/6580251273461760001) ```php // 构造这个四边形 // 但是⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️ // 需要五个点才能封闭住一个多边形,起点和终点的坐标完全一样,表示在此处封闭这个多边形 > db.geo.insert( { fence:{ type:"Polygon", coordinates:[[ [116.3129886235,40.0614323370], [116.3139649476,40.0599789099], [116.3166234822,40.0606813756], [116.3158241839,40.0618350753], [116.3129886235,40.0614323370], ]] } } ); // 返回下面这个表示数据插入成功,多边形构造完毕 > WriteResult({ "nInserted" : 1 }) // 不放心就查询一下吧 > db.geo.find({}) > { "_id" : ObjectId("5d0f45eb0e495c7ff4dcf7a9"), "fence" : { "type" : "Polygon", "coordinates" : [ [ [ 116.3129886235, 40.061432337 ], [ 116.3139649476, 40.0599789099 ], [ 116.3166234822, 40.0606813756 ], [ 116.3158241839, 40.0618350753 ], [ 116.3129886235, 40.061432337 ] ] ] } } // 我们再依照复读机方式,再次画一个更大的多边形 db.geo.insert( { fence:{ type:"Polygon", coordinates:[[ [116.3087829198,40.0634605138], [116.3179899688,40.0663338312], [116.3216392131,40.0599957157], [116.3084395970,40.0574989797], [116.3087829198,40.0634605138], ]] } } ); ``` 我们挑选一个多边形内部的经纬度:[ 116.3148017968,40.0609848161 ]来查询一下,看看是否能够命中多边形。当然了,最为demo一定是命中了的,要不这玩意真的没法往下编了... ...demo里命中的是ID为【5d0f45eb0e495c7ff4dcf7a9】的多边形。 ```php // 查询某点是否在围栏内外 db.geo.find( { fence:{ $geoIntersects:{ $geometry:{ "type" : "Point", "coordinates" : [ 116.3148017968,40.0609848161 ] } } } } ); // 回车后执行命令 > { "_id" : ObjectId("5d0f45eb0e495c7ff4dcf7a9"), "fence" : { "type" : "Polygon", "coordinates" : [ [ [ 116.3129886235, 40.061432337 ], [ 116.3139649476, 40.0599789099 ], [ 116.3166234822, 40.0606813756 ], [ 116.3158241839, 40.0618350753 ], [ 116.3129886235, 40.061432337 ] ] ] } } ``` ------------ ####
第二步:构建Websocket服务器
那个。。。今天我们既不用上古时代的C语言,也不用从群众中来的PHP,今天我们走进新时代:Golang。
别小瞧咱老李,咱活儿全
```php package main import ( "log" "fmt" "net/http" "github.com/gorilla/websocket" "encoding/json" "context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type coordsStruct struct { Lat float64 `json:lat` Lng float64 `json:lng` } type fenceStruct struct { Id primitive.ObjectID "_id,omitempty" } // 配置一些websocket的option项 var upgrader = websocket.Upgrader{ CheckOrigin: func( r *http.Request ) bool { return true }, } func main() { http.HandleFunc( "/", fence ) log.Fatal( http.ListenAndServe( ":8000", nil ) ) } // 地理围栏服务 func fence( w http.ResponseWriter, r *http.Request ) { conn, err := upgrader.Upgrade( w, r, nil ) // 这个地方已经要校验失败,err如果不校验,后面会出错 if err != nil { fmt.Println( "ws upgrade err:", err ) return } defer conn.Close() // 进入到ws服务无限循环中... for { messageType, message, _ := conn.ReadMessage() // 反序列化json var coords coordsStruct err := json.Unmarshal( []byte( message ), &coords ) if err != nil { fmt.Println( "json decode err : ", err ) } // 开始处理经纬度是否在多边形中 var fence fenceStruct clientOptions := options.Client().ApplyURI( "mongodb://127.0.0.1" ) client, err := mongo.Connect( context.TODO(), clientOptions ) if err != nil { fmt.Println( "mongo connect err..." ) } geoCollection := client.Database("momo").Collection("geo") ret := geoCollection.FindOne( context.TODO(), bson.M{"fence":bson.M{"$geoIntersects":bson.M{"$geometry":bson.M{"type":"Point","coordinates":[]float64{coords.Lng,coords.Lat}}}}} ) if err := ret.Decode( &fence ); err != nil { fmt.Println( "Decode err : ", err ) return } fmt.Println( "收到坐标:", string( message ) ) response, err := json.Marshal( fence ) if err != nil { fmt.Println( "json marshal err : ", err ) return } conn.WriteMessage( messageType, []byte( response ) ) } } ``` 将上面文件保存为ws.go,然后执行go run ws.go将Websocket服务器启动起来。 ------------ ####
第三步:构建JS客户端
JS代码太多了,我只放了关键部位的,老规矩所有代码将会放到github里。 ```php var ws = new WebSocket("ws://t.ti-node.com:8000/"); ws.onopen = function( evt ) { console.log("Connection open ..."); //ws.send("Hello WebSockets!"); }; ws.onmessage = function( evt ) { console.log( "Received Message: " + evt.data ); alert( evt.data ); //ws.close(); }; ws.onclose = function(evt) { console.log("Connection closed."); }; if ( navigator.geolocation ) { function locationSuccess( position ) { var coords = position.coords; //alert( coords.latitude+':'+coords.longitude ); var coo = { lat : coords.latitude, lng : coords.longitude }; //alert( JSON.stringify( coo ) ); ws.send( JSON.stringify( coo ) ); } function locationError( error ){ switch(error.code) { case error.TIMEOUT: console.log("A timeout occured! Please try again!"); break; case error.POSITION_UNAVAILABLE: console.log('We can\'t detect your location. Sorry!'); break; case error.PERMISSION_DENIED: console.log('Please allow geolocation access for this to work.'); break; case error.UNKNOWN_ERROR: console.log('An unknown error occured!'); break; } } var options = { // 指示浏览器获取高精度的位置,默认为false enableHighAcuracy: true, // 指定获取地理位置的超时时间,默认不限时,单位为毫秒 timeout: 5000, // 最长有效期,在重复获取地理位置时,此参数指定多久再次获取位置。 maximumAge: 3000 }; setInterval( function() { navigator.geolocation.getCurrentPosition( locationSuccess, locationError, options ); }, 1000 ); // watchPosition只要设备位置发生变化,就会执行 //var watcherId = navigator.geolocation.watchPosition( locationSuccess, locationError, options ); //clearwatch用于终止watchPosition方法 //navigator.geolocation.clearWatch( watcher_id ); } else { alert("Your browser does not support Geolocation!"); } ``` CV黄龙:https://github.com/elarity/wechat-official-accounts-demo-code 上面的HTML && JS代码保存好后,请在手机浏览器上访问该HTML页面地址,因为手机浏览器可以获取手机GPS数据~~~顺利访问后,结果分别如下图所示:
服务端
![](https://ti-node.com/static/upload/6580252151035985920)
客户端
![](https://ti-node.com/static/upload/6580252195847929856) 客户端上报的经纬度一旦命中了多边形,就会返回该多边形的ID...具体细节代码,你们自己丰富?