In the last post I made, looking at analysing weather information using XPO, one of the commenters asked if we could see more examples using different types of associations between objects, so I thought I’d start off by posting something on one to one associations. To do this we are going to look at earthquakes (well why not?).
Now the USGS are good enough to provide us with a feed of earthquakes in, pretty much, real time. Since the bigger the better, we’ll look at quakes magnitude 5 and above happening in the previous 7 days. The USGS publishes an atom feed of these at: http://earthquake.usgs.gov/earthquakes/catalogs/7day-M5.xml.
To use this feed, the first thing we are going to do is to define an Earthquake entity and a GeoPoint entity, these entities will have a one to one relationship:
using System;
using DevExpress.Xpo;
namespace EarthquakeFetcher.Model
{
public class EarthQuake: XPObject
{
public EarthQuake(Session session)
: base(session)
{ }
private string title;
public string Title
{
get
{
return title;
}
set
{
SetPropertyValue("Title", ref title, value);
}
}
private DateTime timeStamp;
public DateTime TimeStamp
{
get
{
return timeStamp;
}
set
{
SetPropertyValue("TimeStamp", ref timeStamp, value);
}
}
private GeoPoint geoPoint;
public GeoPoint GeoPoint
{
get
{
return geoPoint;
}
set
{
if (geoPoint == value)
return;
//GS - Store a reference to the former geoPoint.
GeoPoint previousGP = geoPoint;
geoPoint = value;
if (IsLoading) return;
//GS - Remove the previous reference if there is one.
if (previousGP != null && previousGP.EarthQuake == this)
previousGP.EarthQuake = null;
//GS - Attach this reference
if (geoPoint != null)
geoPoint.EarthQuake = this;
//GS - Signal the change
OnChanged("GeoPoint");
}
}
//GS - Override ToString() to give us a pretty label
public override string ToString()
{
return Title;
}
}
}
using System;
using DevExpress.Xpo;
namespace EarthquakeFetcher.Model
{
public class GeoPoint : XPObject
{
public GeoPoint(Session session)
: base(session)
{ }
private string point;
public string Point
{
get
{
return point;
}
set
{
SetPropertyValue("Point", ref point, value);
}
}
private int elevation;
public int Elevation
{
get
{
return elevation;
}
set
{
SetPropertyValue("Elevation", ref elevation, value);
}
}
private EarthQuake earthQuake;
public EarthQuake EarthQuake
{
get
{
return earthQuake;
}
set
{
if (earthQuake == value)
return;
//GS - Store a reference to the former EarthQuake.
EarthQuake previousQuake = earthQuake;
earthQuake = value;
if (IsLoading) return;
//GS - Remove the previous reference if there is one.
if (previousQuake != null && previousQuake.GeoPoint == this)
previousQuake.GeoPoint = null;
//GS - Attach this reference
if (earthQuake != null)
earthQuake.GeoPoint = this;
//GS - Signal the change
OnChanged("EarthQuake");
}
}
}
}
Note the syntax on the one to one associations. Now then, the next thing we have to do is to read the feed, create objects from it and persist them:
using System;
using System.Xml.Linq;
using DevExpress.Xpo;
using EarthquakeFetcher.Model;
namespace EarthquakeFetcher
{
class Program
{
static void Main()
{
//GS - Fetch the feed
XDocument quakeFeed =
XDocument.Load(@"http://earthquake.usgs.gov/earthquakes/catalogs/7day-M5.xml");
//GS - Add the required namespaces
XNamespace nsAtom = "http://www.w3.org/2005/Atom";
XNamespace nsGeoRSS = "http://www.georss.org/georss";
//GS - Persist the quake information
using (UnitOfWork uow = new UnitOfWork())
{
foreach (var quake in quakeFeed.Descendants(nsAtom + "entry"))
{
new EarthQuake(uow)
{
Title = quake.Element(nsAtom + "title").Value,
TimeStamp = DateTime.Parse(quake.Element(nsAtom + "updated").Value),
GeoPoint = new GeoPoint(uow)
{
Point = quake.Element(nsGeoRSS + "point").Value,
Elevation = Convert.ToInt32(quake.Element(nsGeoRSS + "elev").Value)
}
};
}
uow.CommitChanges();
}
}
}
}
There isn’t anything in the above code worth commenting on I think, just note the addition of the namespaces for atom and GeoRSS. Running this code will cause the present list of quakes to be persisted. Note, this code is for demonstration and instructional purposes only and has had “guard” code removed for clarity. If you were to put this code into production you would have to handle the case of duplicate quake entries being persisted if you were to run this code more than once in any 7 day period.
Now that we have our persisted entities, it’s time to use them. First, we’ll create a view to display them:
The code behind this form looks like this:
using System;
using System.Windows.Forms;
using EarthquakeFetcher.Model;
using System.Diagnostics;
namespace EarthquakeViewer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FillListBox();
SelectFirstItemInListBox();
}
private void FillListBox()
{
QuakeListBox.Items.AddRange(EarthquakeHelper.GetAllEarthquakes.ToArray());
}
private void SelectFirstItemInListBox()
{
if(QuakeListBox.Items.Count > 0)
QuakeListBox.SelectedIndex = 0;
}
private void ShowOnMapButton_Click(object sender, EventArgs e)
{
EarthQuake quake = QuakeListBox.SelectedItem as EarthQuake;
ShowQuakeOnMap(quake);
}
private static void ShowQuakeOnMap(EarthQuake quake)
{
if (!String.IsNullOrEmpty(quake.GeoPoint.Point))
Process.Start(String.Format(@"http://maps.google.com/?q={0}", quake.GeoPoint.Point));
}
private void MapMaxElevationButton_Click(object sender, EventArgs e)
{
ShowQuakeOnMap(EarthquakeHelper.GetHighestElevatedQuake);
}
private void MapMinElevationButton_Click(object sender, EventArgs e)
{
ShowQuakeOnMap(EarthquakeHelper.GetLowestElevatedQuake);
}
}
}
So, from the code we can see that we have a helper class, EarthquakeHelper, that gives us a few helper methods, let’s take a look at those now:
using System;
using System.Collections.Generic;
using System.Linq;
using DevExpress.Xpo;
using EarthquakeFetcher.Model;
namespace EarthquakeViewer
{
public class EarthquakeHelper
{
//GS - Return all quakes. This is safe as there will not be too many in a 7 day period.
public static List<EarthQuake> GetAllEarthquakes
{
get
{
XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
return (from q in quakeQuery select q).ToList<EarthQuake>();
}
}
public static EarthQuake GetHighestElevatedQuake
{
get
{
XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
return (from q in quakeQuery
orderby q.GeoPoint.Elevation descending
select q).First<EarthQuake>();
}
}
public static EarthQuake GetLowestElevatedQuake
{
get
{
XPQuery<EarthQuake> quakeQuery = new XPQuery<EarthQuake>(XpoDefault.Session);
return (from q in quakeQuery
orderby q.GeoPoint.Elevation ascending
select q).First<EarthQuake>();
}
}
}
}
You can see that the GetAllEarthquakes property does what is says on the tin, it returns all of the quakes in the database. Again all “guard” has been removed for clarity and we are assuming that the database only contains the last 7 days worth of data. The other two properties use linq to XPO to return the highest and lowest earthquakes by elevation. These three properties return quakes that are then used by the three buttons on the form to show the location, via Google maps, of the required earthquakes:
This post is not only interesting from an XPO point of view, but also from an earthquake point of view. I mean, who of you knew there were so many earthquakes happening right now? Anyway, that’s it for this post, in the next one we’ll take a closer look at at one to many and many to many associations. Until then, happy XPOing!