Monday, July 13, 2009

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

No comments: