Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Where’s My Car?

Most people are all too familiar with forgetting where they parked their car. Whether or not you’re one of these people, read on to learn about writing a location-aware Windows Phone app that can map, route, take pictures, and save state between invocations.

Introduction

Working with mobile phones gives you access to a completely different form factor, as well as a completely different set of functionality compared to web and desktop development environments. This project demonstrates GPS, camera, isolated storage, map display, web services, and advanced map features. Use it as a starting point for another project, or just learn from the techniques shown here.

You don’t need a Windows Phone to learn from this sample, but GPS simulation is only available in the emulator for the 7.1 beta developer tools, and interacting with the camera on the emulator is no fun at all, so it’s definitely a limited debugging/testing experience. You need to be a registered phone developer in order to debug on a physical phone, but the price ($99) is definitely reasonable if you end up releasing even a single paid app.

Go to create.msdn.com, then click Download the free tools to download the Windows Phone Developer Tools (or use the direct download link provided above this Introduction section). This code is written for the Windows Phone Developer Tools 7.0, but has also been tested with Windows Phone SDK 7.1 Beta 2.  Either way, it’s is a mostly online install, and it’s pretty big so expect it to take some time. Even if you don’t have any development tools, this will give you Visual Studio Express, Blend, and XNA Game Studio. If you have the full-version tools already, it will add new templates.

Project Basics

Upon launching the app, there’s not much to see.  You can touch the car icon in the Map region in order to set your car’s parking location.  To make things easier, you can also touch the camera icon to take a picture of your parking spot or nearby landmark to make it easier to find later.  The data is saved between runs so you don’t need to leave it running.

MainPage - First time

The application takes advantage of a small set of pages:

 

  • MainPage: Summary screen to see the map and photo in the same place.  Essentially, this uses two Image controls and a Map control.  The upper region shows a small map if you’ve set your parking spot.  It also caches a small version of the map in case you can’t get a GPS or data connection when you need to find your car.
  • MapSet: Allows the user to set their car’s position on a map.  It starts out centered on the current location and the user can swipe around to place their car.
  • MapPage: A larger map with panning/zooming support.  This is the one you see after locking in the parking spot.  You can jump to your current position, the car’s position, or generate a route between them.
  • Media: A large view of the photo taken of the parking spot.
  • SettingsPage: Choice of imperial (miles) or metric (kilometers) for distance measurements, and map style (aerial or street).  These are all data-bound to simplify managing the values.

 

It also uses a few IValueConverter classes to take data from my view model and make it look right in the UI:

DistanceConverter

Take a double value (assumed in meters), converts to miles if necessary, and appends the unit (km/mi).  Notice that the code references the WheresTheCarSettings object.  This is used to determine the measurement mode/unit.  This should probably be passed in as a parameter, since this limits the portability for other projects, but I’ll leave that as an exercise for the reader!

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (value == null ) return "";

    double val = Double.Parse(value.ToString());
	string unit;
    if( WheresTheCarSettings.Instance.MeasurementMode == MeasurementMode.Imperial)
    {
        unit = "miles";
        val = (val*3.2808399)/5280.0;
    }
    else
    {
        unit = "km";
        val /= 1000.0;
    }

    return string.Format("{0:0.00} {1}", val, unit);
}

ImageConverter

Takes an image path, checks for it in isolated storage, otherwise assumes it’s in the application XAP file, and then returns an BitmapImage for display.  This is a pretty handy converter since the Source can’t be set to isolated storage otherwise.  The Cache is very important since accessing isolated storage is more expensive than RAM access.  Also, notice that the Dictionary object is holding WeakReference instances pointing to the image.  This allows the garbage collector to trim the cache if memory gets tight.  Be sure to always use the IsAlive property so you know if the object’s been released.

public static Dictionary Cache { get; private set; }

static ImageConverter()
{
    Cache = new Dictionary();
}

#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (value != null && !string.IsNullOrEmpty(value.ToString()))
    {
        string fn = value.ToString();

        if (Cache.ContainsKey(fn))
            if (Cache[fn].IsAlive)
                return Cache[fn].Target;
            else
                Cache.Remove(fn);

        try
        {
            BitmapImage bs = null;
            using (var myStore = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (myStore.FileExists(fn))
                {
                    using (var photoStream = myStore.OpenFile(fn, FileMode.Open))
                    {
                        bs = new BitmapImage();
                        bs.SetSource(photoStream);
                    }
                }
                else
                    bs = (new BitmapImage(new Uri(fn, UriKind.Relative)));

                if( parameter == null || parameter.ToString() != "no-cache")
                    Cache.Add(fn, new WeakReference(bs));
                return bs;
            }
        }
        catch
        {
            // Errors will be handled silently
        }
    }

	// If all else fails...
	return null;
}

ValueToVisibilityConverter

Returns Visibility.Collapsed or Visibility.Visible depending on whether a GeoCoordinate value is non-null and valid.  This converter performs the simple action of making an element invisible if no coordinate is set.  Very simple, but it makes for much more elegant code.

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var geo = value as GeoCoordinate;
	return (Utility.IsCoordNullOrUnknown(geo)) ? Visibility.Collapsed : Visibility.Visible;
}

Location

On Windows Phone, you determine your geographic location by using the GeoCoordinateWatcher class. When you instantiate it, you can specify an accuracy of Default or High. High accuracy comes at the cost of battery life. You can also set your movement threshold. The higher the threshold, the less often you’ll get position events. Call Start to wait for the sensor to turn on, or TryStart for asynchronous usage.

The GeoCoordinateWatcher raises an event for status changes such as starting or no data (StatusChanged), and an event to indicate a location change (PositionChanged). Locations are reported as GeoCoordinate objects, and include latitude, longitude, speed, bearing, and more. All distance measurements are in meters.

Note that like any GPS, the phone won’t be able to get a lock in underground parking, or even in many multilevel parking ramps. There’s not much you can do about this, but hopefully the photo comes in handy then!

Maps

Displaying a map on a Windows Phone application is really easy. The controls that come with the toolkit include a Map control. Simply drop this control onto a page and you are just about ready to go. The only other required step is to register as a developer on Bing. You can find out more information about the steps to follow to obtain your key and register your application here.  This gets you a credential key that you plug into the MAP_CREDENTIALS property of the RouteHelper class of the supplied code.  Note that without this, you won’t even be able to view maps, let alone generate routes. By doing this, you will get a map centered on Latitude = 0, and Longitude = 0. This puts you in the Atlantic Ocean west of South Africa. Without doing anything else you can pan and zoom the map.

Unfortunately, due to a bug in the current build of the controls, you can’t create the Map control in XAML on multiple pages of the same app.  The maps in Where’s My Car are created in the code-behind.  A little ugly, but it works.  Hopefully this will be resolved in Mango.  In XAML, it’s as easy as a one line control declaration to add a map:

 

<Map CredentialsProvider="XXXXXX" />

You can set properties to hide the Bing logo (LogoVisibility="Collapsed"), to hide the copyright (CopyrightVisibility="Collapsed"), to set an explicit zoom level (ZoomLevel="15") and much more. You might notice that I set the zoom bar to be visible in the emulator, but not on the phone. Without having a touchscreen laptop, I wouldn’t be able to zoom the map in the emulator otherwise. My code-based map creation looks this this:

return new Map
{

    CredentialsProvider = new ApplicationIdCredentialsProvider(MapHelper.MAP_CREDENTIALS),
    ZoomLevel = 15,
    Mode = s.UseAerialMode ? (MapMode) new AerialMode() : new RoadMode(),
    ZoomBarVisibility = (Environment.DeviceType == DeviceType.Emulator) ? Visibility.Visible : Visibility.Collapsed,
    CopyrightVisibility = Visibility.Collapsed,
    LogoVisibility = Visibility.Collapsed,
    Margin = new Thickness(0),
};

Setting your location

Another important part of working with a navigation-based map is to be able to set pushpins to indicate the locations of things (in this case, you and your car). The built-in pushpin visualization is fine, but not very special. Changing the look-and-feel is as simple as modifying the template for the Pushpin control.  Here, we change it to use a bitmap image:

<ControlTemplate x:Key="CarPushpinTemplate" TargetType="mpc:Pushpin">
    <Image Source="/images/carPushpin.png" Stretch="None" Margin="-24, 0, 0, -24" />
</ControlTemplate>

When you declare a Pushpin, just set its Template property, and add it as a child of the Map control. A Pushpin control knows where to place itself by its Location property. Note that if the location is null, the pushpin will just lurk in the upper-left corner. Make sure that you hide it when the location isn’t set. I do this with the ValueToVisibilityConverter covered above.  To use it, just set the Visibility property to the same Location property while using my ValueToVisibilityConverter to handle the conversion.

Location and photo set

Routing

The Map control has no ability to perform routing, but it supports adding child controls. I’ve already discussed the Pushpin control as a child. The other important child control is MapLayerMapLayer acts as a container for other controls. By setting the Location properties of the nested items (in latitude/longitude), everything is displayed at the right location, regardless of panning or zooming the map (no scaling is done however).

In order to obtain the routing data, the user must first set their parking location point. The other point is the current phone location. The actual routing happens using the Bing web service. Generate a service reference pointing to the definition (WSDL):

http://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc?wsdl

Then, creating the request entails setting the Credentials property, and optionally setting RouteOptions. For walking directions, set Mode to TravelMode.Walking (though this still uses roads). The Waypoints collection property holds Waypoint objects containing a label along with a Location object with Altitude/Latitude/Longitude properties. For the best performance, I use the CalculateRouteAsync call to get off the UI thread as quickly as possible.

Routing from current position to car

In the response, you get the travel distance and a collection of points that make up the route. Create a MapPolyLine instance, and then based on the Waypoints collection create and add GeoCoordinate objects to the Locations collection. The MapPolyLine object then gets added to a MapLayer object for rendering along with the map. The final step is to use the Result.Summary.BoundingRectangle property to center and size the map in order to show the whole route in one view.

Taking and Storing Pictures

Taking pictures is pretty easy. The built-in camera can be triggered using a CameraCaptureTask object. Just call the Show method and the camera will appear. It’s up to the user to actually take the picture. Note that there is no way to automatically take the photo, and there is at the moment no preview feature.  This will change in Mango, but for now it works well enough for our needs.

An important thing to keep in mind is that showing the camera task causes your app to be tombstoned (deactivated). This means that you need to save any relevant state before triggering it. After the user either takes the photo or cancels it, the app is reactivated. The Completed event of the CameraCaptureTask fires, so be sure to subscribe to the event in the constructor of the page. The Completed event includes a PhotoResult event class. From this you can check the TaskResult property to see if there is actually a photo ready, and use the ChosenPhoto property to get a stream containing the photo data. You can get the stream to process or write to storage.  Here we will open isolated storage and open the “parking.jpg” file, then call the SaveJpeg method of the WriteableBitmap class.

For the sake of efficiency, I prefer to resize the image to a maximum of 1024 width or 768 height, rather than loading a minimum 5MP image each time. The resize is very quick, and results in much less data transfer from isolated storage to memory.

Settings

Settings are kept track of in a custom class, WheresTheCarSettings.  This class has INotifyPropertyChanged properties and Load/Save methods to keep things together.  Saving is done with a data contract rather than raw serialization.  This is an amazingly easy way to load and save an object, and it’s very resilient against changes.  The INotifyPropertyChanged interface is used so disconnected parts of an application (between objects or objects and UI) can be notified of changes to a given object.  This happens because the PropertyChanged event fires whenever a property’s value changes, thus giving related code a chance to react.

#region MeasurementMode (INotifyPropertyChanged Property)
private MeasurementMode _measurementMode;
[DataMember]
public MeasurementMode MeasurementMode
{
	get { return _measurementMode; }
	set
	{
		_measurementMode = value;
		RaisePropertyChanged("MeasurementMode");
	}
}
#endregion

.Settings page

Next Steps

The application is fully functional as-is. It’s been in the Marketplace for a few months now.  There are a number of complaints about not being able to get a GPS lock, or the route not being optimized for walking.  These are true and valid complaints, but it’s difficult to do much about them.  This is where good alerts and status indicators are important!  Also, it could be useful to add a screen to see the walking route in a step-by-step view (already available in the route response). Integrating parking meter information might also be useful, though I’ve already implemented that in a different application—coming soon!

Conclusion

Being able to work with the location and camera enables some great scenarios that aren’t really available in other form factors. Get started by downloading the Windows Phone Developer Tools to see just how easy it is.

About the Author

Arian Kulp is a software developer living in Western Oregon. He creates samples, screencasts, demos, labs, and articles, speaks at programming events, and enjoys hiking and other outdoor adventures with his family.

Tags:

Follow the Discussion

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.