Quake
29 Jan 2012 by Nigel SampsonAn interactive view of the Christchuch earthquakes of September 2010. To build we cover binding between controls using ElementName, creating a value converter and using it in bindings, parsing a web service response using Linq to Xml and using the Bing Maps control.
Setup
Before jumping into the tutorials head to developer.windowsphone.com to download the free tools and get them installed.
In the early hours of September 4th the city of Christchurch, New Zealand was rocked by a 7.1 earthquake causing an estimated four billion dollars of damage but mercifully no loss of life. In order to demonstrate how to use some of the functionality of the Bings map control we’ll plot the quake and some of the aftershocks onto a map.
We’ll be pulling the information from the GeoNet quake web services. There’s a lot of different parameters we can use but we’ll filter it down to they days in question and initially filter our the lower magnitude quakes so as not to clutter the map even more (there were a lot of aftershocks in the following week).
First we’ll add a slider to the UI to control the lower bound for the magnitude so you can visualise the scope of the quakes. We’ll set some sensible minimum and maximum values for the slider and some text blocks to show the current values.
Now we could be complicated and add an method to the Value changed event of the slider and use that to update the text block but there’s a much better way, Element Binding. This allows us to bind the text of the TextBlock directly to the Value of the Slider. We start by giving the Slider a name, this allows us to reference it in the binding. Doing this through the UI of Visual Studio or Expression Blend is pretty easy, it’s under “Element Property” tab in the binding dialog in Blend and the “ElementName” option in the Source tab in Visual Studio.
<Slider x:Name="Magnitude" Minimum="1" Maximum="8" Value="4.5" />
<StackPanel Grid.Column="1" Orientation="Horizontal">
<TextBlock Text="{Binding ElementName=Magnitude, Path=Value}" VerticalAlignment="Center" FontSize="{StaticResource PhoneFontSizeLarge}" />
<TextBlock Text=" to 8" VerticalAlignment="Center" FontSize="{StaticResource PhoneFontSizeLarge}" />
</StackPanel>
If you’ve just wired up your binding right now you’ll notice that the TextBlock displays some very long values due to the double data type and lots of decimal places. What we need to fix this is a ValueConverter, value converters do pretty much what’s advertised on the box, once we’re plugged into a binding they can convert that values on the way through. What we’ll create is a StringFormatConverter, we could make a very specialised converter but we may as well create one we can reuse. Specially this converter will take a format parameter and use it to format the value passed to it (using the current culture). To be a value converter a class must implement the IValueConverter interface, our code is pretty simple and looks like:
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return String.Format(culture, parameter.ToString(), value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Now we have the value converter we can add it to the Resources collection of the page and update the binding to use the converter and format to the string to one decimal place.
<TextBlock Text="{Binding ElementName=Magnitude, Path=Value, Converter={StaticResource StringFormat}, ConverterParameter={0:0.0}}" VerticalAlignment="Center" FontSize="{StaticResource PhoneFontSizeLarge}" />
Now lets add the map the UI, we’ll need to add a reference to the assembly Microsoft.Phone.Controls.Maps and set up a namespace reference. We won’t be setting too many properties, the Mode to Aerial, the ZoomLevel to 6 and the Center to the GPS coordinates of the city of Christchurch. We’re also going to add a MapLayer to the map with a name, it’s to this layer that we’ll be adding the push pins.
<maps:Map ZoomLevel="6" Mode="Aerial" Grid.Row="1" Grid.ColumnSpan="2">
<maps:Map.Center>
<device:GeoCoordinate Latitude="-43.531637" Longitude="172.636645"/>
</maps:Map.Center>
<maps:MapLayer x:Name="QuakeLayer"/>
</maps:Map>
One very important thing I’m not adding to the Map is the CredentialsProvider, when the application starts you’ll notice a white box saying “Invalid Credentials”. You can get these for your application at the Bing Maps Portal and plug them into your application.
Reading the xml from the web service is going to be very much the same as reading the json from the tutorial My IP, we create a WebClient, attach to it’s OpenReadCompleted event and call OpenReadAsync passing our url based on the parameters of the Slider.
private void LoadQuakes()
{
var webClient = new WebClient();
webClient.OpenReadCompleted += OnOpenReadCompleted;
var uri = String.Format("http://magma.geonet.org.nz/services/quake/quakeml/1.0.1/query?startDate=2010-09-03&endDate=2010-09-05&magnitudeLower={0:0.0}&magnitudeUpper=8", Magnitude.Value);
webClient.OpenReadAsync(new Uri(uri, UriKind.Absolute));
}
Once we the web service returns we load the result into a Linq to Xml XDocument and retrieve all the event elements, from there we also parse the latitude, longitude and magnitude of the quake. Since we’re doing all the work in one method we can use an anonymous object to store our event data.
var document = XDocument.Load(e.Result);
if(document.Root == null)
return;
var xmlns = XNamespace.Get("http://quakeml.org/xmlns/quakeml/1.0");
var events = from ev in document.Root.Descendants(xmlns + "event")
select new
{
Latitude = Convert.ToDouble(ev.Element(xmlns + "origin").Element(xmlns + "latitude").Element(xmlns + "value").Value),
Longitude = Convert.ToDouble(ev.Element(xmlns + "origin").Element(xmlns + "longitude").Element(xmlns + "value").Value),
Magnitude = Convert.ToDouble(ev.Element(xmlns + "magnitude").Element(xmlns + "mag").Element(xmlns + "value").Value),
};
When then loop through the events creating a push pin for each and adding it to the map, using it’s Location property to correctly place it on the map itself.
QuakeLayer.Children.Clear();
foreach(var ev in events.OrderBy(ev => ev.Magnitude))
{
var accentBrush = (Brush)Application.Current.Resources["PhoneAccentBrush"];
var pin = new Pushpin
{
Location = new GeoCoordinate
{
Latitude = ev.Latitude,
Longitude = ev.Longitude
},
Background = accentBrush,
Content = ev.Magnitude.ToString("0.0"),
};
QuakeLayer.AddChild(pin, pin.Location);
}
I hope this helps someone, if you wish to donate to the Red Cross Canterbury Earthquake appeal you can at the Red Cross NZ website.
Download the Code
The code for all the tutorials is available to download: Windows Phone 7 Tutorials Solution.