(1)綜合運用以前學到的控制語句、繼承、封裝、接口等知識,完成具有實際運用功能的程序。
(2)通過運用學過的知識進一步的鞏固和掌握學到的知識。
實驗內(nèi)容
使用GPS GATE軟件模擬GPS衛(wèi)星發(fā)出的GPS信號,編寫程序?qū)PS GATE發(fā)出的信息進行接收、解析、處理。將處理好的信息按照固定的格式存儲至文件中(經(jīng)度、緯度、時間、速度、高度)。下面是主要用到的GPS信息的格式:
1. GPS/TRANSIT Data(RMC)推薦定位信息 (在項目中就使用了這個報文的定位數(shù)據(jù))
$GPRMC,(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)*hh(CR)(LF)
(1)UTC時間,hhmmss(時分秒)格式
(2)定位狀態(tài),A=有效定位,V=無效定位
(3)緯度ddmm.mmmm(度分)格式(前面的0也將被傳輸)
(4)緯度半球N(北半球)或S(南半球)
(5)經(jīng)度dddmm.mmmm(度分)格式(前面的0也將被傳輸)
(6)經(jīng)度半球E(東經(jīng))或W(西經(jīng))
(7)地面速率(000.0~999.9節(jié),前面的0也將被傳輸)
(8)地面航向(000.0~359.9度,以真北為參考基準,前面的0也將被傳輸) (9)UTC日期,ddmmyy(日月年)格式
(10)磁偏角(000.0~180.0度,前面的0也將被傳輸)
(11)磁偏角方向,E(東)或W(西)
(12)模式指示(僅NMEA0183 3.00版本輸出,A=自主定位,D=差分,E=估算,N=數(shù)據(jù)無效)
(13) hh(CR)(LF) hh是前面從第一個數(shù)據(jù)到最后一個數(shù)據(jù)的校驗和,(CR)(LF)是回車換行,表示一個字符串的結(jié)束。
具體示例:
$GPRMC,121212.456,A,3232.1234,N,12121.3322,W,0.15,305.12,121299, ,*22
2. GPS Fix Data(GGA)GPS定位信息
$GPGGA,(1),(2),(3),(4),(5),(6),(7),(8),(9),M,(10),M,(11),(12)*hh(CR)(LF)
(1)UTC時間,hhmmss(時分秒)格式
(2)緯度ddmm.mmmm(度分)格式(前面的0也將被傳輸)
(3)緯度半球N(北半球)或S(南半球)
(4)經(jīng)度dddmm.mmmm(度分)格式(前面的0也將被傳輸)
(5)經(jīng)度半球E(東經(jīng))或W(西經(jīng))
(6)GPS狀態(tài):0=未定位,1=非差分定位,2=差分定位,6=正在估算
(7)正在使用解算位置的衛(wèi)星數(shù)量(00~12)(前面的0也將被傳輸)
(8)HDOP水平精度因子(0.5~99.9)
(9)海拔高度(-9999.9~99999.9)
(10)地球橢球面相對大地水準面的高度
(11)差分時間(從最近一次接收到差分信號開始的秒數(shù),如果不是差分定位將為空)
(12)差分站ID號0000~1023(前面的0也將被傳輸,如果不是差分定位將為空)
(13) hh(CR)(LF) hh是前面從第一個數(shù)據(jù)到最后一個數(shù)據(jù)的校驗和,(CR)(LF)是回車換行,表示一個字符串的結(jié)束。
具體示例:
$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F
校驗和是指這一行的所有非數(shù)字字符,按照"字母、空格、句點、正號= 0;負號=1"的規(guī)則換算成0和1后,將這一行中原來的全部數(shù)字加起來,以10為模計算后所得的和。校驗和可以檢查出90%的數(shù)據(jù)存儲或傳送錯誤。按十進制加起來的個位數(shù)字的校驗和,用于精確糾正誤差。
使用異常處理,對接收信息的過程中可能產(chǎn)生的異常進行處理。(選作)編寫C#Winfrom 程序,能夠?qū)⒔馕龅降臄?shù)據(jù)軌跡繪制到相應(yīng)的地圖上。
詳細設(shè)計
該實現(xiàn)主要是實踐我在前文《談?wù)勎姨幚懋惓5囊话惴椒ā分刑岢龅囊恍┯^點,以及一些面向?qū)ο笤O(shè)計的思想,和一些設(shè)計模式的運用。
在看到這個問題的時候,首先要對需求進行分析。該問題的數(shù)據(jù)流程比較清晰,見Figure 1。
Figure 1 數(shù)據(jù)流
從個人經(jīng)驗來講,我認為在指令的解析部分可以抽象出一個比較通用的方式,我們把GPS指令文本流替換為二進制流,將單條指令文本替換為單條指令的二進制信息。
Figure 2 通用數(shù)據(jù)流
我做的抽象框架如所示Figure 3 類視圖所示。
Figure 3 類視圖
首先我們應(yīng)當有一個類來處理整個問題,稱之為CommandProcessor。它負責從外部設(shè)備接收數(shù)據(jù)、并且將處理后的消息通過事件的方式向外部提供。該類還應(yīng)當能夠?qū)螚l指令從流中取出來,因而我在這個類中提供了一個虛的方法GetSingleCommandString(),并且進行了一個通用的簡單實現(xiàn)、即每行為一條指令。這里參數(shù)都用string來表示了,用string來表示二進制數(shù)據(jù)也沒太大問題,但對于純文本的數(shù)據(jù)處理起來則比較方便。
在將命令分出來之后,我們需要將一條單獨的指令轉(zhuǎn)換為一個強類型的命令。我們用ICommand來表示一個被分析后的命令,則問題就是如何由一個string產(chǎn)生一個ICommand。
根據(jù)面向?qū)ο笤O(shè)計的一些原則,數(shù)據(jù)與實現(xiàn)應(yīng)都放在一個類當中。那么對于特殊的Command,它自身才知道如何對它對應(yīng)的string進行分析。我們在ICommand中定義Parse(string s)方法對string進行分析。
ICommand的定義如下,關(guān)于DoCommand()方法在后文將有說明。
01
publicinterfaceICommand
02
{
03
04
///
05
/// If it is right command,return true
06
///
07
///
08
///
09
boolParse(stringstr);
10
voidDoCommand();
11
}
那么如何找到string->ICommand的對應(yīng)關(guān)系呢?
比較簡單的實現(xiàn)是我們維護一個ICommand的列表,然后用窮舉的方式調(diào)用Parse方法,直到該方法返回true。如果沒有方法返回true,則表示該string是一個未知的命令。這樣實現(xiàn)是比較OO,在編程階段添加ICommand比較方便。缺點是效率比較低,而且還需要維護一個已經(jīng)實例化ICommand的列表。而有這樣一個列表,在多線程編程時就會產(chǎn)生一些同步問題。在編程時需要在Parse(string s)方法中先對傳入的string做一個初步檢測,如果非該指令則立即返回false。而在當前的設(shè)計中,沒法對子類重寫Parse方法內(nèi)的內(nèi)容進行約束,如果實現(xiàn)不好則會更大的造成效率的降低。
我現(xiàn)在的實現(xiàn)是,引入一個ICommandFactory對象,那么這個問題就成為了string->ICommandFactory->ICommand的問題了,由于實現(xiàn)接口ICommandFactory與ICommand都是同一開發(fā)者實現(xiàn)的,該開發(fā)者可以分析string的特殊性,在實現(xiàn)ICommandFactory接口的類中找到string->ICommand的關(guān)系。還有一個優(yōu)點是,我們完全可以在自己定義的CommandFactory中實現(xiàn)在上一段提到的方法。以下是該部分具體的實現(xiàn)代碼。
publicinterfaceICommandFactory
2
{
3
///
4
/// return an ICommand object from a string.
5
///
6
///
7
///
8
ICommand Parse(stringstr);
9
}
//實現(xiàn)該接口的一個具體示例
publicclassGPSCommandFactory : ICommandFactory
02
{
03
publicIUnityContainer UnityContainer { get; set; }
04
05
privateICommand Parse
06
where C : ICommand
07
{
08
var cmd = UnityContainer.Resolve
09
if(!cmd.Parse(str))
10
{
11
thrownewInvalidCommandStringException(str, null);
12
}
13
returncmd;
14
}
15
16
#region ICommandFactory Members
17
18
publicICommand Parse(stringstr)
19
{
20
try
21
{
22
stringcommand;
23
command = str.Substring(0, str.IndexOf(','));
24
switch(command)
25
{
26
case"$GPRMC":
27
returnParse
28
case"$GPGGA":
29
returnParse
30
}
31
thrownewInvalidCommandStringException(str, null);
32
}
33
catch(ArgumentOutOfRangeException ex)
34
{
35
…
36
}
37
}
38
39
#endregion
40
}
在解決完命令分析的問題之后,最后的一個問題是如何將分析出的命令應(yīng)用的問題。在這個設(shè)計中,我通過事件來通知其他類。
在前文給出ICommand的描述中給出了一個DoCommand()方法。我們可以在需要應(yīng)用的項目中重寫該方法,做出相應(yīng)的動作。這樣就完全的實現(xiàn)了多態(tài),在接收到CommandReceived事件的消息后直接調(diào)用DoCommand()方法就好了,不需要對Command類型進行顯示分析。當然,我們需要借助IOC容器來進行這樣的實現(xiàn)。這里不闡述IOC容器的具體功能,有興趣Google下就好啦。當然這樣實現(xiàn)也許還是有一些復(fù)雜性的,我們需要對每一個Command類進行重寫,然后更改IOC容器的映射關(guān)系。
還有一種實現(xiàn)是使用is操作,對ICommand對象進行測試,然后將ICommand中的信息進行利用。下面給出第二種實現(xiàn)方法的一個示例。
01voidCommandProcessor_CommandRecevied(objectsender, CommandEventArgs e)
02
{
03
Invoke(newThreadStart(delegate()
04
{
05
if(e.Command isGPGGACommmand)
06
{
07
ProcessCommand((GPGGACommmand)e.Command);
08
}
09
if(e.Command isGPRMCommand)
10
{
11
ProcessCommand((GPRMCommand)e.Command);
12
}
13
}));
14
15
//throw new NotImplementedException();
16
}
這樣一個基本的命令分析器的Library工程就基本寫完了。然后就應(yīng)當著手解決GPS消息的問題了。在前面的示例中已經(jīng)貼了一些實現(xiàn)。
分析GPS的消息,很容易的發(fā)現(xiàn)每條命令是以$\.+?, 方式開始的,我們可以根據(jù)這樣的特性實現(xiàn)ICommandFactory,這里再貼一下主要的實現(xiàn)代碼
1
stringcommand;
2
command = str.Substring(0, str.IndexOf(','));
3
switch(command)
4
{
5
case"$GPRMC":
6
returnParse
7
case"$GPGGA":
8
returnParse
9
}
然后就可以產(chǎn)生Command了。我們根據(jù)要求建立了兩個Command,分別是GPRMCommand和GPGGACommmand。然后根據(jù)對應(yīng)的命令格式重寫Parse()方法。而消息分割正好是每行一條命令的方式,因而就無需重寫CommandProcessor的GetSingleCommand方法了。然后就完了,不需要寫什么了,基本的東西都已經(jīng)在我們之前談到的項目中定義好了。
具體視圖如下:
Figure 4 GPS類視圖
如果您有興趣,可以在這里下載代碼,如果有問題歡迎與我聯(lián)系:)
http://loningproject.googlecode.com/svn/trunk/cnblogs/gps.7z