室内导航解决方案

定位

解决方案使用的网上一个SDK叫蜂鸟云,但是收费有点贵其实完全可以自己动手写的之后再说怎么用webgl写三维地图并不是很难。下面就拿蜂鸟云来阐述解决方案怎么搞。目前蜂鸟云自己不带定位功能,定位需要我们自己写,结合常规的定位手段可以实现微信集成的所有定位方法只有三个第一是GPS,这个东西在前端直接使用html5标准中的GPS函数就可以得到无需依赖第三方当时这个东西只有在微信里面才能得到较好的效果。第二是wifi定位,这个东西可以在微信里面做到但是精度相当低,第三就是ibeacon定位,这个原理和wifi一样但是无论是布置成本还是精度都可以满足我们的需求,同时具备我们可以使用的API所以综合分析这是室内导航最好的方案了。

原理其实很简单,首先是设备,ibeacon每一个设备都是一个蓝牙信号发射源,手机或者其他设备的蓝牙打开之后便可以检索到这个beacon,每一个beacon都具备一个返回值那就是距离,手机相聚这个beacon的距离会被返回到前端去,这样一来就可以进行三角定位了,ibeacon的每一个设备都有两个id,第一是uuid这个是一组设备共用的id,我们可以将不等数量的beacon设置为同一个uuid来进行分组最终的使用价值在于对楼层的区分。然后第二个是majorid这个id是设备唯一标示,通过它我们可以将空间中的坐标对应到一个唯一的beacon上面这样就满足了定位所需的全部条件。

但是麻烦的是蓝牙模块无论在手机上还是pc或者mac上面都是属于系统功能模块,对应的html标准中没有任何办法可以对系统蓝牙进行调用,所以想要使用ibeacon设备首先是就是返回的问题,我想到的唯一个解决方案就是微信,微信是在微信自己的app中进行了一定的封装,这个封装将一些系统功能暴露给了微信开发人员,所以我们才能得到系统的一些信息,本质上就是接口,将系统功能的调用做了很多个接口出来,微信开发过程中所使用的sdk无论是小程序还是公众号开发都是在sdk中调用了微信的接口,再由微信去调用系统的功能。对应微信之中起先我是向用公众号搞定获取的在jssdk中也有对应的模块,但是这个功能是企业公众号的功能同时之后开启摇一摇周边之后才能使用,尝试了很多次都发现这个东西的申请根本没有办法通过,即便通过了对周边设备的数量也有限制,所以干脆使用微信小程序来进行开发,这个东西其实都具备摇一摇周边的模块但是在小程序里面ibeacon已经不是周边的一个api而是在设备分类下面的,这就是说我们可以直接进行设备获取不用通过微信来搞设备的管理。这就基本上确定了使用微信小程序进行功能的开发。

然后就是对应的三边算法了,其实很简单就是解一个中学几何体的难度。

已知三点位置 (x1, y1), (x2, y2), (x3, y3)
已知未知点 (x0, y0) 到三点距离 d1, d2, d3

以 d1, d2, d3 为半径作三个圆,根据毕达哥拉斯定理,得出交点即未知点的位置计算公式:

( x1 - x0 )^2 + ( y1 - y0 )^2 = d1^2 
( x2 - x0 )^2 + ( y2 - y0 )^2 = d2^2 
( x3 - x0 )^2 + ( y3 - y0 )^2 = d3^2

设未知点位置为 (x, y), 令其中的第一个球形 P1 的球心坐标为 (0, 0),P2 处于相同纵坐标,球心坐标为 (d, 0),P3 球心坐标为 (i, j),三个球形半径分别为 r1, r2, r3,z为三球形相交点与水平面高度。则有:

r1^2 = x^2 + y^2 + z^2
r2^2 = (x - d)^2 + y^2 + z^2
r3^2 = (x - i)^2 + (y - j)^2 + z^2

当 z = 0 时, 即为三个圆在水平面上相交为一点,首先解出 x:

x = (r1^2 - r2^2 + d^2) / 2d

将公式二变形,将公式一的 z2 代入公式二,再代入公式三得到 y 的计算公式:

y = (r1^2 - r3^2 - x^2 + (x - i)^2 + j^2) / 2j

计算式大概就这个样子,对应的最终的值只需要加个x0y0就可以了。

整体框架

服务端

定位搞定了之后出现一个最严重的问题,微信小程序是一个微信自己东西,它不是一个web所以我们只用的蜂鸟云库没有办法直接使用在微信里面,同时我们的蜂鸟云库构建出来的地图是一个基于webgl开发的一个东西,这个也没有办法移植到微信里面去,但是我们又必须使用微信小程序来提供对应的系统设备接口,这里就是一个麻烦事情了。我想到的解决办法是微信小程序里面的webview控件,这个东西和原生安卓的webview一模一样意思就在于将一个web显示在界面上,但是不支持套嵌只能直接使用。不过对于这个项目来讲没有什么关系。这样一来就可以完成地图的展示了,但是即便是webview其显示的内容不可能是本地在小程序里面的前端数据主要原因是微信小程序限制前端代码的容量只有2M,而且webgl本质上基于canvas绘制显示的方式,这里使用的图片和模型还必须在前端,因为不能跨域,所以地图只能部署在一个web服务器上面用微信去显示,这样一来通讯就是一个非常难的东西了。首先通讯在微信内部查完了api都没有找到一个可以直接用于通讯的东西,而且webview内部是不能调小程序提供的ibeacon接口的,所以只能使用常规手段进行通讯。有两个方案,第一是http请求,这个东西无论怎么求都没有办法对应到一个特殊的实例,比如说小程序打开了一个web这里是指很多小程序都打开了这个web同时,web也具备了不同的实例,而且地图前端是一个webgl页面,我们不能直接刷新这个页面同时,这个页面的三维部分是一个html整体更不能这样做,所以http通讯方案基本排除。第二种是websocket,但是websocket也不能直接进行浏览器与浏览器的通讯,原因在于每一个web实例不具备常规TCP通讯那种端口,只有服务端程序具备这个东西而websocket只能是客服端程序。直接使用小程序的websocket是不能通讯到web的,所以我们必须使用一个服务器程序来进行消息的转发。简直就是一个坑。

所以开始尝试进行服务端程序的编写,这里有又有一个问题,我不会linux的C++开发,连IDE都没一个可以开发linux程序的,使用windows会造成服务器性能大减这里没有什么其他办法的时候我想起杜伟给我说的php服务端程序框架workman: https://github.com/walkor/workerman/

这个东西可以完成对http、tcp、websocket等通讯的监听从而实现服务端程序逻辑,使用起来非常简单,而且按照官方的描述程序具备多线程高并发能力,同时保证没有内存泄露我就直接拿来用了。逻辑部分只需要在应用层的回调函数上写自己的逻辑就可以了,我这里是新建了一个run.php文件放在库的目录下,然后在服务器使用php run.php start -d便可以开始语句,这里就是重载了几个回调函数。但是根据杜伟之前的反应可能有一个小问题那就是线程间的全局量没有同步,如果这个问题实际我们只开一个线程就可以了,毕竟同时使用的人不会太多。这简直是我搞用过最简单、最方便的服务端程序开发。下面是代码:

<?php require_once __DIR__ . '/Autoloader.php'; use Workerman\Worker; $WX = array(); $Web = array(); //证书数据 $context = array( 'ssl' => array(
        // 使用绝对路径
        'local_cert'  => '/www/wwwroot/www.ushitong.cn/websocket/214694916700020.pem',
        'local_pk'    => '/www/wwwroot/www.ushitong.cn/websocket/214694916700020.key',
        'verify_peer' => false,
    )
);

// 创建监听
$ws_worker = new Worker("websocket://0.0.0.0:9545",$context);

//开启ssl
$ws_worker->transport = 'ssl';

// 开启四个线程
$ws_worker->count = 4;

// 连接来了
$ws_worker->onConnect = function($connection)
{
  
};

// 消息来了
$ws_worker->onMessage = function($connection, $data)
{
 	$jsondata = json_decode($data);
  	//转数组
  	if($jsondata->type == 'Connect')
    {
      //这次消息是验证连接
      if($jsondata->from == 'WX')
      {
        //这次消息来自wx
        $thisdata = array(
          'connect' => $connection,
          'openid' => $jsondata->openid
        );
        array_push($WX,$thisdata);
        //添加小程序连接到$WX
      }
      
      if($jsondata->from == 'Web')
      {
        //这次消息是来自web
        $thisdata = array(
          'connect' => $connection,
          'openid' => $jsondata->openid
        );
        array_push($Web,$thisdata);
        //添加web连接到$Web
      }
      
    }
 	elseif($jsondata->type == 'Message')
    {
      //这次消息是接受到消息
      if($jsondata->from == 'WX')
      {
        //循环$Web匹配本次消息的openid对应有就将小程序的消息转发给web
        foreach ($Web as $everyweb) {
  			if($jsondata->openid == $everyweb->openid)
            {
              $everyweb->connect->send($jsondata->data);
            }
		}
      }
    }
};

// 连接断了
$ws_worker->onClose = function($connection)
{
  //连接断开便删除数组中对应的值,释放内存(不确定会不会进行垃圾回收,不知道php机制)
  foreach( $WX as $key => $value ) {
    if($value->connect == $connection)
    {
      unset($WX[$key]);
    }
  }
  $WX=array_values($WX);
  //同时移除微信和web
  foreach( $Web as $key => $value ) {
    if($value->connect == $connection)
    {
      unset($Web[$key]);
    }
  }
  $Web=array_values($Web);
};

// 开始服务
Worker::runAll();

小程序开发

小程序里面就简单了就只有两个东西,第一是获取ibeacon设备信息并发送,第二是打开webview确定通讯的唯一性。首先是通讯唯一的问题,这里需要针对每一个用户绝对唯一的一个全局id来进行握手协议的构建,最简单的方法就是使用用户的openid。这里还有一个麻烦事就是微信的websocket只能使用wss协议,干死腾讯。我们得到openid之后便打开webview,这里的url里面就可以完成一次单次通讯将openid传给web让web也使用openid来进行握手协议的构建,这样一来就可以实现小程序传消息给服务器然后服务器发送这条消息到对应的web上面去。

握手协议的逻辑也很简单,首先是连接,连接是没有办法发送定制数据的,websocket下连接数据需要自定子协议但是腾讯不支持,干死腾讯,所以我们的握手是在websocket连接之后发送的第一条消息进行的。逻辑大致如下:首先由小程序进行连接然后马上发送一个数据,这个数据是一个json包含信息为连接类型:是小程序还是web、消息类型:是握手还是发送数据、用户openid来进行身份的定义。然后就可以进行数据的发送,每一次发送的数据内容与握手内容一致,但消息类型变为发送数据,多了一个map为beacon设备的数组。

web那边也很简单,握手消息与小程序一样的,消息来源变为web。这样一来就可以将消息对应进行发送与接受了,参考服务端程序的开发就可以知道握手逻辑,这个东西可以自己定义。小程序js部分的代码如下:

var OpenId = {'from':'WX','type':'Connect','openid':''};

wx.login({
  success(loginCode) {
    var appid = '不给你看';  
    var secret = '更不能给你看了';
    wx.request({
      url: 'https://api.weixin.qq.com/sns/jscode2session?appid='+appid+'&secret='+secret+'&grant_type=authorization_code&js_code=' + loginCode.code,
      
      header: {
        'content-type': 'application/json'
      },
      //请求openid
      success(res) {

        OpenId.openid = res.data.openid; //获取openid
        
        if (wx.canIUse('connectSocket')) {
          //验证API可用

          wx.connectSocket({ //连接socket到服务器
            url: 'wss://www.ushitong.cn:9545',
            header: {
              'content-type': 'application/json'
            },

            method: "GET",
            success(res) {
              console.log(res.errMsg);

            },
            fail(res) {
              wx.showToast(
                {
                  title: '连接服务器失败',
                  icon: "none",
                  duration: 2000
                })
            }
          })

          //监听socket状态
          wx.onSocketOpen(function (res) {

            //连接成功后发送握手消息Openid
            wx.sendSocketMessage({
              data: JSON.stringify(OpenId),
              success(res) {
                
                console.log(JSON.parse(res.data).openid);

                var thisopenid = OpenId.openid;

                var uuidArray = ['550e8400-e29b-41d4-a716-446655440000',
                  '550e8400-e29b-41d4-a716-446655440050'
                ];
                //检索周围uuid为上述的ibeacon设备。及我们自己部署的设备
                if (wx.canIUse('startBeaconDiscovery')) {
                  //检测设备是否可用ibeaconAPI
                  wx.openBluetoothAdapter({
                    //检测蓝牙是否开启,不能使用getBluetoothAdapterstat,会返回init错误
                    success(res) {
                      //蓝牙开启
                      wx.startBeaconDiscovery({
                        //初始化ibeacon设备的搜索
                        uuids: uuidArray,
                        success(res) {
                          //初始化成功
                          wx.onBeaconUpdate(function (res) {

                            //周边ibeacon返回状态更新时调用
                            if (res.beacons.length > 0 && res.beacons != null) {
                              //返回不为空数组或空对象
                              var devices = res.beacons;
                              var len = devices.length;
                              for (var i = 0; i < len; i++) {
                                for (var j = 0; j < len - 1 - i; j++) {
                                  if (devices[j].rssi < devices[j + 1].rssi) {
                                    var temp = devices[j + 1];
                                    devices[j + 1] = devices[j];
                                    devices[j] = temp;
                                  }
                                }
                              }
                              //冒泡排序,根据ibeacon信号强度将所有设备重排序

                              var senddata = { 'from': 'WX', 'type': 'Message', 'openid': thisopenid, 'data': devices};

                              wx.sendSocketMessage({
                                data: JSON.stringify(senddata),
                                fail(res) {
                                  console.log(res.errMsg)
                                }
                              });

                            } else {
                              //返回空数组或空对象定义为信号丢失
                              wx.showToast(
                                {
                                  title: '丢失信号,重新定位...',
                                  icon: "none",
                                  duration: 2000
                                })
                            }
                          });
                        },
                        fail(res) {
                          //初始化ibeacon,其他错误,一般不会出现
                          wx.showToast({
                            title: res.errMsg,
                            icon: "none",
                            duration: 5000
                          });
                        }
                      });
                    },
                    fail(res) {
                      //蓝牙未开启
                      wx.showToast(
                        {
                          title: '蓝牙设备未打开,请打开蓝牙',
                          icon: "none",
                          duration: 5000
                        }
                      );
                    }
                  });
                } else {
                  //设备不可用ibeaconAPI
                  wx.showToast(
                    {
                      title: '系统或设备不支持ibeacon定位,请尝试升级微信',
                      icon: "none",
                      duration: 5000
                    }
                  );
                }

              },
              fail(res)
              {
                wx.showToast(
                  {
                    title: '发送消息失败',
                    icon: "none",
                    duration: 2000
                  })
              }
            });

            wx.onSocketMessage(function (res) {
              //接受到服务器消息
            })

          })

          wx.onSocketError(function (res) {
            wx.showToast(
              {
                title: 'websocket连接失败,请检查网络',
                icon: "none",
                duration: 5000
              }
            );
          })
        } else {

          wx.showToast(
            {
              title: '系统或设备不支持websocket通讯,请尝试升级微信',
              icon: "none",
              duration: 5000
            });
        }
      }
    })
  }
})

web消息处理

这里就是通过web将小程序发送过来的ibeacon设备数据进行解析,计算然后得到坐标的过程。由于没有一个实际测试环境所有我还没有进行对定位部分完整测试,但是只要接收到的数据没有丢失那么即便有问题也是很简单的修改。具体的流程如下,首先的定义每一个ibeacon设备的原始坐标,这个需要在一个应用场景之中进行部署测绘得到坐标然后让坐标数据对应到每一个ibeacon设备的majorid上面去。然后结合距离数据和这些原始的坐标数据进一步计算出用户的位置。楼层部分的计算其实不算很完美但是我也只想到这一个方法。那就是得到每一个uuid设备的平均距离,然后去距离最短的uuid便得到一个楼层的uuid,这个有待优化,但是理论下应该使用良好。代码还没有写完,由于没有设备也没法测试,但是逻辑就是这个逻辑不会有什么变化,涉及到数据对地图的操作其实只有两个就是打开地图和更新坐标而已,已经留好了函数但是用写进去就OK,这里就是基本封装。下面就是web部分的ibeacon通讯js代码:

var ws; //全局websocket

var uuid_1 = '';
var uuid_2 = '';
//定义楼层对应的设备uuid

function OpenMap() {
    //打开地图
}

function UpdataLocation(vector, uuid) {
    //根据ibeacon设备计算更新坐标
}

function Vector2D(X, Y) {
    //定义向量类进行基础封装方便操作
    var _this = this;
    this.x = X;
    this.y = Y;
    this.add = function (vector) {
        return new Vector2D(this.x + vector.x, this.y + vector.y);
    };
    this.sub = function (vector) {
        return new Vector2D(this.x - vector.x, this.y - vector.y);
    };
    this.getlength = function () {
        return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
    }
}

function Beacon(uuid, majorid, vector, distance) {
    //定义ibeacon设备类进行基础封装方便操作
    var _this = this;
    this.uuid = uuid;
    this.majorid = majorid;
    this.vector = vector;
    this.distance = distance;
}

function getVector(majorid) {
    //尚未写完,通过一个majorid得到这个设备的坐标
    var x = 0;
    var y = 0;
    return new Vector2D(x, y);
}

function ConstructBeacons(allbeacons) {
    //通过小程序返回的数组创建ibeacon类数组
    var Beacons = [];
    for (var i = 0; i < allbeacons.length; i++) {
        var beacon = new Beacon(allbeacons[i].uuid, allbeacons[i].major, getVector(allbeacons[i].major), allbeacons[i].proximity)
        Beacons.push(beacon);
    }
    return Beacons;
}

function getAverageDistance(Beacons) {
    //便利数组内所有的设备得到设备的平均距离,用于计算楼层
    var i;
    var distance = 0;
    for (i = 0; i < Beacons.length; i++) {
        distance = distance + Beacons[i].distance;
    }
    return (distance / (i + 1))
}

function getFloor(Beacons) {
    //通过设备的uuid将数组拆为两层楼的两个数组
    var floor_1 = [];
    var floor_2 = [];

    for (var i = 0; i < Beacons.length; i++) {
        switch (Beacons[i].uuid) {
            case uuid_1:
                floor_1.push(Beacons[i])
                break;
            case uuid_2:
                floor_2.push(Beacons[i])
                break;
            default:
                break;
        }
    }
    var d1 = getAverageDistance(floor_1);
    var d2 = getAverageDistance(floor_2);
    if (d1 < d2) { return floor_1; } else { return floor_2; }
} function getLocation(beacon_1, beacon_2, beacon_3) {
    //通过三个设备进行三边定位计算出当前位置的坐标 
    var x1 = beacon_1.vector.x;
    var y1 = beacon_1.vector.y;
    var d1 = beacon_1.distance;
    var d2 = beacon_2.distance;
    var d3 = beacon_3.distance;
    var d = (beacon_1.vector.sub(beacon_2.vector)).length;
    var i = beacon_3.sub(beacon_1).x;
    var j = beacon_3.sub(beacon_1).y;
    var x = (Math.pow(d1, 2) - Math.pow(d2, 2) + Math.pow(d, 2)) / 2 * d;
    var y = (Math.pow(d1, 2) - Math.pow(d3, 2) - Math.pow(x, 2) + Math.pow((x - i), 2) + Math.pow(j, 2)) / 2 * j;
    return new Vector2D((x + x1), (y + y1));
} function CreatMap(openid) {
    //创建地图 
    ws = new WebSocket("wss://www.ushitong.cn:9545");
    //新建wensocket
    ws.onopen = function () {
        var connectdata = { 'from': 'Web', 'type': 'Connect', 'openid': openid };
        ws.send(JSON.stringify(connectdata));
        //发送身份验证消息
        OpenMap();
        //打开地图 
        ws.onmessage = function (evt) {
            //接收到小程序传来的消息 
            var allbeacons = JSON.parse(evt.data); var thistimebeacons = ConstructBeacons(allbeacons);
            //创建ibeacon数组 
            var floorbeacons = getFloor(thistimebeacons);
            //根据楼层拆分数组并得到对应楼层的数组 
            if (floorbeacons.length >= 3) {
                //ibeacon数量足够进行三角定位
                var Location = getLocation(floorbeacons[0], floorbeacons[1], floorbeacons[2]);

                UpdataLocation(Location, floorbeacons[0].uuid); //为地图更新数据
            }
            else {
                console.log('信号不好');
                //接受消息中的设备数量不足三个
            }
        };
    };

    ws.onclose = function () {
        console.log('连接关闭');
    };
}

地图部分

使用这个库我一边写一边学了解了一个基础应用大致流程如下:

首先是建立一个全局的地图实例里面可以定义很多的东西,我们项目主要用的就是一个显示和导航的问题,这里主要的参数就是具体打开哪一个地图,这个地图就是在官网里面做好的地图数据可以部署到我们的服务器上面也可以用他们的但是都要收你的钱。然后还有就是key和appname这个是在官网申请的然后填进来就可以了。

然后便打开某一个具体的地图,这里我使用的官网里面那个案例里面的离线地图。

然后控制地图的可见性,绑定地图点击操作,创建导航实例等等,详细讲这个东西也没有什么必要性我在代码里面写好了很多注释,反正看懂没有什么问题,下面就是地图部分的js代码:

var map; //地图对象
var fmapID = '10347'; //地图ID
var navi; //导航对象
var loaded = false;
var navigation = false;
//用于判断当前状态
var nowpoint = {'x':12961615.455,'y':4861820.5649999995,'groupID':1}
var endpoint = {'x':0.0,'y':0.0,'groupID':0};
//初始化当前点,终点
var locationMarker;
//当前点图标

function UpdataLocation(x,y,groupid)
{
	//更新当前点位置
	if(loaded)
	{
		nowpoint.x = x;
		nowpoint.y = y;
		nowpoint.groupID = groupid;
		//坐标
		if(locationMarker)
		{
			locationMarker.setPosition
		    ({
		        x:nowpoint.x,  //设置定位点的x坐标
		        y:nowpoint.y,  //设置定位点的y坐标
		        groupID:nowpoint.groupID  //设置定位点所在楼层
			});
		}
		if(navigation)
		{
			if(navi)
			{
				navi.locate(nowpoint);//更新导航状态,最终会回调至onwalk函数
			}
		}
	}
}

$("#one").click(function()
{
	map.visibleGroupIDs = [1];
	map.focusGroupID = 1;
})

$("#two").click(function()
{
	map.visibleGroupIDs = [2];
	map.focusGroupID = 2;
})

$("#three").click(function()
{
	map.visibleGroupIDs = [3];
	map.focusGroupID = 3;
})

$("#four").click(function()
{
	map.visibleGroupIDs = [4];
	map.focusGroupID = 4;
})

$("#five").click(function()
{
	map.visibleGroupIDs = [5];
	map.focusGroupID = 5;
})

$("#six").click(function()
{
	map.visibleGroupIDs = [6];
	map.focusGroupID = 6;
})
//html的楼层按钮

function StartNavigation()
{
	if(!navigation)
	{
		navigation = true; //改变状态
	}
}
//开始导航

function StopNavigation()
{
	if(navigation)
	{
		navi.clearNaviLines(); //清除导航线
		navi.clearMarkers(); //清除终点图表
		navigation = false; //改变状态
	}
}
//结束导航


function openmap()
{
	//打开地图
	map = new fengmap.FMMap
	({
		//初始化地图对象
		container: document.getElementById('map'), //webgl的容器,目标为div
		mapServerURL: './data/' + fmapID, //地图文件位置
		mapThemeURL: 'data/theme', //地图主题位置
		defaultThemeName: '2001', //地图主题ID
		defaultMapScaleLevel: 20, //地图缩放
		focusAlphaMode: true, //聚焦楼层是否启用透明
		focusAnimateMode: true, //聚焦楼层是否启用动画
		focusAlpha: 0.3, //非聚焦楼层的透明度
		viewModeAnimateMode: true, //视图变化是否启用动画
		defaultTiltAngle: 30, //默认角度
		key: '72db26bb2eb32245a0194763732a0951', //appkey
		appName: '啊实打实', //appname
	});

	map.openMapById(fmapID); //导入地图文件(模型)
	
	map.on('loadComplete', function() 
	{
		map.visibleGroupIDs = nowpoint.groupID; //设置可见的楼层
		map.focusGroupID = nowpoint.groupID; //设置聚焦楼层为当前位置所在楼层。
		navi = new fengmap.FMNavigation //初始化导航对象
		({
		    map: map,
		    locationMarkerUrl: './img/pointer.png', //导航中用到的定位标注图标,这个东西只有启用模拟才有用
		    followPosition:true,    //设置跟随定位的默认为true
		    followAngle:true,       //设置地图是否选择,默认false
		    autoClearNaviLine:true, //是否自动清除导航线
		
		    // 设置导航线的样式
		    lineStyle: 
		    {
		        type: fengmap.FMLineType.FMARROW,   // 导航线样式
		        lineWidth: 10, // 设置线的宽度
		        godColor: '#FF0000',   //设置线的颜色
		        godEdgeColor: '#4a82d2',   //设置边线的颜色
		    }
		});
		
	    locationMarker = new fengmap.FMLocationMarker
	    ({
	        url:'./img/pointer.png', //导航图表位置
	        size:64,  //设置图片显示尺寸
	        height:10,  //设置图片高度,默认是5
	    });
	    
	    map.addLocationMarker(locationMarker); //添加导航图标
	    
	    locationMarker.setPosition
	    ({
	        x:12961615,  //设置定位点的x坐标
	        y:4861820,  //设置定位点的y坐标
	        groupID:1  //设置定位点所在楼层
	        
		});
		loaded = true; //改变状态加载完成
	});
	
	map.on('mapClickNode', function(event) 
	{
		//webgl全局点击事件
		if(event.x != undefined && event.y != undefined && event.groupID != undefined)
		{
			if(event.nodeType!=4)
			{
				//点击到地图这种的模型
				endpoint.x = event.x;
				endpoint.y = event.y;
				endpoint.groupID = event.groupID;
				//设置终点位置

		    	navi.setStartPoint
		    	({
				    x: nowpoint.x,
				    y: nowpoint.y,
				    groupID: nowpoint.groupID,
				    size: 64
				    //设置起点
				});
				
				navi.setEndPoint
				({
				    x: endpoint.x,
				    y: endpoint.y,
				    groupID: endpoint.groupID,
				    url: './img/end.png',
				    size: 64
				    //设置终点
				});
				
				var groups = []; //可见楼层数组
				
				for(var i = nowpoint.groupID;i<=endpoint.groupID;i++)
				{
					groups.push(i);
				}//起点楼层与终点层加期间的所有楼层
				
				map.visibleGroupIDs = groups;  //设置可见楼层
				
				map.focusGroupID = nowpoint.groupID;  //设置聚焦楼层
				
				navi.drawNaviLine(); //绘制导航相同
		
				navi.locate(nowpoint); //设置位置
				
				navi.on('walking', function(data)
				{
					//位置更新的回调,更新来源于navi.locate()函数
					if(nowpoint.groupID == endpoint.groupID)
					{
						map.visibleGroupIDs = [nowpoint.groupID]
					}
					else
					{
						var groups = []; //可见楼层数组
				
						for(var i = nowpoint.groupID;i<=endpoint.groupID;i++)
						{
							groups.push(i);
						}//起点楼层与终点层加期间的所有楼层
						map.visibleGroupIDs = groups; //设置可见的组
					}
					
					if(data.remain<10) //定制导航条件
					{
						StopNavigation(); //停止导航
					}
				});
				
				//navi.locate(endpoint);
			}
		}
		else
		{
			if(navi) //导航对象
			{
				navi.clearNaviLines(); //清除导航线
				navi.clearMarkers(); //清除终点图标
				map.visibleGroupIDs = [nowpoint.groupID]; //可见楼层
				map.focusGroupID = nowpoint.groupID;  //聚焦楼层
			}
		}
	});
}

代码里面有一个东西没有写完,但是没买什么必要,我也是在没有找到对应的视角定位操作方法,那就是开始导航按钮功能,这个我留了一个函数但是没有写什么东西进去,然后有一个布尔量来标示当前的状态十分是在导航之中,由于没有找到视角控制的方法所以就没有怎么去写这个东西,我会继续尝试找,如果谁找到了给我说一下。

室内导航解决方案》有6条留言

  1. 博主您好,我最近正在用蜂鸟的SDK做一个停车场的项目,有一些关于定位的问题还是不太懂,可不可以加您一个联系方式交流一下呢?万分感谢,我的微信号是Y_4841

    1. 惯性信息不过是对导航坐标的一个预测补充而已,不是都有开源算法了吗?它并不是室内导航的难点与重点,当定位精度问题解决之后这都不是问题。

  2. 想请教一下,如果是地下停车场,网络情况很差,甚至没网的情况您你怎么解决的

    1. 没有服务器数据作为支撑的话基本上除了离线方案就无解了,离线方案的话也比较简单将所有的算法、信标数据、地图数据、空间数据等全部本地化封装APP。