Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

The Win7 Sensor and Location API: Introduction to Working with Location in .NET 4 Beta 2

Pete Brown - 23 January 2010

Windows 7 introduced the Sensor and Location API. I’ve previously blogged about the sensor API, but couldn’t wait to get into the Location side of it.

Location API Overview

The location API is a specialized part of the overall sensor API. Many portable devices, laptops and tablets are starting to include GPS and WIFI equipment, both of which can be used to idenfity your current location. Given the ubiquity and interest in it, location was important enough to break out from the rest of the API to enhance usability and discoverability.

image 

(image source MSDN)

There are multiple flavors of location data. First is the default location data set in Windows 7. If you write an application that uses the Location API, and the user has no location devices, and has not previously entered a default location, they will be automatically prompted:

image

image

The default location is the fallback location for sensor-less machines. It also tends to work best on stationary PCs, as you’d have to update the address information otherwise. The second type of information is coordinate (latitude/longitude) information. We’ll cover both after we deal with initializing the device.

Starting a Provider (or Watcher)

To use the location API, you simply create a new GeoLocationProvider (changed in RC/RTW, see note at the end of this post) and hook the appropriate events. The first event of interest is the StatusChanged event. That will tell you if the device is ready, or you lose signal etc.

GeoLocationProvider provider = new GeoLocationProvider();

provider.StatusChanged += (s, ev) =>
    {
        StatusText.Text = ev.Status.ToString();
    };

    // other events here

StatusText.Text = "Waiting for data...";

provider.Start();

To start reporting data, call the Start method.

Once you have the device wired up and ready, you may want to capture the address information.

Civic Address Location

Accessing the address location information is pretty simple. In Beta 2, use the LocationChanged event on the GeoLocationProvider.

provider.LocationChanged += (s, ev) =>
    {
        StringBuilder builder = new StringBuilder();

        builder.AppendFormat("Address1: {0}\n", ev.Location.Address.AddressLine1);
        builder.AppendFormat("Address2: {0}\n", ev.Location.Address.AddressLine2);
        builder.AppendFormat("Building: {0}\n", ev.Location.Address.Building);
        builder.AppendFormat("FloorLevel: {0}\n", ev.Location.Address.FloorLevel);
        builder.AppendFormat("City: {0}\n", ev.Location.Address.City);
        builder.AppendFormat("StateProvince: {0}\n", ev.Location.Address.StateProvince);
        builder.AppendFormat("CountryRegion: {0}\n", ev.Location.Address.CountryRegion);
        builder.AppendFormat("PostalCode: {0}\n", ev.Location.Address.PostalCode);

        LocationText.Text = builder.ToString();

        UpdateDrawing(ev.Location);
    };

On my machine, since I only have the zip code in the default address, that is all that is reported here.

More interesting than address, for this example anyway, is the coordinate location returned from the GPS.

Coordinate Location

Coordinate location (lat/long) is what I was most interested in playing with. It’s important to note, though, that both lat/long and address are part of the same overall location API, and can be used in similar ways in your own applications.

image If you’re looking for a GPS that will work with the API, pick up a copy of Microsoft Streets and Trips 2010 – the one with the black UBlox GPS included. You can then download the Win7 driver for that from http://www.ublox.com/en/usb-drivers/windows-7-driver.html

For my own sample application, that’s what I used. The device reports lat/long and timestamp as you move about. If you use it, I recommend using the extension USB cable (or better yet, get an extension that is both longer and has a firmer grip) in order to minimize interference from the laptop. Some laptops emit enough interference that it makes it next to impossible to get a signal. The reason I recommend a cable with a firmer grip is because the GPS dongle easily pulls out of the extension cord. You could use tape or something to remedy that, but as I mention below, you’ll probably want a longer cable anyway.

image The reason you’ll want a longer cable is to allow you to stick the sensor (assuming this is in your car) up on your dash or window and keep the laptop somewhere like the console or the seat next to you. The little cable that comes with streets & trips is barely long enough for that in my Odyssey.

The other thing you’ll want to do is get some sort of velcro or sticky back to keep the usb dongle up on your dash. The device weighs almost nothing and the USB cable easily drags it down off the dash – especially when you’re flying around the corner, not watching where you’re going because you’re looking down at the screen on your tablet while your son in the back seat is asking what the heck you’re doing with that computer in the car ;)

Anyway…

Getting lat and long is really simple. My main bit of advice for you here is to try and minimize the churn by making sure you’re far enough away from the last reported lat/long to care about it. This is important when plotting points or otherwise working with the data. The GPS reports lat/long so often that unless you’re on the highway going 100mph (again, not that I’d do that, heh) the distance changes can be pretty minute.

Back to the LocationChanged event, plug in this code to get the coordinates, heading and speed (the ublox dongle only reports the coordinates, you can calc heading and speed)

builder.AppendFormat("Latitude: {0}\n", ev.Location.Coordinate.Latitude);
builder.AppendFormat("Longitude: {0}\n", ev.Location.Coordinate.Longitude);
builder.AppendFormat("Heading: {0}\n", ev.Location.Heading);
builder.AppendFormat("Speed: {0}\n", ev.Location.Speed);

So, I put that all together and built a little WPF 4 application that shows your position on a grid. I inverted my Y axis, unfortunately (math kills me every time), but otherwise it worked great. I call it “GPS Etch-a-Sketch”

image

I put the Dell XT2 tablet in the Odyssey and drove my son to his gym class at Rolly Pollies. On the way back, I captured the activity in Camtasia, and also snagged this screenshot. On this machine, I have no default location information (not even a zip code) but was not prompted for any since I did have a location reporting device attached.

The red half-moon in the middle of the track is a rendering artifact. I think it’s the video driver in this Dell, or it could be a result of running this with Camtasia on.

Here’s the video (available starting around 2:30pm 1/23/2010), sped up to 800% so you didn’t have to live through 20 minutes of driving, including all the stop lights and the Arby’s drive through (with the speed-up, I couldn’t resist sticking the Benny Hill music in there <g>)

Here’s the code I used for plotting the points (lines, actually) on the window

private void UpdateDrawing(GeoLocation location)
{
    if (!double.IsNaN(location.Coordinate.Latitude) && !double.IsNaN(location.Coordinate.Longitude))
    {
        if (!_firstReading)
        {
            // plot a line from last point to this one.
            Point plotPoint = TranslateLatLongToScreenPoint(location.Coordinate.Latitude, location.Coordinate.Longitude);


            // avoid plotting the same 1pt lines over and over
            if ((int)Math.Round(plotPoint.X) != (int)Math.Round(_previousPoint.X) &&
                (int)Math.Round(plotPoint.Y) != (int)Math.Round(_previousPoint.Y))
            {
                Line line = new Line();

                line.X1 = _previousPoint.X;
                line.Y1 = _previousPoint.Y;
                line.X2 = plotPoint.X;
                line.Y2 = plotPoint.Y;

                // TODO: vary thickness or color by speed. If not varying, can use a polyline instead
                line.StrokeThickness = 3;
                line.Stroke = Brushes.Green;

                // add the line to the plot area
                PlotArea.Children.Add(line);

                // setup for next line
                _previousPoint = plotPoint;

            }

            // move the turtle and set its heading (TODO)
            Canvas.SetLeft(Turtle, plotPoint.X - Turtle.ActualWidth / 2);
            Canvas.SetTop(Turtle, plotPoint.Y - Turtle.ActualHeight / 2);

        }
        else
        {
            _firstReading = false;

            _startingLatitude = location.Coordinate.Latitude;
            _startingLongitude = location.Coordinate.Longitude;

            // calculate top left corner of plot area by figuring out the center
            _plotAreaTopLeftLatitude = _startingLatitude - ((PlotArea.ActualHeight / 2.0) / PixelsPerDegreeLatitude);
            _plotAreaTopLeftLongitude = _startingLongitude - ((PlotArea.ActualWidth / 2.0) / PixelsPerDegreeLongitude);

            // calculating the prev point after calculating top left will effectively center the plotting
            _previousPoint = TranslateLatLongToScreenPoint(location.Coordinate.Latitude, location.Coordinate.Longitude);

#if DEBUG
            // if algorithm worked, this will be 0,0
            Point debugPoint = TranslateLatLongToScreenPoint(_plotAreaTopLeftLatitude, _plotAreaTopLeftLongitude);
            System.Diagnostics.Debug.WriteLine("Top x={0}, y={1}", debugPoint.X, debugPoint.Y);
#endif

            DrawBackgroundGrid(_previousPoint);
        }


        // store new coordinates as last old coordinates
        _previousLatitude = location.Coordinate.Latitude;
        _previousLongitude = location.Coordinate.Longitude;

    }

You can see that on the first reading from the GPS, I identify the center of the drawing (the start point) and draw the grid. I also set up the lat/long of the top-left based on all that.

My TranslateLatLongToScreenPoint function is messed up on the Y axis, so I’ll refrain from posting the code here.

Finally, here’s the Xaml. Rather than monkey around with z-order, I just created three different canvasses for me to draw on: the background (grid lines), plot area (the green dots/lines) and the overlay (the red circle). Also, to keep the background lines sharp, I turned on SnapsToDevicePixels.

<Window x:Class="LocationDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowState="Maximized"
        SnapsToDevicePixels="True"
        Title="GPS Etch a Sketch"
        Height="700"
        Width="1200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="275" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        
        <StackPanel Grid.Column="0" Margin="5">
            <TextBlock x:Name="StatusText"
                       FontSize="14" />
            <TextBlock x:Name="LocationText"
                       FontSize="14"
                       TextWrapping="Wrap"/>    
        </StackPanel>

        <Canvas x:Name="BackgroundArea"
                Background="White"
                Grid.Column="1">
        </Canvas>
        
        <Canvas x:Name="PlotArea"
              Background="Transparent"
              Grid.Column="1">
        </Canvas>
        
        <Canvas x:Name="OverlayArea"
                Background="Transparent"
                Grid.Column="1">

            <Ellipse x:Name="Turtle"
                     Width="15"
                     Height="15"
                     Fill="Red"
                     Opacity="0.75" />

        </Canvas>
    </Grid>
</Window>

Important RC / RTW Changes

Note that the information here applies to Beta 2. Gavin Gear blogged about upcoming changes in the rc and release of .NET 4. Here are two diagrams from his blog.

Current (Beta 2):

image

Planned (RC/RTW):

image

Those changes make a lot of sense since the Address information, reported in every update event, doesn’t change like the coordinate information does.

I’ll update this application (fix some bugs and otherwise clean it up) and post it as a sample after RTW. I don’t want to put a real sample out there using the Beta 2 API.

           
posted by Pete Brown on Saturday, January 23, 2010
filed under:            

2 comments for “The Win7 Sensor and Location API: Introduction to Working with Location in .NET 4 Beta 2”

  1. Robertsays:
    Pete,

    I know this is one of your old posts, but I thought I would follow-up anyway. I remember you had gotten this device to play around with, and today I went to get one myself.

    Unfortunately, I got the Microsoft Streets & Trips 2009 version, which doesn't make me happy ... I not sure why they didn't have the 2010 version. Not sure if I should just return it (Office Depot), or stick with it.

    I went looking for the driver, but the page you (and Microsoft) reference returns a 404. Fortunately, I found the page, it's now at:

    http://www.u-blox.com/en/download/usb-drivers/windows-7-driver.html

    I'm now using VS2010 and .NET 4, so any hints regarding the subject of your blog post would be appreciated.

Comment on this Post

Remember me