精华内容
下载资源
问答
  • 起因 早上起来,看到有人问Python获取一张JPG格式图片拍摄的时候的GPS定位的代码。GPS应该说是个敏感的信息,既然有人想读取我们的信息,那么我们至少应该直到我们的敏感信息被保存在了哪里。 研究了一天,四处搜集...

    起因

    早上起来,看到有人问Python获取一张JPG格式图片拍摄的时候的GPS定位的代码。GPS应该说是个敏感的信息,既然有人想读取我们的信息,那么我们至少应该直到我们的敏感信息被保存在了哪里。
    研究了一天,四处搜集文档,对着一张JPG格式文件的二进制代码,终于摸到了点门道。结论就是并不是所有的图片都带着GPS等信息,例如我们微信发送图片的时候,如果不发送原图,很多信息都会被抹除(抹除APP1标签,下文有介绍)。
    这里顺路推荐一个Linux下查看二进制文件的一个命令行工具:hexedit,Ubuntu下使用命令sudo apt install hexedit就可以安装,虽然没有UltraEdit这种好用,但是对于查看文件也足够了。
    写代码,肯定要先直到原理,因此下面需要简单介绍下JPEG这个东西。

    JPG 简介

    JPEG(Joint Photographic Experts Group,联合图像专家组)标准定义了一套对静态图片进行压缩的算法,用于对图像或者视频进行压缩。JPEG标准定义了四种操作,分别是顺序DCT(sequential Discrete Cosine Transform) 模式、渐进DCT(progressive DCT)模式、无损(Lossless)模式和分层(hierarchical)模式。根据不同模式会对原是图像进行多次扫描,每扫描一次就得到一帧。每一帧前面会添加有一些压缩的参数,例如量化表、霍夫曼编码表等。这套算法可以对图像数据或者视频数据进行压缩,但是却没有定义怎么将这些压缩后的数据通过一个图片格式保存。因此JFIF(JPEG File Interchange Format)就成了一个事实上的标准,它通过标签段的方式,为这些压缩数据提供了额外的信息。
    另外还有一种表示JPEG图片的格式是Exif( Exchangeable image file Format),它并不是一个新的标准,而是通过组合已有标准而成的格式。对压缩数据的标准使用的是(ISO/IEC 10918-1),和JFIF的标准一样,只不过增加了一个额外表示信息的标签APP1,这个APP1的格式标准使用的是(TIFF Rev. 6.0)。因此他们俩主要区别就是附加信息的标签不同,JFIF的标签是APP0,而Exif的标签是APP1。而例如拍摄图片的所用的相继型号等信息,就是储存在APP1标签里面。

    我们的主要目的是获取图片属性信息,因此我们主要介绍Exif的格式。

    Exif图片的主要结构如图所示,被特定的标签被分成了一段一段的数据。所谓标签,就是2个有特定数值的字节,它们对应的名字和数值如图所示。这些标签其实就是一组特定的数值,每个标签占两个字节,其中灰色的是必须有的结构,白色的根据情况有可能没有。

    1584186733395.jpeg

    Exif格式中可能存在的标签以及每个标签对应的值和含义如下:
    image.png

    APP1

    因为我们关注的重点是APP1标签,所以我们看一下APP1的结构,其他标签等如DQT等示关于压缩数据的,如果需要处理图片内容的可以详细看,这里就忽略了。APP1有两种形式,一种是带缩略图信息的,另一种是不带缩略图信息的,分别如下面图和图所示:
    Basic structure of jpg.jpeg

    Structure with thumbnail.jpeg

    我们已经直到APP1标签的值示0xFFE1;而Length记录了APP1段的长度,长度不能超过64KB,因为Length占用2个字节记录长度,因此最大子能表示64KB;Exif标示符占6个字节,内容是0x45 0x78 0x69 0x66 0x00 0x00,也就是Exif四个字符外加两个空字符;而TIFF头如下图所示:

    TIFF Header.jpeg

    TIFF头之后就是IFD和IFD的值。0th IFD主要存储主图的信息,1st IFD可能存储着缩略图的信息。

    IFD(Image File Directory) 结构

    JPEG图片的信息存储分为两部分:IFD和IFD Value。IFD是一个线索,通过这个线索可以找到IFD Value。举个栗子,IFD就是租房中介,IFD Value是房子。中介手上会有所有房子的信息,你找到了中介,就能直到所有房子的数量、户型、地址。
    IFD由三部分组成:

    1. 第一部分:占据2个字节,这两个字节记录一共有多少条信息;
    2. 根据第一部分记录的信息的数量,每条信占用12个字节,着十二个字节又可以分为四个部分:
      1) Tag:2个字节表示这条信息的类型,比如是记录长、宽还是拍摄时间等信息;
      2) Type:2个字节表示这些记录存储为二进制的信息怎么翻译,比如是翻译成整数、小数还是字符串等;
      3)Count:4个字节表示这个记录的值有多少个,不一定示多少个字节。例如当值的类型示无符号整型的时候,因为一个无符号整型数值占用两字节,Count = 1就表示两个字节;
      4) Offset:4个字节表示找到这条记录的偏移地址,以TIFF头为基准。如果记录的数使用着四个字节就装的下,那么Offset记录的就不是地址而是实际的值,如果所用到的字节小于4,那么从最左边的字节开始使用,也就是Offset的低位开始。例如存储一个SHORT类型1的时候,大端格式中着四个字节的内容就是0x0001 0000,而小端格式就是0x0000 0000 0000 0001
    3. 第三部分占用4个细节,记录的是下一个IFD的偏移地址,如果没有下一个IFD了,这四个字节的值就是0x00000000。

    IFD的结构如下:
    IFD结构

    IFD中Type的含义如下:
    image.png

    0th IFD中Tag的值和其对应意思如下:
    1584175240316.jpeg

    可以看到,上面的Tag表格中并为包含GPS信息,因为GPS自己有一个属于自己的IFD,在0th IFD中只有一个指向GPS IFD的指针:
    image.png
    GPS IFD.jpeg

    有了上面的铺垫,我们就可以开始编程了。

    Coding

    下面是我根据我的理解写的代码,额外用到了一个numpy库,平常工作中用的比较多,更重要的是它能够将图片按照我的想法读取进内存。
    代码的思路是是这样:

    1. 确定这是一张JPG图片:通过SOI标签确定,这个标签在文件的最开头,内容是0xFFD8;
    2. 找到APP1标签,理论上APP1标签是要紧跟着SOI标签的,但是我在查看图片二进制内容的时候发现并不是所有的照片都这样,因此使用搜索APP1的方法;
    3. 找到0th IFD,按照上文中的解释来找;
    4. 把找到的标签的、TIFF头的地址都记录下来
    5. 获取所有的IFD;
    6. 看找到的IFD有没有GPS IFD的指针
    7. 通过GPS IFD的指针找到GPS IFD;
    8. 读取GPS信息

    下面是代码实现,另外源代码在本人Github地址为:https://github.com/zmychou/jpg-info-extractor
    时间很晚了,就先草草收场了。

    import numpy as np
    
    IMAGE = []
    
    markers = {
        'APP1': [0xFF, 0xE1],
        'SOI': [0xFF, 0xD8]
    }
    
    
    
    class APP1(object):
        attributes_name = {
            271: 'Manufacturer',
            272: 'Model',
            306: 'Last Modify',
            29: 'GPS Date',
            305: 'Software'
        }
        byte_count = {
            'ASCII': 1,
            'BYTE': 1,
            'SHORT': 2,
            'LONG': 4,
            'RATIONAL': 8
        }
    
        type_of_ifd = {
            1: 'BYTE',
            2: 'ASCII',
            3: 'SHORT',
            4: 'LONG',
            5: 'RATIONAL'
        }
    
        class Field(object):
    
            def __init__(self, _tag, _type, _count, _offset, _is_offset):
                self.tag = _tag
                self.type = _type
                self.count = _count
                self.offset = _offset
    
        exif_identifier = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]
    
        def __init__(self, img, app1_offset):
            self._image = img
            self.app1_offset = app1_offset
            self._exif_identifier_offset = 4
            self.tiff_header_length = 8
            self.ifd_offset = 0
            self.little_endian = [0x49, 0x49]
            self.big_endian = [0x4D, 0x4D]
            self.marker = [0xFF, 0xE1]
    
            self.endian = self._image[self.app1_offset + 10: self.app1_offset + 12]
            self.ifd_offset = self.zero_ifd_offset()
            self.number_of_zero_ifd = self.get_number_of_fields()
    
        def get_field(self, fields_num, ifd_offset, ):
    
            fields = []
            for i in range(fields_num):
                start = ifd_offset + 2 + i * 12
                raw_ifd = self._image[start: start + 12]
                tag = self.read_bytes_in_value(raw_ifd[0: 2], False)
                type = self.read_bytes_in_value(raw_ifd[2: 4], False)
                count = self.read_bytes_in_value(raw_ifd[4: 8], False)
                offset = self.read_bytes_in_value(raw_ifd[8: 12], False)
    
                # todo: evaluate is_offset args
                field = APP1.Field(tag, type, count, offset, False)
                fields.append(field)
            return fields
    
        def get_fields(self):
            fields = self.get_field(self.number_of_zero_ifd, self.ifd_offset)
    
            # Get GPS info
            for field in fields:
                if field.tag == 0x8825:
                    self.gps_ifd_offset = field.offset + self.tiff_header_offset
                    break
            gps_fields_raw = self._image[self.gps_ifd_offset: self.gps_ifd_offset + 2]
            fields_of_gps = self.read_bytes_in_value(gps_fields_raw, False)
            gps_fields = self.get_field(fields_of_gps, self.gps_ifd_offset)
            fields.extend(gps_fields)
            return fields
    
        def read_attribute(self, field):
            ifd_type = APP1.type_of_ifd[field.type]
            byte_count = APP1.byte_count[ifd_type] * field.count
            start = self.tiff_header_offset + field.offset
            end = start + byte_count
            raw = self._image[start: end]
            if ifd_type == 'ASCII':
                attr = [APP1.attributes_name[field.tag], ': ']
                for r in raw:
                    attr.append(chr(r))
                print(''.join(attr))
    
        def get_number_of_fields(self):
            raw = self._image[self.ifd_offset: self.ifd_offset + 2]
            return self.read_bytes_in_value(raw, False)
    
        def zero_ifd_offset(self):
            raw = self._image[self.tiff_header_offset + 4: self.tiff_header_offset + 8]
            return self.read_bytes_in_value(raw, False) + self.tiff_header_offset
    
        def read_bytes_in_value(self, bytes, ignore_endian):
            if not ignore_endian and self.is_little_endian:
                bytes.reverse()
            value = 0
            for byte in bytes:
                value = value << 8
                value = value + byte
            return value
    
        @property
        def exif_identifier_offset(self):
            return self.app1_offset + self._exif_identifier_offset
    
        @property
        def tiff_header_offset(self):
            return self.app1_offset + 10
    
        @property
        def is_little_endian(self):
            endian = self.endian == self.little_endian
            return endian
    
    def compare_bytes(candidate, target):
        return candidate == target
    
    def find_marker(marker, start):
        candidate = copy_bytes(start, 2)
        return marker == candidate
    
    def get_app1_marker_offset():
        length = len(IMAGE)
        for i in range(2, length, 1):
            if find_marker(markers['APP1'], i):
                exif_identifier = copy_bytes(i + 4, 6)
                if compare_bytes(exif_identifier, APP1.exif_identifier):
                    return i
        return -1
    
    def copy_bytes(start, length):
        if start > len(IMAGE) or start + length > len(IMAGE):
            raise Exception('Copy bytes fail: out of range.')
        return IMAGE[start: start + length]
    
    def load_image(path):
        n = np.fromfile(path, dtype=np.ubyte)
        n = n.tolist()
        return n
    
    def main():
        global IMAGE
        IMAGE = load_image('1424054533.jpg')
        if not find_marker(markers['SOI'], 0) :
            print('Given image seems not a JPEG image.')
            return
    
        app1 = APP1(IMAGE, get_app1_marker_offset())
        fields = app1.get_fields()
        for field in fields:
            app1.read_attribute(field)
            if field.tag == 0x8825:
                print('has GPS info')
    
    if __name__ == '__main__':
        main()
    

    运行结果如下,获取到的图片的部分信息:

    Model: MI 6 
    Software: sagit-user 9 PKQ1.190118.001 V11.0.2.0.PCACNXM release-keys 
    Last Modify: 2020:03:15 19:49:17 
    has GPS info
    Manufacturer: Xiaomi 
    GPS Date: 2020:03:15 
    

    公众号二维码

    首发于个人微信公众号TensorBoy。微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取更多最新文章!

    References

    http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf
    http://lad.dsc.ufcg.edu.br/multimidia/jpegmarker.pdf
    http://www.npes.org/pdf/TIFF-v6.pdf
    https://www.w3.org/Graphics/JPEG/jfif3.pdf
    http://www.fileformat.info/format/jpeg/egff.htm
    https://tools.ietf.org/html/rfc2435

    展开全文
  • java实现GPS定位问题

    2017-05-26 01:48:26
    想做一个app(非纯生)应用,java编写如何能实现登录用户通过查找实时显示他的位置(地图上)
  • 參閱:http://www.dotblogs.com.tw/jeff-yeh/archive/2008/06/11/4266.aspx#13956 之前有寫了一篇"讓人知道你在那"的文章,希望能自己寫程式去運用GPS定位功能,而這篇文章將會帶到兩個部份,第一個部份是PDA回傳資料...

     

    參閱:http://www.dotblogs.com.tw/jeff-yeh/archive/2008/06/11/4266.aspx#13956

     

    之前有寫了一篇"讓人知道你在那"的文章,希望能自己寫程式去運用GPS的定位功能,而這篇文章將會帶到兩個部份,第一個部份是PDA回傳資料時所用的Web Service,另一個就是PDA的GPS訊號抓取與轉換,而這部份並不是採用PAPAGO或其它的SDK,全部是採自己Coding的做法,在寫這個過程中,其實有點頭大,因為Coding的電腦是在室內,所以PDA是無法抓到GPS的訊號,所以只好從戶外拉了一條好長的GPS延長線到電腦邊,最後從抓到的經緯度丟到google map上去顯示,定出來的位置還算準確,確實定位出目前我的所在位置,另一個常在一般的衛星導航系統看到的情況,就是有時會亂跳,因為車子不可能在可行駛的道路以外,所以當經緯度偏離航道時,就會開始"亂跳",想辦法跳回最近的航道,這有好有壞,有時GPS是對的,位置真的不在道路上,但導航系統一直挑附近的道路跳,可是有時GPS訊號差,經緯度的誤差很大,明明在這條巷子,結果地圖顯示在另一條巷子裡,而導航程式本來就是用來帶領駕駛人行駛方向的,所以"車子"當然要在道路上.

     

    廢話了一堆,接下來就開始進入這次的主題,首先,為了儲存PDA所回傳的資料,所以在DataBase開了一個Table來儲存,資料結構大至上如下 :

    資料庫 : GPSDB

    TableName : GPSTrace

    Columns :

    UID (int,非null) 識別自動加1

    Latitude (nvarchar(10),非null) 預設值 25.048346

    Longitude(nvarchar(10),非null) 預設值 121.516396

    UpdTime (DateTime,非null) 預設值 getdate()

    資料結構大致如上,另外安全性的帳號問題,就各位依自己的喜好去設,配合GPS及下篇MAP展示做修改即可.

     

    此次的DEMO方向是PDA會回來Update資料,並不會做Insert的動作,所以只會儲存最新的所在位置,而第一次DB並沒有資料,所以Update一定會出錯,由於資料操作並非此次重點,因此先手動輸入兩筆資料進去.

    SampleData

    Web Service的部份也很簡單,只是很單純的Update的動作. 所以以下的Code只是為了達到Update的DEMO目地,其它安全性,例如Connection string不應直接寫在這裡面,這部份就依個人環境需求去改,如果再帶下去,文章會又臭又長,如果有需要,再額外開篇來討論.

     

    [WebMethod]
    public int UpdTrace(int UID,string Lat,string Long)
    {
        string strconn = "Data Source=localhost;Initial Catalog=GPSDB;User Id=GPSUser;Password=gpsuser;"; //ID及PWD隨個人設定.
        SqlConnection conn = new SqlConnection(strconn);
        string strcmd = "update gpdtrace set Lat=@Lat,Long=@Long,UpdTime=getdate() where UID=@UID";
        SqlCommand cmd = new SqlCommand(strcmd, conn);
        cmd.Parameters.AddWithValue("@Lat", Lat);
        cmd.Parameters.AddWithValue("@Long", Long);
        cmd.Parameters.AddWithValue("@UID", UID);
        try
        {
            conn.Open();
            return cmd.ExecuteNonQuery();
        }

        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }

        finally
        {
            conn.Close();
            conn.Dispose();
            cmd.Dispose();
        }

    }

     

     

    PDA DB及Web Service好了,接下來要帶的就是PDA的GPS程式寫法(前面帶很快,因為我比較有興趣的是GPS的抓法^^),先大致看一下PDA的UI設計. 最上方的GPS Port是供使用者選擇GPS所使用的Port,因為每台Smart Device的GPS Port不見得相同,其它就都是唯讀的資訊,而資訊來源就是衛星訊號,從UI上看到的資訊只是衛星訊號的一部份,而衛星的訊號也只是抓其中的一種GPGGA出來用,其它還有GPGSV,GPGSA等等可以運用,衛星的訊號請參考GPS - NMEA sentence Information所提供的資料,在此就不再累述這些訊號一次.

     

     

     

     

     

     

     

     

     

    而這次所運用的GPGGA訊號,基本上會抓到這類字串$GPGGA,095031.254,2501.9891,N,12133.8101,E,1,07,7.0,123.9,M,15.0,M,0.0,0000*74

    每個逗號隔開視為一個欄位,而每個欄位值所代表的意義如下清單

     

    Name

    Example Data

    Description

    Sentence Identifier $GPGGA Global Positioning System Fix Data
    UTC Time 095031.254 09:50:31
    Latitude 2501.9891,N 北緯
    Longitude 12133.8101,E 東經

    Fix Quality:
    - 0 = Invalid
    - 1 = GPS fix
    - 2 = DGPS fix

    1 Data is from a GPS fix
    Number of Satellites 07 7 Satellites are in view
    Horizontal Dilution of Precision (HDOP) 7.0 Relative accuracy of horizontal position
    Altitude 123.9,M 123.9 meters above mean sea level
    Height of geoid above WGS84 ellipsoid 15.0,M 15.0,M
    Time since last DGPS update 0.0 因為採GPS修正,不是DGPS,所以沒有資料
    DGPS reference station id 0000  
    Checksum *74 Used by program to check for transmission errors

     

    瞭解了從衛星收到這字串的意義後,接下來就是怎麼去轉換經緯度的部份了,經度跟緯度的轉換方式都一樣,所以就拿其中一個來說明.

    1. 2501.9891 /100 = 25.019891

    2. 把小數點後的值從0.019891直接轉為19891

    3. 再取小數點部份的值(19891/ 60)*10000=3315166

    4. 由於第二步移了一個0,所以3315166轉小數時,要加一個0進去,所以=0.03315166

    5. 所以緯度= 25+0.03315166=25.03315166

    同樣的方式,算出經度=121.5635

    *註此定出來的點是台北101

     

    說完了GPS字串及轉換的方式,接下來就是Coding的部份.

    1. 在下拉的ComboBox,供使用者選擇GPS Port的部份,先加入四個Port進去

    this.cmbPort.Items.Add("COM1");
    this.cmbPort.Items.Add("COM2");
    this.cmbPort.Items.Add("COM3");
    this.cmbPort.Items.Add("COM4");

     

    2.加入一個Timer,Interval=1000

     

    3.加入一個Serial Port

     

    4. 在[開始]的Button加入一個click event. 如此,將GPS的Port打開,藉由Timer的Tick事件來更新資料

     

            private void btnStart_Click(object sender, EventArgs e)
            {
                serialPort1.PortName = cmbPort.SelectedItem.ToString();//依下拉選擇的com port設定Serial Port
                try
                {
                    serialPort1.Open(); //開啟serial port
                    txtPortStatus.Text = "";
                }

                catch (Exception ex)
                {
                    txtData.Text = ex.Message;
                }

      
                if (serialPort1.IsOpen)//以下為依port的開關狀態來決定UI的動作
                {
                    txtPortStatus.Text = "開啟";
                    txtQuality.Text = "無";
                    txtSTQty.Text = "0";
                    txtSeaLevel.Text = "0";
                    cmbPort.Enabled = false;
                    btnStart.Enabled = false;
                    btnStop.Enabled = true;
                    timer1.Enabled = true;
                }

                else
                {
                    timer1.Enabled = false;
                    txtPortStatus.Text = "關閉";
                    txtQuality.Text = "無";
                    txtSTQty.Text = "0";
                    txtSeaLevel.Text = "0";
                    cmbPort.Enabled = true;
                    btnStart.Enabled = true;
                    btnStop.Enabled = false;
                }

            }

     

     

    5. 在[結束]的button加入一個click event. 如此,將gps的port關閉 

    div style="border-right: #cccccc 1px solid; padding-right: 4px; border-top: #cccccc 1px solid; padding-left: 4px; font-size: 10pt; padding-bottom: 4px; border-left: #cccccc 1px solid; width: 98%; color: #000000; word-break: break-all; line-height: 16px; padding-top: 4px; border-bottom: #cccccc 1px solid; font-family: Verdana,細明體; background-color: #eeeeee">        privatevoidbtnstop_click(objectsender, eventargs e)
            span id="CodeFunction1884_expand_text" style="display: inline">{
                try
                span id="CodeFunction7354_expand_text" style="display: inline">{
                    serialport1.close();//關閉serial port br />                txtPortStatus.Text = "";
                }
                catch(Exception ex)
                span id="CodeFunction3702_expand_text" style="display: inline">{
                    txtPortStatus.Text = ex.Message;
                }
      
                if(serialPort1.IsOpen)//以下為依port的開關狀態來決定UI的動作 br />            span id="CodeFunction5770_expand_text" style="display: inline">{
                    txtPortStatus.Text = "開啟"
                    txtQuality.Text = "無"
                    txtSTQty.Text = "0"
                    txtSeaLevel.Text = "0"
                    cmbPort.Enabled = false
                    btnStart.Enabled = false
                    btnStop.Enabled = true
                    timer1.Enabled = true
                }
                else
                span id="CodeFunction6204_expand_text" style="display: inline">{
                    timer1.Enabled = false
                    txtPortStatus.Text = "關閉"
                    txtQuality.Text = "無"
                    txtSTQty.Text = "0"
                    txtSeaLevel.Text = "0"
                    cmbPort.Enabled = true
                    btnStart.Enabled = true
                    btnStop.Enabled = false
                    btn_upload.Enabled = false
                }
            }/div>

     

     

    6. 在[回報]的Button加入一個click event. 如此回報目前所在位置 

    div style="border-right: #cccccc 1px solid; padding-right: 4px; border-top: #cccccc 1px solid; padding-left: 4px; font-size: 10pt; padding-bottom: 4px; border-left: #cccccc 1px solid; width: 98%; color: #000000; word-break: break-all; line-height: 16px; padding-top: 4px; border-bottom: #cccccc 1px solid; font-family: Verdana,細明體; background-color: #eeeeee">        privatevoidbtn_upload_Click(objectsender, EventArgs e)
            span id="CodeFunction1323_expand_text" style="display: inline">{
                GPSWS.Service1 ws = newGPS.GPSWS.Service1(); //將之前做好的WS加入參考使用 br />            ws.Url="http://localhost/gpsws/service1.asmx";//設定Web Service的位址 br />            try
                span id="CodeFunction7656_expand_text" style="display: inline">{
                    ws.UpdTrace(1, txtLatitude.Text, txtLongitude.Text);//上傳至資料庫 br />            }
                catch(Exception ex)
                span id="CodeFunction2505_expand_text" style="display: inline">{
                    MessageBox.Show(ex.Message);
                }
            }
    /div>

     

     

    7. 在Timer的Tick事件加入判斷從Serial port的GPS訊息,並呈現於UI上 

    div style="border-right: #cccccc 1px solid; padding-right: 4px; border-top: #cccccc 1px solid; padding-left: 4px; font-size: 10pt; padding-bottom: 4px; border-left: #cccccc 1px solid; width: 98%; color: #000000; word-break: break-all; line-height: 16px; padding-top: 4px; border-bottom: #cccccc 1px solid; font-family: Verdana,細明體; background-color: #eeeeee">        privatevoidtimer1_Tick(objectsender, EventArgs e)
            span id="CodeFunction3491_expand_text" style="display: inline">{
                if(serialPort1.IsOpen)
                span id="CodeFunction4457_expand_text" style="display: inline">{
                    stringGPSData = serialPort1.ReadExisting(); //將serialPort所取得的資料存到字串內 br />                txtData.Text = GPSData;//就是顯示在UI下方的那個大TextBox br />                string] gpsArr = GPSData.Split('$');//依$拆成多個字串陣列 br />                for(inti = 0; i < gpsArr.Length; i++)
                    span id="CodeFunction4543_expand_text" style="display: inline">{
                        stringstrTemp = gpsArr[i];
                        string] lineArr = strTemp.Split(',');
                        if(lineArr[0] == "GPGGA"//因為我們只用到GPGGA的訊息,所以其它的訊息不用 br />                    span id="CodeFunction7075_expand_text" style="display: inline">{
                            try
                            span id="CodeFunction5683_expand_text" style="display: inline">{
                                //Latitude 緯度 br />                            Double dLat = Convert.ToDouble(lineArr[2]);
                                dLat = dLat / 100;
                                string] lat = dLat.ToString().Split('.');
                                stringla =(((Convert.ToDouble(lat[1]) / 60)*10000)).ToString("#";
                                for(inta = 0; a < lat[1].Length; a++)
                                span id="CodeFunction9228_expand_text" style="display: inline">{
                                    if(lat[1].Substring(a, 1) == "0"
                                    span id="CodeFunction4374_expand_text" style="display: inline">{
                                        la = "0"+ la;
                                    }
                                    else
                                    span id="CodeFunction2713_expand_text" style="display: inline">{
                                        break
                                    }
                                }
                                Latitude = lat[0].ToString() + "."+ la.Substring(0,6);
      
                                //Longitude 經度 br />                            Double dLon = Convert.ToDouble(lineArr[4]);
                                dLon = dLon / 100;
                                string] lon = dLon.ToString().Split('.');
                                stringlo = (((Convert.ToDouble(lon[1]) / 60)*10000)).ToString("#";
                                for(intb = 0; b < lon[1].Length; b++)
                                span id="CodeFunction9303_expand_text" style="display: inline">{
                                    if(lon[1].Substring(b, 1) == "0"
                                    span id="CodeFunction1344_expand_text" style="display: inline">{
                                        lo = "0"+ lo;
                                    }
                                    else
                                    span id="CodeFunction8133_expand_text" style="display: inline">{
                                        break
                                    }
                                }
                                Longitude = lon[0].ToString() + "."+ lo.Substring(0,6);
      
                                //Display br />                            txtLatitude.Text = Latitude;
                                txtLongitude.Text = Longitude;
                                txtSTQty.Text = lineArr[7]; //衛星數 br />                            txtSeaLevel.Text = lineArr[9];//海平面高度 br />                            switch(lineArr[6])//訊號品質 br />                            span id="CodeFunction4900_expand_text" style="display: inline">{
                                    case"0"
                                        txtQuality.Text = "品質太差"
                                        break
                                    case"1"
                                        txtQuality.Text = "GPS fix(SPS)"
                                        break
                                    case"2"
                                        txtQuality.Text = "DGPS fix"
                                        break
                                    case"3"
                                        txtQuality.Text = "PPS fix"
                                        break
                                    case"4"
                                        txtQuality.Text = "即時性動態測量"
                                        break
                                    case"5"
                                        txtQuality.Text = "Float RTK"
                                        break
                                    case"6"
                                        txtQuality.Text = "Estimated"
                                        break
                                    case"7"
                                        txtQuality.Text = "手動輸入模式"
                                        break
                                    case"8"
                                        txtQuality.Text = "Simulation mode"
                                        break
                                    default
                                        txtQuality.Text = "無"
                                        break
                                }
                                btn_upload.Enabled = true
                            }
                            catch
                            span id="CodeFunction7245_expand_text" style="display: inline">{
                                txtLatitude.Text = "GPS 訊號不足"
                                txtLongitude.Text = "GPS 訊號不足"
                                txtQuality.Text = "無"
                                txtSTQty.Text = "0"
                                txtSeaLevel.Text = "0"
                                btn_upload.Enabled = false
                            }
                        }
                    }
                }
                else
                span id="CodeFunction7526_expand_text" style="display: inline">{
                    txtLatitude.Text = "COM Port 已關閉"
                    txtLongitude.Text = "COM Port 已關閉"
                    txtQuality.Text = "無"
                    txtSTQty.Text = "0"
                    txtSeaLevel.Text = "0"
                    btn_upload.Enabled = false
                }
            }/div>

      以上的Code就完成了GPS的訊號抓取,轉換,呈現與回報資料庫的動作了,當然,一定可以寫的更好,做更多的運用,使用更多來自衛星的訊息,而這些只是用來實作出用VS2005 C#來達成這個想法,所以看起來較簡略,其它就依各自的需求與想法去延伸.當初花了一點時間在找經緯度轉換的公式,而這也是這整個的關鍵所在,所以這一整個程式碼部份,光看GPS訊號相關,其實就從Serial Port抓訊息,轉換與呈現,原本以為很複雜,結果還好

    展开全文
  • GPS定位系统一般来说是一套部署在服务器端可以用来监控管理智能定位终端的GPS等数据信息的综合软件!通常在车辆领域较多,最常见的就是租赁公司的车辆需要安装车辆定位设备,通过这套软件可以实时的查看到车辆的...

    GPS定位系统一般来说是一套部署在服务器端可以用来监控管理智能定位终端的GPS等数据信息的综合软件!通常用在车辆领域较多,最常见的就是租赁公司的车辆需要安装车辆定位设备,通过这套软件可以实时的查看到车辆的位置,是否在安全区域行驶,是否行驶超过里程,是否行驶到外地,危险区域等等!
    在这里插入图片描述
    GPS定位系统还可以有非常多的应用;比如GPS终端加入温度传感器,这样后台服务器端就能获取到GPS的实时数据和温度的实时数据,这就可以给冷链物流运输行业提供非常有效的车辆管理方案,可以实时了解到车辆的温度状态和位置数据,知道车辆温度是否正常,过低过高都会触发报警,可以监控车厢内部是否存在密封泄露的情况,因为如果运输的是药品和新鲜食材的话,车厢内的温度是至关重要的,需要实时的关注!

    还有更多比如油耗,拍照,视频,压力,重量传感器等数据,这将都可以衍生出对应行业的深度应用系统!
    在这里插入图片描述
    GPSBD就是一套专为二次开发而设计的GPS定位系统,这套系统集成了【实时定位】【轨迹回放】【电子围栏】【指令下发】【统计报表】【报警提醒】【多地图切换】【多语言切换】等行业基础核心功能,有了这些功能,就可以方便的在位置行业开发对应的业务层系统,即可实现不同行业的深度解决方案应用!

    GPSBD充分考虑到二次开发过程中的通用性,系统一开始设计就考虑到模块与模块之间解耦关联,模块之间都可以单独开发和替换,避免出现开发过程中出现改动一块地方的代码,而需要去改动全局代码的情况,极大的方便的开发者二次开发的便利性!
    GPSBD系统采用前后端分离的架构,方便多人同时开发,前端人员代码改动跟后端独立分开,后端人员代码改动也不会影响到前端,实现最佳的二次开发结构!

    如果拿一套普通的系统来做二次开发,那将是无比痛苦的,因为一套系统的架构和结构,一开始就定性了是为了什么而设计的;一套专为二次开发而设计的系统从始至终都需要考虑到通用性,因为你不知道这套系统到底是用来开发那个行业的应用的,所以你的功能设计模块设计就必须充分考虑到通用性;并且不能做细致,因为你一旦做细致了,最终开发者拿到手,发现这些功能他都用不上,他想的跟你做的完全不同,那将给开发者带来很大的麻烦,他还要给这些没用的东西去一一删除,如果保留下来,将导致系统存在大量冗余的代码,将导致后期系统运行的存在不稳定性和卡顿的情况!
    在这里插入图片描述
    所以一套专为二次开发而设计的系统将是非常重要的!GPSBD将致力于打造最适合二次开发的GPS定位系统!

    展开全文
  • GPS如何定位你在哪的?

    千次阅读 2013-04-12 10:26:31
    GPS芯片可以内置在各种设备中,包括专用GPS定位仪、汽车、智能手机等等。 2. GPS的定位原理是什么 那么GPS芯片是怎么定位的呢?其基础原理是非常简单的:假设GPS卫星的坐标是已知的,你的位置(x,y,z)是未

    1. GPS是怎么定位的
    GPS的定位是靠GPS接收芯片来定位的,这个芯片可以接收、处理GPS信号,并且输出坐标值,而各个应用就是用这个坐标值在地图上标志你的位置的。GPS芯片可以内置在各种设备中,包括专用GPS定位仪、汽车、智能手机等等。


    2. GPS的定位原理是什么
    那么GPS芯片是怎么定位的呢?其基础原理是非常简单的:假设GPS卫星的坐标是已知的,你的位置(x,y,z)是未知的,那么卫星1(x1,y1,z1)到你的距离公式是:(x1-x)²+(y1-y)²+(z1-z)²=ρ1²
    其中ρ是卫星1到你的距离,也是已知的。这是中小学就学到的数学公式,这里面未知参数只有。根据基础数据原理,我们知道,要解算这三个未知数,必须要有三组这样的公式,即:
    (x1-x)²+(y1-y)2+(z1-z)²=ρ1²
    (x2-x)²+(y2-y)²+(z2-z)²=ρ2²
    (x3-x)²+(y3-y)²+(z3-z)=ρ3²
    也就是说理论上只要3颗卫星就能够定位了。
    但是事实上,卫星到你的位置的距离是很难准确获取的,它受到无线信号传播的各种误差影响,比如电离层、对流层以及GPS其他误差影响。但是我们可以认为不同卫星的这些误差对你的影响是一样的,我们把所有这些改正和误差认为都是∆ρ,这也是一个未知数,那么上面的公式就演变成:
    (x1-x)²+(y1-y)²+(z1-z)²=ρ1²+∆ρ
    (x2-x)²+(y2-y)²+(z2-z)²=ρ2²+∆ρ
    (x3-x)²+(y3-y)²+(z3-z)²=ρ3²+∆ρ
    (x4-x)²+(y4-y)²+(z4-z)²=ρ4²+∆ρ
    这里面的未知数是(x,y,z,∆ρ),要解算这四个数,就必须要至少4颗卫星,这也是为什么GPS定位必须要4颗卫星的原因,当然观测的卫星书越多,就可以分解开,解算越准确。


    3. 为什么GPS卫星位置是已知的
    在GPS定位原理中GPS卫星的位置是已知的,那么它的位置是怎么确定的呢?GPS卫星的位置由地面观测站持续观测,计算其位置,然后把计算后的位置发送到GPS卫星,然后再由GPS卫星通过无线电波广播给全球。
    这就要求地面上有主控站、观测站、注入站等地面设施,并且要求在全球尽可能的平均分布布站,尤其是观测站,这需要全球的部署能力,这也是为什么美国有天然做GPS卫星布设能力的原因,其他国家很难在全局布局。
    同时,GPS卫星的轨道在距离地面2万公里的同步轨道,卫星位置比较固定,并且轨道近似圆形,这也便于观测站确定卫星的位置。


    4. 如何计算卫星到你的距离
    在GPS定位原理中GPS卫星到你的位置的距离是已知的,那么这又是怎么确定的呢?
    在GPS卫星上有原子钟来计时,有振荡器产生一定频率的无线电波;同时在你的GPS芯片上也有时钟计时,并且也有振荡器跟GPS卫星同时产生同样频率的无线电信号,当GPS芯片接受到卫星传来的无线电信号时,就与芯片自身产生的信号做比对,得到信号传播的时间差,再乘以光速就得到了距离。
    一般来说GPS芯片上的时钟是石英钟,跟卫星上的原子钟不是一个量级,这就使GPS芯片不能与GPS卫星完全同步,这也是导致GPS误差的一个因素,叫做时钟误差。
    GPS发出的信号频度不止一种,有300米波长的,也有20厘米左右波长的信号,这就意味着距离计算的误差可能多达300米,也可能误差有20厘米。那么是不是波长越短越好呢?这不一定,因为波长越短,计算复杂度越高,对设备要求越高,看用途而已。一般智能手机用的是什么波长的,我还真没仔细研究,不清楚。


    5. 为什么GPS不能立即定位?
    大家都知道GPS芯片的定位时间是要耗时的,比如智能手机有时需要几分钟才能定位,为什么这么慢呢?上面说了,GPS的距离计算是用卫星信号与GPS芯片产生的同时、同频率的信号偏差来,可是能比对的只是一个波长内的信号偏差,在这个波长偏差之前你并不知道GPS信号已经传播了多少个完整波长,必须要通过连续的卫星信号分析,才能确定出卫星信号传播的准确波长数目,从而精确计算距离,这个计算过程是GPS定位慢的主要耗时原因。
    于此同时,GPS信号在电离层、对流层、钟差等等大量可能的误差,也都是当成未知数通过长时间连续卫星信号分析才能计算出来,这也是GPS定位慢的重要原因。


    6. 如何来加速GPS定位?
    上面说了,GPS定位慢的原因是在计算各种误差参数,假如在100平方公里范围内,我们认为各种误差参数和大部分的信号传播整周是一样的,那么我们在地面上建立一个GPS接收站,它的坐标是已知的,通过长时间定位,计算出这些参数,然后把这些参数发送给周围的GPS芯片,这些GPS芯片就大大节省了结算时间,实现快速定位,这就是A-GPS的原理。


    7. 如何提高定位精度?
    因为距离相近的GPS芯片的各项误差影响因素可以认为是相同的,那么把几个GPS芯片的定位数据求差,就能够去掉大部分误差的影响,这就是差分GPS原理。再通过长时间观测,就能大大的提高定位精度,甚至可以达到毫米级,不过这一般用在测绘等专业领域。
    我们一般智能手机用的定位精度是15米左右级别的,它的定位精度提高一般不用差分方法,而是采用应用策略来提高。比如说,在北京的道路上,道路之间的距离肯定会大于15米,GPS定位数据也是有方向的,那么我们根据GPS芯片距离最近的道路距离,以及GPS方向与道路的方向是否一致,把GPS的坐标纠正到道路上,实现准确定位。


    8. 全球四大卫星导航系统
    另外,现在的定位系统已经不止美国的GPS一家,欧洲的伽利略系统、俄罗斯的Glonass系统、中国的北斗系统都已经具备类似的定位能力。如果定位芯片能够整合多个定位系统也是可以提高定位精度和速度的,比如说最新的小米2就支持GPS和Glonass定位。
    其他提高定位精度的方法,比如观测卫星的选取等,这些比较专业,不说它了,我也说不清楚。


    9. 为何中国要建设自己的卫星定位系统
    GPS卫星系统目前共有30颗左右卫星围着地球转,具体数目不清楚,他们位置在离地面2万公里的同步轨道相对均匀的布设。GPS系统不仅是卫星群,还包括1个主控站,在几大洲都有注入站。给卫星注入各种数据,同时在全球还有密布不少观测站来观测卫星坐标、状态等。
    美国已经发布这个系统30多年了,一直免费全球使用,的确很有战略眼光,也很有经济头脑。虽然这个系统投入巨大且全球免费使用,但是因此带来的经济和战略意义不用多说。另外,上面说了正因为GPS定位靠的是无线信号的比对、计算,因此美国是可能干扰信号的,也就是说能够降低别人的定位精度,而自己的定位精度高。事实上美国也是这么干的,一直到了98年左右才取消了干扰。但是在战时,美国随时可以恢复干扰,这也是为什么欧洲、俄罗斯、中国都要建自己的定位系统的原因。

    展开全文
  • AT+CREG=2 //设置参数,2为返回详细信息,包含基站的地区区域码和基站码 注意:GPRS命令后面都要有回车 AT+CREG?下面为返回值+CREG: 2,1,"5183","66F0"16进制转换第一个参数 5183 Lac:20867第一个参数 66F0 Cid...
  • 在无人驾驶或者机器人系统中,定位方式可以分为两类:以GNSS为代表的绝对定位方式,和以IMU航迹推算为代表的相对定位。两种方式各有优缺点,绝对定位的频率较低,无法满足系统实时性的要求,但是不会产生累计误差;...
  • 如何用GPS找回丢失手机是所在位置

    千次阅读 2012-07-01 16:24:41
    废话不多说了看看今天要学 的东西,今天是学习如何用GPS定位系统找回自己丢失的手机,因为是第一次做,而且仅仅是实现了返回手机所在的经纬度,如果手机真的丢了,其实还是真的找不回来的。。所以呢这个代码...
  • 使用exifread获取手机图片发Gps信息,从而获取到位置信息。 纯当娱乐。
  • 如何用GPS的数据计算距离

    千次阅读 2008-09-26 15:24:00
    但很多地方可以找到前人测出的GPS点和海拔,于是我想要是能把这些难懂的GPS数据转化成清楚明白的位置甚至是一张简单的示意图就好了。(而且有图再拿个指北针也不容易迷路) 在绿野的通讯版我发帖询问,得到了清楚...
  • Android开发地图导航的时候不知道大家有没有遇到过使用getLastKnownLocation()函数返回的location却总是显示为null的情况,本人在这几天就遇到过这问题,妹的。。。足足折磨我一天的时间,太痛苦咯~~,好在经过...
  • ArcGIS for Android Runtime100 基本操作(四)——GPS定位

    万次阅读 热门讨论 2017-09-20 19:15:55
    对于定位大家应该都不陌生,在Android设备里,主要存在GPS定位、网络基站定位、AGPS定位等。在这里我们不对各位定位方式做深入阐述,默认以AGPS来说明下如何在Runtime100实现定位。  其实ArcGIS Runtime系列一直...
  • 近日,涧西区智慧养老服务平台正式投,通过“互联网+居家养老”服务模式,为辖区11万余名60岁以上的老年人提供专业的居家养老服务,包括家政服务、助餐、助浴等,并采取全程“GPS定位+后台追踪”的方式,保证服务...
  • 大多数人在购买儿童手表时,最关心的问题就是:手表...来跟小编一起看下吧~一、七重定位,室外定位最高精度达5米荣耀小K 2对定位功能可谓下足了功夫,支持七重定位,即GPS、北斗卫星、WLAN、基站、室内定位,A-GPS、...
  • 安卓能找到一大波能虚拟定位的工具,那ios如何测试。我们Awareness的开发测试也遇到了这个问题。一开始我设置了园区1号楼和6号楼2个点,但是iphone的后台LBSPush位置偏移比较严重,好难进入围栏,每天都能有1万多步...
  • 批量生产时如何进行GPS生产测试 转载自:https://ask.openluat.com/article/1013 GPS在首次冷启动时,定位时间会较长,如果通过定位与否的方式去进行生产的GPS功能测试的话,不仅花费时间很长,而且不可靠。下面介绍...
  • GPS/LBS定位技术

    2019-09-26 18:48:28
    第一种趋势显而易见,位置服务确实已经成为了iPhone中的一个主要功能(现有超过1000个基于位置的应用程序,Foursquare等应 ...那么这两种趋势间存在什么样紧密的联系,混合定位技术如何为用户提供一个最佳的基于...
  • 安卓能找到一大波能虚拟定位的工具,那ios如何测试。我们Awareness的开发测试也遇到了这个问题。一开始我设置了园区1号楼和6号楼2个点,但是iphone的后台LBSPush位置偏移比较严重,好难进入围栏,每天都能有1万多步...
  • 前面文章介绍如何开发定位器硬件,单片机软件,服务器软件,上位机客户端软件,下面介绍如何使用android studio开发客户端APP显示轨迹。 能自己做的事从来不求人,前面C#实现了PC端显示定位数据轨迹,android...
  • 如何改善GPS的漂移?

    2012-08-29 02:35:00
    GPS飘移一直以来都是令用户和厂家比较头疼的问题,也是很多用户选择GPS的首选指标,好的产品定位精准,漂移很小,那么如何改善GPS的漂移? 1)纯软件方法。 通过软件滤波算法在设备里或者在定位平台上进行过滤。...
  • 如何使用 GPS 接收机

    2009-04-11 23:44:00
    GPS作为野外定位的最佳工具,在户外运动中有广泛的应用,在国内也可以越来越经常地看见有人使用了。 GPS 不象电视或收音机,打开就能,它更象一架相机,你需要有一定的技巧。现在介绍一些 GPS 使用办法和经验。 ...
  • 如何利用USB获得GPS信息

    千次阅读 2019-03-22 20:00:35
    最近在使用GPS定位,想要在自己电脑上得到GPS位置,然后弄个差分组网,提高局部定位精度。 硬件 我使用的是M8N GPS,之前是在无人机上面的 自己搞了个接头把TTL转成了USB 如下图所示: 注意:我的GPS模块使用的...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 131
精华内容 52
关键字:

如何用gps定位