Android设备通过NTRIP协议获取差分数据实现高精度定位


转载自http://www.jianshu.com/p/7b93952febc0


项目背景

  • 最近在做一个Android的APP项目中有个功能,需要用到Ntrip协议从差分服务器获取差分数据,并将差分数据通过蓝牙传送至高精度手持设备(华信TR502接收机)之后返回固定解的高精度定位数据(NMEA0813协议数据),解出位置信息后在APP地图上显示并描绘运动轨迹并将运动轨迹保存至手机,最后将获取的数据(GGA和GST格式的数据)重新封装添加自己的信息后实时回传至服务器。
  • 现将过程中一些代码技术做出总结,方便之后记忆和查阅

Ntrip协议从差分服务器获取差分数据

Ntrip协议(基于HTTP的应用层RTCM网络传输的协议)实际是在TCP/IP协议上进行封装的,依然使用Socket进行数据通信,项目中我们直接将获取到的差分数据封装进了设备的BluetoothSocket因此在获取数据前需要先开始蓝牙连接设备并建立BluetoothSocket。接下来简述过程和部分代码。

  1. 连接设备蓝牙并建立蓝牙数据通道
    目前使用手机或平板本身的功能与设备蓝牙初次配对,因此在App里先进行搜索已配对的设备
    BluetoothAdapter.getDefaultAdapter().getBondedDevices();
    点击连接设备并创建BluetoothSocket
    btSocket = btDevice.createRfcommSocketToServiceRecord(uuid);
    btSocket.connect();
  2. Ntrip协议获取差分数据
    主业务逻辑代码如下
    NetWorkServiceNtrip netWorkService=new NetWorkServiceNtrip (
    this,
    ip,
    port,
    account,
    pwd,
    mountedId,
    btSocket
    );
    netWorkService.getDifferentialData();
    NetWorkService源码如下
    public class NetWorkServiceNtrip {    
    private static final String CMD_HEAD = "$FCMDB,";    
    private static final String CMD_END = ",*FF\r\n";   
    //差分服务器IP地址
    private String mIP;    
    //差分服务器端口
    private String mPort;    
    //用户名
    private String mUserID;    
    //密码
    private String mPwd;    
    //挂载点
    private String mMountedpoint;   
    //与差分服务器的Socket 
    private Socket mSocket;    
    //Android设备蓝牙通信Socket
    private BluetoothSocket bluetoothSocket;   
    // 与差分服务器的Socket 的数据输出流
    private DataOutputStream dos;    
    //蓝牙通信的输出流
    private OutputStream btDos;    
    // 与差分服务器的Socket 的数据输入流
    private DataInputStream dis;    
    //获取挂载点线程
    private NetWorkServiceNtrip.UpdateSourceTableThread mUpdateSourceTableThread;    
    private NetWorkServiceNtrip.ReportGGA2Service mReportGGA2Service = null;   
    //获取差分数据线程
    private NetWorkServiceNtrip.AcquireDataThread mAcquireDataThread;    
    //获取挂载点
    private ArrayList<String> mountedPoints = null;    
    private String feedBackState = null;    
    //获取连接状态
    public String getFeedBackState() { return this.feedBackState; }    
    public ArrayList<String> getMountedPoints() { return this.mountedPoints; }    
    private Context mContext;   
    //构造函数 
    public NetWorkServiceNtrip(Context context,String ipAddress, String port, String userID, String password, String mountedPoint, BluetoothSocket bluetoothSocket) {        
     this.mContext=context;       
     this.mIP = ipAddress;        
     this.mPort = port;        
     this.mUserID = userID;        
     this.mPwd = password;        
     this.mMountedpoint = mountedPoint; 
     this.bluetoothSocket=bluetoothSocket;    
    }    
    //连接差分服务器并获取挂载点列表
    public synchronized void connect2Server() { 
     if(this.mUpdateSourceTableThread != null) {
         this.mUpdateSourceTableThread.release();    
         this.mUpdateSourceTableThread = null;       
      }        
     this.mUpdateSourceTableThread = new   NetWorkServiceNtrip.UpdateSourceTableThread((NetWorkServiceNtrip.UpdateSourceTableThread)null);        
     this.mUpdateSourceTableThread.start();    
    }    
    //连接差分服务器获取差分数据
    public synchronized void getDifferentialData() {       
     if(this.mAcquireDataThread != null) {            
         this.mAcquireDataThread.cancle();                
         this.mAcquireDataThread = null;        
     }        
     this.mAcquireDataThread = new NetWorkServiceNtrip.AcquireDataThread((NetWorkServiceNtrip.AcquireDataThread)null);        
     this.mAcquireDataThread.start();    
    }    
    //连接差分服务器
    private void getCorsServiceSocket(String ip, String port) {        
     try {            
         if(this.mSocket == null) {                
             InetAddress e = Inet4Address.getByName(ip);                   
             this.mSocket = new Socket(e, Integer.parseInt(port));              
         }            
         if(this.dos == null) {                
              this.dos = new DataOutputStream(this.mSocket.getOutputStream());            
         }            
         if(this.dis == null) {                
             this.dis = new DataInputStream(this.mSocket.getInputStream());            
         }            
         if(this.bluetoothSocket != null) {                
             this.btDos = this.bluetoothSocket.getOutputStream();            
         }            
         Log.d("getCorsServiceSocket","Successful");        
         } catch (UnknownHostException var4) {            
             var4.printStackTrace();        
         } catch (NumberFormatException var5) {            
             var5.printStackTrace();        
         } catch (IOException var6) {            
             var6.printStackTrace();        
         }    
    }    
    //获取差分数据线程
    private class AcquireDataThread extends Thread {        
     private boolean _run;        
     private byte[] buffer;        
     private AcquireDataThread(AcquireDataThread acquireDataThread) {            
         this._run = true;            
         this.buffer = new byte[256];        
     }        
     public void run() {            
         if(NetWorkServiceNtrip.this.mSocket != null) {                
             NetWorkServiceNtrip.this.mSocket = null;            
         }            
         try {                
             NetWorkServiceNtrip.this.getCorsServiceSocket(NetWorkServiceNtrip.this.mIP, NetWorkServiceNtrip.this.mPort);                
             if(NetWorkServiceNtrip.this.dos!=null){                    
                 //这里将发送的请求参数封装成Ntrip协议格式
                 NetWorkServiceNtrip.this.dos.write(UtilNtrip.CreateHttpRequsets(NetWorkServiceNtrip.this.mMountedpoint,NetWorkServiceNtrip.this.mUserID,NetWorkServiceNtrip.this.mPwd).getBytes());                
             }                
             boolean e = true;                
             while(this._run) {                    
                 if(NetUtils.isConnected(mContext)){                        
                     int e1 = NetWorkServiceNtrip.this.dis.read(this.buffer, 0, this.buffer.length);        
                     //自己的业务逻辑中将差分数据大小存入了SharePreference中           
                     UserPreferences.getInstance(mContext).setChaFenDataSize(e1);                        
                     if(e1 >= 1) {                            
                         String e1x = new String(this.buffer);                            
                         if(e1x.startsWith("ICY 200 OK")) {                                
                             if(NetWorkServiceNtrip.this.mReportGGA2Service == null) {                                    
                                 NetWorkServiceNtrip.this.mReportGGA2Service = NetWorkServiceNtrip.this.new ReportGGA2Service(NetWorkServiceNtrip.this.dos, (NetWorkServiceNtrip.ReportGGA2Service)null);                                    
                                 NetWorkServiceNtrip.this.mReportGGA2Service.start();                                
                             }                                
                             NetWorkServiceNtrip.this.feedBackState = "ICY 200 OK";                            
                         } else if(e1x.contains("401 Unauthorized")) {                                
                             NetWorkServiceNtrip.this.feedBackState = "401 UNAUTHORIZED";                            
                         } else {                                
                             NetWorkServiceNtrip.this.feedBackState = "SUCCESSFUL";                                
                             if(NetWorkServiceNtrip.this.btDos != null) {   
                             //此处将差分服务器的数据直接写入了蓝牙的BluetoothSocket中发送出去                                 
                                 String head = "$FCMDB," + String.valueOf(e1 + 17) + ",";                                        
                                 NetWorkServiceNtrip.this.btDos.write(head.getBytes());                                    
                                 NetWorkServiceNtrip.this.btDos.write(this.buffer, 0, e1);                                    
                                 Log.d("buffer",UtilNtrip.bytesToHexString(this.buffer));                                    
                                 NetWorkServiceNtrip.this.btDos.write(",*FF\r\n".getBytes());                                
                             }                            
                         }                        
                     }                    
                 }                
             }            
         } catch (UnknownHostException var5) {                
             var5.printStackTrace();            
         } catch (IOException var6) {                
             var6.printStackTrace();                
             try {                    
                 NetWorkServiceNtrip.this.dos.close();                    
                 NetWorkServiceNtrip.this.dis.close();                
             } catch (IOException var4) {                    
                 var4.printStackTrace();                
             }            
         }        
     }        
     public void cancle() {            
         try {                
             this._run = false;                
             NetWorkServiceNtrip.this.mSocket.close();            
         } catch (IOException var2) {                
             var2.printStackTrace();            
         }        
     }    
    }    
    private class ReportGGA2Service extends Thread {        
     private DataOutputStream dos;        
     private boolean _run;        
     private ReportGGA2Service(DataOutputStream dos, ReportGGA2Service reportGGA2Service) {            
         this.dos = null;            
         this._run = false;            
         this.dos = dos;        
     }        
     public void run() {            
         while(!this._run) {                
             try {                    
                 this.dos.write(Praser.getGGAMsg().getBytes());                    
                 Thread.sleep(180000L);                
             } catch (Exception var2) {                    
                 this.Cancle();                
             }            
         }        
     }        
     public void Cancle() {            
         try {                
             this._run = true;            
         } catch (Exception var2) {          
         }        
         }    
     }    
    private class UpdateSourceTableThread extends Thread {        
     private UpdateSourceTableThread(UpdateSourceTableThread updateSourceTableThread) {        
     }        
     public void run() {            
         try {                
             NetWorkServiceNtrip.this.getCorsServiceSocket(NetWorkServiceNtrip.this.mIP, NetWorkServiceNtrip.this.mPort);                
             if(NetWorkServiceNtrip.this.dos == null) {                    
                 NetWorkServiceNtrip.this.mSocket.setSoTimeout(5000);                        
                 NetWorkServiceNtrip.this.dos = (DataOutputStream)NetWorkServiceNtrip.this.mSocket.getOutputStream();                
             }                
             NetWorkServiceNtrip.this.dos.write(Util.Request2NtripServer().getBytes());                
             byte[] e = new byte[1024];                
             StringBuilder sb = new StringBuilder();                
             boolean len = true;                
             String sourceString;                
             int var14;                
             while((var14 = NetWorkServiceNtrip.this.dis.read(e, 0, e.length)) != -1) {                    
                 sourceString = new String(e, 0, var14);                    
                 sb.append(sourceString);                
             }                
             sourceString = sb.toString();                
             if(sourceString.startsWith("SOURCETABLE 200 OK")) {                    
                 ArrayList mountPoints = new ArrayList();                    
                 String[] linStrings = sourceString.split("\r\n");                    
                 String[] var10 = linStrings;                    
                 int var9 = linStrings.length;                    
                 for(int var8 = 0; var8 < var9; ++var8) {                        
                     String line = var10[var8];                        
                     if(line.startsWith("STR")) {                            
                         String[] dataStrings = line.trim().split(";");                                
                         mountPoints.add(dataStrings[1]);                        
                     }                    
                 }                    
                 NetWorkServiceNtrip.this.mountedPoints = mountPoints;                
             }                
             this.release();                
             NetWorkServiceNtrip.this.mUpdateSourceTableThread = null;            
             } catch (UnknownHostException var12) {                
                 var12.printStackTrace();            
             } catch (IOException var13) {                
                 var13.printStackTrace();            
             }        
         }        
         private void release() {            
             try {                
                 if(NetWorkServiceNtrip.this.dos != null) {                    
                     NetWorkServiceNtrip.this.dos.close();                
                 }                
                 if(NetWorkServiceNtrip.this.dis != null) {                    
                     NetWorkServiceNtrip.this.dis.close();                
                 }                
                 if(NetWorkServiceNtrip.this.mSocket != null) {                    
                     NetWorkServiceNtrip.this.mSocket.close();                
                 }            
             } catch (IOException var2) {                
                     var2.printStackTrace();            
             }        
         }   
     }
    }
    UtilNtrip源码如下
    public class UtilNtrip {    
     public static String CreateHttpRequsets(String mountPoint, String userId, String password) {        
         String msg = "GET /" + mountPoint + " HTTP/1.0\r\n";        
         msg = msg + "User-Agent: NTRIP GNSSInternetRadio/1.4.11\r\n";        
         msg = msg + "Accept: */*\r\n";        
         msg = msg + "Connection: close\r\n";        
         String tempString = userId + ":" + password;        
         byte[] buf = tempString.getBytes();        
         String code = Base64.encodeToString(buf, 2);        
         msg = msg + "Authorization: Basic " + code + "\r\n";        
         msg = msg + "\r\n";        
         return msg;    
     }    
     public static final String bytesToHexString(byte[] bArray) {        
         StringBuffer sb = new StringBuffer(bArray.length);        
         String sTemp;        
         for (int i = 0; i < bArray.length; i++) {            
             sTemp = Integer.toHexString(0xFF & bArray[i]);            
                 if (sTemp.length() < 2)                
                     sb.append(0);            
                     sb.append(sTemp.toUpperCase());        
                 }        
         return sb.toString();    
     }
    }

    绘制并保存轨迹、重新封装数据格式后回传

    获取到NMEA0831格式数据后,APP的该功能块主要做了两个工作一是将轨迹绘制在Shape底图上,二是将数据拆分并添加上自己的字段信息后重新封装后将数据回传至远程服务器端,该功能块采用TCP/IP协议进行回传,绘制运动轨迹我们采用Arcgis for Android的引擎。由于差分数据已经被写入了BluetoothSocket中传入手持高精度设备中(华信TR502接收机),该设备会自动计算并获取高精度NMEA0831格式数据,设备自动通过BluetoothSocket传输数据,因此只需要通过读取BluetoothSocket中的数据就可获取高精度信息。
    读取蓝牙数据主要代码
    try{
     inputStream = btSocket.getInputStream();
     if (inputStream != null) {
         BufferedReader reader = new BufferedReader(new   InputStreamReader(inputStream, "ASCII"));
         while ((line = reader.readLine()) != null) {
             ...
             ...
             //绘制轨迹
             drowRoute(double lng, double lat);
             ...
             //封装并回传数据
             sendTCPData(GGAUtils.cutString(msg, GROUP, DEVICE));
             ...
             ...
         }
    }catch(IOException e){
     e.printStackTrace();
    }
    获取到数据后提取出经纬度信息调用Arcgis for Android的接口绘制运动轨迹,保存轨迹时调用接口将轨迹图形转换为Json格式存于本地或数据库中。
    drowRoute中的主要代码
    private void drawRoute(double lng, double lat) {  
     //自身的定位图层,不断刷新清除之前的定位点  
     locationLayer.removeAll();    
     //是否重新开始定位
     if (isFristLoaction && lat != 0) {        
         isFristLoaction = !isFristLoaction;    
         //创建Arcgis的点
         lastPoint = new Point(lng, lat);    
         //创建Arcgis线条  
         poly = new Polyline();        
         //创建Aicgis图形
         polyGraphic = new Graphic(poly, sls);  
         //开始绘制线条的点         
         poly.startPath(lastPoint);    
     } else {        
         Point wsgpoint = new Point(lng, lat);  
         //投影坐标转换      
         Point mapPoint = (Point) GeometryEngine.project(wsgpoint, SpatialReference.create(4326), mapView.getSpatialReference());        
         if (MDistance(lastPoint.getX(), lastPoint.getY(), mapPoint.getX(), mapPoint.getY()) >= 0.05 && MDistance(lastPoint.getX(), lastPoint.getY(), mapPoint.getX(), mapPoint.getY()) <= 20) {            
             pathGraphlayer.removeAll();  
             //绘制轨迹线条          
             poly.lineTo(mapPoint);            
             lastPoint = mapPoint; 
             //将线条添加到轨迹图层上           
             pathGraphlayer.addGraphic(polyGraphic);    
             //有轨迹则显示保存路径按钮        
             if (polyGraphic != null) {                
                 pathBtn.post(new Runnable() {                    
                 @Override                    
                 public void run() {                        
                     pathBtn.setVisibility(View.VISIBLE);                    
                 }                
             });            
         } 
         //这里跑的时候发现这个BUG,GraphicsLayer在addGraphic时有长度限制,不知道是版本原因还是什么           
         if (pathGraphlayer.getGraphicIDs().length > 8800) {               
             pathGraphlayer.removeAll();            
         }        
     }        
     locagraphic = new Graphic(mapPoint, locationMS);            
     locationTS = new TextSymbol(15, "latitude:" + lat + "\n" + "longitude:" + lng, Color.BLACK);        
     Graphic locaTSgra = new Graphic(mapPoint, locationTS);        
     locationLayer.addGraphic(locaTSgra);        
     locationLayer.addGraphic(locagraphic);    
     }
    }
    保存路径很简单的转化为Json格式保存在了本地
    FileUtils.writeStrToFile(
    new Date().getTime() + "",
    GeometryEngine.geometryToJson(mapView.getSpatialReference(), polyGraphic.getGeometry()),
    fileName);
    重新封装并回传数据
    这一部分主要是对收到NMEA0831格式数据进行拆分合并,使用TCP/IP进行回传。该功能块只筛选出了GGA和GST协议格式的数据,主要就是使用的是StringBuilder进行字符串的一些操作。
    回传数据使用SocketChannel建立通信信道,连接的建立、重连机制、判断服务器是否关闭、发送信息或者数据、关闭连接等使用常用的网络编程技术。

    总结

    该功能块还使用了手机或平板自带的GPS进行轨迹绘制,发现确实差距比较大,这种方式精度很高只有2厘米左右误差,底图也是专业的高精度测绘仪器测量出来并做成Shape地图,作为Android开发者第一次接触这种设备,学到了很多。该功能块主要使用网络编程技术,通信功能比较多,线程也比较多,现在通信的库比较多导致自身的一些基础的知识点也没有完全掌握,Arcgis for Android 还需要不断学习。


作者:一袋小乞丐
链接:http://www.jianshu.com/p/7b93952febc0
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值