Complex Polygons in Bing Maps

In Bing Maps we can easily create simple polygons. Simple polygons consist of a single exterior ring of coordinates. However, in more advance applications it is useful to be able to draw more complex polygons. Take for instance the borders of Lesotho, which is a land locked country within the main exterior borders of South Africa. In this case to properly represent this country’s borders we would need to have a hole in the polygon. In this blog post we are going to see how you can create complex polygons such as MultiPolygons and polygons with holes using both JavaScript and .NET code in Windows Store Apps.

Complex Polygons in the JavaScript API

The Bing Maps JavaScript controls, both for the web and for Windows Store apps support complex polygons. To enable this support you first need to load the AdvancedShapes module into your application. This module updates the definition of the Polygon and EntityCollection classes. The new Polygon class is initialized with an array of rings, where each ring is an array of locations. The changes to the EntityCollectionare simply to allow it to support the updated complex polygon class.

To implement complex polygons, first create a new Windows Store App in Visual Studio.

Open Visual Studio and create a new project in JavaScript. Select the Blank App template, name the application, and then press OK.

image

Next, add a reference to the Bing Maps SDK.

Right click on the References folder and press Add Reference. Select Windows → Extensions, and then select Bing Maps for JavaScript. If you do not see this option, be sure to verify that you have installed the Bing Maps SDK for Windows Store apps. Press OK.

image

Add a new JavaScript file in the js folder called ComplexShapes.js by right clicking on the js folder and selecting Add → New Item. We will add our application specific logic to this file to keep things clean.

Now open the default.html file and add a reference to the Bing Maps SDK along with the modules API. Add a reference to the ComplexShapes.js file. In the body of the document add a div for the map and a button for adding a complex polygon to the map. To accomplish this, update the HTML in the default.html file to the following:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>ComplexPolygons</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    <!-- ComplexPolygons references -->
    <link href="http://blogs.msdn.com/css/default.css" rel="stylesheet" />
    <script src="http://blogs.msdn.com/js/default.js"></script>

    <!-- Bing Map Control references -->
    <script type="text/javascript" src="ms-appx:///Bing.Maps.JavaScript//js/veapicore.js"></script>
    <script type="text/javascript" src="ms-appx:///Bing.Maps.JavaScript//js/veapiModules.js"></script>

    <!-- Our Bing Maps JavaScript Code -->
    <script src="http://blogs.msdn.com/js/ComplexPolygons.js"></script>
</head>
<body>
    <div id='MyMap'></div>

    <div style="position:absolute;bottom:0px;left:50%;margin-left:-110px;background-color:black;padding:10px;">
        <button id="AddComplexPolygonBtn">Add Complex Polygon</button>
    </div>
</body>
</html>

Next open the ComplexShapes.js file. In this file we will need to add the logic to load the map and the AdvancedShapes module. We will also need to add the logic to add a complex polygon to the map when the button is pressed. In the button handler, clear the map, create some mock data and create a complex polygon with it, then add the polygon to the map. Add the following code to the ComplexShapes.js file to accomplish this.

(function () {
    var map = null;

    function GetMap() {
        map = new Microsoft.Maps.Map(document.getElementById("MyMap"), { credentials: "YOUR_BING_MAPS_KEY" });

        Microsoft.Maps.loadModule('Microsoft.Maps.AdvancedShapes');
    }


    function initialize() {
        Microsoft.Maps.loadModule('Microsoft.Maps.Map', { callback: GetMap });

        document.getElementById("AddComplexPolygonBtn").addEventListener("click", AddComplexPolygon_Tapped, true);
    }

    function AddComplexPolygon_Tapped() {
        //Clear the map
        map.entities.clear();

        //Create mock data
        var rings = [
            [new Microsoft.Maps.Location(40, -100), new Microsoft.Maps.Location(45, -100), new Microsoft.Maps.Location(45, -90), new Microsoft.Maps.Location(40, -90)],
            [new Microsoft.Maps.Location(41, -99), new Microsoft.Maps.Location(43, -97), new Microsoft.Maps.Location(41, -95)],
            [new Microsoft.Maps.Location(44, -91), new Microsoft.Maps.Location(43, -93), new Microsoft.Maps.Location(44, -95)]
        ];

        //Create a complex polygon
        var polygon = new Microsoft.Maps.Polygon(rings, {
            fillColor: new Microsoft.Maps.Color(150, 0, 255, 0),
            strokeColor: new Microsoft.Maps.Color(150, 0, 0, 255)
        });
       
        //Add the polyon to the map.
        map.entities.push(polygon);
    }

    document.addEventListener("DOMContentLoaded", initialize, false);
})();

If you run this application and press the button, you should see a square with two triangular holes cut out of it displayed on the map.

image

Complex Polygons in the Native Windows Store Apps

The Native Bing Maps Windows Store SDK currently does not have built in support for complex polygons. However, we can easily use the techniques used in the past to add complex polygons to previous versions of Bing Maps and add this support to the Window Store SDK. Below are some blog posts I’ve written in the past on this topic:

A complex polygon consists of closed rings that represent each part of the polygon, such as the outer boundary or a hole. We can use the standard polygon class in Bing Maps to create complex polygons by concatenating these rings together and tying them back to a single common location. By tying the rings back to a single common location the polygon ends up back tracking on itself and cancels out that part of the polygon. Here is an example of how to order locations so as to draw a triangle with a hole in it. #

image

To implement complex polygons in .NET, first create a new Windows Store App in Visual Studio.

Open Visual Studio and create a new project in C# or Visual Basic. Select the Blank App (XAML) template, name the application and then press OK.

image

Next, add a reference to Bing Maps SDK. Right click on the References folder and press Add Reference. Select Windows → Extensions, and then select Bing Maps for C#, C++ and Visual Basic. If you do not see this option, be sure to verify that you have installed the Bing Maps SDK for Windows Store apps. While you are here, also add a reference to the Microsoft Visual C++ Runtime Package, as this is required by the Bing Maps SDK when developing using C# or Visual Basic. Press OK.

image

In Solution Explorer, set the Active solution platform in Visual Studio by right clicking on the Solution folder and selecting Properties. Select Configuration Properties → Configuration. Find your project and under the Platform column, set the target platform to x86, and press OK.

image

Now open the MainPage.xaml file and update the XAML to the following:

<Page
    x:Class="ComplexPolygons.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ComplexPolygons"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:m="using:Bing.Maps"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <m:Map Name="MyMap" Credentials="YOUR_BING_MAPS_KEY"/>

        <Border Background="Black" Width="220" Height="65" Padding="10" VerticalAlignment="Bottom">
            <Button Content="Add Complex Polygon" Tapped="AddComplexPolygon_Tapped" Width="200" Height="45"/>
        </Border>
    </Grid>
</Page>

This will add a map and a button to the app for adding a complex polygon. Next open the MainPage.xaml.cs or MainPage.xaml.vb file and update it with the following code. This code adds a MapShapeLayerto the map and the event handler for the button without any logic in it yet.

C#

using Bing.Maps;
using System.Collections.Generic;
using Windows.UI;
using Windows.UI.Xaml.Controls;

namespace ComplexPolygons
{
    public sealed partial class MainPage : Page
    {
        private MapShapeLayer shapeLayer;

        public MainPage()
        {
            this.InitializeComponent();

            //Add a MapShapeLayer to the map to display the polygon in
            shapeLayer = new MapShapeLayer();
            MyMap.ShapeLayers.Add(shapeLayer);
        }

        private void AddComplexPolygon_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
        }
    }
}

VB

Imports Bing.Maps
Imports Windows.UI

Public NotInheritable Class MainPage
    Inherits Page

    Private shapeLayer As MapShapeLayer

    Sub New()
        InitializeComponent()

        'Add a MapShapeLayer to the map to display the polygon in
        shapeLayer = New MapShapeLayer()
        MyMap.ShapeLayers.Add(shapeLayer)
    End Sub

    Private Sub AddComplexPolygon_Tapped(sender As Object, e As TappedRoutedEventArgs)
    End Sub
End Class

Before adding logic to the button handler, we will first create a method for generating a complex polygon. This method will take in a list of rings which are LocationCollections, a fill color, a stroke color and width. This method will also return a MapPolygon and a list of MapPolyline’s which can be used to outline the polygon. This method will ensure all the rings are closed and concatenate them together and tie them to a base location as described earlier to create the complex polygon. Add the following code to the MainPage.xaml.cs or MainPage.xaml.vbfile:

C#

public void CreatePolygon(List<LocationCollection> rings, Color? fillColor, Color? strokeColor, double width, out MapPolygon polygon, out List<MapPolyline> outlines)
{
    outlines = new List<MapPolyline>();

    if (rings != null && rings.Count >= 0 && rings[0].Count >= 3)
    {
        //Get the first location in the first ring. This will be used as a base point for all rings.
        var baseLocation = rings[0][0];

        var exteriorRing = new LocationCollection();

        foreach (var r in rings)
        {
            //Ensure ring is closed.
            r.Add(r[0]);

            if (r.Count >= 3)
            {
                //Add ring to main list of locations
                foreach (var l in r)
                {
                    exteriorRing.Add(l);
                }

                //Loop back to starting location
                exteriorRing.Add(baseLocation);

                if (strokeColor.HasValue && width > 0)
                {
                    //Create Polyline to outline ring
                    outlines.Add(new MapPolyline()
                    {
                        Color = strokeColor.Value,
                        Width = width,
                        Locations = r
                    });
                }
            }
        }

        if (fillColor.HasValue && exteriorRing.Count > 3)
        {
            polygon = new MapPolygon()
            {
                Locations = exteriorRing,
                FillColor = fillColor.Value
            };

            return;
        }
    }

    polygon = null;
}

VB

Private Sub CreatePolygon(rings As List(Of LocationCollection), fillColor As Color?, strokeColor As Color?, width As Double, ByRef polygon As MapPolygon, ByRef outlines As List(Of MapPolyline))
    outlines = New List(Of MapPolyline)

    If (rings IsNot Nothing And rings.Count >= 0 And rings(0).Count >= 3) Then
        'Get the first location in the first ring. This will be used as a base point for all rings.
        Dim baseLocation = rings(0)(0)

        Dim exteriorRing = New LocationCollection()

        For Each r In rings
            'Ensure ring is closed.
            r.Add(r(0))

            If (r.Count >= 3) Then
                'Add ring to main list of locations
                For Each l In r
                    exteriorRing.Add(l)
                Next

                'Loop back to starting location
                exteriorRing.Add(baseLocation)

                If (strokeColor.HasValue And width > 0) Then
                    'Create Polyline to outline ring
                    Dim p = New MapPolyline()
                    p.Color = strokeColor.Value
                    p.Width = width
                    p.Locations = r

                    outlines.Add(p)
                End If
            End If
        Next

        If (fillColor.HasValue And exteriorRing.Count > 3) Then
            polygon = New MapPolygon()
            polygon.Locations = exteriorRing
            polygon.FillColor = fillColor.Value
            Return
        End If
    End If

    polygon = Nothing
End Sub

We can now add logic to the button event handler. In this event handler we will want to clear the shape layer of any data, create mock data and use it to generate a complex polygon, and then finally add the polygon and/or the outlines of the polygon to the map. This last step is a great place to add any additional logic you wish to add to the polygon, such as a tap event. Update the AddComplexPolygon_Tappedevent handler with the following code to do this:

C#

private void AddComplexPolygon_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    //Clear any shapes in the map shape layer.
    shapeLayer.Shapes.Clear();

    //Create mock data
    var rings = new List<LocationCollection>(){
        new LocationCollection(){ new Location(40, -100), new Location(45, -100), new Location(45, -90), new Location(40, -90) },
        new LocationCollection(){ new Location(41, -99), new Location(43, -97), new Location(41, -95) },
        new LocationCollection(){ new Location(44, -91), new Location(43, -93), new Location(44, -95) }
    };

    //Create the colors for styling the complex polygon
    var fillColor = Windows.UI.Color.FromArgb(150, 0, 255, 0);
    var strokeColor = Windows.UI.Color.FromArgb(150, 0, 0, 255);

    MapPolygon polygon;
    List<MapPolyline> outlines;

    //Create the complex polygon.
    CreatePolygon(rings, fillColor, strokeColor, 5, out polygon, out outlines);

    //If the polygon is not null add it to the shape layer
    if (polygon != null)
    {
        shapeLayer.Shapes.Add(polygon);
    }

    //If the outlines for the complex polygon are not null add them to the shape layer.
    if (outlines != null)
    {
        foreach (var l in outlines)
        {
            shapeLayer.Shapes.Add(l);
        }
    }
}

VB

Private Sub AddComplexPolygon_Tapped(sender As Object, e As TappedRoutedEventArgs)
    'Clear any shapes in the map shape layer.
    shapeLayer.Shapes.Clear()

    'Create mock data
    Dim rings = New List(Of LocationCollection)

    rings.Add(New LocationCollection())
    rings(0).Add(New Location(40, -100))
    rings(0).Add(New Location(45, -100))
    rings(0).Add(New Location(45, -90))
    rings(0).Add(New Location(40, -90))

    rings.Add(New LocationCollection())
    rings(1).Add(New Location(41, -99))
    rings(1).Add(New Location(43, -97))
    rings(1).Add(New Location(41, -95))

    rings.Add(New LocationCollection())
    rings(2).Add(New Location(44, -91))
    rings(2).Add(New Location(43, -93))
    rings(2).Add(New Location(44, -95))

    'Create the colors for styling the complex polygon
    Dim fillColor = Windows.UI.Color.FromArgb(150, 0, 255, 0)
    Dim strokeColor = Windows.UI.Color.FromArgb(150, 0, 0, 255)

    Dim polygon As MapPolygon
    Dim outlines As List(Of MapPolyline)

    'Create the complex polygon.
    CreatePolygon(rings, fillColor, strokeColor, 5, polygon, outlines)

    'If the polygon is not null add it to the shape layer
    If polygon IsNot Nothing Then
        shapeLayer.Shapes.Add(polygon)
    End If

    'If the outlines for the complex polygon are not null add them to the shape layer.
    If outlines IsNot Nothing Then
        For Each l In outlines
            shapeLayer.Shapes.Add(l)
        Next
    End If
End Sub

The application is now complete. Run it and press the button to generate a complex polygon on the map.

image

You can download the full source code for this blog from the MSDN samples here. You can also find a WPF version of this code sample here.

If you are looking for some other great resources on Bing Maps for Windows Store apps, look through this blog, or check out all the Bing Maps MSDN code samples.

- Ricky Brundritt, EMEA Bing Maps TSP

ComplexPolygonThumbnail.png