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