The products listed above, and their associated names, icons and logos, are the intellectual property of Microsoft Corporation.
Rate this Article
Average Rating: 
Whole StarWhole StarWhole StarWhole StarEmpty Star
Total number of ratings: 6
Leave your own rating
Enter Title
Virtual Earth Silverlight - Lots of (Multi)Polygons
Author: Hugh Saalmans 
Background
 
For all the great things about the new Virtual Earth Silverlight Map Control – it’s missing support for geometry collections and complex polygons. Along with points, lines and polygons - these base geometry types are necessary for visualising the world in rich, web based mapping applications.
This article aims to explain how to add support for geometry collections, consisting of simple and complex polygons, to Silverlight applications using the VE Map Control.  Because we're only dealing with collections of polygons - we’ll refer to geometry collections as multipolygons throughout this article.
Some experience with Silverlight and the VE Silverlight Map Control is recommended prior to reading this article.
Firstly, some definitions:
Geometry Collections are distinct sets of geometries that combine to represent a single entity. e.g. Any country made up of multiple islands, like Japan.
Simple Polygons are polygons without any holes. They’re defined by a string of points whose first and last coordinates are the same, and that enclose an area.
Complex Polygons are polygons with one or more holes in them.  e.g. the Pentagon building.  They are otherwise known as doughnut polygons or island polygons.
Polylines are a string of points that may or may not meet at the beginning or end. Polylines don’t enclose anything.

Multipolygons are sets of simple and complex polygons.

 

  Sample multipolygon map image

Fun with polygons

 

Acknowledgements
 
This work is an extension to the JavaScript based concepts put forward by Richard Brundritt (Infusion Development) in his Feb 2009 blog entry.  If you have a problem involving geometries, chances are Richard’s blog will have an answer for you.
Thanks also to John O’Brien (Soul Solutions) for his C# port of the Google Polyline Encoding algorithm.  One of his many helpful VE articles.
 
Getting Started
 
Create a new Silverlight project, add the Microsoft.VirtualEarth.MapControl as a reference in your Silverlight project, and add this code to your Page.xaml.
You’ll get a nice map, centred on AU, with a bordered text box waiting to be used:
<UserControl x:Class="minus34.multiPolyDemo.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:m="clr-namespace:Microsoft.VirtualEarth.MapControl;
        assembly=Microsoft.VirtualEarth.MapControl">
    <Grid x:Name="LayoutRoot" Background="White">
        <m:Map x:Name="map" Center="-28, 135" ZoomLevel="4" Mode="AerialWithLabels" />
        <Canvas x:Name="Canvas1">
            <Border x:Name="ShapeInfo" BorderThickness="1" Background="DarkGray"
                BorderBrush="#00AA00" Opacity="0.9" CornerRadius="8" Visibility="Collapsed">
                <TextBlock x:Name="ShapeInfoTxt" Foreground="#0B56B2" FontSize="15"
                    FontWeight="SemiBold" Margin="5,2,5,2" FontFamily="Trebuchet MS" />
            </Border>
        </Canvas>
    </Grid>
</UserControl>
 
In your code behind, add a global map layer instance to the map and setup some events to trigger a request for the multi-polygons, and to capture mouse movement for dynamic labeling on the map:
//Add MapLayer and Map events
mapLayer = new MapLayer();
map.AddChild(mapLayer);
 
map.ViewChangeEnd += map_ViewChangeEnd;
map.MouseMove += map_MouseMove;
 
Note we’re only requesting new data when the map’s stopped moving, hence we don’t setup the ViewChangeStart event.
This is important as one of the pains of using the VE JavaScript Map Control is that you have little control over the multitude of events that fire when the user goes wild with pans and zooms.  The potential result being a flood of requests to your server for data if you haven’t put in some sort of request counter.
Using the VE Silverlight Map Control - if you were to request new data whenever a zoom or pan started – you could have a flooding issue if you have an overly caffeinated user (although I haven’t tested the full impact of it).
 
Adding Multi-Polygons to the Map
 
Taking the theory that what’s good in JavaScript is great in Silverlight, and applying Richard’s Brundritt’s concept - the solution is as follows:
1. Group all polygons (complex or simple) under a single parent class
 
For this, we need a custom class to contain all the properties for the polygons, as well as the polygons themselves.  Hence the need for a new MapMultiPolygon class.
The MapMultiPolygon class contains replicas of the key Microsoft.VirtualEarth.MapControl.MapPolygon properties, as well as Lists of polygons and polylines. More on why we use polylines later on.
These Lists define a multipolygon geometry, which for the sake of simplicity can consist of the following combination of geometries:
·         One or more simple polygons;
·         One or more complex polygons;
·         Any number of simple or complex polygons.

The MapMultiPolygon Class

The important things to note in the class diagram above are the unique ID and (non-unique) name properties.  The need for a Uid comes about because you can’t have duplicate element names due to XAML naming conventions.
The AddPolygon and AddPolyline methods allow the geometric components of the multi-polygon to be added into Lists of extended MapPolygons and MapPolylines.

The extensions to these classes primarily allows for the Uid of the multi-polygon to be stored in each object as a parent ID. This enables us to get over the naming limitation mentioned above, and also allows us to use VE Silverlight events on the multipolygons – like this MouseEnter event on Tasmania below:

 

The AddPolygon method is below.  Note the Polygon class is the extended MapPolygon class (not the System.Windows.Shapes.Polygon class).

The MapPolygon class has been extended with the Uid and also a boolean for deciding whether to show the boundary of the polygon (true for simple polygons, false for complex polygons):
public void AddPolygon(LocationCollection locations, bool lineVisibility)
{
    Polygon polygon = new Polygon(this.Uid, lineVisibility)
    {
        Locations = locations,
        Fill = this.Fill,
        Opacity = this.Opacity
    };
 
    if (lineVisibility)
    {
        polygon.Stroke = this.Stroke;
        polygon.StrokeThickness = this.StrokeThickness;
        polygon.StrokeLineJoin = this.StrokeLineJoin;
    }
    else
    {
        polygon.Stroke = null;
    }
 
    this.Polygons.Add(polygon);
}
 
 2. Split each complex polygon up and add its geometric components to a MapMultiPolygon
 
The idea is to create:
1.    A single polygon that consists of a LocationCollection of points that encompasses the exterior of the polygon as well as the interior ring(s).  Only the Fill for this polygon is displayed.
2.    A set of Polylines representing the polygon exterior as well as the interior ring(s). These are used to show the polygon’s outline.
The code below takes Google encoded polylines as input from a WCF Service.  They are decoded into LocationCollections and then assembled as simple or complex polygons. Again for simplicity’s sake – simple and complex polygons are delivered as ComplexPolygon objects (via WCF). Having separate types might earn you a small performance improvement, but it complicates things.
MapMultiPolygon multiPolygon = new MapMultiPolygon();
multiPolygon.Uid = multiPolyId;
multiPolygon.Name = multiPolyname;
 
foreach (ComplexPolygon complexPoly in complexPolygonsFromWCF)
{
    string extRingString = complexPoly.ExteriorRing;
    List<string> intRingStrings = complexPoly.InteriorRings;
    LocationCollection extLocs = DecodeLatLong(extRingString);
 
    //Test if NOT a complex polygon
    if (intRingStrings.Count == 0)
    {
        //If a simple polygon - add polygon with fill and stroke
        multiPolygon.AddPolygon(extLocs, true);
    }
    else
    {
        //If a complex polygon - display polygon outline with polylines
        //and create single polygon with holes in it
        multiPolygon.AddPolyline(extLocs);
 
        //Add interior rings to exterior ring by combining coordinates
        //into a single LocationCollection, and add polylines for the rings
        foreach (string intRingString in intRingStrings)
        {
            LocationCollection intLocs = DecodeLatLong(intRingString);
 
            //Add interior rings as polylines
            multiPolygon.AddPolyline(intLocs);
 
            //Connect interior ring to exterior ring by the closest coords
            extLocs = orderInteriorRing(extLocs, intLocs);
        }
 
        //If a complex polygon - add resulting polygon with fill only
        multiPolygon.AddPolygon(extLocs, false);
    }
}
 
The ComplexPolygon class comes from the MultiPolygon class in the WCF service.  These server-side class definitions are:
[DataContract]
public class MultiPolygon
{
    [DataMember]
    public String Id { get; set; }
 
    [DataMember]
    public String Name { get; set; }
 
    [DataMember]
    public List<ComplexPolygon> ComplexPolygons { get; set; }
}
 
[Serializable]
public class ComplexPolygon
{
    public String ExteriorRing { get; set; }
    public List<String> InteriorRings { get; set; }
 
    public ComplexPolygon(String ExteriorRing, List<String> InteriorRings)
    {
        this.ExteriorRing = ExteriorRing;
        this.InteriorRings = InteriorRings;
    }
} 
The exterior and interior ring strings above are encoded polylines.
 
 3. Add events to the MapMultiPolygon and add it to the map
 
Events should be set for each polygon in the MapMultiPolygon separately.  We don’t need events on the polylines (in this example).
MouseEnter and MouseLeave are good events for showing a dynamic popup with some info about the boundary the mouse is over.  They are a great improvement over the VE JavaScript Map Control.  Mouse events in JavaScript aren’t impossible, but they aren’t this easy either...
foreach (Polygon polygon in multiPolygon.Polygons)
{
    polygon.MouseEnter += Polygon_MouseEnter;
    polygon.MouseLeave += Polygon_MouseLeave;
}
 
To add the MapMultiPolygon to the map, we use one of its methods:
multiPolygon.AddToMapLayer(mapLayer);
 
 
public void AddToMapLayer(MapLayer mapLayer)
{
    foreach (Polygon polygon in Polygons)
    {
        mapLayer.AddChild(polygon);
    }
    foreach (Polyline polyline in Polylines)
    {
        mapLayer.AddChild(polyline);
    }
}
 
Events
 
This article uses mouse events for the simple purpose of showing the user the name of the boundary they’ve moused over.  For this we need:
  • A MouseOver event on the map to track the movement of the mouse;
  • A MouseEnter event on each polygon to get the name of the polygon, change its display and switch the popup on;
  • A MouseLeave event on each polygon to change its display back and switch the popup off.
If you’re new to Silverlight – the catch with the MouseOver event handler is setting the position of the popup in the Canvas:
void map_MouseMove(object sender, MouseEventArgs e)
{
    Point p = e.GetPosition(null);
    Canvas.SetLeft(ShapeInfo, p.X + 11);
    Canvas.SetTop(ShapeInfo, p.Y + 22);
}
 
The MouseEnter event handler looks like this:
private void Polygon_MouseEnter(object sender, MouseEventArgs e)
{
    Polygon selectedPoly = (Polygon)sender;
    string selectedId = selectedPoly.ParentId;
 
    //Get the selected polygon's parent multiPolygon using LINQ
    MapMultiPolygon multiPolygon =
        (from m in multiPolygons where m.Uid == selectedId select m).Single();
 
    //Store the current display settings
    previousFill = multiPolygon.Fill;
    previousStroke = multiPolygon.Stroke;
    previousStrokeThickness = multiPolygon.StrokeThickness;
           
    //Set the display to highlight the multiPolygon
    multiPolygon.Fill = highlightFillBrush;
    multiPolygon.Stroke = highlightBrush;
    multiPolygon.StrokeThickness = 3;
 
    //Show the name of the multiPolygon in the popup
    ShapeInfoTxt.Text = multiPolygon.Name;
    if (ShapeInfo.Visibility == Visibility.Collapsed) ShapeInfo.Visibility = Visibility.Visible;
}
 
Data Prep
 
If you have lots of data to display, there can be a fair amount of data preparation involved.  In this example, the data is accessed via Views in SQL Server 2008 that allow a WCF service to return all the multipolygons within the map extents.
Data Storage
 
The multipolygon data is stored in the DB using the server-side MultiPolygon custom class mentioned above, which primarily comprises of strings of encoded polylines.  These MultiPolygon objects were originally generated by a C# application that:
  1. Took SqlGeometry objects;
  2. Thinned their coordinates using the SqlGeometry.Reduce() method;
  3. Encoded them into strings;
  4. Built Lists of ComplexPolygons with the strings;
  5. Added them to MultiPolygon objects;
  6. Serialised the MultiPolygons into byte arrays;
  7. Compressed the byte arrays to cut down their storage size; and
  8. Stored them in the DB in a VARBINARY(MAX) field.
This may seem like a bit of overkill.  But if you want good performance on thousands of polygons – you’ll need to do some, if not all of this beforehand.
It would be AWESOME to be able to use the VE Silverlight Map Control, SQL Server 2008 and WCF to simply send the data by using a class like this:
publicclass MyDreamMultiPolygon
{
    public String Id { get; set; }
    public String Name { get; set; }
    public SqlGeometry Geometry { get; set; }
}
 
But we’re a few technology iterations away from that...
So, in the mean time we have to come up with customised ways of transporting geographic information between our data store and our UI.  Hence the need for something like the server-side MultiPolygon and ComplexPolygon classes containing encoded polylines.
Spatial Querying
 
Where does the SQL Server 2008 spatial querying come into it?  The MBR or envelope of the source geometries is used to select the geometries that are inside the map extents.  The SQL used is:
SELECT * FROM MyView WHERE [MBR].STIntersects(
    geometry::STPolyFromText('POLYGON((50.8 13.6,50.8 -58.3,180.0 -58.3,180.0 13.6,50.8 13.6))'
    ,4326)) = 1
 
The map extents are sent in the WCF request which is triggered by the ViewChangeEnd event handler. The WCF code uses ADO.NET to get the data from the DB using the SQL above. You can’t use LINQ as it doesn’t support SqlGeometry (insert sad face here).
To get the map extents in Silverlight, you can use the code below.  Just remember – the VE Silverlight Map Control wraps the earth endlessly – so check if the map extents go across the date line, otherwise your East value will be West of your West value…!
void map_ViewChangeEnd(object sender, MapEventArgs e)
{
    LocationRect bounds = map.GetBoundingRectangle();
    double left = bounds.West;
    double bottom = bounds.South;
    double right = bounds.East;
    double top = bounds.North;
    int zoomLevel = Convert.ToInt32(map.ZoomLevel);
 
    //Fix the World wrap issue if the map extents go over the International Date Line
    if (right < left) right = 180;
 
    //Get some MultiPolygons!
    wcfService.GetMultiPolygonsAsync(left, bottom, right, top, zoomLevel);
}
 
Demo, Source Code and An Implementation
 
Demo
You can have a play with the code in this article at: http://www.minus34.com/demoSLWeb. The demo has multiple layers of polygons - keep zooming in on a capitol city to see them all.
Source Code
You can download the Page.xaml, Page.xaml.cs and MapMultiPolygon.cs files here.
Implementation

For an example of what can be done using the MapMultiPolygon Class: Check out http://www.minus34.com/GeoDemo. It combines demographic data from the 2006 Australian census with 8 layers of geographic boundaries.

 

Copyright 2009. Sponsored by nsquared.   |  Terms Of Use  |  Privacy Statement
Content on this site is generated from the developer community and shared freely for your enjoyment and benefit. This site is run independently of Microsoft and does not express Microsoft's views in any way.