專案程式下載:MsGpsSample.zip
從 ComPort 讀取 GPS 訊息
從硬體的角度看,GPS 是透過 ComPort 傳送給應用程式的,因此您可以直接讀取 ComPort 取得 GPS 資訊,但這並不是一個好辦法,因為該應用程式讀取 ComPort 後會造成其他程式無法讀取,因而導致 ComPort 被鎖定的問題。
但是,讀取 ComPort 的方法雖然不應被使用,但是對理解 GPS 程式設計的原理有很大幫助,讀者可以參考下列文章。
微軟手機的 GPS API
許多支援 GPS 的程式開發環境,都會由系統提供衛星定位的 API,以方便應用程式取得衛星座標,像是 iPhone, Android, Windows Mobile 等手機系統,都提供了這樣的 GPS API。
微軟的 Windows Mobile 6.0 中並沒有提供 .NET 版本的 GPS 元件給 C#, VB 等語言使用。於是使用者必須自行對 C++ 所寫的 *.dll 程式加以封裝,以便提供 C#, VB 等語言使用。
要撰寫微軟手機的衛星定位程式,最簡單的方式是參考其 Code Samples for Windows Mobile 範例程式集,這個範例程式集包含了許多有用的 Windows Mobile 6 的範例程式,像是相機、電話、藍芽、簡訊、Direct3D 繪圖、Direct Show 多媒體等範例程式,是學習 Windows Mobile 手機程式設計者的重要資源。
在這個範例程式集當中,包含了一個由 C# 撰寫的衛星 GPS 接收器範例 GPS Application,其中主要包含兩個部分,一個是 Microsoft . WindowsMobile . Samples . Location 這個 GPS 函式庫,該函式庫是將系統的 GPS 功能,透過 System . Runtime . InteropServices 封裝後,提供給 C# 使用,另一個則是 GpsSample 的範例程式,該程式會顯示接收到的 GPS 座標於螢幕上。
想要撰寫微軟手機的衛星定位的應用程式者,只要看懂 GpsSample 的寫法,即可撰寫出很好的衛星程式,不需要詳細理解 Microsoft . WindowsMobile . Samples . Location 函式庫的內容。GpsSample 的主要程式 GpsTest.Form1 ,其程式重點摘要如下。
C# 的 GPS 接收範例
微軟的 Gps 系統函式主要包含 GPSOpenDevice(), GPSCloseDevice(), GPSGetPosition(), GPSGetDeviceState() 等函數,您必須搭配 CreateEvent(), CloseEvent(), WaitForMultipleObjects(), EventModify() 等系統函數,才能順利將其封裝為 C# 元件。以下是 C# 在封裝 GPS 系統函數時所需使用的函數宣告。
#region PInvokes to gpsapi.dll
[DllImport("gpsapi.dll")]
static extern IntPtr GPSOpenDevice(IntPtr hNewLocationData, IntPtr hDeviceStateChange, string szDeviceName, int dwFlags);
[DllImport("gpsapi.dll")]
static extern int GPSCloseDevice(IntPtr hGPSDevice);
[DllImport("gpsapi.dll")]
static extern int GPSGetPosition(IntPtr hGPSDevice, IntPtr pGPSPosition, int dwMaximumAge, int dwFlags);
[DllImport("gpsapi.dll")]
static extern int GPSGetDeviceState(IntPtr pGPSDevice);
#endregion
#region PInvokes to coredll.dll
[DllImport("coredll.dll")]
static extern IntPtr CreateEvent(IntPtr lpEventAttributes, int bManualReset, int bInitialState, StringBuilder lpName);
[DllImport("coredll.dll")]
static extern int CloseHandle(IntPtr hObject);
const int waitFailed = -1;
[DllImport("coredll.dll")]
static extern int WaitForMultipleObjects(int nCount, IntPtr lpHandles, int fWaitAll, int dwMilliseconds);
const int eventSet = 3;
[DllImport("coredll.dll")]
static extern int EventModify(IntPtr hHandle, int dwFunc);
#endregion
}
以上程式對於不熟悉微軟系統 C++ 程式的人而言,會形成一些障礙。還好,微軟在其 Code Samples for Windows Mobile 範例程式集中提供了一個 GPS 接收器範例 GPS Application ,並在該範例中將 GPS 封裝為 Microsoft.WindowsMobile.Samples.Location 專案,您只要將這個專案 (Microsoft.WindowsMobile.Samples.Location.csproj) 加入到您的方案總管中,就可以使用較簡單的方式取的衛星資訊,以下是該專案的檔案畫面,僅供參考。
微軟的 GPS 封裝專案
結語
雖然我們可以利用讀取 ComPort 的方式取得衛星座標訊息,但是應用程式的設計師最好不要這麼作,因為這會讓其他程式無法開啟該 ComPort 以讀取衛星資訊。正確的方法是利用系統所提供的 GPS 函數,利用回呼 (Callback) 的方式取得 GPS 座標,才不會霸佔 ComPort 而造成其他程式的問題。
微軟手機雖然支援 GPS 功能,但卻沒有將 GPS 功能封裝入 .NET Compact Framework 當中,因此必須使用 C# 撰寫系統呼叫的方式執行,這畢竟是一大缺點,希望微軟能有所改進,否則市場當然會被 Android 與 iPhone 不斷侵吞, Windows Mobile 手機的佔有率也正在不斷下降當中。
程式範例
private EventHandler updateDataHandler;
GpsDeviceState device = null;
GpsPosition position = null;
Gps gps = new Gps();
private void Form1_Load(object sender, System.EventArgs e) {
updateDataHandler = new EventHandler(UpdateData);
...
gps.DeviceStateChanged += new DeviceStateChangedEventHandler(gps_DeviceStateChanged);
gps.LocationChanged += new LocationChangedEventHandler(gps_LocationChanged);
}
protected void gps_LocationChanged(object sender, LocationChangedEventArgs args) {
position = args.Position;
// call the UpdateData method via the updateDataHandler so that we update the UI on the UI thread
Invoke(updateDataHandler);
}
void gps_DeviceStateChanged(object sender, DeviceStateChangedEventArgs args) {
device = args.DeviceState;
// call the UpdateData method via the updateDataHandler so that we update the UI on the UI thread
Invoke(updateDataHandler);
}
void UpdateData(object sender, System.EventArgs args) {
if (gps.Opened) {
...
顯示 device.ServiceSate , device.DeviceState
顯示 position.Latitude, position.LatitudeInDegreesMinutesSeconds
顯示 position.Longitude, position.LongitudeInDegreesMinutesSeconds
顯示 position.Longitude, position.LongitudeInDegreesMinutesSeconds
if (position.SatellitesInSolutionValid&&position.SatellitesInViewValid&&position.SatelliteCountValid) {
顯示 position.GetSatellitesInSolution().Length
position.GetSatellitesInView().Length
position.SatelliteCount
position.Time.ToString()
}
}
private void Form1_Closed(object sender, System.EventArgs e) {
if (gps.Opened) gps.Close();
}
在上述程式中,請務必用 Invoke 的方式取得衛星訊息,否則會導致系統當機,這是因為 Windows Mobile 的視窗本身無法完整的支援多執行緒功能,因此需要利用 Invoke 函數能讓視窗程式能正確被 GPS 回呼函數所呼叫。
以下是 GpsSample 的完整程式,有興趣者可詳細閱讀之。
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Data;
using Microsoft.WindowsMobile.Samples.Location;
namespace GpsTest
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.MenuItem exitMenuItem;
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.Label status;
private MenuItem menuItem2;
private MenuItem startGpsMenuItem;
private MenuItem stopGpsMenuItem;
private EventHandler updateDataHandler;
GpsDeviceState device = null;
GpsPosition position = null;
Gps gps = new Gps();
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.exitMenuItem = new System.Windows.Forms.MenuItem();
this.menuItem2 = new System.Windows.Forms.MenuItem();
this.startGpsMenuItem = new System.Windows.Forms.MenuItem();
this.stopGpsMenuItem = new System.Windows.Forms.MenuItem();
this.status = new System.Windows.Forms.Label();
//
// mainMenu1
//
this.mainMenu1.MenuItems.Add(this.exitMenuItem);
this.mainMenu1.MenuItems.Add(this.menuItem2);
//
// exitMenuItem
//
this.exitMenuItem.Text = "Exit";
this.exitMenuItem.Click += new System.EventHandler(this.exitMenuItem_Click);
//
// menuItem2
//
this.menuItem2.MenuItems.Add(this.startGpsMenuItem);
this.menuItem2.MenuItems.Add(this.stopGpsMenuItem);
this.menuItem2.Text = "GPS";
//
// startGpsMenuItem
//
this.startGpsMenuItem.Text = "Start GPS";
this.startGpsMenuItem.Click += new System.EventHandler(this.startGpsMenuItem_Click);
//
// stopGpsMenuItem
//
this.stopGpsMenuItem.Enabled = false;
this.stopGpsMenuItem.Text = "Stop GPS";
this.stopGpsMenuItem.Click += new System.EventHandler(this.stopGpsMenuItem_Click);
//
// status
//
this.status.Location = new System.Drawing.Point(0, 0);
this.status.Size = new System.Drawing.Size(237, 173);
this.status.Text = "label1";
//
// Form1
//
this.ClientSize = new System.Drawing.Size(240, 268);
this.Controls.Add(this.status);
this.Menu = this.mainMenu1;
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.Closed += new System.EventHandler(this.Form1_Closed);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
Application.Run(new Form1());
}
private void exitMenuItem_Click(object sender, EventArgs e)
{
if (gps.Opened)
{
gps.Close();
}
Close();
}
private void Form1_Load(object sender, System.EventArgs e)
{
updateDataHandler = new EventHandler(UpdateData);
status.Text = "";
status.Width = Screen.PrimaryScreen.WorkingArea.Width;
status.Height = Screen.PrimaryScreen.WorkingArea.Height;
gps.DeviceStateChanged += new DeviceStateChangedEventHandler(gps_DeviceStateChanged);
gps.LocationChanged += new LocationChangedEventHandler(gps_LocationChanged);
}
protected void gps_LocationChanged(object sender, LocationChangedEventArgs args)
{
position = args.Position;
// call the UpdateData method via the updateDataHandler so that we
// update the UI on the UI thread
Invoke(updateDataHandler);
}
void gps_DeviceStateChanged(object sender, DeviceStateChangedEventArgs args)
{
device = args.DeviceState;
// call the UpdateData method via the updateDataHandler so that we
// update the UI on the UI thread
Invoke(updateDataHandler);
}
void UpdateData(object sender, System.EventArgs args)
{
if (gps.Opened)
{
string str = "";
if (device != null)
{
str = device.FriendlyName + " " + device.ServiceState + ", " + device.DeviceState + "\n";
}
if (position != null)
{
if (position.LatitudeValid)
{
str += "Latitude (DD):\n " + position.Latitude + "\n";
str += "Latitude (D,M,S):\n " + position.LatitudeInDegreesMinutesSeconds + "\n";
}
if (position.LongitudeValid)
{
str += "Longitude (DD):\n " + position.Longitude + "\n";
str += "Longitude (D,M,S):\n " + position.LongitudeInDegreesMinutesSeconds + "\n";
}
if (position.SatellitesInSolutionValid &&
position.SatellitesInViewValid &&
position.SatelliteCountValid)
{
str += "Satellite Count:\n " + position.GetSatellitesInSolution().Length + "/" +
position.GetSatellitesInView().Length + " (" +
position.SatelliteCount + ")\n";
}
if (position.TimeValid)
{
str += "Time:\n " + position.Time.ToString() + "\n";
}
}
status.Text = str;
}
}
private void Form1_Closed(object sender, System.EventArgs e)
{
if (gps.Opened)
{
gps.Close();
}
}
private void stopGpsMenuItem_Click(object sender, EventArgs e)
{
if (gps.Opened)
{
gps.Close();
}
startGpsMenuItem.Enabled = true;
stopGpsMenuItem.Enabled = false;
}
private void startGpsMenuItem_Click(object sender, EventArgs e)
{
if (!gps.Opened)
{
gps.Open();
}
startGpsMenuItem.Enabled = false;
stopGpsMenuItem.Enabled = true;
}
}
}
Post preview:
Close preview