前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >持续搞【附近】---长连接坐标流和“地理围栏”(五)

持续搞【附近】---长连接坐标流和“地理围栏”(五)

作者头像
老李秀
发布2019-11-13 16:30:51
9630
发布2019-11-13 16:30:51
举报
文章被收录于专栏:可能是东半球最正规的API社区

我们经过【附近】系列的二、三、四篇章后,已经基本了解了市面上用于解决LBS问题的几种常见方案和做法,当然除了PostGre外... ...那个有兴趣的哥们可以考虑补一篇PostGre版本直接投稿。实际上前面的思路是很简单的,算是循序渐进类型的,从MySQL到MongoDB再到ES,大概就是从GeoHASH到Google S2再到R树们。我没有在文章里显式地说这些但是背后就是这些,往深处地挖掘全靠诸位自己了~

今天这篇可以彻底摆脱这些了,说句实话我自己都快恶心地想吐了,今儿个咱整点儿稍微不一样的:

所以今天主要问题就两个:

  • 多边形围栏
  • 长连接坐标流

众所周知,我们在使用下面这款著名租车软件的时候,总是会弹出下面的运营范围提示,我贴一张图你们感受一下:

一般是我们开完车后停车的时候,会提示我们:你停的这个地方尚在我们运营范围之外,如果你非要这里停车,我们会象征性收取你5块钱运营费之类云云。那么,一般我们此时该怎么办?那就是掉头往回骑一直到APP提醒你在运营范围之内即可...

除此之外,在【次著名】已下架的陌生社交APP --- 探探的卡牌界面上你有时候会看到如下提示:某某某在史各庄与曾您擦肩而过(由于本人从来没有安装过此类软件,所以并不能提供截图了)。

此处的一个关键技术点就是多边形~我们在数据库里添加一坨坐标,画成一个闭合的多边形。在使用APP的时候,APP与服务器建立一个长连接,不断地上报自己的坐标,一旦上报的坐标位于划好的多边形内部的时候,就算命中了某多边形,根据这个结果就可以分别做我们自己的业务逻辑了。

长连接这种鬼东西,直接用四层的TCP是不可能的。一来是咱自己能力有限,悟性较低,始终无法【精通】这门学问;二来是咱不会写APP demo,只能靠浏览器临时客串当客户端。综上所属,最佳选择就是Websocket协议。

贵在能用

下面的环节是我们最爱的CV环节!


第一步:搞好数据库

事到如今,我们让是得辛苦MongoDB出来临时客串顶一下帮我们存储地理多边形。我们创建一个Mongodb 2dsphere索引,其次插入一个地理多边形。我们的数据库momo,数据表是geo:

代码语言:javascript
复制
// 选择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  
}]

我们在地图上选择四个点来封闭一下老李和巨蛀暂住的著名小区,入下图所示,用黄褐色线条框起来的封闭四边形:

代码语言:javascript
复制
// 构造这个四边形
// 但是⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
// 需要五个点才能封闭住一个多边形,起点和终点的坐标完全一样,表示在此处封闭这个多边形
> 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】的多边形。

代码语言:javascript
复制
// 查询某点是否在围栏内外
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。

别小瞧咱老李,咱活儿全

代码语言:javascript
复制
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里。

代码语言:javascript
复制
        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数据~~~顺利访问后,结果分别如下图所示:

服务端

客户端

客户端上报的经纬度一旦命中了多边形,就会返回该多边形的ID...具体细节代码,你们自己丰富?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能API社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档