Map-Driven Search in Windows Store Apps

The Bing Search API gives developers the ability to incorporate search results powered by Bing into applications and websites. In addition to web search results, we can also retrieve images, videos and news results, and embed them in apps based on query terms, location information and other filter criteria. Traditional search experiences involve capturing user search intent via typed text input, but in this post, we will build an app in which the user can retrieve search results based on their navigation and interaction with a dynamic Bing Map. We will build a Windows Store App (C# and XAML) using Bing Maps for Windows Store Apps and the Bing Search API.

App Concept

The purpose of our application is to enable our users to explore location-centric search content through an intuitive map-based UI. To do this, we leverage the rich event model exposed by the Bing Maps for Windows Store Apps control to signal location-based search intent as the user interacts with the dynamic map. The specific events that we will use to trigger a search will be:

  • Double-tap- When the user double-taps the map at any time, we will override the default zooming behavior, and instead will retrieve contextual location data about the map coordinates tapped, using the text-based location information as part of our search query. We will include logic to take into account the current zoom factor of the map in determining the level of precision to use in our contextual location data (using only the country name, for example, when the user is looking at a world-view map).
  • Tapping a Landmark – When the user taps on one of the Landmarks which are presented as part of the Bing Maps base maps, we will use the name of the landmark, as well as relevant contextual location data, as part of our search query.

We will also enable the adding of additional text-based search terms that the user can specify, which will be appended to our contextual location search terms.

Scenarios that we enable with this type of map-driven search include:

  • Travel and Hospitality– Users can view relevant photos and videos of restaurants, entertainment venues and other landmarks when planning itineraries.
  • Real Estate– Users can view news stories relating to streets or neighborhoods they are considering purchasing a property in to understand more about crime, community events, and the general composition of the area.
  • Location-based site-search – We can tap into the advanced abilities of the Bing Engine to search for content from a specific website with location-specific content, to present our own location-centric enterprise content to our app users.

Building Our App

The prerequisites for building our application include:

  • Windows 8.1 and Visual Studio 2013
  • The Bing Maps SDK for Windows Store Apps
  • A subscription to the Bing Search API in the Windows Azure Marketplace(For details on signing up for a Windows Azure Marketplace account and obtaining an account key, see the documentation here.)

Configuring Our Project

In Visual Studio 2013, we will first create a new project using the Visual C# Windows Store Blank App (XAML) template, and will name our project MapDrivenSearch.

We now add the following references to our project:

  • Bing Maps for C#, C+ + or Visual Basic
  • Microsoft Visual C+ + Runtime Package

We must use Configuration Managerto select an individual platform to compile for, rather than All CPU, to satisfy a Visual C++ Runtime requirement:

Laying Out Our UI

Our UI will be map-centric, with a right-side ScrollViewer for displaying search input and output, along with an app bar on the bottom to allow selection of search source type.

In our XAML code, we add:

  • XML namespace declarations for Bing.Maps
  • A Map control, to which we add our Bing Maps Key as Credentials
  • A ProgressBar to enable users to track asynchronous search requests
  • An Image containing a Bing logo to attribute Bing in accordance with the Bing Product Guidelines found here
  • A TextBox to allow non-location search modifying terms
  • A Grid in which we will display the current search criteria
  • A ScrollViewer containing a ListBox to present our search results
  • DataTemplates for each of the four search result types we will present: Web, Images, Video, and News
  • An AppBarButton in our AppBar with a Flyout to allow selection of search type

Our final markup should appear as shown below:

<Page
    x:Class="MapDrivenSearch.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MapDrivenSearch"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:bm="using:Bing.Maps"
    mc:Ignorable="d">
    <Page.Resources>
        <DataTemplate x:Name="dtImages">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Title}" FontSize="15" FontWeight="Bold"  
                           Foreground="RoyalBlue" />
                <Image Source="{Binding Thumbnail.MediaUrl}" Height="{Binding Thumbnail.Height}" 
                       Width="{Binding Thumbnail.Width}"></Image>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Source: " FontSize="11" FontStyle="Italic" FontWeight="Bold" />
                    <TextBlock Text="{Binding DisplayUrl}" FontSize="11" FontStyle="Italic" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Name="dtWeb">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Title}" FontSize="15" FontWeight="Bold" 
                           Foreground="RoyalBlue"/>
                <TextBlock Text="{Binding Description}" FontSize="11"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Source: " FontSize="11" FontStyle="Italic" FontWeight="Bold" />
                    <TextBlock Text="{Binding DisplayUrl}" FontSize="11" FontStyle="Italic" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Name="dtNews">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Title}" FontSize="15" FontWeight="Bold" 
                           Foreground="RoyalBlue"/>
                <TextBlock Text="{Binding Description}" FontSize="11"/>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Source: " FontSize="11" FontStyle="Italic" FontWeight="Bold" />
                    <TextBlock Text="{Binding Source}" FontSize="11" FontStyle="Italic" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
        <DataTemplate x:Name="dtVideo">
            <StackPanel Orientation="Vertical">
                <TextBlock Text="{Binding Title}" FontSize="15" FontWeight="Bold" 
                           Foreground="RoyalBlue"/>
                <Image Source="{Binding Thumbnail.MediaUrl}" Height="{Binding Thumbnail.Height}" 
                       Width="{Binding Thumbnail.Width}" HorizontalAlignment="Left" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Source: " FontSize="11" FontStyle="Italic" FontWeight="Bold" />
                    <TextBlock Text="{Binding MediaUrl}" FontSize="11" FontStyle="Italic" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>

    <Grid Background="Gray">
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition Width="350"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="195"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <bm:Map Name="myMap" Grid.Column="0" Grid.RowSpan="2" Credentials="Your Bing Maps Key" 
                LandmarkTapped="myMap_LandmarkTapped" 
                DoubleTappedOverride="myMap_DoubleTappedOverride" />
        <ProgressBar x:Name="progressBar" IsIndeterminate="True" Height="10" Width="300" 
                     Visibility="Collapsed" Grid.Column="0" Grid.RowSpan="2" 
                     VerticalAlignment="Top" />
        <StackPanel Margin="10,10,20,10" Grid.Column="1" Grid.Row="0" >
            <TextBlock Text="Optional Search Term(s)" FontSize="24"/>
            <StackPanel Orientation="Horizontal"  Margin="0,3,0,10">
                <TextBox Name="tbSearchTerm" HorizontalAlignment="Left" 
                            Width="240" Height="25" Text="" />
                <Image Height="23" Width="63" Source="./Assets/Logo_63x23_White.png" 
                       Margin="10,0,0,0"/>
            </StackPanel>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="50"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="25"/>
                    <RowDefinition Height="20"/>
                    <RowDefinition Height="20"/>
                    <RowDefinition Height="20"/>
                    <RowDefinition Height="20"/>
                </Grid.RowDefinitions>
                <TextBlock Text="Current Query" FontSize="15" FontStyle="Italic" FontWeight="Bold" 
                           Grid.Row="0" Grid.ColumnSpan="2"/>
                <TextBlock Text="Terms: " FontSize="13" Grid.Row="1" Grid.Column="0" 
                           FontStyle="Italic"/>
                <TextBlock x:Name="tbQuery" Text="" FontSize="13" Grid.Row="1" Grid.Column="1"/>
                <TextBlock Text="Type: " FontSize="13" Grid.Row="2" Grid.Column="0" 
                           FontStyle="Italic"/>
                <TextBlock x:Name="tbType" Text="" FontSize="13" Grid.Row="2" Grid.Column="1"/>
                <TextBlock Text="Lat: " FontSize="13" Grid.Row="3" Grid.Column="0" 
                           FontStyle="Italic"/>
                <TextBlock x:Name="tbLat" Text="" FontSize="13" Grid.Row="3" Grid.Column="1"/>
                <TextBlock Text="Lon: " FontSize="13" Grid.Row="4" Grid.Column="0" 
                           FontStyle="Italic"/>
                <TextBlock x:Name="tbLon" Text="" FontSize="13" Grid.Row="4" Grid.Column="1"/>
            </Grid>
        </StackPanel>
        <ScrollViewer Grid.Column="1" Grid.Row="1">
            <ListBox Name="SearchResults"  
                    Margin="0,0,0,0" SelectionChanged="SearchResults_SelectionChanged">
            </ListBox>
        </ScrollViewer>
    </Grid>
    <Page.BottomAppBar>
        <AppBar IsSticky="True" IsOpen="True">
            <StackPanel x:Name="RightPanel" Orientation="Horizontal" HorizontalAlignment="Right">
                <AppBarButton x:Name="btnChooseType" Icon="Filter"  Label="Search Type">
                    <Button.Flyout>
                        <Flyout x:Name="flySearchTypes">
                            <ListBox x:Name="lstTypes"  SelectionChanged="lstTypes_SelectionChanged">
                                <ListBoxItem Tag="web" IsSelected="true">
                                    <TextBlock>Web</TextBlock>
                                </ListBoxItem>
                                <ListBoxItem Tag="image">
                                    <TextBlock>Images</TextBlock>
                                </ListBoxItem>
                                <ListBoxItem Tag="video">
                                    <TextBlock>Videos</TextBlock>
                                </ListBoxItem>
                                <ListBoxItem Tag="news">
                                    <TextBlock>News</TextBlock>
                                </ListBoxItem>
                            </ListBox>
                        </Flyout>
                    </Button.Flyout>
                </AppBarButton>
            </StackPanel>
        </AppBar>
    </Page.BottomAppBar>
</Page>

Data Contracts for REST Services

Our application will access two distinct RESTful Bing services to power our map-driven search functionality:

  • We will leverage the Bing Maps REST Locations API for reverse geocoding functionality, enabling us to obtain text-based contextual location data as the user interacts with the map. To facilitate interacting with this service, we add a new item to our project: a Visual C# Code File, which we will name BingMapsRESTServices.cs. We will populate this file with the JSON data contracts for the REST Services found here. For a more complete review of how to consume the REST services via .NET, see the technical articleon MSDN.
  • We will also leverage the REST-based Bing Search API. This API uses the OData specification, and can return responses in both XML and JSON formats. In our application, we will retrieve JSON responses, and will construct another JSON data contract to enable us to serialize and present the desired search results content. Our JSON data contract will be populated in another new item in our project: a Visual C# Code File, which we will name BingSearchService.Common.JSON.cs. The content of the populated data contract is included in the downloadable application code.

Adding Our C# Code

In our MainPage.xaml.cs code-behind, we add several using statements. We also use a private a variable for our Application Key that we created via the Azure Marketplace. This key will be used for authentication when making requests to the Bing Search API.

using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.Serialization.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Bing.Maps;
using BingMapsRESTService.Common.JSON;
using BingSearchService.Common.JSON;


namespace MapDrivenSearch
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        // Our account key from the Azure Marketplace.
        string BingSearchKey = "your account key”;
…

Event Handlers

Our first event handler will handle the LandmarkTapped event. When a given Landmarkis tapped, we will:

  • Show our progress bar to notify our users that an operation is taking place
  • Retrieve the name of the Landmarkfrom the event arguments
  • Issue a reverse geocoding request to obtain additional location context
  • Combine our Landmarkname and location context with any additional search terms the user may have entered, and execute our search
  • Hide our progress bar on completion
private async void myMap_LandmarkTapped(object sender, LandmarkTappedEventArgs e)
{
    // Show progress bar.
    progressBar.Visibility = Visibility.Visible;

    try
    {
        // Retrieve name of clicked landmark.
        string landmarkName = e.Landmarks[0].Name;

        // Retrieve reverse geocoding information.
        string queryLocation = await RevGeo(e.Landmarks[0].Location, 
            "Neighborhood,PopulatedPlace,AdminDivision1,AdminDivision2,CountryRegion");

        // Query will be landmark name + reverse geocoded location text + any additional 
        // search terms entered.
        string query = landmarkName + " " + queryLocation + " " + tbSearchTerm.Text;

        // execute search: 
        await Search(query, new Bing.Maps.Location(
            e.Landmarks[0].Location.Latitude, e.Landmarks[0].Location.Longitude));
    }
    catch (Exception)
    {
        // Handle error here.
    }
    progressBar.Visibility = Visibility.Collapsed;
}

Our second event handler will cover the DoubleTappedevent. Whenever the user double-taps the map area, we will:

  • Show our progress bar to notify our users that an operation is taking place
  • Designate our event as ‘handled’, to prevent the default zooming behavior from occurring
  • Determine the pixel coordinates of the tapped location, and the corresponding map coordinates
  • Determine the appropriate precision to use for our contextual location information via the GetEntityValueFromZoom method
  • Issue a reverse geocoding request to obtain additional location context
  • Combine our location context with any additional search terms the user may have entered, and execute our search
  • Hide our progress bar on completion
private async void myMap_DoubleTappedOverride(object sender, DoubleTappedRoutedEventArgs e)
{
    // Show progress bar
    progressBar.Visibility = Visibility.Visible;

    // Handle to avoid zooming on double tap.
    e.Handled = true;

    try
    {
        // Retrieve tapped location.
        var pos = e.GetPosition(myMap);
        Bing.Maps.Location location;

        if (myMap.TryPixelToLocation(pos, out location))
        {
            // Get Entity String for search.
            string EntityValue = GetEntityValueFromZoom(myMap.ZoomLevel);

            // Retrieve reverse geocoding information.
            string queryLocation = await RevGeo(location, EntityValue);

            // Query will be landmark name + reverse geocoded location text + any additional 
            // search terms entered.
            string query = queryLocation + " " + tbSearchTerm.Text;

            // Execute search.
            await Search(query, location);
        }

    }
    catch (Exception)
    {
        // Handle error here.
    }
    progressBar.Visibility = Visibility.Collapsed;
}

Obtaining Location Context for Search

In our event handler for the double-tap scenario, we used the GetEntityValueFromZoom method to determine the appropriate precision level for our contextual location information. This method takes a zoom-factor parameter, and returns a string we can use as a parameter in our reverse geocoding request to influence the precision of the entity the service will return. Thus, if a user is viewing a world-scale map, the location context will be the country name, whereas if they are looking at a street-scale map, the location context will include the street name itself. The zoom ranges used have been nominally selected, and can be adapted as desired:

private string GetEntityValueFromZoom(double zoom)
{
    // Determine EntityType to query for depending on zoom factor.
    // If user is looking at country-level views, we don't want to return neighborhood info 
    // for our search.
    string EntityValue;
    if (zoom > 16)
    {
        EntityValue = "Address,Neighborhood,PopulatedPlace,AdminDivision1,CountryRegion";
    }
    else if (zoom <= 16 && zoom > 14)
    {
        EntityValue = "PopulatedPlace,AdminDivision1,CountryRegion";
    }
    else if (zoom <= 14 && zoom > 10)
    {
        EntityValue = "PopulatedPlace,AdminDivision1,CountryRegion";
    }
    else if (zoom <= 10 && zoom > 5)
    {
        EntityValue = "AdminDivision1,CountryRegion";
    }
    else
    {
        EntityValue = "CountryRegion";
    }
    return EntityValue;
}

In both of our event handlers, we leveraged the RevGeo method to obtain the location context information to use in our search queries. This method takes in a Location and an entity precision string as parameters. The latitude and longitude are obtained from the Location, and used in conjunction with our Bing Maps session credentials and our entity precision string to issue a request to the REST Locations Service Search by Point API. The GetResponse method is used to issue the actual web request, and serialize the response. The service will return contextual information about the coordinates supplied (typically referred to as ‘reverse geocoding’). The includeEntityTypesparameter allows us to specify a desired level of precision for our reverse geocoding request, and we populate this with our entity precision string. The results from our request can include one or more location results, and we will retrieve metadata from the first returned location result.

If the result is an Address Entity Type, we will attempt to remove the street number from the AddressLine, and will concatenate the resulting street name with the locality, administrative district, and country components of the location into our location context result. Otherwise, we will simply use the Name of the location returned.

private async Task<string> RevGeo(Bing.Maps.Location location, string EntityValue)
{
    // Start building our contextual location content.
    string strLocName = "";
    if (location != null)
    {

        // Retrieve Bing Maps session credentials.
        string BingMapsCreds = await myMap.GetSessionIdAsync();

        // Obtain coordinates from location parameter.
        double revgeoLat = location.Latitude;
        double revgeoLon = location.Longitude;

        // Create the request URL for the reverse geocoding service.
        Uri revGeoRequest = new Uri(
            string.Format(
            "http://dev.virtualearth.net/REST/v1/Locations/{0},{1}?includeEntityTypes={2}&key={3}",
            revgeoLat, revgeoLon, EntityValue, BingMapsCreds));

        // Make a request and get the response.
        BingMapsRESTService.Common.JSON.Response r = await GetResponse(revGeoRequest);

        if (r != null &&
            r.ResourceSets != null &&
            r.ResourceSets.Length > 0 &&
            r.ResourceSets[0].Resources != null &&
            r.ResourceSets[0].Resources.Length > 0)
        {
            BingMapsRESTService.Common.JSON.Location FoundLocation = r.ResourceSets[0].Resources[0] 
                as BingMapsRESTService.Common.JSON.Location;

            // If we have an address result, we will attempt to search using an input of
            // <streetname>, <locality> <state> <country>
            if (FoundLocation.EntityType == "Address")
            {
                strLocName = Regex.Replace(
                    FoundLocation.Address.AddressLine, @"^[\s\d]*", @"", RegexOptions.None);
                if (FoundLocation.Address.Locality != "") strLocName += " " 
                    + FoundLocation.Address.Locality;
                if (FoundLocation.Address.AdminDistrict != "") strLocName += " " 
                    + FoundLocation.Address.AdminDistrict;
                if (FoundLocation.Address.CountryRegion != "") strLocName += " " 
                    + FoundLocation.Address.CountryRegion;
            }
            else
            {
                strLocName = FoundLocation.Name;
            }
        }
        else
        {
            // No results found error.
        }
    }
    else
    {
        // Invalid input error.
    }
    return strLocName;
}

private async Task<BingMapsRESTService.Common.JSON.Response> GetResponse(Uri uri)
{
    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
    var response = await client.GetAsync(uri);

    using (var stream = await response.Content.ReadAsStreamAsync())
    {
        DataContractJsonSerializer ser = new DataContractJsonSerializer(
            typeof(BingMapsRESTService.Common.JSON.Response));
        return ser.ReadObject(stream) as BingMapsRESTService.Common.JSON.Response;
    }
}

Obtaining Search Results

Our event handlers used the Search method to issue our Search API request. This method takes our query string parameter, and a Location for additional context. The desired source type of search results (Web, Image, Video or News) is determined based on the currently selected item in the lstTypes ListBox. A URI is then constructed using the Compositeservice operation, which is capable of handling requests for all supported search source types (Note that alternate service operations are available for each specific source type as well). The URI will include our desired source type, our query string, and latitude and longitude parameters to further influence our search results by location. We also specify a maximum of 20 results to be returned, and that the response format be JSON.

Our GetSearchResponse method will issue the actual web request, and serialize the response based on our data contract. An important thing to note is that we use NetworkCredentials which are created using our account key from the Azure Marketplace as credentials for an HttpClientHandler which we use when instantiating our HttpClientobject. This will handle the authentication for the Search API.

The Search API itself will allow us to tap into the data that is collected and indexed by the Bing search engine. The API supports an array of input parameters for filtering, influencing, and otherwise affecting the results that the API will return.

For more information on the Search API, we can refer to the Quick Start and Code Samples, the Schema Tabular Documentation, and the FAQ.

Once we have obtained our results from the Search API, we will present them to the end user, using a DataTemplate that is specific to the search source type the user has specified. We retrieve the appropriate result set, and bind it to the SearchResults ListBox using the corresponding DataTemplate.

private async Task Search(string query, Bing.Maps.Location location)
{

    // Retrieve the search type.
    ListBoxItem selection = (ListBoxItem)lstTypes.SelectedItem;
    string strSearchType = selection.Tag.ToString();

    // Create the request URL for the Search service.
    Uri queryRequest = new Uri(
        string.Format(
        "https://api.datamarket.azure.com/Bing/Search/v1/Composite?Sources=%27{0}%27&Query=%27{1}%27&Latitude={2}&Longitude={3}&$top=20&$format=json",
        strSearchType, query, location.Latitude, location.Longitude));

    // Make a request to Search API and get the response.
    BingSearchService.Common.JSON.Response r = await GetSearchResponse(queryRequest);

    // Set itemsource and data template based on selection.
    switch (strSearchType)
    {
        case "web":
            SearchResults.ItemsSource = r.ResultSet.Results[0].Web;
            SearchResults.ItemTemplate = dtWeb;
            break;
        case "image":
            SearchResults.ItemsSource = r.ResultSet.Results[0].Image;
            SearchResults.ItemTemplate = dtImages;
            break;
        case "video":
            SearchResults.ItemsSource = r.ResultSet.Results[0].Video;
            SearchResults.ItemTemplate = dtVideo;
            break;
        case "news":
            SearchResults.ItemsSource = r.ResultSet.Results[0].News;
            SearchResults.ItemTemplate = dtNews;
            break;

        default:
            SearchResults.ItemsSource = r.ResultSet.Results[0].Web;
            SearchResults.ItemTemplate = dtWeb;
            break;
    }

    // Display current query text and location to user.
    tbQuery.Text = query;
    tbLat.Text = location.Latitude.ToString();
    tbLon.Text = location.Longitude.ToString();
    tbType.Text = strSearchType;

}

private async Task<BingSearchService.Common.JSON.Response> GetSearchResponse(Uri uri)
{
    System.Net.Http.HttpClientHandler handler = new System.Net.Http.HttpClientHandler();
    handler.Credentials = new NetworkCredential(BingSearchKey, BingSearchKey);
    System.Net.Http.HttpClient client = new System.Net.Http.HttpClient(handler);

    var response = await client.GetAsync(uri);

    using (var stream = await response.Content.ReadAsStreamAsync())
    {
        DataContractJsonSerializer ser = new DataContractJsonSerializer(
            typeof(BingSearchService.Common.JSON.Response));
        return ser.ReadObject(stream) as BingSearchService.Common.JSON.Response;
    }
}

When a user selects an item in the SearchResults ListBox, we will use a SelectionChanged event handler to present the appropriate web content to the end user with the help of the Windows.System.Launcher.LaunchUriAsync method. We specify the ViewSizePreference.UseHalf as one of our launcher options:

private async void SearchResults_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var listBox = sender as ListBox;

    // When a search result is selected, we will present the relevant
    // content to the user, preferably splitting the screen between our app and IE.
    if (SearchResults.SelectedItem != null)
    {
        // Set our preferences such that the desired content uses half of the screen.
        var options = new Windows.System.LauncherOptions();
        options.DesiredRemainingView = Windows.UI.ViewManagement.ViewSizePreference.UseHalf;

        // String for URL.
        string url = "";
                
        // Use the appropriate item URL to launch the content, depending on result type.
        if (SearchResults.SelectedItem.GetType() == typeof(WebResult))
        {
            var item = listBox.Items[listBox.SelectedIndex] as WebResult;
            url = item.Url;
        }
        else if (SearchResults.SelectedItem.GetType() == typeof(ImageResult))
        {
            var item = listBox.Items[listBox.SelectedIndex] as ImageResult;
            url = item.SourceUrl;

        }
        else if (SearchResults.SelectedItem.GetType() == typeof(NewsResult))
        {
            var item = listBox.Items[listBox.SelectedIndex] as NewsResult;
            url = item.Url;

        }
        else if (SearchResults.SelectedItem.GetType() == typeof(VideoResult))
        {
            var item = listBox.Items[listBox.SelectedIndex] as VideoResult;
            url = item.MediaUrl;
        }

        var success = await Windows.System.Launcher.LaunchUriAsync(new Uri(url));
    }
            
}

We add a method to resubmit the current search if the user changes their search source type, and we can now run our application:

Some scenarios to try include:

  • Selecting the News search source type, and double-tapping on streets near you to see local happenings; we can optionally include an additional search term such as ‘crime’ or ‘site:<your local news site>’ to further focus our search
  • Selecting the Images search source type, and double-tapping restaurant or bar landmarks in New York City to see pictures of the restaurant or menu to help you plan a night out
  • Selecting the Web search source type, adding an additional search term such as ‘site:microsoft.com citizenship’ and double-tapping countries or cities to see search results for community-centric initiatives by geography
  • Typical local search scenarios involve presenting physical point locations on the map based on text-based input, but as we have seen with our app, we can use maps integrated with search as a way to explore and find core search content as well.

The complete source code for the project can be found here. You will need to insert your Bing Maps key into MainPage.xaml, and your Azure Marketplace account key into the code-behind.

- Geoff Innis, Bing Maps Technical Specialist

MapDrivenSearch-Thumbnail.png