Quantcast
Channel: Maps Blog
Viewing all 102 articles
Browse latest View live

How to Create a Speech-Powered Geocoding and Routing Experience in a Windows Store App

$
0
0

In a previous post, we saw how to incorporate speech into a JavaScript-based Windows Store app. In this post, we will speech-enable a XAML-based Windows Store app to provide a speech-driven geocoding and routing experience.

Two very common activities in map-based applications include searching for locations, and obtaining driving directions. In many scenarios, it can be more convenient and faster for users to provide the input for these activities via speech rather than keyboard, especially when using devices in which a full keyboard is not present. In this post, we will enable speech-based geocoding and routing using Bing Maps for Windows Store Apps, the Bing Speech Recognition Control for Windows 8.1, and the speech synthesis capabilities of the Windows 8.1 SDK.

The prerequisites for building our application include:

We can refer to the Bing Speech Recognition Control documentation for detailed instructions on installing and registering the control and enabling projects for speech recognition.

Speech-enabling 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 SpeechGeoRoute.

We now add the following references to our project:

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

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

SpeechPoweredGeocodingRoutingConfigManager

We will add Microphone and Location capabilities to our app in our Package.appmanifest, as detailed in the Bing Speech Recognition Control documentation. We add “microphone” and “location” capabilities, and an Extensions section as shown below:


<Capabilities>
<CapabilityName="internetClient"/>
<DeviceCapabilityName="location"/>
<DeviceCapabilityName="microphone"/>
</Capabilities>
<Extensions>
<ExtensionCategory="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>Microsoft.Speech.VoiceService.MSSRAudio.dll</Path>
<ActivatableClassActivatableClassId="Microsoft.Speech.VoiceService.MSSRAudio.Encoder"ThreadingModel="both"/>
</InProcessServer>
</Extension>
<ExtensionCategory="windows.activatableClass.proxyStub">
<ProxyStubClassId="5807FC3A-A0AB-48B4-BBA1-BA00BE56C3BD">
<Path>Microsoft.Speech.VoiceService.MSSRAudio.dll</Path>
<InterfaceName="IEncodingSettings"InterfaceId="C97C75EE-A76A-480E-9817-D57D3655231E"/>
</ProxyStub>
</Extension>
<ExtensionCategory="windows.activatableClass.proxyStub">
<ProxyStubClassId="F1D258E4-9D97-4BA4-AEEA-50A8B74049DF">
<Path>Microsoft.Speech.VoiceService.Audio.dll</Path>
<InterfaceName="ISpeechVolumeEvent"InterfaceId="946379E8-A397-46B6-B9C4-FBB253EFF6AE"/>
<InterfaceName="ISpeechStatusEvent"InterfaceId="FB0767C6-7FAA-4E5E-AC95-A3C0C4D72720"/>
</ProxyStub>
</Extension>
</Extensions>


Laying Out Our UI

Our UI will be map-centric, with a right-side ScrollViewer for displaying geocoding and routing output, along with an app bar on the bottom to allow the user to trigger speech input.

SpeechPoweredGeocodingRoutingUI

In our XAML code, we add:

  • XML namespace declarations for Bing.Speech.Xaml and Bing.Maps
  • A Map control, to which we add our Bing Maps Key as Credentials
  • A ProgressBar to enable users to track asynchronous geocoding and routing requests
  • A MediaElement to enable playing of our synthesized speech
  • A ListBox element which we will data bind to our geocoding results
  • A StackPanel element which we will data bind to our directions itinerary
  • A SpeechRecognizerUx element in our AppBar for speech capture
  • AppBarButtons in our AppBar for initiating speech capture, and clearing the map

Our final markup should appear as shown below:

<Page
x:Class="SpeechGeoRoute.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SpeechGeoRoute"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:bm="using:Bing.Maps"
xmlns:bs="using:Bing.Speech.Xaml"
mc:Ignorable="d">

<GridBackground="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinitionWidth="375"/>
</Grid.ColumnDefinitions>

<!-- Map and Progress bar -->
<bm:MapCredentials="InsertBingMapsKeyHere"x:Name="myMap"Grid.Column="0"></bm:Map>
<ProgressBarx:Name="progressBar"IsIndeterminate="True"Height="10"Width="300"Visibility="Collapsed"Grid.Column="0"VerticalAlignment="Top"/>
<!-- Right Side Panel -->
<ScrollViewerBackground="Gray"Grid.Column="1">
<StackPanelMargin="10,10,20,10">
<!-- Captured Speech -->
<TextBlockText="Speech Captured:"FontSize="24"Foreground="LightGray"/>
<TextBlockx:Name="txtSpeech"HorizontalAlignment="Left"FontSize="20"/>
<MediaElementx:Name="media"AutoPlay="False"></MediaElement>

<!-- Geocode Results Panel -->
<ListBoxx:Name="lbGeocodeResults"
SelectionChanged="lbGeocodeResults_SelectionChanged"Margin="0,10,0,0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlockText="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

<!-- Route Itinerary Panel -->
<StackPanelx:Name="spRouteResults">
<ListBoxItemsSource="{Binding RouteLegs}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxx:Name="lbItinerary"ItemsSource="{Binding ItineraryItems}"SelectionChanged="lbItinerary_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlockText="{Binding Instruction.Text}"
TextWrapping="Wrap"Width="300"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
<Page.BottomAppBar>
<AppBarIsSticky="True"IsOpen="True">
<StackPanelx:Name="RightPanel"Orientation="Horizontal"HorizontalAlignment="Right">
<!-- speech control-->
<bs:SpeechRecognizerUxx:Name="srSpeechControl"Height="85"VerticalAlignment="Center"/>
<!-- button to capture speech -->
<AppBarButtonx:Name="btnSpeech"Icon="Microphone"Label="Find or Route"Click="btnSpeech_Click">
</AppBarButton>
<AppBarSeparator></AppBarSeparator>
<!-- clear map button -->
<AppBarButtonx:Name="btnClearMap"Icon="Clear"Label="Clear Map"Click="btnClearMap_Click">
</AppBarButton>
</StackPanel>
</AppBar>
</Page.BottomAppBar>
</Page>

Adding Our C# Code

In our MainPage.xaml.cs code-behind, we add the following using statements:

using Bing.Speech;
using Bing.Maps;
using Bing.Maps.Search;
using Bing.Maps.Directions;
using System.Threading.Tasks;
using Windows.Devices.Geolocation;

We now declare private variables for our SpeechRecognizer and Geolocator, and add a handler for our page loaded event. This handler will:

  • instantiate our Geolocator, to enable us to find the user’s current location as the start point for routes
  • instantiate our SpeechRecognizer with our credentials we obtained when registering the control
  • associate our SpeechRecognizer with the SpeechRecognizerUx element in our XAML
  • add custom ‘Tips’ to guide users, including a tip on how to obtain driving directions by saying ‘Directions to’ and a destination place name

private SpeechRecognizer speechRecognizer;
private Geolocator geolocator;

public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// instantiate geolocator, to find start point for routes:
geolocator = new Geolocator();

// configure speech credentials:
var credentials = new SpeechAuthorizationParameters();
credentials.ClientId = "yourClientId";
credentials.ClientSecret = "yourClientSecret";
speechRecognizer = new SpeechRecognizer("en-US", credentials);
srSpeechControl.SpeechRecognizer = speechRecognizer;

// Add tips specific to usage:
srSpeechControl.Tips = newstring[]
{
"Say a place name to navigate the map to that location.",
"If you are not getting accurate results, try using a headset microphone.",
"Speak with a consistent volume.",
"To obtain directions, say 'Directions to' and a place name."
};
}


When the user clicks the Microphone button in our AppBar, we will use the Speech Recognition Control to capture the user’s speech, and take action.

  • if the user’s speech contains the phrase ‘Directions to’, we will attempt to obtain driving directions, using the content of the speech after this phrase as our destination
  • if the user’s speech does not contain that phrase, we will attempt to geocode the spoken location
  • if the ‘Confidence’ of the speech recognition is not sufficient, we will inform the user
private async void btnSpeech_Click(object sender, RoutedEventArgs e)
{
try
{
var result = await speechRecognizer.RecognizeSpeechToTextAsync();

// Check confidence, and proceed unless confidence is Rejected:
if (result.TextConfidence != SpeechRecognitionConfidence.Rejected)
{
// Clear current map contents:
ClearMap();

// Show captured speech:
txtSpeech.Text = result.Text;

// If user requested directions, calculate directions
// Otherwise, attempt to geocode input
int dirIndex = result.Text.IndexOf("directions to");
if (dirIndex > -1)
{
string destination = result.Text.Remove(0, dirIndex + 13);
Speak("Getting directions to " + destination);
await Directions(destination);

} else {
await Geocode(result.Text);
}
} else {
// Inform user of Rejected confidence:
Speak("I didn't understand that.");
}

}
catch (Exception)
{
// inform user of error, and ensure progress bar is hidden:
Speak("Error encountered processing speech.");
progressBar.Visibility = Visibility.Collapsed;
}
}

When we capture speech for geocoding, we use the SearchManager to issue an asynchronous geocoding request, using the captured text as our ‘Query’ input.

It is worth noting that using speech recognition to capture location information can work very well for populated places (such as ‘Atlanta, Georgia’) administrative districts (such as ‘Florida’ or ‘Ontario’) countries, and landmarks (such as ‘The Empire State Building’). It is less effective in capturing locations with numeric components, such as full street addresses, or zip codes.

We analyze our response data, and for each location returned, we place a pushpin on the map, with a Tapped event handler. When tapped, the name of the location will be spoken to the user. We also use data binding to populate our geocoding results ListBox with the location data. When the selection in the ListBox is changed, the selected location will also be shown and the name spoken to the user:

private async Task Geocode(string query)
{

if (!string.IsNullOrWhiteSpace(query))
{

// display progress bar:
progressBar.Visibility = Visibility.Visible;

// Set the address string to geocode
GeocodeRequestOptions requestOptions = new GeocodeRequestOptions(query);

// Make the geocode request
SearchManager searchManager = myMap.SearchManager;
LocationDataResponse response = await searchManager.GeocodeAsync(requestOptions);


if (response != null&&
response.HasError != true&&
response.LocationData.Count > 0 )
{

int i = 1;

foreach (GeocodeLocation l in response.LocationData)
{
//Get the location of each result
Bing.Maps.Location location = l.Location;

//Create a pushpin each location
Pushpin pin = new Pushpin()
{
Tag = l.Name,
Text = i.ToString()
};

i++;

//Add a Tapped event, which will speak the name of the location:
pin.Tapped += (s, a) =>
{
var p = s as Pushpin;
Speak(p.Tag asstring);
};

//Set the location of the pushpin
MapLayer.SetPosition(pin, location);

//Add the pushpin to the map
myMap.Children.Add(pin);

}

//Set the map view based on the best view of the first location
myMap.SetView(response.LocationData[0].Bounds);

//Pass the results to the item source of the GeocodeResult ListBox
lbGeocodeResults.ItemsSource = response.LocationData;
}
else
{
Speak("No geocoding results found.");
}
// hide progress bar:
progressBar.Visibility = Visibility.Collapsed;
}
else
{
Speak("Error encountered geocoding input.");
}
}

When we identify the desire to obtain driving directions, we use the DirectionsManager to issue an asynchronous request for directions, using the captured destination text as our destination waypoint, and we use the coordinates of the user’s current location, obtained from the Geolocator, as our starting waypoint.

We check our route response, and if we have successfully obtained a route, we use the first route returned, and:

  • display the route path
  • add labeled pushpins for the start and endpoints of the route
  • use data binding to populate our route results ListBox with the itinerary instructions for our route; note that we have also added an event handler such that when the selection in the ListBox is changed, the selected itinerary instruction will be shown and spoken to the user
  • Inform the user of the calculated drive time to the destination with speech
public async Task Directions(string destination)
{

// show progress bar:
progressBar.Visibility = Visibility.Visible;

// get current location for starting point:
Geoposition locCurrent = await geolocator.GetGeopositionAsync();


// Set the start (current location) and end (spoken destination) waypoints
Waypoint startWaypoint = new Waypoint(new Location(locCurrent.Coordinate.Point.Position.Latitude, locCurrent.Coordinate.Point.Position.Longitude));
Waypoint endWaypoint = new Waypoint(destination);

WaypointCollection waypoints = new WaypointCollection();
waypoints.Add(startWaypoint);
waypoints.Add(endWaypoint);

// configure directions manager:
DirectionsManager directionsManager = myMap.DirectionsManager;
directionsManager.Waypoints = waypoints;
directionsManager.RenderOptions.AutoSetActiveRoute = false;

// Calculate route directions
RouteResponse response = await directionsManager.CalculateDirectionsAsync();

// Ensure we have a calculated route:
if (response.HasError != true&& response.Routes.Count > 0)
{

// Use first route returned:
Route myRoute = response.Routes[0];

// Display the route on the map
directionsManager.ShowRoutePath(myRoute);

//Add custom start and end pushpins
Pushpin start = new Pushpin()
{
Text = "A",
Background = new SolidColorBrush(Colors.Green)
};

myMap.Children.Add(start);
MapLayer.SetPosition(start,
new Bing.Maps.Location(myRoute.RouteLegs[0].ActualStart.Latitude,
myRoute.RouteLegs[0].ActualStart.Longitude));

Pushpin end = new Pushpin()
{
Text = "B",
Background = new SolidColorBrush(Colors.Red)
};

myMap.Children.Add(end);
MapLayer.SetPosition(end,
new Bing.Maps.Location(myRoute.RouteLegs[0].ActualEnd.Latitude,
myRoute.RouteLegs[0].ActualEnd.Longitude));

//Pass the route to the Data context of the Route Results StackPanel:
spRouteResults.DataContext = myRoute;

// set the view to display the route path:
myMap.SetView(myRoute.Bounds);

// inform user that directions are being calculated:
Speak("Driving time to destination is: " + (Math.Round(myRoute.TravelDuration / 60)).ToString() + " minutes.");
} else {
// no route has been calculated:
Speak("Error finding route.");
}
// hide progress bar:
progressBar.Visibility = Visibility.Collapsed;
}

In our previous code blocks, we have used synthesized speech to convey information to the user. This is accomplished by using a SpeechSynthesizer object to create an audio stream and output speech based on a plain text string:

private async void Speak(string txtSpeech)
{
// The object for controlling the speech synthesis engine (voice).
Windows.Media.SpeechSynthesis.SpeechSynthesizer synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer();

// Generate the audio stream from plain text.
Windows.Media.SpeechSynthesis.SpeechSynthesisStream stream = await synth.SynthesizeTextToStreamAsync(txtSpeech);

// Send the stream to the media object.
this.media.AutoPlay = true;
media.SetSource(stream, stream.ContentType);
media.Play();

}

We now have the core of our app in place. With the addition of some code to handle our ListBox selection events, and clearing of the data on the map, our application is ready to go.

When running the application we find that we can quickly navigate our map from place to place by simply tapping our Microphone button and speaking location names, often faster than we would be able to even with a full keyboard present. The synthesized speech of place names and itinerary instructions adds reinforcement and context to the visual map presentation as well.

SpeechPoweredGeocodingRouting

Try the following activities:

  • Click the Microphone button and say “The Eiffel Tower”
  • Click the Microphone button and say “London”; you should see multiple options presented in the right panel, which you can select to have the place name spoken, and presented on the map
  • Click the Microphone button and say “Directions to Redmond, Washington” (or a suitable nearby town or landmark); After the directions have been calculated and presented, you should be informed of the driving time in minutes from your current location; select one of the itinerary items in the right panel, to navigate to that maneuver location, and have the maneuver instruction spoken

By powering our map navigation with Bing Speech, we can extend the usability of our mapping applications across a wider range of scenarios and devices, and make our end-user experience more efficient and engaging.

The complete source code for the project can be found here.

- Geoff Innis, Bing Maps Technical Specialist


How to Fine-tune Location Coordinates with the Custom Geocoding Refinement Tool

$
0
0

When presenting custom location data in Bing Maps applications, a frequent requirement is to geocode the addresses of your location data prior to presenting them on the map, ensuring that you have latitude and longitude coordinates that accurately reflect the real-world locations. Bing Maps offers batch geocoding capabilities via the Bing Spatial Data Services Geocode Dataflow API, and also via the Bing Maps Account Center, enabling data sources containing addresses to be geocoded en masse.

Occasionally, however, some addresses may not get geocoded as precisely as desired. Reasons for this can include incomplete or inaccurately captured address data, the use of ‘vanity’ addresses, and more. In these instances, being able to specify the precise location of addresses via a map-based utility can be very beneficial. Administrators can leverage the accurate base maps and rich imagery available in Bing Maps to identify the correct location, and capture the corresponding latitude and longitude.

For data sources that are managed via the Bing Maps Account Center, this ability is built into the Entity Data Editing interface. Many implementations take an alternate architectural approach, however, and host the location data in another location, such as SQL Azure, or an on-premises database.

This post includes a utility built with the Bing Maps AJAX v7 map control and Bing Maps REST Services to allow us to obtain accurate longitude and latitude coordinates via a map-based interface, with the ability to:

  • geocode address data and visualize the resulting geocoding metadata
  • visualize coordinates on maps and imagery
  • refine pushpin locations by dragging them with our mouse
  • save resulting pushpin coordinates to our data source

The Application

Download the source code for the application, unzip it, and enter your own Bing Maps key in the placeholder on line 66. Now open the HTML page in a web browser:

CustomGeocodingRefinement

Using the Utility

The utility can be used as-is to achieve a number of geocoding tasks. You can enter an address or place name into the ‘Geocode Address’ input box. The input text will be geocoded via the Bing Maps REST Services Locations API, and any geocoding matches found will be displayed as pushpins on the map.

Click any of the pushpins to view the metadata about the geocoding match. You can view the type of Entity the result represents, the confidence of the result, any returned match codes, and the calculation method used to derive the coordinates. Insight into what each of these attributes represents can be found here. The latitude and longitude of the pushpin are also displayed.

You can drag any of the pushpins with your mouse to move them to a new location. If you do this, you will see the coordinates of the new location reflected in the infobox, along with an alert that the pin has been dragged. By doing this, you can achieve a ‘rooftop-level’ precision, even for addresses which are not able to be geocoded to this precision level via the Locations API. You can also drag the pushpin to the desired location on larger properties, such as the main entrance of a large retail property or hotel.

If you have existing coordinates, you can enter them into the ‘Latitude’ and ‘Longitude’ input boxes, and assuming the coordinates entered are valid WGS84 decimal coordinates, the location will be displayed with a pushpin.

You can also simply navigate the map to your desired location, and CTRL + left-mouse-click to place a pushpin at that location.

At any time, you can open up the infobox for any pushpin, and select the Reverse Geocode action, which will use the Bing Maps REST Locations API to get location information associated with the current latitude and longitude of the pushpin.

Customizing the Utility

The utility can be extended to integrate more seamlessly with your data source or database. Possible extensions include:

  • Dynamically retrieving source address or coordinate information for existing location records directly by ID or attribute. This would enable administrators or other users to visualize the coordinates of existing records based on incoming issue reports
  • Customizing the handler function associated with the Save Data action in the infobox. This function can be adapted to save the updated information directly to the data source or database

While the batch geocoding capabilities of Bing Maps Spatial Data Services can meet most geocoding requirements for address data in locator-style applications, having a tool that can help you easily fine-tune location coordinates can help you ensure accuracy of data for end-users.

The source code for the application can be found here.

- Geoff Innis, Bing Maps Technical Specialist

Capture Addresses from Artifacts Using Bing Maps and Bing Optical Character Recognition Control

$
0
0

One of the most intriguing capabilities that Bing offers for empowering always-connected devices is the ability to visually recognize and understand artifacts in the real world. This is made available to developers via the Bing Optical Character Recognition (OCR) control for Windows 8.1 store apps. This control enables text from the outside world to be read by taking a picture from an on-device camera and analyzing the picture via a web service, with text and position information returned for interpretation. In this post, we will leverage the Bing OCR control to capture addresses from real-world artifacts, and will geocode the addresses with Bing Maps REST Locations API to enable us to obtain accurate coordinates and other metadata, as well as visualize the locations using Bing Maps for Windows Store Apps.

The prerequisites for building and successfully testing our application include:

  • Windows 8.1 and Visual Studio 2013
  • A Windows 8.x certified device with a built in rear facing camera that supports 1280x720 or 640x480 resolution in photo mode
  • The Bing Maps SDK for Windows Store Apps
  • The Bing OCR Control
  • A subscription to the Bing OCR Control in the Windows Azure Marketplace
  • Registration of your application in the Windows Azure Marketplace

We can refer to Bing OCR Control documentation for detailed instructions on installing and registering the control, and enabling projects for optical character recognition.

Enabling Our Project for OCR

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 OCRAddressCapture.

We now add the following references to our project:

  • Bing Maps for C#, C+ + or Visual Basic
  • Microsoft Visual C+ + Runtime Package
  • Bing Optical Character Recognition (OCR) Control

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

ConfigurationManager-CaptureAddressesWithOCR

We will add Webcam capabilities to our app in our Package.appmanifest, as detailed in the Bing OCR Control documentation:

packageappmanifest-CaptureAddressesWithOCR

Laying Out Our UI

Our UI will be divided into four quadrants:

  • In the top-left corner, we will place the Bing OCR control; the control is effectively a viewing area showing video from the available camera, which, when clicked or tapped, will capture the current image and send it to the OCR service for processing
  • In the top-right corner, we will display a Canvas which will be used to display the captured image; onto the canvas, individual ToggleButtons will be positioned for each Word the OCR control recognizes
  • In the bottom-left corner, we will show a Bing Map displaying the results of geocoding requests
  • In the bottom-right corner, we will display the address components and coordinates for geocoding results

Also included in the UI will be Buttons to allow the user to choose when to submit selected address components for geocoding, and to clear all current data.

UI-CaptureAddressesWithOCR

In our XAML code, we add:

  • XML namespace declarations for Bing.Ocr and Bing.Maps
  • Column and Row Definitions for our main Grid
  • An OcrControl with a basic InstructionOverlay for user information
  • A Canvas element to present OCR results, along with a bordered TextBlock in the same Grid cell to enhance presentation
  • Two Buttons to enable resetting of data, and submitting of geocoding requests
  • A bordered Map control, to which we add our Bing Maps Key as Credentials
  • A bordered Grid to present geocoding results in

Our final markup should appear as shown below:

<Page
x:Class="OCRAddressCapture.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:OCRAddressCapture"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:bm="using:Bing.Maps"
xmlns:ocr="using:Bing.Ocr"
mc:Ignorable="d">

<GridBackground="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinitionHeight="50"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ocr:OcrControlx:Name="ocr"Grid.Row="0"Grid.Column="0"Margin="10,10,10,10"BorderBrush="Gray">
<ocr:OcrControl.InstructionOverlay>
<TextBlockText="Click or tap to capture image."IsHitTestVisible="False"/>
</ocr:OcrControl.InstructionOverlay>
</ocr:OcrControl>
<BorderGrid.Column="1"Grid.Row="0"Margin="10,10,10,10"BorderThickness="2"BorderBrush="Gray">
<TextBlockVerticalAlignment="Center"HorizontalAlignment="Center"FontSize="30"Foreground="Gray">
Captured OCR image will appear here.
</TextBlock>
</Border>
<Canvasx:Name="OcrResultsCanvas"Grid.Row="0"Grid.Column="1"Margin="10,10,10,10">
</Canvas>
<Buttonx:Name="btnReset"Grid.Row="1"Grid.Column="0"Content="Reset All"HorizontalAlignment="Center"Click="btnReset_Click"Foreground="Gray"BorderBrush="Gray"></Button>
<Buttonx:Name="btnGeocode"Grid.Row="1"Grid.Column="1"Content="Submit Selected Address Fields"HorizontalAlignment="Center"Click="btnGeocode_Click"Foreground="Gray"BorderBrush="Gray"></Button>
<BorderGrid.Column="0"Grid.Row="2"Margin="10,10,10,10"BorderThickness="2"BorderBrush="Gray">
<bm:MapName="myMap"Credentials="your bing maps key"BorderThickness="2"BorderBrush="Gray"/>
</Border>
<BorderGrid.Column="1"Grid.Row="2"Margin="10,10,10,10"BorderThickness="2"BorderBrush="Gray">
<GridMargin="10,10,10,10"Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinitionWidth="180"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinitionHeight="50"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinitionHeight="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlockText="Address of Selected Result"FontSize="30"FontWeight="Bold"Grid.ColumnSpan="2"Grid.Column="0"Grid.Row="0"Foreground="Gray"></TextBlock>
<TextBlockText="Display Name:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="1"Foreground="Gray"/>
<TextBlockx:Name="txtDisplay"Text=""FontSize="20"Grid.Column="1"Grid.Row="1"Foreground="Gray"/>
<TextBlockText="Street:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="2"Foreground="Gray"/>
<TextBlockx:Name="txtStreet"Text=""FontSize="20"Grid.Column="1"Grid.Row="2"Foreground="Gray"/>
<TextBlockText="Town:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="3"Foreground="Gray"/>
<TextBlockx:Name="txtTown"Text=""FontSize="20"Grid.Column="1"Grid.Row="3"Foreground="Gray"/>
<TextBlockText="State:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="4"Foreground="Gray"/>
<TextBlockx:Name="txtState"Text=""FontSize="20"Grid.Column="1"Grid.Row="4"Foreground="Gray"/>
<TextBlockText="Zip/Postal:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="5"Foreground="Gray"/>
<TextBlockx:Name="txtPC"Text=""FontSize="20"Grid.Column="1"Grid.Row="5"Foreground="Gray"/>
<TextBlockText="Country:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="6"Foreground="Gray"/>
<TextBlockx:Name="txtCountry"Text=""FontSize="20"Grid.Column="1"Grid.Row="6"Foreground="Gray"/>
<TextBlockText="Latitude:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="7"Foreground="Gray"/>
<TextBlockx:Name="txtLat"Text=""FontSize="20"Grid.Column="1"Grid.Row="7"Foreground="Gray"/>
<TextBlockText="Longitude:"FontSize="20"FontWeight="Bold"Grid.Column="0"Grid.Row="8"Foreground="Gray"/>
<TextBlockx:Name="txtLon"Text=""FontSize="20"Grid.Column="1"Grid.Row="8"Foreground="Gray"/>
</Grid>
</Border>
</Grid>
</Page>

Adding Our C# Code

We will leverage the Bing Maps REST Locations API for geocoding functionality, as this exposes more geocoding metadata than the Bing.Maps.Search API in the Windows Store Apps control. We will 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 article on MSDN.

In our MainPage.xaml.cs code-behind, we add the following using statements:

using Bing.Maps;
using Bing.Ocr;
using BingMapsRESTService.Common.JSON;
using System.Runtime.Serialization.Json;
using System.Threading.Tasks;

We now declare a private variable to capture our OCR image width, and add a handler for our page loaded event. This handler will:

  • apply our OCR credentials we obtained when registering the control
  • assign event handlers for the OCR Completed, Failed, and FrameCaptured events
  • initiate the OCR control image preview, such that we are ready to capture an image

privatedouble imageWidth;

public MainPage()
{
this.InitializeComponent();
this.Loaded += MainPage_Loaded;
}

async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// client id and client secret for OCR control:
ocr.ClientId = "your client id";
ocr.ClientSecret = "your client secret";

// Assign event handlers for OCR:
ocr.Completed += ocr_Completed;
ocr.Failed += ocr_Failed;
ocr.FrameCaptured += ocr_FrameCaptured;

// Start the OCR control:
await ocr.StartPreviewAsync();
}


When the end-user taps or clicks the OCR control video preview area, the OCR control will raise the FrameCaptured event. We will add an event handler which will:

  • retrieve the captured BitmapImage
  • use the captured image as the background for our Canvas on which we will place the Words recognized during OCR
  • determine the width in pixels of the captured image; this width will enable us to determine an appropriate scale to use when placing found Words on our results Canvas
void ocr_FrameCaptured(object sender, FrameCapturedEventArgs e)
{
// Retrieve captured bitmap image:
var bitmap = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
bitmap.SetSource(e.CapturedImage);

// determine image width for scale calculation and placement of results on canvas:
imageWidth = bitmap.PixelWidth;

// Set Background of canvas with captured image:
ImageBrush ibBackground = new ImageBrush();
ibBackground.ImageSource = bitmap;
OcrResultsCanvas.Background = ibBackground;

}

When the OCR control receives a response from the Bing OCR Service, the Completed event is raised. Our event handler will:

  • determine an appropriate scale to use for placement of results in the Canvas, based on the actual width of the OcrResultsCanvas as compared to the imageWidth of the captured image
  • clear any previously captured words from the OcrResultsCanvas
  • loop through each captured Word in each Line, and show each word in a ToggleButton that we position as children on the OcrResultsCanvas
  • restart the OCR control preview camera
private async void ocr_Completed(object sender, Bing.Ocr.OcrCompletedEventArgs e)
{
// Determine scale, for presentation of words for selection:
var scale = OcrResultsCanvas.ActualWidth / imageWidth;

// Clear results canvas of words:
OcrResultsCanvas.Children.Clear();

// Confirm that we have captured text:
if (e.Result.Lines.Count == 0)
{
// Inform user of error
Notify("No text found.");
await ocr.StartPreviewAsync();
return;
}

// Read the captured content, and present for selection
foreach (Bing.Ocr.Line l in e.Result.Lines)
{
foreach (Word word in l.Words)
{
ToggleButton tbWord = new ToggleButton();
tbWord.Content = word.Value;
OcrResultsCanvas.Children.Add(tbWord);
Canvas.SetLeft(tbWord, word.Box.Left * scale);
Canvas.SetTop(tbWord, word.Box.Top * scale);

}
}


// Restart the preview camera.
await ocr.StartPreviewAsync();
}

If an error occurs in the OCR Control, the Failed event will be raised. Our event handler will capture the error details, and notify the user. The OCR Control will also be started or reset as appropriate:

async void ocr_Failed(object sender, Bing.Ocr.OcrErrorEventArgs e)
{
// Display error message.
string errorText = e.ErrorMessage;

// Give guidance on specific errors.
switch (e.ErrorCode)
{
case Bing.Ocr.ErrorCode.CameraBusy:
errorText += "\nClose any other applications that may be using the camera and try again.";
break;
case Bing.Ocr.ErrorCode.CameraLowQuality:
errorText += "\nAttach a camera that meets the requirements for OCR and try again.";
break;
case Bing.Ocr.ErrorCode.CameraNotAvailable:
errorText += "\nAttach a camera and try again.";
break;
case Bing.Ocr.ErrorCode.CameraPermissionDenied:
errorText += "\nTurn camera permissions on in your application settings.";
break;
case Bing.Ocr.ErrorCode.NetworkUnavailable:
errorText += "\nCheck your Internet connection and try again.";
break;
default:
errorText += "\nNotify your application provider.";
break;
}

Notify(errorText);

// Continue or cancel, depending on the error.
if (e.ErrorCode == Bing.Ocr.ErrorCode.Success) await ocr.StartPreviewAsync();
else
{
await ocr.ResetAsync();
}
}

Since captured images may contain text that is not address-related, we give the user the ability to select the address elements from the captured words. This is done by tapping or clicking each of the ToggleButtons containing the address elements. Once the desired address elements have been selected, the user can click the btnGeocode Button, which will initiate the geocoding process. The event handler will ensure that there are some child elements on the OcrResultsCanvas, and if so, will concatenate the content of all selected child elements into an address string. We are making the assumption that the address elements will appear in our captured text in an appropriate order, and concatenating them as such. Once we have created our address string, we pass it as a parameter to our asynchronous Geocode method.

private async void btnGeocode_Click(object sender, RoutedEventArgs e)
{
// Check to see if we have any selected words:
if (OcrResultsCanvas.Children.Count > 0)
{
// Construct address string from selected words:
// Note that we are appending the selected elements
// in the order they were captured in the image
// To-do: Consider allowing selection of order of
// address elements.
string address = "";
foreach (ToggleButton tbWord in OcrResultsCanvas.Children)
{
// If the word is selected, we append it to our address string
if (tbWord.IsChecked == true)
{
address += tbWord.Content + " ";
}
}

// If we have an address string, we will geocode it:
if (address != "")
{
// geocode address
await Geocode(address);
}
else
{
// inform user no words have been selected:
Notify("No elements have been selected.");
}
}
else
{
// inform user no words have been captured
Notify("No address elements are available.");
}
}

Our Geocode method will clear our map and address display fields, and use the received address string as the query parameter in a request to the Bing Maps REST Locations API. The JSON response data will be serialized against the data contracts we previously added to our project. We loop through each Location returned in our results, and:

  • add a Pushpin to our map, numbering it and adding the Location as the pushpin Tag
  • add a Tapped event handler to each pushpin, which will display the address and coordinates when the pushpin is selected
  • display the details of the first Location result by default
  • set the map view as appropriate, depending on whether we have a single result, or multiple results
private async Task Geocode(string query)
{
// Clear pushpins from map:
myMap.Children.Clear();

// Clear all address fields:
ClearAddressFields();

// Ensure we have a query value:
if (!string.IsNullOrWhiteSpace(query))
{
// Obtain Map session credentials:
string BingMapsKey = await myMap.GetSessionIdAsync();

//Create the request URL for the Geocoding service
Uri geocodeRequest = new Uri(
string.Format("http://dev.virtualearth.net/REST/v1/Locations?q={0}&key={1}",
query, BingMapsKey));

//Make a request and get the response
Response r = await GetResponse(geocodeRequest);

if (r != null&&
r.ResourceSets != null&&
r.ResourceSets.Length > 0 &&
r.ResourceSets[0].Resources != null&&
r.ResourceSets[0].Resources.Length > 0)
{
LocationCollection locations = new LocationCollection();

int i = 1;

foreach (BingMapsRESTService.Common.JSON.Location l in r.ResourceSets[0].Resources)
{
//Get the location of each result
Bing.Maps.Location location = new Bing.Maps.Location(l.Point.Coordinates[0], l.Point.Coordinates[1]);

//Create a pushpin for each location
Pushpin pin = new Pushpin()
{
// make location available to be used in pushpin Tapped handler
Tag = l,
Text = i.ToString()
};

i++;

//Add a tapped event that will display the address of the location
pin.Tapped += (s, a) =>
{
var p = s as Pushpin;
DisplayAddress(p.Tag as BingMapsRESTService.Common.JSON.Location);
};

//Set the location of the pushpin
MapLayer.SetPosition(pin, location);

//Add the pushpin to the map
myMap.Children.Add(pin);

//Add the coordinates of the location to a location collection
locations.Add(location);
}

// show first address found:
BingMapsRESTService.Common.JSON.Location topLoc = r.ResourceSets[0].Resources[0] as BingMapsRESTService.Common.JSON.Location;
DisplayAddress(topLoc);

//Set the map view based on the location collection:
if (locations.Count == 1)
{
myMap.SetView(new LocationRect(new Bing.Maps.Location(topLoc.BoundingBox[2], topLoc.BoundingBox[1]), new Bing.Maps.Location(topLoc.BoundingBox[0], topLoc.BoundingBox[3])));
}
else
{
myMap.SetView(new LocationRect(locations));
}

}
else
{
Notify("No Results found.");
}
}
else
{
Notify("Invalid Location Data Input");
}
}

The DisplayAddress method shows the address and coordinate details for geocoding results as appropriate:

privatevoid DisplayAddress(BingMapsRESTService.Common.JSON.Location location)
{
// populate address details for first result:

if (location.Address.FormattedAddress != null) txtDisplay.Text = location.Address.FormattedAddress;
if (location.Address.AddressLine != null) txtStreet.Text = location.Address.AddressLine;
if (location.Address.Locality != null) txtTown.Text = location.Address.Locality;
if (location.Address.AdminDistrict != null) txtState.Text = location.Address.AdminDistrict;
if (location.Address.PostalCode != null) txtPC.Text = location.Address.PostalCode;
if (location.Address.CountryRegion != null) txtCountry.Text = location.Address.CountryRegion;
txtLat.Text = location.Point.Coordinates[0].ToString();
txtLon.Text = location.Point.Coordinates[1].ToString();
}

We add in additional methods to our class to handle the retrieving of the REST responses, resetting of data, and notifying user of errors.

When running the application, we are able to point our device camera towards clear text, and capture an image by tapping the OCR Control. If the capture was successful, we can select the address elements from the results, and tap the submit button to have the location data geocoded. If the input is able to be geocoded, all results will be displayed on the map, and the address details of each result can be viewed by tapping the respective pushpin.

FinalApp-CaptureAddressesWithOCR

By capturing address details from real-world artifacts using the OCR Control and geocoding them with Bing Maps, we can enable scenarios such as:

  • Quickly and accurately capturing address details for new contacts from business cards or other promotional material, and adding them into our CRM system
  • Capturing addresses from real-estate brochures and viewing the house locations using rich Bing Maps imagery
  • Obtaining driving directions to restaurants offices, or other locations using printed menus, billboards, or printed listings

By integrating the robust cloud-based optical character recognition capabilities of the OCR Control with the powerful location-based tools offered by Bing Maps, developers can build applications that help bridge the gap between what we see in the real world and what we can do on our devices.

The complete source code for the project can be found here.

- Geoff Innis, Bing Maps Technical Specialist

Map-Driven Search in Windows Store Apps

$
0
0

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 Manager to select an individual platform to compile for, rather than All CPU, to satisfy a Visual C++ Runtime requirement:

ConfigurationManager-BingMapsMapDrivenSearch

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.

MapDrivenSearch

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>
<DataTemplatex:Name="dtImages">
<StackPanelOrientation="Vertical">
<TextBlockText="{Binding Title}"FontSize="15"FontWeight="Bold"
Foreground="RoyalBlue"/>
<ImageSource="{Binding Thumbnail.MediaUrl}"Height="{Binding Thumbnail.Height}"
Width="{Binding Thumbnail.Width}"></Image>
<StackPanelOrientation="Horizontal">
<TextBlockText="Source: "FontSize="11"FontStyle="Italic"FontWeight="Bold"/>
<TextBlockText="{Binding DisplayUrl}"FontSize="11"FontStyle="Italic"/>
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplatex:Name="dtWeb">
<StackPanelOrientation="Vertical">
<TextBlockText="{Binding Title}"FontSize="15"FontWeight="Bold"
Foreground="RoyalBlue"/>
<TextBlockText="{Binding Description}"FontSize="11"/>
<StackPanelOrientation="Horizontal">
<TextBlockText="Source: "FontSize="11"FontStyle="Italic"FontWeight="Bold"/>
<TextBlockText="{Binding DisplayUrl}"FontSize="11"FontStyle="Italic"/>
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplatex:Name="dtNews">
<StackPanelOrientation="Vertical">
<TextBlockText="{Binding Title}"FontSize="15"FontWeight="Bold"
Foreground="RoyalBlue"/>
<TextBlockText="{Binding Description}"FontSize="11"/>
<StackPanelOrientation="Horizontal">
<TextBlockText="Source: "FontSize="11"FontStyle="Italic"FontWeight="Bold"/>
<TextBlockText="{Binding Source}"FontSize="11"FontStyle="Italic"/>
</StackPanel>
</StackPanel>
</DataTemplate>
<DataTemplatex:Name="dtVideo">
<StackPanelOrientation="Vertical">
<TextBlockText="{Binding Title}"FontSize="15"FontWeight="Bold"
Foreground="RoyalBlue"/>
<ImageSource="{Binding Thumbnail.MediaUrl}"Height="{Binding Thumbnail.Height}"
Width="{Binding Thumbnail.Width}"HorizontalAlignment="Left"/>
<StackPanelOrientation="Horizontal">
<TextBlockText="Source: "FontSize="11"FontStyle="Italic"FontWeight="Bold"/>
<TextBlockText="{Binding MediaUrl}"FontSize="11"FontStyle="Italic"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</Page.Resources>

<GridBackground="Gray">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinitionWidth="350"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinitionHeight="195"/>
<RowDefinition/>
</Grid.RowDefinitions>
<bm:MapName="myMap"Grid.Column="0"Grid.RowSpan="2"Credentials="Your Bing Maps Key"
LandmarkTapped="myMap_LandmarkTapped"
DoubleTappedOverride="myMap_DoubleTappedOverride"/>
<ProgressBarx:Name="progressBar"IsIndeterminate="True"Height="10"Width="300"
Visibility="Collapsed"Grid.Column="0"Grid.RowSpan="2"
VerticalAlignment="Top"/>
<StackPanelMargin="10,10,20,10"Grid.Column="1"Grid.Row="0">
<TextBlockText="Optional Search Term(s)"FontSize="24"/>
<StackPanelOrientation="Horizontal"Margin="0,3,0,10">
<TextBoxName="tbSearchTerm"HorizontalAlignment="Left"
Width="240"Height="25"Text=""/>
<ImageHeight="23"Width="63"Source="./Assets/Logo_63x23_White.png"
Margin="10,0,0,0"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinitionWidth="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinitionHeight="25"/>
<RowDefinitionHeight="20"/>
<RowDefinitionHeight="20"/>
<RowDefinitionHeight="20"/>
<RowDefinitionHeight="20"/>
</Grid.RowDefinitions>
<TextBlockText="Current Query"FontSize="15"FontStyle="Italic"FontWeight="Bold"
Grid.Row="0"Grid.ColumnSpan="2"/>
<TextBlockText="Terms: "FontSize="13"Grid.Row="1"Grid.Column="0"
FontStyle="Italic"/>
<TextBlockx:Name="tbQuery"Text=""FontSize="13"Grid.Row="1"Grid.Column="1"/>
<TextBlockText="Type: "FontSize="13"Grid.Row="2"Grid.Column="0"
FontStyle="Italic"/>
<TextBlockx:Name="tbType"Text=""FontSize="13"Grid.Row="2"Grid.Column="1"/>
<TextBlockText="Lat: "FontSize="13"Grid.Row="3"Grid.Column="0"
FontStyle="Italic"/>
<TextBlockx:Name="tbLat"Text=""FontSize="13"Grid.Row="3"Grid.Column="1"/>
<TextBlockText="Lon: "FontSize="13"Grid.Row="4"Grid.Column="0"
FontStyle="Italic"/>
<TextBlockx:Name="tbLon"Text=""FontSize="13"Grid.Row="4"Grid.Column="1"/>
</Grid>
</StackPanel>
<ScrollViewerGrid.Column="1"Grid.Row="1">
<ListBoxName="SearchResults"
Margin="0,0,0,0"SelectionChanged="SearchResults_SelectionChanged">
</ListBox>
</ScrollViewer>
</Grid>
<Page.BottomAppBar>
<AppBarIsSticky="True"IsOpen="True">
<StackPanelx:Name="RightPanel"Orientation="Horizontal"HorizontalAlignment="Right">
<AppBarButtonx:Name="btnChooseType"Icon="Filter"Label="Search Type">
<Button.Flyout>
<Flyoutx:Name="flySearchTypes">
<ListBoxx:Name="lstTypes"SelectionChanged="lstTypes_SelectionChanged">
<ListBoxItemTag="web"IsSelected="true">
<TextBlock>Web</TextBlock>
</ListBoxItem>
<ListBoxItemTag="image">
<TextBlock>Images</TextBlock>
</ListBoxItem>
<ListBoxItemTag="video">
<TextBlock>Videos</TextBlock>
</ListBoxItem>
<ListBoxItemTag="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 article on 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 Landmark is tapped, we will:

  • Show our progress bar to notify our users that an operation is taking place
  • Retrieve the name of the Landmark from the event arguments
  • Issue a reverse geocoding request to obtain additional location context
  • Combine our Landmark name 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 DoubleTapped event. 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:

privatestring 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";
}
elseif (zoom <= 16 && zoom > 14)
{
EntityValue = "PopulatedPlace,AdminDivision1,CountryRegion";
}
elseif (zoom <= 14 && zoom > 10)
{
EntityValue = "PopulatedPlace,AdminDivision1,CountryRegion";
}
elseif (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 includeEntityTypes parameter 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 Composite service 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 HttpClient object. 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;
}
elseif (SearchResults.SelectedItem.GetType() == typeof(ImageResult))
{
var item = listBox.Items[listBox.SelectedIndex] as ImageResult;
url = item.SourceUrl;

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

}
elseif (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:

FinalApp-MapDrivenSearch

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.

For more Bing Maps developer contact, check out the Bing Developer Center.

- Geoff Innis, Bing Maps Technical Specialist

Make Clickable Shapes in the Native Bing Maps Control

$
0
0

The native Bing Maps Windows Store control has two types of shapes: polygons and polylines. These shapes are great for representing areas and paths on the map. Often it is useful to be able to associate some information or metadata with these shapes. In past versions of Bing Maps we could easily store this information in the Tag property of the shape. This makes it easy to retrieve this data when a shape is clicked or tapped. Unfortunately,the MapPolygon and MapPolyline shapes in the native Bing Maps Windows Store control do not have a Tag property. Recently on the Bing Maps forums, one of our Bing Maps engineers pointed out that the MapPolygon and MapPolyline classes are both DependancyObjects. This means that we could create a DependencyProperty which adds a “Tag” property to these shapes. In this blog post we are going to see just how easy this is to do.

To get started, open up Visual Studio and create a new project in either C# or Visual Basic. Select the Blank App template, call the application ClickableShapes, and press OK.

AddNewProjects-ClickableShapes

Add a reference to the Bing Maps SDK. To do this, right click on the References folder and press AddReference. Select WindowsExtensions, and then select Bing Maps for C#, C++ and Visual Basic and Microsoft Visual C++ Runtime. If you do not see this option ensure that you have installed the Bing Maps SDK for Windows Store apps.

ReferenceManager-ClickableShapes

If you notice that there is a little yellow indicator on the references that you just added. The reason for this is that the C++ runtime package requires you to set the Active solution platform in Visual Studio to one of the following options; ARM, x86 or x64. To do this, right click on the Solution folder and select Properties. Then go to Configuration Properties → Configuration. Find your project and under the Platform column set the target platform to x86. Press OK and the yellow indicator should disappear from our references.

ConfigurationManager-ClickableShapes

Next open the MainPage.xaml file and update it with the following XAML. This will add a map to the app. Make sure to set the Credentials attribute of the Map element to a valid Bing Maps key.

<Page
x:Class="ClickableShapes.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ClickableShapes"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:Bing.Maps"
mc:Ignorable="d">

<GridBackground="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<m:MapName="MyMap"Credentials="YOUR_BING_MAPS_KEY"/>
</Grid>
</Page>

Both the MapPolygon and MapPolyline classes derive from a common class called MapShape. Rather than creating two DependencyProperties,we can instead create a single one on the MapShape class. Open the MainPage.xamls.cs or MainPage.xaml.vb file and update it with the following code. This will create a DependencyProperty on the MapShape class called “Tag”.

C#

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

namespace ClickableShapes
{
publicsealedpartialclass MainPage : Page
{
private MapShapeLayer shapeLayer;

publicstaticreadonly DependencyProperty TagProp = DependencyProperty.Register("Tag", typeof(object), typeof(MapShape),new PropertyMetadata(null));

public MainPage()
{
this.InitializeComponent();
}
}
}

Visual Basic

Imports Bing.Maps
Imports Windows.UI
Imports Windows.UI.Popups

PublicNotInheritableClass MainPage
Inherits Page

Private shapeLayer As MapShapeLayer

PublicSharedReadOnly TagProp As DependencyProperty = DependencyProperty.Register("Tag", GetType(Object), GetType(MapShape), New PropertyMetadata(Nothing))

PublicSubNew()
Me.InitializeComponent()
EndSub
EndClass

Next we will add an event handler for when the map is loaded in the constructor of the app. In this event handler we will add a MapShapeLayer to the map for loading our shapes to. We will then generate a test polygon and polyline to the map. We will add some string as metadata to the Tag property by using the SetValue method on the shape. Update the constructor and add the MyMapLoaded event handler to the MainPage.xamls.cs or MainPage.xaml.vb file using the following code.

C#

public MainPage()
{
this.InitializeComponent();

MyMap.Loaded += MyMapLoaded;
}

privatevoid MyMapLoaded(object sender, RoutedEventArgs e)
{
//Add a shape layer to the map
shapeLayer = new MapShapeLayer();
MyMap.ShapeLayers.Add(shapeLayer);

//Create mock data points
var locs = new LocationCollection();

locs.Add(new Location(0, 0));
locs.Add(new Location(10, 10));
locs.Add(new Location(10, 0));

//Create test polygon
var polygon = new MapPolygon();
polygon.Locations = locs;
polygon.FillColor = Colors.Red;

//Set the tag property value
polygon.SetValue(TagProp, "I'm a polygon");

//Add a tapped event
polygon.Tapped += ShapeTapped;

//Add the shape to the map
shapeLayer.Shapes.Add(polygon);

var locs2 = new LocationCollection();
locs2.Add(new Location(20, 20));
locs2.Add(new Location(40, 40));
locs2.Add(new Location(50, 20));

//Create test polyline
var polyline = new MapPolyline();
polyline.Locations = locs2;
polyline.Width = 5;
polyline.Color = Colors.Blue;

//Set the tag property value
polyline.SetValue(TagProp, "I'm a polyline");

//Add a tapped event
polyline.Tapped += ShapeTapped;

//Add the shape to the map
shapeLayer.Shapes.Add(polyline);
}

Visual Basic

PrivateSub MyMapLoaded(sender AsObject, e As RoutedEventArgs)
'Add a shape layer to the map
shapeLayer = New MapShapeLayer()
MyMap.ShapeLayers.Add(shapeLayer)

'Create mock data points
Dim locs = New LocationCollection()

locs.Add(New Location(0, 0))
locs.Add(New Location(10, 10))
locs.Add(New Location(10, 0))

'Create test polygon
Dim polygon = New MapPolygon()
polygon.Locations = locs
polygon.FillColor = Colors.Red

'Set the tag property value
polygon.SetValue(TagProp, "I'm a polygon")

'Add a tapped event
AddHandler polygon.Tapped, AddressOf ShapeTapped

'Add the shape to the map
shapeLayer.Shapes.Add(polygon)

Dim locs2 = New LocationCollection()
locs2.Add(New Location(20, 20))
locs2.Add(New Location(40, 40))
locs2.Add(New Location(50, 20))

'Create test polyline
Dim polyline = New MapPolyline()
polyline.Locations = locs2
polyline.Width = 5
polyline.Color = Colors.Blue

'Set the tag property value
polyline.SetValue(TagProp, "I'm a polyline")

'Add a tapped event
AddHandler polyline.Tapped, AddressOf ShapeTapped

'Add the shape to the map
shapeLayer.Shapes.Add(polyline)
EndSub

Finally we will need to create the event handler for when the shapes are tapped. When a shape is tapped we will be able to use the GetView method on the shape to retrieve the Tag property. We will then take this value and display it to the user using a MessageDialog. Add the following event handler to the MainPage.xamls.cs or MainPage.xaml.vb file.

C#

private async void ShapeTapped(object sender, TappedRoutedEventArgs e)
{
if (sender is MapShape)
{
var poly = sender as MapShape;
var tag = poly.GetValue(TagProp);

if (tag != null&& tag isstring)
{
var msg = new MessageDialog(tag asstring);
await msg.ShowAsync();
}
}
}

Visual Basic

Private Async Sub ShapeTapped(sender AsObject, e As TappedRoutedEventArgs)
IfTypeOf sender Is MapShape Then
Dim poly = TryCast(sender, MapShape)
Dim tag = poly.GetValue(TagProp)

If tag IsNot NothingAndAlsoTypeOf tag IsStringThen
Dim msg = New MessageDialog(TryCast(tag, String))
Await msg.ShowAsync()
EndIf
EndIf
EndSub

The application is now complete. Deploy the app by pressing F5 or clicking on the Debug button. When the app is running tap or click on the shapes on the map. When the event is fired a message will be displayed that contains the metadata stored in the Tag property of the shape.

FinalApp-ClickableShapes

Note in this example I simply stored a string in the Tag property, but you can store any object you want in it. You can download the full source code for this code sample from the Visual Studio code gallery here.

If you are looking for some other great resources on Bing Maps for Windows Store apps, look through this blog, go to the Bing Developer Center blog, or check out all the Bing Maps MSDN code samples.

- Ricky Brundritt, EMEA Bing Maps TSP

15 New 3D Cities Available in the Bing Maps Preview App

$
0
0

In December, we launched the Bing Maps Preview app on Windows 8.1, introducing a new way to explore the world in a touch-friendly way. We’ve been flying, driving and processing since then and have just added 15 more 3D cities to the list we launched with.

With the help of world builders from the video game industry, expert photogrammetrists, high definition aerial cameras and a massive data pipeline crunching petabytes of imagery, we are delivering a more natural way to experience our planet.  Touch it, tilt it, tap it or turn it to immerse yourself and experience maps in new ways.

Taking advantage of our advanced geometry engine, street labels and markers dynamically adapt to your vantage point.  So as you traverse the landscape the map adapts to how your eye sees the 3D space.

You can now explore 360-degree views of these new cities:

Germany

  • Duisburg
  • Dresden

Spain

  • Marbella
  • Murcia

United States

  • Montgomery, AL
  • Bakersfield, CA
  • Sacramento, CA
  • San Francisco, CA
  • Gainesville, FL
  • Tallahassee, FL
  • Columbus, GA
  • Baton Rouge, LA
  • Shreveport, LA
  • Gulport, MS
  • Seattle, WA

SanFran_CA_3D
Image of San Francisco, California

Check out all of the cities currently available in 3D at the 3D Cities website. Get the Preview app at the Windows Store and experience the cities for yourself.

- Bing Maps Team

Teaching Bing Maps Some New (Language) Tricks

$
0
0

We are excited to announce the preview release of dual labeling, ‘neutral ground truth’ (ngt) and additional language support. This new update to the AJAX Version 7.0 control allows you to create maps with labels in two different languages. This release also includes the ‘ngt’ language value that shows map labels in the native language of each country respectively. We have also added support for additional languages.

Now you can create a map that shows labels in two languages: primary language and secondary language. The primary language is used for map labels, the navigation control and directions when supported.

To create a map with dual labels using AJAX Version 7.0, you must specify two languages in the mkt parameter. For example, mkt=pl-pl,en-us will show map labels in Polish and English. You can also use a new custom culture ‘ngt’ to show map labels in the local language of the region. For example mkt=ngtwill show Russian map labels within Russia, Polish labels within Poland, and so on.

The example below displays map labels in both English-United States (en-us) and the local language:

<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-us,ngt"></script>

EnglishNGT

For travelling consumers, this map style is a great benefit, particularly in regions where a user may not know the local language. With dual labels, the map can show users their native language and the local language. Having labels in a primary and secondary language can help users better understand and navigate the region they are visiting while simplifying their travelling experience.

Documentation outlining this release is available on MSDN in the Localizing the Maptopic.

We have also added support for the following new languages:

  • de-AT German-Austria
  • de-CH German-Switzerland
  • fr-CH French-Switzerland
  • ko-KR Korean-Korea
  • pl-PL Polish-Poland
  • ru-RU Russian-Russia
  • zh-HK Chinese-Hong Kong SAR
  • zh-TW Chinese-Taiwan

- Bing Maps Team

A New Route for Bing Maps Developers

$
0
0

About 9 years ago, I started a little MapPoint Web Service blog targeted at helping Enterprise customers and developers build geospatial applications. Having run the consulting arm of Vicinity Corporation, overseeing the customer migration from Vicinity web services to Microsoft web services and spinning up an Evangelist role, I’d see the same questions over and over again.

The premise of my blogging started as a result of trying to eliminate writing the same email to different people multiple times over. After all, if one person had a question, it made sense that someone else was going to eventually ask the same question. So, I started publishing the info and allowing the search engines to index my content. My life was finally scalable! I took over consumer blogging for the Bing Maps (then Virtual Earth) website and content updates. I wrote with fleeing glory as I brought my coding efforts to the then Virtual Earth blog. I was stoked to see my coding entries in-line with imagery acquisition announcements, features releases and 3D updates.

Then I started working with an amazing PR Manager who taught me the value of timing “big” blog posts for outlet syndication. Okay, so I wouldn’t publish blogs at 2AM because I was so excited about finishing the code. And, she wanted to review the post before I published it, which took some getting used to. But listening to her was the best move I made, as she helped get the blog syndicated into news outlets, expanding the reach beyond what I’d ever expected.

So, why ramble on? Because I feel it’s an end of an era. I don’t post much on the blogs anymore, but I ensure there is a plan and strategy for the content. I should post more, and I plan to . Anyway, it is time again to change the blogging strategy. When once I thought never the two shall twain…it’s back to the future time.

As such, I want to welcome you to the new home of Bing Maps blog content for developers – the Bing Dev Center. The Bing Dev Center is now the place for all developer-focused content about Bing Maps. Now, developer resources are living in one place so you can keep up with Bing Maps blog posts, check in on the forums and access controls and documentation downloads.

Bing Dev Center

The Bing Maps blog has been going strong for many years. We will continue to include updates on imagery refreshes, Maps App releases, and other Bing Maps news on the current Bing Maps blog. Technical content and posts specific to the developer audience will live on the Bing Dev Center.

Bing Dev Center

To make sure you can easily find the content you care about during this transition, we will include short blurbs on the Bing Maps blog pointing you to any new technical content available on the Bing Dev Center.

We have an exciting year ahead with and plan to have a steady flow of Bing Maps content coming your way. Make sure to bookmark the Bing Dev Center along with Bing Maps Blog to get the latest and greatest from Bing Maps.

- Chris Pendleton

Principal Program Manager Lead

Bing Maps


Learn How to Create Heat Maps in Native Windows Store Apps

$
0
0

Find out how I came up with a solution for creating heat maps in native Windows Store apps and document the reusable library that I created. Heat maps, also known as density maps, are a type of overlay on a map used to represent data using different colors. They are often used to show the data hot spots on a map. If you are working in JavaScript there is a client-side heat map module available in the Bing Maps V7 Modules project, which can be used in Windows Store apps. Using this module is fairly easy and only takes a few lines of code to setup. Creating heat maps in native Windows Store apps is a bit harder to do...  READ MORE

Learn How to Create a Location-aware Windows Store App (Unabridged version)

$
0
0

LocationIntelligenceWindowsStoreApps-BookCoverAre you developing a location-based Windows Store app using web or managed programming languages? Do you need help with how to get started?

A new eBook entitled, Location Intelligence for Windows Store Apps, is now available for free download. Written by Ricky Brundritt (EMEA Bing Maps TSP at Microsoft), the eBook delves into location intelligence and the different options for creating location-aware apps in Windows 8.1.

The first half of the book focuses on the inner workings of Window Store apps and the various location-related tools available (e.g. sensors and the Bing Maps SDK). The second half walks you through the process of creating several useful location-intelligent apps. READ MORE

How to Create Custom Shapes in Windows Store Apps (C#)

$
0
0

In the Bing Maps SDK for Windows Store Apps there are five main types of data that you can add to the map: Pushpin, MapPolyline, MapPolygon, MapTileLayer, and UIElements. UIElements can be added to the map just like pushpins and are a great way to create custom shapes to the map.  In this blog post we will see how we can make use of the ability to add a UIElement to the map to create custom shapes that have a lot more customization options. The main shapes we will focus on are the MapPolyline and MapPolygon classes. The MapPolyline class lets you set the width and color of the line, the MapPolygon class only lets you set the fill color. If you have used any of the other Bing Maps SDK’s you may remember that there were a lot more options in other versions, such as being able to create dashed lines, and apply brushes to shapes instead of just colors.  READ MORE

How to Create a Customer Ranked Auto Suggest with Bing Maps and Azure Mobile Services

$
0
0

From time to time I come across developers who want to have an auto suggest search box to use with their map. Your first thought might be to simply use the Bing Maps geocoding services to do this, however this often ends up generating a large number of transactions. If you are using Bing Maps under the free terms of use this can result in your application quickly exceeding the free usage limits. If you have an enterprise license for Bing Maps these additional transactions could potential increase the cost of your license significantly… READ MORE

Geo-fencing with Bing Spatial Data Services and Azure Mobile Services

$
0
0

Triggering certain actions such as sending notifications or alerts when a device enters or leaves an area is often referred to as geo-fencing. The geo-fence, the boundary of the area of interest, can be dynamic like a radius around a school or around your own device, it can be pre-defined such as a neighborhood, city or county, or it can be an area defined and digitized for a specific purpose. In this post, we will walk through an end-to-end scenario that will look at the Bing SDS to store and query geo-fences, the tracked device, the Azure Mobile Services as the hub to process the tracked locations and send notifications as well as a Windows Store app to catch and display tile and toast notifications…READ MORE

Image Overlays with Bing Maps (Native)

$
0
0

A while back I wrote a blog posts called Image Overlays with Bing Maps (JavaScript). In this blog post we saw a couple of different ways to overlay an image on the map and bind it to a bounding box such that it stays correctly positioned when panned and scales as you zoom the map. In this blog post we are going to look at two different methods for doing this in the Native Bing Maps SDK for Window Store Apps. The first method will consist of creating a custom control that stretches and positions an image as the map is zoomed and panned. The second method will make use of the Custom Polygon control from a recent blog post. We will overlay the image on the custom polygon using an image brush…READ MORE

Staying Fit with Bing Maps

$
0
0

I just feel better when I have a certain level of fitness. So, I came up with the idea of the ‘Donkey Bike’. The idea was to create a virtual trip to visit my parents in Germany and track the progress with every rotation of my wheel and every stroke of the rower. I’d rather choose a scenic route than the direct one, so the first part of the journey is a 6,886 km ride from Redmond, WA to Nova Scotia, Canada. With 1,081 km down, a flat tire (yes, that can happen even in the basement), a few interesting scenes discovered on the imagery and a few pounds lighter than when I started the journey, I thought it’s time to share some details on the somewhat unusual ‘Donkey Bike’… READ MORE


Bing Spatial Data Services Meets 3D

$
0
0

At //build 2014 there were many exciting announcements. It would have been easy to miss one of the implications of the moment of glory for Internet Explorer 11 during the keynote on day 1. The demo of the new FishGL website gave a hint about the enhanced support for WebGL in IE11 and, while I find fish quite yummy, I was more intrigued about what it means for mapping. After installing the Windows 8.1 Update, I quickly checked on Cesium, a free and open source map control that uses by default Bing Maps imagery and the Bing Maps geocoder. With this release of IE11 you can now take advantage of the latest and greatest in the Cesium control. In this quick tutorial we will visualize data stored in the Bing Spatial Data Services (SDS) through the Cesium map control in 3D… READ MORE

Visualize Large Complex Data with Local Tile Layers in Bing Maps Windows Store Apps (C#)

$
0
0

Tile Layers are a creative way to visualize large complex data. By visualizing data as an image the map control only needs to reposition a set of images rather than every single data point of the data. This greatly improves performance and often reduces the amount of data the user will have to download. Tile layers generally consist of a large number of images that have a specific naming convention and are hosted online. Every once and a while I get requests from people who want to be able to host the tile layers locally. Creating a local tile layer is not an easy task.  In this blog post instead of going through the 900+ lines of code required to create a local tile layer I will describe the process required to create one and how to make use of the library I created in the code samples…READ MORE

How to Save and Share Map Screenshots in Windows Store Apps (.NET)

$
0
0

A while ago I wrote a blog post on How to Share Maps Using the Search Charm in Windows Store Apps. In that blog post we made use of the Bing Maps REST Imagery service to generate a static image of the map that we could share in an email.  But what if you want a true screenshot of the map? In this blog post we are going to take a look at how to take a screenshot of the map which we can then either share via the Share charm or save as a local file for use later in documents or presentations…READ MORE

How to Create Nicely Formatted Directions in Bing Maps Windows Store apps (.NET)

$
0
0

Not long after the Bing Maps Windows Store apps SDK was first released we published a blog post on how to calculate and display routes. We created a simple input form to allow the user to enter a start and end location and then displayed the route instructions using aListBox. This works fine, however the directions in the Maps app in Windows 8 have a lot more options in the input form, and the route instructions have nice turn-by-turn icons. Wouldn’t it be nice if we could have nice looking directions in our app or better yet if we didn’t have to directly access the Bing Maps REST services to calculate our route? In this blog post we are going to take a look at how we can create a similar user experience for directions in our own app much easier than the original post we published…READ MORE

Bing Maps Styling Improvements Now Live!

$
0
0

Today, we are pleased to announce several rendering and styling improvements to Bing Maps on http://www.bing.com/maps to enhance aesthetics and usability.

Gradient Land Color

We introduced a new gradient coloring for land areas that adjusts the brightness to your zoom level. The land color will now be lighter when zoomed out and get darker when zoomed in at higher levels of detail. Not only do these updates improve the overall aesthetic appeal of the maps, but they also help increase the contrast, making streets and other features more visible.

Before

BingMapsStyling-GradientColorBefore

 

After

BingMapsStyling-GradientLandColorAfter

Localized Landmark Icons

We also added new, culturally-accurate symbols for landmarks. Instead of using the same icons for landmarks like banks or pharmacies globally, we introduced new symbols that are relevant no matter where you are.

BingMapsStyling-LocalizedLandmarkIcons

 

Transit Icon Consolidation

Additionally, we removed some instances where public transit icons were duplicated on the map in the United States.

BingMapsStyling-TransitIconConsolidation

Park Trails and Pathways

New styling helps clearly differentiate park trails and pathways from roads.

Before

BingMapsStyling-trails_before

 

After

BingMapsStyling-trails_after

 - Bing Maps Team

Viewing all 102 articles
Browse latest View live