Monday, July 13, 2009

Silverlight 3 beta 1 and Virtual Earth part 2 (The Map)

In my previous post I covered getting GEO spatial data into SQL and creating a service Silverlight can use to get the data in a usable format. In this post we are going to talk about how to create a Silverlight application to overlay geo spatial data on the Virtual Earth map. First thing first. Download the Silverlight Virtual Earth map control. Once you have done that start a new Visual Studio 2008 Silverlight project and add a reference to the Microsoft.VirtualEarth.MapControl and copy the following code into the XAML of your default window or page.
<Grid x:Name="LayoutRoot" Background="White">
    <m:Map Name="MyMap" ZoomLevel="4" Center="39.36830,-95.27340" Grid.Row="1"/>
</Grid>

Make sure you add this namespace as well for the VE add-in

xmlns:m="clr-namespace:Microsoft.VirtualEarth.MapControl;assembly=Microsoft.VirtualEarth.MapControl"

That is all you have to do. If you run your application you should now have a VE map on it. Now lets get to the fun stuff. In your code behind for the page add a using statement to Microsoft.VirtualEarth.MapControl and add a service reference to the web service you created to provide the spatial data.  Here is what my initialization method looks like:

InitializeComponent();
 
SLSpatialTypesService.SpatialTypeServiceClient serviceClient = new GovSage.SLSpatialTypesService.SpatialTypeServiceClient();
serviceClient.GetCongDistsAsync();
serviceClient.GetCongDistsCompleted += new EventHandler<GovSage.SLSpatialTypesService.GetCongDistsCompletedEventArgs>
    (serviceClient_GetCongDistsCompleted);

As you can see all I am doing is instantiating my service and setting up an event callback for when the call completes. The real work happens when this call comes back.

   1: void serviceClient_GetCongDistsCompleted(object sender, GovSage.SLSpatialTypesService.GetCongDistsCompletedEventArgs e)
   2: {
   3:     foreach (KeyValuePair<string, ObservableCollection<string>> locations in e.Result)
   4:     {
   5:         MapPolyline polyline = new MapPolyline();
   6:         polyline.Stroke = new SolidColorBrush(Colors.White);
   7:         polyline.StrokeThickness = 1;
   8:         polyline.Opacity = 0.5;
   9:         polyline.Locations = new LocationCollection();
  10:         polyline.Name = locations.Key;
  11:         
  12:         foreach (string location in locations.Value)
  13:         {
  14:             string[] latlong = location.Split(new char[] {','});
  15:             Location thisLocation = new Location(Double.Parse(latlong[0]), Double.Parse(latlong[1]));
  16:            
  17:             polyline.Locations.Add(thisLocation);
  18:         }
  19:  
  20:         switch (MyMap.Children.Count)
  21:         {
  22:             case 1:
  23:                 polyline.Fill = new SolidColorBrush(Colors.Blue);
  24:                 break;
  25:             case 2:
  26:                 polyline.Fill = new SolidColorBrush(Colors.Green);
  27:                 break;
  28:             case 3:
  29:                 polyline.Fill = new SolidColorBrush(Colors.Purple);
  30:                 break;
  31:             case 4:
  32:                 polyline.Fill = new SolidColorBrush(Colors.Red);
  33:                 break;
  34:             case 5:
  35:                 polyline.Fill = new SolidColorBrush(Colors.Yellow);
  36:                 break;
  37:             case 6:
  38:                 polyline.Fill = new SolidColorBrush(Colors.Brown);
  39:                 break;
  40:             case 7:
  41:                 polyline.Fill = new SolidColorBrush(Colors.LightGray);
  42:                 break;
  43:             default:
  44:                 polyline.Fill = new SolidColorBrush(Colors.Cyan);
  45:                 break;
  46:         }
  47:         polyline.MouseEnter += new MouseEventHandler(polyline_MouseEnter);
  48:         polyline.MouseLeave += new MouseEventHandler(polyline_MouseLeave);
  49:         MyMap.Children.Add(polyline);
  50:     }
  51: }

I get a KeyValuePair back from the web service. To create all my overlays I loop through each key value I get back. My key is my area name and the second value is an ObservableCollection of type string. In the first few lines (5-10) all I am doing is creating a new MapPolyline for each item and setting its properties and instantiating its LocationCollection. Then for each item in my ObservableCollection I loop and create a new location in my polyline. All I have to do is split the string value on the “,” and create a new location based on the latitude and longitude I am return and add it to the location collection. In my case I create 7 different MapPolyline objects each having about a hundred locations attached to them. 

At this point I have created all the objects I need to overlay on my map. Once I leave the inner foreach statement I hit a case statement that simply looks at how many children my map has and sets the polyline fill color for that object to a certain color. This allows each overlay area to have a different color.

Down at line 47 I start the code to add each MapPolyline to my map. Once you have added each as a child you should now be able to load your map and see your overlays (just make sure the latitude and longitude items you added for your locations actually matches the area you are expecting. If not your MapPolylines may get added to a completely different area of the map).

My map now looks like this and as I move in and out my MapPolylines adjust as well.

 

image

If your map scrolling is not smooth you need to do more reducing (reducing was talked about in part 1).

Silverlight 3 beta 1 and Virtual Earth part 1 (GEO Data)

I recently did some playing with Silverlight 3 beta 1 and the Virtual Earth (VE) CTP. I wanted to map out the congressional districts for Colorado on the VE map. It was a little tricky but not bad once I figured a few things out.

First thing to figure out was where to get the geo mapping data to overlay on the VE CTP map. The site with a lot of this information is the U.S. Census web sites (you can find all the Congressional district data here). I decided to download and work with the shape file (.shp). Once you have these downloaded the next trick is figuring out how to get this data into SQL Server 2008. The tool I used was Shape2SQL from SharpGIS. It works great and is pretty simple to use. There is also a CodePlex SQL Server spatial tools project that may have some helpful tools.

Now that we have our spatial data imported into SQL we can start using it. The first thing to be aware of is that .Net 3.5 does not natively understand SQLGeometry types (seems silly but it is true). To over come this I simply added the Microsoft.SqlServer.Types assembly to my project. I created a web service my Silverlight application could use to query and get the needed spatial data.

   1: [ServiceContract(Namespace = "")]
   2: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   3: public class SpatialTypeService
   4: {
   5:     [OperationContract]
   6:     public Dictionary<string, List<string>> GetCongDists()
   7:     {
   8:         string connString = // your string here
   9:  
  10:         //Create string for SQL statement to find geometries within the map view   
  11:         StringBuilder sb = new StringBuilder();
  12:         sb.Append("SELECT Name, [GEOM].Reduce(.002) FROM [Districts]");
  13:  
  14:         //Connect to database   
  15:         SqlConnection SQLConn = new SqlConnection(connString);
  16:         SQLConn.Open();
  17:  
  18:         //Select all polygons that intersect the map extents   
  19:         SqlCommand SQLCMD = new SqlCommand(sb.ToString(), SQLConn);
  20:         SqlDataReader dr = SQLCMD.ExecuteReader();
  21:  
  22:         Dictionary<string,List<string>> locations = new Dictionary<string,List<string>>();
  23:  
  24:         while (dr.Read())
  25:         {
  26:             List<string> location = new List<string>();
  27:             SqlGeometry geo = (SqlGeometry)dr.GetValue(1);
  28:             // We start at 1 because the STPointN array cannot get a zero past to it
  29:             for (int i = 1; i < geo.STNumPoints(); i++)
  30:             {
  31:                 location.Add(geo.STPointN(i).STY.ToString() + "," + geo.STPointN(i).STX.ToString());
  32:             }
  33:             locations.Add(dr.GetValue(0).ToString(),location);
  34:         }
  35:         return locations;
  36:     }
  37: }

Most of this code is just a standard WCF contract. Lets look at the couple key areas here that deal with our GEO data. First, notice at line 12 we have our SQL statement to select the data.

  12:         sb.Append("SELECT Name, [GEOM].Reduce(.002) FROM [Districts]");

Here we are Selecting Name and GEOM data for all the districts we have. Notice the “Reduce(.002)” call we make. This Reduce function is key to keep performance of mapping this data fast. For Reduce you can pass in a number between 1 and 0. This call reduces the total number of mapping points SQL returns to us. The closer to 1 you get the less mapping points are returned. I used the Shape2SQL SQL Spatial tool to quickly play with the number and see what the returned mapping shape is (screen shapes below). The key here is to reduce as much as possible without changing the shape of what is returned. In my case .002 takes me from over a thousand points per zone to around 100 points. This makes zooming in and out on the VE CTP perform A LOT better.

imageHere is what my areas look like at .002:

 

 

 

 

image

Here is what it looks like at .1. You can see how my shapes have changed a lot and in my case to much. Depending on what you are trying to do, just play with the reduce function until you get the points as low as you can.

 

 

Now for the work we do to get all the values ready and in a form our Silverlight app can use (Lines 24 – 34).

Dictionary<string,List<string>> locations = new Dictionary<string,List<string>>();
 
while (dr.Read())
{
    List<string> location = new List<string>();
    SqlGeometry geo = (SqlGeometry)dr.GetValue(1);
    // We start at 1 because the STPointN array cannot get a zero past to it
    for (int i = 1; i < geo.STNumPoints(); i++)
    {
        location.Add(geo.STPointN(i).STY.ToString() + "," + geo.STPointN(i).STX.ToString());
    }
    locations.Add(dr.GetValue(0).ToString(),location);
}

I created a dictionary item to hold the name of each area and a list of its points. Once I run my select I loop through the datareader for each record and create a collection of all its points. To do this I load up the GEOM data as a SqlGeometry type. I can then see how many STNumPoints each GEOM object has and get the X and Y value for each point. I simply load that up as a string in the list that is ‘X,Y’ format.

This will get you in a position for your Silverlight application to consume the data. In my next post I will cover what my Silverlight application is doing.

Here is a screenshot of where we are going though:

image

Wednesday, April 29, 2009

Silverlight 3 beta 1 Errors

I have been playing with Silver 3 beta 1 and ran into a couple errors that really stumped me for a bit. Here is how you fix them in case any one else runs into them as well.

1- Object reference not set when adding a Silverlight WCF service: This is caused by your source control settings. I solved this issues by going to Tools –> Options –> Source Control and changing it from TFS to None. No idea why this causes the error but it did solve the issue for me.

2- Unable to find ‘DynamicTypeService’ error. This one was a little more work. In this folder C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE you will find a file called “Microsoft.VisualStudio.ServicesProxy.dll” this file needs to be removed (I just changed the extension to .old) before installing the Silverlight 3 beta 1 VS tools (which installs the SDK and the runtime). I of course had already installed all this so I uninstalled it all, renamed the file and installed it again (the installed with install the above dll again). Now everything is working.

Hope this helps. Thanks to Tim Anderson’s blog that got me going in the right direction on this. His issues were with Silverlight 2 beta 2 but the fixes are the same. Who knows why Microsoft felt the need to repeat this issue for the Silverlight 3 beta.

Wednesday, April 22, 2009

Sitecore – One man’s opinion

I recently spent about 6 months building a web site in Sitecore 5.3.2. As this was my first time using Sitecore I wanted to put down some thoughts I have about the platform. While there was definitely some very frustrating aspects to Sitecore there are also so very slick and cool things about it. While the install I was working on was version 5.3.2 I took a trip to San Francisco to get certified in version 6.0. 

Learning Curve

Sitecore is build on .Net, XPATH and heavy use of XSLTs. If you are familiar with these technologies the learning curve for Sitecore is not to bad. I had a development team of 5 developers offshore. The team was able to get up to speed on Sitecore in about a week. Really the trickiest part of Sitecore to learn is the administration UI and the use of templates, masters and standard values (these are all Sitecore objects). You really need to think through how you will use these objects.

The UI

The UI for Sitecore is pretty interesting. It is all browser based. When you login to what they call the “desktop” interface it looks like you have a windows desktop sitting in your browser window, and sure enough this is how it acts too. There are a lot of tools available to you in this interface. All the tools work pretty well. The only area I have to complain about is the installation wizard. This tool lets you import packages from other Sitecore environments. This process moves Sitecore items (and even physical files) from one environment to another. The process works well with small packages, but (and this can be a big but) if your packages get large the process is really slow, time consuming and error prone.

The API

The API is actually really robust. We were pleasantly surprised by how robust its API was. There was not anything we needed to do that we could not find an API for. 

XSLT

All the content data you create is stored in Sitecore in XML format. In my opinion using XSLT to transform XML data for UI display is pretty slick and a great way to do UI and content separation. You can make Sitecore API calls in your XSLTs using Sitecore’s built in XSLT extension into its API (the documentation for this though is week and you are normally left looking at standard code behind API calls and guessing how to create that call in an XSLT format).

Overall the XSLT ability in Sitecore is impressive but you have to beware that when you have large Sitecore content trees the XPATH calls in your XSLTs to get your data can get slow really fast. When possible it is best to use Sitecore’s search engine to pull data from large content trees. XSLT extension are a great way to do this.

Moving it, Testing it and Source Control

This is really the area I have the most complaint around Sitecore. The nature of using a lot of XSLTs makes unit testing hard to do. Sitecore does not make this any easier given how the XSLT pages are dependant on the Sitecore datacontext object. You can mock up an datacontext object for unit tests but it is really slow to instantiate. You are still then left to create your own XSLT unit test framework. In our case 90% of our UI was created using XSLTs. Sitecore also provides no build in approach to unit testing for its objects. So when you create a template there is no out of the box solution to unit testing it and making sure default values really do default correctly.

Once you have created everything in your development environment and you are ready to move it to your test or production environment it can get interesting. You can create a package to move all the Sitecore objects and the physical files. This can be a large package an when trying to move from an offshore environment to an onshore environment can cause issues. I have also found Sitecore package installation to be unreliable (Sitecore objects will install corrupted sometimes).

Sitecore also has no source control. Everything you create for sitecore (even xslt, aspx and ascx files) have a Sitecore item associated with them which is stored in the database. All the templates, masters, standard values, etc you create for your Sitecore site is all stored in the database. The only source control you have for these items is a database backup, and let me tell you now, you will need this. The Sitecore database likes to become corrupt. The UI is not sophisticated enough yet to stop you from doing something that will corrupt you database (easiest way this happens is you set up your template inheritance so as to create a circular reference. Once you do this most likely your only way back is to restore a backup).