How to Launch Maps in a Universal App

At the Microsoft //Build/ Conference last April, the Windows Phone 8.1 SDK (WP8.1) was released as a preview. With the release of this SDK one of the new templates added to Visual Studios now allows you to create universal apps. Universals apps allow you to build an app for Windows and Windows Phone while, at the same time, share code, user controls, styles, strings, and other assets between the two projects in Visual Studio. This saves time having to develop code for each platform. You can find more information on creating universal apps here.

In the past we wrote a blog post about being able to launch the built-in Maps app in a Windows Store app using a process called Protocol Activation. The Maps app in Windows is registered for protocol activation and the URI schema is documented here. This same schema is supported in WP8.1 apps as well. In this blog post we are going create a set of reusable tools inside of a universal app for launching the maps app in Windows 8 and WP8.1.

To get started open Visual Studio and create a new project in C#. Select the Blank App template, call the application UniversalMapLauncher, and then press OK.

Next add a new class called MapLaucher to the UniversalMapLauncher.Shared project. In this class we will create three static methods for launching the map app. The first method will launch the map for the specified center point and zoom level. The second method will launch map using a query search (i.e. Pizza in New York). The third method will launch the map and calculate a route between two locations. Update the MapLauncher.cs file with the following code do this:

using System;
using Windows.UI.Popups;

namespace UniversalMapLauncher
{
    public static class MapLauncher
    {
        private static string baseUri = "bingmaps:?";

        public static void LaunchMap(double latitude, double longitude, int zoom)
        {
            string uri = string.Format("{0}cp={1:N5}~{2:N5}&lvl={3}", baseUri, latitude, longitude, zoom);

            Launch(new Uri(uri));
        }

        public static void LaunchMap(string query)
        {
            string uri = baseUri + "&q=" + Uri.EscapeDataString(query);

            Launch(new Uri(uri));
        }

        public static void LaunchMap(string start, string end)
        {
            string uri = string.Format("{0}rtp=adr.{1}~adr.{2}", baseUri, Uri.EscapeDataString(start), Uri.EscapeDataString(end));

            Launch(new Uri(uri));
        }

        private static async void Launch(Uri uri)
        {
            // Launch the URI
            var success = await Windows.System.Launcher.LaunchUriAsync(uri);

            if (!success)
            {
                //Failed to launch maps 
                var msg = new MessageDialog("Failed to launch maps app.");
                await msg.ShowAsync();
            }
        }
    }
}

We can now use this class anywhere within our apps to launch the map app. Since we are creating a universal app let’s take things a step further and create a shared user control for testing out the MapLauncher class. To do this right click on the UniversalMapLauncher.Shared project and select Add → New Item. Select the User Control template and call it MapLauncherControl.xaml.

This user control will consist of three input forms for testing the different methods for launching the map app. Open up the MapLauncherControl.xaml file and update it with the following XAML.

<UserControl
    x:Class="UniversalMapLauncher.MapLauncherControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UniversalMapLauncher"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="700"
    d:DesignWidth="400">
    
    <UserControl.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="16"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Margin" Value="0,0,10,0"/>
        </Style>
    </UserControl.Resources>

    <StackPanel>
        <TextBlock Text="Map Launcher" Margin="10" 
                   HorizontalAlignment="Center" 
                   FontSize="20" FontWeight="Bold"/>
        
        <ItemsControl>
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer VerticalScrollBarVisibility="Disabled" 
                                  HorizontalScrollBarVisibility="Auto">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Horizontal"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.Items>
                <StackPanel Width="300">
                    <TextBlock Text="Using Center &amp; Zoom:" FontWeight="Bold"/>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="Latitude:"/>
                        <TextBox Name="LatitudeTbx" InputScope="Number" Width="150" Text="51.5"/>
                    </StackPanel>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="Longitude:"/>
                        <TextBox Name="LongitudeTbx" InputScope="Number" Width="150" Text="-0.1"/>
                    </StackPanel>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="Zoom:"/>
                        <TextBox Name="ZoomTbx" InputScope="Number" Text="15"/>
                    </StackPanel>

                    <Button Content="Launch Map"
                            HorizontalAlignment="Center" 
                            Tapped="LaunchCenterZoom_Tapped"/>
                </StackPanel>

                <StackPanel Width="300">
                    <TextBlock Text="Using What &amp; Where:" FontWeight="Bold"/>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="Query:"/>
                        <TextBox Name="QueryTbx" Width="250" Text="Pizza in New York"/>
                    </StackPanel>

                    <Button Content="Launch Map" 
                            HorizontalAlignment="Center" 
                            Tapped="LaunchWhatWhere_Tapped"/>
                </StackPanel>

                <StackPanel Width="300">
                    <TextBlock Text="Using Route:" FontWeight="Bold"/>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="Start:"/>
                        <TextBox Name="StartTbx" Width="250" Text="Portland, OR"/>
                    </StackPanel>

                    <StackPanel Orientation="Horizontal" Margin="10">
                        <TextBlock Text="End:"/>
                        <TextBox Name="EndTbx" Width="250" Text="Seattle, WA"/>
                    </StackPanel>

                    <Button Content="Launch Map"
                            HorizontalAlignment="Center" 
                            Tapped="LaunchRoute_Tapped"/>
                </StackPanel>
            </ItemsControl.Items>
        </ItemsControl>
    </StackPanel>
</UserControl>

Next we need to add the code behind for this control so that it launches the map when the user presses one of the buttons. Open the MapLauncherControl.xaml.cs file and update it with the following code.

using System;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;

namespace UniversalMapLauncher
{
    public sealed partial class MapLauncherControl : UserControl
    {
        public MapLauncherControl()
        {
            this.InitializeComponent();
        }

        private void LaunchCenterZoom_Tapped(object sender, TappedRoutedEventArgs e)
        {
            double lat, lon;
            int zoom;

            if (!double.TryParse(LatitudeTbx.Text, out lat))
            {
                ShowMessage("Invalid latitude value.");
                return;
            }

            if (!double.TryParse(LongitudeTbx.Text, out lon))
            {
                ShowMessage("Invalid longitude value.");
                return;
            }

            if (!int.TryParse(ZoomTbx.Text, out zoom))
            {
                ShowMessage("Invalid zoom value.");
                return;
            }

            MapLauncher.LaunchMap(lat, lon, zoom);
        }

        private void LaunchWhatWhere_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(QueryTbx.Text))
            {
                ShowMessage("Invalid query value.");
                return;
            }

            MapLauncher.LaunchMap(QueryTbx.Text);
        }

        private void LaunchRoute_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(StartTbx.Text))
            {
                ShowMessage("Invalid start value.");
                return;
            }

            if (string.IsNullOrWhiteSpace(EndTbx.Text))
            {
                ShowMessage("Invalid end value.");
                return;
            }
            
            MapLauncher.LaunchMap(StartTbx.Text, EndTbx.Text);
        }

        private async void ShowMessage(string msg)
        {
            var dialog = new MessageDialog(msg);
            await dialog.ShowAsync();
        }
    }
}

At this point we are done creating our shared assets. To implement this new control in the Windows and Windows Phone apps simply add the following XAML to the Grid in the MainPage.xaml files of the Windows and Windows Phone projects.

<local:MapLauncherControl HorizontalAlignment="Center" VerticalAlignment="Center"/>

If you run either of the apps, you will see the MapLauncherControl displayed. When the app becomes narrow from either snapping the window in a Windows app or loading it in a Windows Phone app the control will allow you to horizontally scroll to get to the other input forms. The control will look like this.

You can edit the fields in the forms or simply press one of the buttons to launch the map. When using the Windows app the map will load beside the app. Here is a screenshot of the app when calculating a route from Portland to Seattle.

When using the Windows Phone app the map app will load up and take the full screen. If you press the back button you will be able to get back to your app. Here is a screenshot of the map app after doing a “What & Where” search for “Pizza in New York”:

Full source code for this blog post can be downloaded from the MSDN Code Samples Gallery here.

You can find additional information about maps in WP8.1 here:

- Ricky Brundritt, EMEA Bing Maps TSP