Monday, June 1, 2009

Using the Virtual Earth Silverlight Control - Part One

Debra and I took a trip around the country last month, 7500 miles of driving over 25 days. You can read about it at http://www.wiredwalkabout.com/.

One of the goals of the trip was to fieldtest technology, including the Virtual Earth Silverlight control. You can see the MIX09 presentation here at http://videos.visitmix.com/MIX09/T34F.

So you might be thinking, why not just do this from home? I believe that you can see the issues more clearly if you put yourself into the situations in which you expect the software to be used. After all, Pixar animators traveled to South America as inspiration for the movie "UP", and traveled along Route 66 in preparation for the movie "Cars".

Along the way we were using a Garmin to plan our trip. This is an extremely valuable tool, but it was missing some features that would have been nice to have, so I decided to investigate the Virtual Earth Silverlight control to see what it would take to reproduce/extend the features in the Garmin. I have also done Telco applications in the past and I wanted to investigate how the mapping requirement in these applications could be accomplished in Silverlight.



Since this investigation was going to be more like building an application and less like an experiment, I am starting by writing code to persist my POCO domain model (Plain Old CLR Objects). I am using the OpenFileSave feature in Silverlight 3 to save my domain model as XML. The rest of this blog entry explains my model for persistence.

For this application I am implementing my own XML serializer. The result (so far) is as follows. As the implementation grows I expect this file format to change. XML gives me some flexibility in reusing data from older files.


<?xml version="1.0" encoding="utf-8"?>
<ModelSerializer>
<PointsOfInterest>
<PointOfInterest Latitude="39.730288305639185" Longitude="-83.310517678613749" />
<PointOfInterest Latitude="34.004710072445405" Longitude="-89.814423928613749" />
<PointOfInterest Latitude="32.572126585547366" Longitude="-96.669892678613763" />
<PointOfInterest Latitude="30.436679980300028" Longitude="-98.032197366113763" />
<PointOfInterest Latitude="31.304215774927229" Longitude="-103.74508799111376" />
<PointOfInterest Latitude="33.272989725895094" Longitude="-111.96286142861376" />
<PointOfInterest Latitude="36.445446009018042" Longitude="-118.24704111611376" />
<PointOfInterest Latitude="38.845986120977358" Longitude="-120.35641611611376" />
<PointOfInterest Latitude="37.463816739355629" Longitude="-122.11422861611376" />
<PointOfInterest Latitude="41.82646221909387" Longitude="-121.71872080361376" />
<PointOfInterest Latitude="45.511996049055" Longitude="-122.24606455361376" />
<PointOfInterest Latitude="47.478111391249712" Longitude="-122.20211924111376" />
<PointOfInterest Latitude="46.3676556511152" Longitude="-117.06051767861376" />
<PointOfInterest Latitude="45.604305565410648" Longitude="-110.68844736611376" />
<PointOfInterest Latitude="44.07759180028043" Longitude="-103.52536142861376" />
<PointOfInterest Latitude="43.410903675485677" Longitude="-96.582002053613763" />
<PointOfInterest Latitude="41.924626769221476" Longitude="-89.902314553613749" />
</PointsOfInterest>
</ModelSerializer>



The PointsOfInterest and PointOfInterest classes both implement the PersistTo XML and RecoverFrom XML methods defined in the IMapModel interface. Here is the code:



public interface IMapModel
{
XElement PersistTo(ModelSerializer oSerializer, XElement oSelf);
IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement);
}

public class PointsOfInterest : List<PointOfInterest>, IMapModel
{
#region Serializer Methods
public virtual XElement PersistTo(ModelSerializer oSerializer, XElement oSelf)
{
XElement oXElement = new XElement(GetType().Name);
oSelf.Add(oXElement);

foreach (IMapModel oIMap in this)
oIMap.PersistTo(oSerializer, oXElement);

return oSelf;
}
public virtual IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement)
{
string sType = GetType().Name;
XElement oRefElement = oElement.Element(sType);
if (oElement.HasElements)
foreach (XElement oSubElement in oRefElement.Elements())
{
IMapModel oChild = oSerializer.Import(this, oSubElement);

PointOfInterest oPOI = oChild as PointOfInterest;
if (oPOI != null)
this.Add(oPOI);
}

return this;
}
#endregion
}

public class PointOfInterest : IMapModel
{
public Location Loc { get; set; }

#region Serializer Methods
public virtual XElement PersistTo(ModelSerializer oSerializer, XElement oSelf)
{
XElement oXElement = new XElement(GetType().Name);
oSelf.Add(oXElement);

oXElement.Add(new XAttribute("Latitude",Loc.Latitude));
oXElement.Add(new XAttribute("Longitude", Loc.Longitude));

return oXElement;
}
public virtual IMapModel RecoverFrom(ModelSerializer oSerializer, XElement oElement)
{
Loc = new Location(double.Parse(oElement.Attribute("Latitude").Value), double.Parse(oElement.Attribute("Longitude").Value));
return this;
}
#endregion
}


This is not the first time I have implemented my own serializer. Most of my applications implement a highly interrelated domain model, combining objects sourced from many data sources into a domain model with many pointers and references. I have found the additional control I get over the serialization implementation to outweigh the effort of implementation. However, before this series of posts is finished, I plan on investigating the Silverlight 3 tools for persistence in XAML, XML and JSON.
Here is the code for ModelSerializer and the save and restore methods that start the process.


public class ModelSerializer
{

#region Compute Type

public Type ComputeType(XElement oElement)
{
string sType = oElement.Name.ToString();
return ComputeType(sType);
}

public Type ComputeType(object oObject)
{
if (oObject.GetType().IsSubclassOf(typeof(Type)))
return ComputeType(oObject as Type);
else
return ComputeType(oObject.ToString());
}

private Dictionary<String, Type> m_oTDictionary = null;
public Dictionary<String, Type> TypeHash
{
get
{
if (m_oTDictionary == null)
{
m_oTDictionary = new Dictionary<String, Type>();
m_oTDictionary.Add("double", typeof(Double));
m_oTDictionary.Add("string", typeof(String));
m_oTDictionary.Add("boolean", typeof(Boolean));
m_oTDictionary.Add("bool", typeof(Boolean));
m_oTDictionary.Add("integer", typeof(Int32));
m_oTDictionary.Add("int32", typeof(Int32));
m_oTDictionary.Add("object", typeof(object));
}
return m_oTDictionary;
}
set
{
m_oTDictionary = value;
}
}
#endregion


private XDocument m_oDocument = null;
public XDocument Document
{
get
{
if (m_oDocument == null)
m_oDocument = new XDocument();

return m_oDocument;
}
set
{
m_oDocument = value;
}
}

public void Write(StreamWriter oStream)
{
Document.Save(oStream);
}

public XDocument Read(StreamReader oStream)
{
Document = XDocument.Parse(oStream.ReadToEnd());
return Document;
}


public virtual IMapModel CreateObject(XElement oElement)
{
Type oType = ComputeType(oElement);
try
{
return Activator.CreateInstance(oType) as IMapModel;
}
catch (Exception ex)
{
oType = ComputeType(oElement);
string sType = oElement.Name.ToString();
string sMessage = string.Format("Type {0} not created. {1}", sType, ex.Message);
MessageBox.Show(sMessage);
}
return null;
}

public XElement Export(IMapModel oObject, XElement oParent)
{
return SerializeObject(oObject, oParent);
}


public IMapModel Import(IMapModel oObject, XElement oElement)
{
IMapModel oResult = CreateObject(oElement);
if (oResult != null)
DeSerializeObject(oResult, oElement);

return oResult;
}

public virtual void Serialize(IMapModel oObject)
{
Serialize(oObject, Document);
}
public virtual void Serialize(IMapModel oObject, XDocument oDocument)
{
XElement oElement = new XElement(GetType().Name);
oDocument.Add(oElement);
SerializeObject(oObject, oElement);
}


public virtual IMapModel DeSerialize(IMapModel oObject)
{
IMapModel oResult = DeSerialize(Document, oObject);
return oResult;
}

public virtual IMapModel DeSerialize(XDocument oDocument, IMapModel oTarget)
{
XElement oRoot = oDocument.Root;
IMapModel oResult = DeSerializeObject(oTarget, oRoot);
return oResult;
}

public virtual XElement SerializeObject(IMapModel oObject, XElement oElement)
{
return oObject.PersistTo(this, oElement);
}

public virtual IMapModel DeSerializeObject(IMapModel oObject, XElement oElement)
{
oObject.RecoverFrom(this, oElement);
return oObject;
}

}



void btnSave_Click(object sender, RoutedEventArgs e)
{

Button oButton = sender as Button;

SaveFileDialog sfd = new SaveFileDialog()
{
DefaultExt = "xml",
Filter = "XML files (*.xml)*.xmlAll files (*.*)*.*",
FilterIndex = 1
};

bool? result = sfd.ShowDialog();
if (result == true)
{
try
{
using (StreamWriter oStream = new StreamWriter(sfd.OpenFile()))
{
ModelSerializer oSerializer = new ModelSerializer();
oSerializer.Serialize(m_oList);
oSerializer.Write(oStream);
oStream.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}

void btnLoad_Click(object sender, RoutedEventArgs e)
{
Button oButton = sender as Button;

OpenFileDialog ofd = new OpenFileDialog()
{
Filter = "XML files (*.xml)*.xmlAll files (*.*)*.*",
FilterIndex = 1
};

bool? result = ofd.ShowDialog();
if (result == true)
{
try
{
using (StreamReader oStream = ofd.File.OpenText())
{
ModelSerializer oSerializer = new ModelSerializer();
oSerializer.Read(oStream);
oSerializer.DeSerialize(m_oList);
oStream.Close();
}

foreach (PointOfInterest oPOI in m_oList)
RenderPoint(oPOI);

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}


All the source code is on Codeplex:

http://vearthmappingexample.codeplex.com/

No comments: