Creating an Earthquake Monitoring Application with Visiblox

The following post describes how to develop an Earthquake Monitor application based on live data from the USGS, using Silverlight, the MVVM Pattern, Visiblox and the Silverlight Bing Maps Interactive SDK. Click here to see the live application.

Earthquake Monitor Screenshot

Project Structure

When developing applications for Silverlight, the MVVM pattern provides an ideal architecture. MVVM makes code much more manageable and allows the use of data binding with limited code behind for the different views of the application.

The view should contain the xaml files and components that are visible to the users of the application. In this case, this will be the map view, the charts and the various configuration and comparison windows. In this project, we have model objects representing Earthquakes, Feeds and Feed Categories. The ViewModel sits between these two layers. The logic to load, parse and filter the data received from the USGS will is contained within the ViewModel, and it presents this data to the View. If you want to find out more information about the MVVM pattern, Jeremy Likness has a great introductory article over CodeProject.

Loading Data from the USGS

The first challenge presented when developing Earthquake Monitor is how to load data on the latest global earthquakes into the Silverlight project. The USGS provides a list of RSS feeds on their Latest Earthquakes: Feeds & Data page. These feeds are updated with the latest earthquake data from around the world. There are 8 feeds in total, containing various quake information based on data from the past hour, day or week and for different magnitude ranges.

Accessing Feeds without a CrossDomain or ClientAccessPolicy

Unfortunately, when using Silverlight, it is not possible to access these feeds directly. When a Silverlight xap file and the feed that it accesses are not located in the same domain, Silverlight requires that either a clientaccesspolicy.xml or a crossdomain.xml file be in place alongside the feed. If this is not the case, you will receive the infamous Silverlight SecurityException. The USGS site does not have a clientaccesspolicy or a crossdomain file on their site that will allow your Silverlight project access to their feeds. This is a common problem when using a Silverlight to consume feeds from the Internet that you don't have any control over. One solution is to use FeedBurner. FeedBurner allows you to take an existing feed and republish it with a new URL. Thankfully, FeedBurner does have a ClientAccessPolicy.xml file that allows access from all domains. This makes it perfect for accessing feeds without client access or cross domain policy files in their domain.

Downloading and Parsing RSS in Silverlight

Once you have added the feeds you require to feedburner, the next step is to download and parse this RSS data, into a sorted list of "Earthquake" objects. The code to do this is placed in the DataProvider class, inside the ViewModel layer of the application. The ViewModel then creates a DataProvider and uses it to fetch data from feedburner.

To download a list of Earthquakes, the DataProvider accepts the Uri of the feed we wish to download. The following code can then be used to download the response:

WebClient rssQuakeService = new WebClient();
rssQuakeService.DownloadStringCompleted += quakeDownloadComplete;
rssQuakeService.DownloadStringAsync(_uri, UriKind.RelativeOrAbsolute);

An event handler is attached to the WebClient which will be executed when the download is complete. The download is then started with the specified URI. Once the download is finished, the quakeDownloadComplete method will run. At this point, we have our data and it is ready to be parsed.

Before we parse the data, we must create a new thread. If the data were parsed on the main thread, the UI would be blocked. As we have no control over the size of the data, it is wise to avoid this through the use of a BackgroundWorker. This will keep the UI Thread running smoothly while our RSS is parsed into a list of "Earthquake" objects. This is done with the following code:

BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(ParseRSS);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FinishedParsing);
bw.RunWorkerAsync();

Similarly to the WebClient described above, we assign event handlers to events on the BackgroundWorker. The DoWork event will be raised on a separate thread and cause the ParseRSS method to be invoked. When the ParseRSS method finishes, FinishedParsing will be run on the UIThread to tell us that our list is now ready to use. To start all of this, we invoke the RunWorkerAsync method.

The feeds from the USGS are returned in RSS format. To parse RSS, we use an XDocument, which helps to simplify the process. We create an XDocument by using the static Parse method. This should create an XDocument based on the string that we have just downloaded. Then, using a LINQ query, we filter out header information and create a new list of Earthquakes based on this document, like so:

private void ParseRSS(object sender, DoWorkEventArgs e)
{
    // Create a new quake list, potentially replacing the old one.
    _quakeList = new List<Earthquake>();

    // Parse the RSS feed as an XDocument
    XDocument channel = XDocument.Parse(_downloadString);

    // Generate Earthquakes from the RSS channel.
    _quakeList = channel.Descendants()
        .Where(item => item.Name == "item")
        .Select((item, index) => CreateEarthquakeFromXElementAndIndex(item, index))
        .ToList();

    // Sort the List. Lowest Magnitude to Highest Magnitude.
    _quakeList.Sort(SortComparators.CompareQuakesByMagnitude);
}

This linq query selects the items within the XDocument where the item has the name "item". It then passes this filtered list of items to our CreateEarthquakeFromXElementAndIndex method. To see how to parse the RSS for each Earthquake item in detail, look at the CreateEarthquakeFromXElementAndIndex method of QuakeDataProvider in the source code provided. Finally, we sort _quakeList using a SortComparator, and store this inside our QuakeDataProvider. Once this ParseRSS method is completed, the FinishedParsing method is called, and we are ready to update the ViewModel with our new list of earthquakes.

Displaying Earthquakes on a Map Using the Silverlight Bing Maps SDK

In order to display a map in our application, we will use the Bing Maps Silverlight Control. You can see examples of the functionality that the control provides here. In order to get started with the Control, you should follow the Getting Started Using the Silverlight Map Control tutorial over at MSDN. Once you have registered and installed the library, adding a map to an application is simple. This is the only code required:

<m:Map x:Name="EarthquakeLocationMap" CredentialsProvider="<YOUR CREDENTIALS HERE>"/>

We customise this slightly by adding the Mode="AerialWithLabels" tag and setting the position in a grid, but essentially, this is the only XAML required to add a map to an application.

Drawing to the Map

Once we have our map set up, we will want to indicate where the Earthquakes we have loaded have taken place by drawing a circle on the map. The code I've used in the sample project is based on a great tutorial by Chris Pietschmann over at his blog that illustrates how to calculate the correct location for a circle given a longitude and latitude and how to draw a circle around that point.

You can see how this is done in this application by looking at the MainPage.xaml.cs methods:

DrawCircleOnMap(...)
DrawCircleOnMapsFromEarthQuake(...)

The code to do this is surprisingly simple, and very concise. One issue I encountered however was how to know when to remove the old quakes from the map and redraw the new ones if the feed is updated/changed. We can easily add methods to do this in the view, which remove the existing MapPolylines and draw new ones on the map. One option is to pass the ViewModel a reference to the View. When the ViewModel updates the Earthquake list, it could then call these methods on the View. This goes against the MVVM pattern however, as the ViewModel should have no knowledge of the View, and must not update the UI directly.

Therefore, in order update the view while conforming to the MVVM pattern, we use property changed events. In our view, we subscribe to property changed events on the ViewModel using the following code at the end of our constructor in MainPage.xaml.cs.

// Register for property changed events so we know when to update the view.
_viewModel.PropertyChanged += ViewModelPropertyChanged;

Our ViewModelPropertyChanged method will then simply check what has changed and take action accordingly:

private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == MonitorViewModel.SelectedQuakeProperty)
    {
        if (_viewModel.SelectedQuake == null)
            return;

        Location loc = new Location(_viewModel.SelectedQuake.Lat, _viewModel.SelectedQuake.Long);
        MoveMapToLocation(loc, 3);
    }

In the first half of this property changed method, we listen to when the SelectedQuakeProperty changes. If we detect a change, we call the MoveMapToLocation method based on a location generated from the Lat and Long properties of the selected quake. In the second half of the method, we listen for changes to the QuakeList itself, using the following code:

    else if (e.PropertyName == MonitorViewModel.QuakeListProperty)
    {
        // If the quake list changes, clear the maps and redraw the quakes.
        ClearMaps();

        foreach (Earthquake q in _viewModel.QuakeList)
        {
            DrawCircleOnMapsFromEarthQuake(q);
            q.PropertyChanged += new PropertyChangedEventHandler(QuakePropertyChanged);
        }

        // Recalculate the trends.
        CalculateTrends();
    }
}

If the QuakeListProperty changes, we start by clearing all maps of all earthquake circles through the ClearMaps method. We then draw new circles for every quake in the QuakeList, attaching property changed handlers so we know when to hide/display quakes when their visibility is changed. Finally, we call CalculateTrends, as the data displayed in the analysis charts is no longer valid. This is discussed further in the charting section below.

Using Visiblox to Plot the Earthquake Data

In order to compare the earthquakes in the application and help to visualise the data, I decided to add charts to the application. In order to do so, I used the Visiblox Charting Library. Visiblox offers high performance, great interactivity and an easy to use API. Visiblox also has strong support for the MVVM architecture, supporting binding directly to a series with it's BindableDataSeries class.

Adding Data to the Magnitude Chart

The magnitude chart is the main chart that is visible when the application is opened. It is set up in MainPage.xaml. Within this XAML, we create the chart "Series" using the following code:

<visiblox:ColumnSeries x:Name="columnSeries"
                       SelectedItem="{Binding Path=SelectedQuake, Mode=TwoWay}"
                       SelectionMode="SinglePoint">
    <visiblox:ColumnSeries.DataSeries>
        <visiblox:BindableDataSeries XValueBinding="{Binding Path=Index}"
                                     YValueBinding="{Binding Path=Magnitude}"
                                     ItemsSource="{Binding Path=FilteredQuakeList, Mode=TwoWay}"/>
    </visiblox:ColumnSeries.DataSeries>
</visiblox:ColumnSeries>

As you can see here, the code to bind to the series is extremely simple, thanks to the Visiblox BindableDataSeries. In the our MonitorViewModel class, we simply have a FilteredQuakeList, which we bind to the series ItemsSource in the XAML. This FilteredQuakeList is an ObservableCollection containing Earthquake model objects. Using the BindableDataSeries, we simply specify the names of the values we want to bind to for the X and Y values. In our case, the Index, and the Magnitude. Simple!

We also create a two way binding between the SelectedItem of the ColumnSeries and the SelectedQuake property in the MonitorViewModel. When one of the columns is selected in the magnitude chart, the chart will then update the SelectedQuake property on the view model (which is listened to in MainPage.xaml.cs and used to move the map to the selected quake). Similarly when the SelectedQuake property changes on the view model (due to a click event on an earthquake on the map or the grid in the analysis panel) the Magnitude chart will be informed so it knows which column to display the selected style on.

Binding the selected Earthquake to Visiblox and the Map

For the Earthquake Distribution chart, we take a similar approach:

<visiblox:LineSeries x:Name="FifteenHundredSeries"
                     ShowLine="False" ShowPoints="True"
                     SelectedStyle="{StaticResource SelectedStyle}"
                     SelectedItem="{Binding Path=SelectedQuake, Mode=TwoWay}"
                     SelectionMode="SinglePoint">
    <visiblox:LineSeries.DataSeries>
        <visiblox:BindableDataSeries XValueBinding="{Binding Path=Depth}"
                                     YValueBinding="{Binding Path=Magnitude}"
                                     ItemsSource="{Binding Path=FilteredQuakeList}"/>
    </visiblox:LineSeries.DataSeries>
</visiblox:LineSeries>

We use the exact same BindableDataSeries structure, but this time for a LineSeries. We also create another two way binding to the SelectedQuake property, meaning selected earthquakes will be represented by the selected style on this chart too. For the other charts in the analysis, we don't do any binding - instead choosing to calculate them using code behind in the MainPage.xaml.cs file. To see how this is done, look at the CalculateTrends() method. We set up as much of the charts as possible, but calculate the trends using the DataBinner helper class.

Earthquake Trends Panel

Styling the Charts with a Visiblox Theme

In order to give the charts in the application a custom style, we can use a Visiblox Theme. Themes allow you to define styles for the Visiblox chart types, and then they are automatically applied to every component contained in the theme. In order to implement a theme, we surround our "LayoutRoot" element in MainPage.xaml with a "visiblox:theme" tag as follows:

<visiblox:Theme ThemeUri="Resources/SunriseMod.xaml">
    <Grid x:Name="LayoutRoot" Background="Black">
        ...
    </Grid>
</visiblox:Theme>

This tells everything inside the "visiblox:Theme" tags to use the styles defined inside of the Resources/SunriseMod.xaml file. This is a very large XAML file that contains a huge list of all of the Visiblox elements and their styles. Editing the file is very simple though, as the variables used in the theme are all defined at the top of the XAML file as resources. Changing a style that will apply to all the charts inside the theme is therefore a simple case of modifying a resource at the head of the theme XAML file.

Other Points of Interest

I've listed a few other points of interest in this project below. Although not a comprehensive list, there are some nice features that I thought I should share!

Fullscreen

Fullscreen was implemented by simply adding a button to the toolbar of the MainPage.xaml, which excutes the following code:

if (Application.Current.Host.Content.IsFullScreen)
{
    Fullscreen_Button_TextBlock.Text = "Full Screen";
    Application.Current.Host.Content.IsFullScreen = false;
}
else
{
    Fullscreen_Button_TextBlock.Text = "Exit Full Screen";
    Application.Current.Host.Content.IsFullScreen = true;
}

This snippet can be used in any Silverlight Application to make your application fullscreen. However, text entry is disabled in fullscreen Silverlight applications.

Map Customisation

By setting the "Mode" property of the Map Control, you can customise the appearance of the map. Aerial will use satellite imagery, AerialWithLabels will use imagery with location names, and the default is a standard map.

Conclusion

One of the major challenges I faced when developing the application was the consumption of the various feeds from the USGS. Thankfully, FeedBurner gives us a way to work around this problem. Although it isn't perfect, for sample applications like this it can be extremely useful. Also, when developing real world applications for a client, it will most likely be the case that you either have access to the feed, or you can kindly request that the owners of the feed host a clientaccesspolicy.xml or a crossdomain.xml file on the feed server. Once you have access to a feed, parsing the data itself is incredibly straight forward.

After resolving these initial issues, I found it incredibly simple to develop the application thanks to the Bing Maps SDK and the Visiblox charting library. The majority of my time was spent on adding styling and retemplating the controls in the application.

Using Bing Maps is simple once you have a Credentials Key. The XAML to add a map to your application is concise, and drawing UIElements to a map through its Children property is surprisingly easy. Drawing the circles in the correct place was made even easier by the incredibly helpful tutorial by Chris Pietschmann.

When using Visiblox to add charts to the application, I found their API to be simple, clear and easy to use. The speed of interaction with the charts was great, and the library had no adverse effects on the application's performance. Thanks to the BindableDataSeries (and the ability to bind to the SelectedItem) it was incredibly easy to set up the charts and synchronise any changes to selection throughout the entire application.

Thanks go to Ian Sullivan who helped to implement the charts on the analysis tab and calculating the various trends.

Please send any questions to cgrant@scottlogic.com, and I'd love to hear your comments below!

Source Files & Downloads

You can download the source code for Earthquake monitor below. Please note that this download will not work out of the box. First, you will have to download Visiblox from the Visiblox website and add the Silverlight .dll file to the project. Then, download the Bing Maps Silverlight Control SDK from the Microsoft Download Center, and add the Microsoft.Maps.MapControl.Common.dll and Microsoft.Maps.MapControl.dll files into the project libraries. Finally, you'll have to replace the YOUR CREDENTIALS HERE text on line 132 of MainPage.xaml with a Credentials Provider string. For more information on installing the Bing Maps Silverlight Control SDK, please refer to the getting started guide.

Download Earthquake Monitor Source.

Open Earthquake Monitor

blog comments powered by Disqus