专栏首页可能是东半球最正规的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:

// 选择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  
}]

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

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

// 查询某点是否在围栏内外
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。

别小瞧咱老李,咱活儿全

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里。

        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...具体细节代码,你们自己丰富?

本文分享自微信公众号 - 高性能API社区(high-performance-api),作者:老李秀

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • PHP网络编程之epoll开启篇

    这个公众号自从去年6月份到现在已经半年了,将近80篇的原创大概换来了550元的广告费。这不是我一个人的钱(主要是大家浏览量带来的支持),再加上我们最近遇到的事情...

    老李秀
  • PHP中on回调的实现(十六节)

    各位好,我是老李。和老李一同完成《PHP网络编程》,虽然我知道实际上从头到尾可能只有我一个人在搞。我告诉你们一定要好好在家好好学习、远程工作,不要折腾地自己最后...

    老李秀
  • 携手老李一起整山寨Workerman(八)

    大家好,我还是那个文风浮夸词藻华丽、内容正规内涵犀利、写出一篇文章往那里一放,就能吸引极少数泥腿子的老李。

    老李秀
  • [快学Python3]读写Excel - openpyxl库

    什么是openpyxl openpyxl是一个第三方的pythonexcel读写库,支持Excel2010 xlsx/xlsm/xltx/xltm文件格式。 o...

    苦叶子
  • YAMLException: incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulate

    使用hexo g出现如下错误,一顿排查,发现,是文章的文件名和文章的title有非法字符,原title为:ssh: connect to host github...

    IT云清
  • python的数与字符串

    在python中我们现在有不同的版本现在有python2.7与python与3.0 要说他们的区别就在与输出的不同

    py3study
  • Android中极简的js与java的交互库-SimpleJavaJsBridge

    最近接触android中js与java交互的东西很多,当然它们之间的交互方式有几种,但是我觉得这几种交互方式都存在一定的不足,这是我决定编写SimpleJava...

    用户2802329
  • 7G Vue.js 教程,55集从基础到2.0,简单灵活易上手,项目开发实用款!

    一款MVVM框架,用于创建 Web 交互界面的库,通过响应式在修改数据的时候更新视图。其两大核心:数据驱动和组件化。

    养码场
  • 剑指OFFER之丑数(九度OJ1214)

    题目描述: 把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求...

    用户1154259
  • Quartz.net官方开发指南 第十一课: 高级(企业级)属性

    Clustering 集群 ( Clustering从0.6版本开始可用了) 目前,集群只能用在使用ADO.NET-Jobstore的情况。特新包括负载均衡和容...

    张善友

扫码关注云+社区

领取腾讯云代金券