Click here to Skip to main content
15,886,873 members
Articles / Mobile Apps / Windows Mobile

Track your position using a Windows Mobile 6 device

Rate me:
Please Sign up or sign in to vote.
4.59/5 (23 votes)
29 Dec 2008CPOL5 min read 130.9K   3.2K   111  
Track your geographical position using a Windows Mobile 6 device and some Google Map programming.
using System;
using System.Runtime.InteropServices;
using System.Collections;
using System.Text;


namespace PositionSenderMobile.GPS
{
    public delegate void LocationChangedEventHandler(object sender, LocationChangedEventArgs args);
    public delegate void DeviceStateChangedEventHandler(object sender, DeviceStateChangedEventArgs args);

    /// <summary>
    /// Summary description for GPS.
    /// </summary>
    public class Gps
    {
        // handle to the gps device
        IntPtr gpsHandle = IntPtr.Zero;

        // handle to the native event that is signalled when the GPS
        // devices gets a new location
        IntPtr newLocationHandle = IntPtr.Zero;

        // handle to the native event that is signalled when the GPS
        // device state changes
        IntPtr deviceStateChangedHandle = IntPtr.Zero;

        // handle to the native event that we use to stop our event
        // thread
        IntPtr stopHandle = IntPtr.Zero;

        // holds our event thread instance
        System.Threading.Thread gpsEventThread = null;


        event LocationChangedEventHandler locationChanged;

        /// <summary>
        /// Event that is raised when the GPS locaction data changes
        /// </summary>
        public event LocationChangedEventHandler LocationChanged
        {
            add
            {
                locationChanged += value;

                // create our event thread only if the user decides to listen
                CreateGpsEventThread();
            }
            remove
            {
                locationChanged -= value;
            }
        }


        event DeviceStateChangedEventHandler deviceStateChanged;

        /// <summary>
        /// Event that is raised when the GPS device state changes
        /// </summary>
        public event DeviceStateChangedEventHandler DeviceStateChanged
        {
            add
            {
                deviceStateChanged += value;

                // create our event thread only if the user decides to listen
                CreateGpsEventThread();
            }
            remove
            {
                deviceStateChanged -= value;
            }
        }

        /// <summary>
        /// True: The GPS device has been opened. False: It has not been opened
        /// </summary>
        public bool Opened
        {
            get { return gpsHandle != IntPtr.Zero; }
        }

        public Gps()
        {
        }

        ~Gps()
        {
            // make sure that the GPS was closed.
            Close();
        }

        /// <summary>
        /// Opens the GPS device and prepares to receive data from it.
        /// </summary>
        public void Open()
        {
            if (!Opened)
            {
                // create handles for GPS events
                newLocationHandle = CreateEvent(IntPtr.Zero, 0, 0, null);
                deviceStateChangedHandle = CreateEvent(IntPtr.Zero, 0, 0, null);
                stopHandle = CreateEvent(IntPtr.Zero, 0, 0, null);

                gpsHandle = GPSOpenDevice(newLocationHandle, deviceStateChangedHandle, null, 0);

                // if events were hooked up before the device was opened, we'll need
                // to create the gps event thread.
                if (locationChanged != null || deviceStateChanged != null)
                {
                    CreateGpsEventThread();
                }
            }
        }

        /// <summary>
        /// Closes the gps device.
        /// </summary>
        public void Close()
        {
            if (gpsHandle != IntPtr.Zero)
            {
                GPSCloseDevice(gpsHandle);
                gpsHandle = IntPtr.Zero;
            }

            // Set our native stop event so we can exit our event thread.
            if (stopHandle != IntPtr.Zero)
            {
                EventModify(stopHandle, eventSet);
            }

            // block until our event thread is finished before
            // we close our native event handles
            lock (this)
            {
                if (newLocationHandle != IntPtr.Zero)
                {
                    CloseHandle(newLocationHandle);
                    newLocationHandle = IntPtr.Zero;
                }

                if (deviceStateChangedHandle != IntPtr.Zero)
                {
                    CloseHandle(deviceStateChangedHandle);
                    deviceStateChangedHandle = IntPtr.Zero;
                }

                if (stopHandle != IntPtr.Zero)
                {
                    CloseHandle(stopHandle);
                    stopHandle = IntPtr.Zero;
                }
            }
        }

        /// <summary>
        /// Get the position reported by the GPS receiver
        /// </summary>
        /// <returns>GpsPosition class with all the position details</returns>
        public GpsPosition GetPosition()
        {
            return GetPosition(TimeSpan.Zero);
        }


        /// <summary>
        /// Get the position reported by the GPS receiver that is no older than
        /// the maxAge passed in
        /// </summary>
        /// <param name="maxAge">Max age of the gps position data that you want back. 
        /// If there is no data within the required age, null is returned.  
        /// if maxAge == TimeSpan.Zero, then the age of the data is ignored</param>
        /// <returns>GpsPosition class with all the position details</returns>
        public GpsPosition GetPosition(TimeSpan maxAge)
        {
            GpsPosition gpsPosition = null;
            if (Opened)
            {
                // allocate the necessary memory on the native side.  We have a class (GpsPosition) that 
                // has the same memory layout as its native counterpart
                IntPtr ptr = Utils.LocalAlloc(Marshal.SizeOf(typeof(GpsPosition)));

                // fill in the required fields 
                gpsPosition = new GpsPosition();
                gpsPosition.dwVersion = 1;
                gpsPosition.dwSize = Marshal.SizeOf(typeof(GpsPosition));

                // Marshal our data to the native pointer we allocated.
                Marshal.StructureToPtr(gpsPosition, ptr, false);

                // call native method passing in our native buffer
                int result = GPSGetPosition(gpsHandle, ptr, 500000, 0);
                if (result == 0)
                {
                    // native call succeeded, marshal native data to our managed data
                    gpsPosition = (GpsPosition)Marshal.PtrToStructure(ptr, typeof(GpsPosition));

                    if (maxAge != TimeSpan.Zero)
                    {
                        // check to see if the data is recent enough.
                        if (!gpsPosition.TimeValid || DateTime.Now - maxAge > gpsPosition.Time)
                        {
                            gpsPosition = null;
                        }
                    }
                }

                // free our native memory
                Utils.LocalFree(ptr);
            }

            return gpsPosition;            
        }

        /// <summary>
        /// Queries the device state.
        /// </summary>
        /// <returns>Device state information</returns>
        public GpsDeviceState GetDeviceState()
        {
            GpsDeviceState device = null;

            // allocate a buffer on the native side.  Since the
            IntPtr pGpsDevice = Utils.LocalAlloc(GpsDeviceState.GpsDeviceStructureSize);
            
            // GPS_DEVICE structure has arrays of characters, it's easier to just
            // write directly into memory rather than create a managed structure with
            // the same layout.
            Marshal.WriteInt32(pGpsDevice, 1);	// write out GPS version of 1
            Marshal.WriteInt32(pGpsDevice, 4, GpsDeviceState.GpsDeviceStructureSize);	// write out dwSize of structure

            int result = GPSGetDeviceState(pGpsDevice);

            if (result == 0)
            {
                // instantiate the GpsDeviceState class passing in the native pointer
                device = new GpsDeviceState(pGpsDevice);
            }

            // free our native memory
            Utils.LocalFree(pGpsDevice);

            return device;
        }

        /// <summary>
        /// Creates our event thread that will receive native events
        /// </summary>
        private void CreateGpsEventThread()
        {
            // we only want to create the thread if we don't have one created already 
            // and we have opened the gps device
            if (gpsEventThread == null && gpsHandle != IntPtr.Zero)
            {
                // Create and start thread to listen for GPS events
                gpsEventThread = new System.Threading.Thread(new System.Threading.ThreadStart(WaitForGpsEvents));
                gpsEventThread.Start();
            }
        }

        /// <summary>
        /// Method used to listen for native events from the GPS. 
        /// </summary>
        private void WaitForGpsEvents()
        {
            lock (this)
            {
                bool listening = true;
                // allocate 3 handles worth of memory to pass to WaitForMultipleObjects
                IntPtr handles = Utils.LocalAlloc(12);

                // write the three handles we are listening for.
                Marshal.WriteInt32(handles, 0, stopHandle.ToInt32());
                Marshal.WriteInt32(handles, 4, deviceStateChangedHandle.ToInt32());
                Marshal.WriteInt32(handles, 8, newLocationHandle.ToInt32());

                while (listening)
                {
                    int obj = WaitForMultipleObjects(3, handles, 0, -1);
                    if (obj != waitFailed)
                    {
                        switch (obj)
                        {
                            case 0:
                                // we've been signalled to stop
                                listening = false;
                                break;
                            case 1:
                                // device state has changed
                                if (deviceStateChanged != null)
                                {
                                    deviceStateChanged(this, new DeviceStateChangedEventArgs(GetDeviceState()));
                                }
                                break;
                            case 2:
                                // location has changed
                                if (locationChanged != null)
                                {
                                    locationChanged(this, new LocationChangedEventArgs(GetPosition()));
                                }
                                break;
                        }
                    }
                }

                // free the memory we allocated for the native handles
                Utils.LocalFree(handles);

                // clear our gpsEventThread so that we can recreate this thread again
                // if the events are hooked up again.
                gpsEventThread = null;
            }
        }

        #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

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
I'm a software engineer, working in this field since May 1989. Currently doing C# .NET development and project management using SCRUM.

Comments and Discussions