Bing blogs

This is a place devoted to giving you deeper insight
into the news, trends, people and technology behind Bing.

Maps Blog

September
12

ESRI Shapefiles and Bing Maps WPF

In last week’s blog posts I talked about several different ways of overlaying ESRI shapefile data onto Bing Maps. In this post, I will walk through how to develop a simple application for loading a locally store shapefile onto the Bing Maps WPF control using the ESRI Shapefile Reader CodePlex project.

Creating the project

First, you will need to download the ESRI Shapefile Reader project. Once this completes, you can unzip and run the Visual Studio project. You will notice there are two libraries in the project. The first is called Catfood.Shapefile; this is the main library that has the logic for reading and parsing Shapefiles. The second is just a basic application for reading some metadata from a shapefile. We are really only interested in the first project.

Open up Visual Studios and create a new WPF application call BingMapsShapefileViewer. Next, right click on the solution and add an Existing project. Locate and add the Catfood.Shapefile project. Next, right click on the References folder of the BingMapsShapefileViewer project and add a reference to the Catfood.Shapefile project. Your solution should look like this:

ESRI solution image

Adding the Map

Adding a map to the WPF control is pretty straight forward. You will first need to add a reference to the WPF Map control library (Microsoft.Maps.MapControl.WPF.dll), which is usually located in the C:\Program Files (x86)\Bing Maps WPF Control\V1\Libraries directory.

Now that you have a reference to the map control in your project we can go ahead and add a map in the xaml of the MainWindow.xaml file. While we’re at it, let’s add a MapLayer for the shapefile data as a child of the map control and add two buttons: one to load in a shapefile and another to clear the map. Your xaml should look like this:

<Window x:Class="BingMapsShapefileViewer.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

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

        Title="MainWindow" Height="350" Width="525">

    <Grid>

        <m:Map Name="MyMap" CredentialsProvider="YOUR_BING_MAPS_KEY">

            <m:Map.Children>

                <m:MapLayer Name="ShapeLayer"/>

            </m:Map.Children>

        </m:Map>

 

        <StackPanel HorizontalAlignment="Right">

            <Button Content="Load Shapefile" Click="LoadShapefile_Clicked"/>

            <Button Content="Clear Map" Click="ClearMap_Clicked"/>

        </StackPanel>

    </Grid>

</Window>

 

If you build the project you should see a map with two buttons on it like this:

ESRI map with buttons

Reading a Shapefile

Now that we have our base application created we can add the logic to read in a shapefile when a user clicks the “Load Shapefile” button. To keep things easy we will only support shapefiles that are in the proper projection, WGS84, as re-projecting the data would make this much more complex and best covered in a future blog post.

We will use an OpenFileDialog to allow us to easily select the shapefile we wish to load to the map. Once a file is selected we can pass the file path into the Shapefile class from our Shapefile Reader library. Your code will look like this:

private void LoadShapefile_Clicked(object sender, RoutedEventArgs e)

{

    ShapeLayer.Children.Clear();

 

    OpenFileDialog dialog = new OpenFileDialog();

    dialog.Title = "Select an ESRI Shapefile";

    dialog.Filter = "ESRI Shapefile (*.shp) |*.shp;";

 

    bool? valid = dialog.ShowDialog();

 

    if (valid.HasValue && valid.Value)

    {

        using (Shapefile shapefile = new Shapefile(dialog.FileName))

        {

            //Logic to Process Shapefile

        }

    }

}

 

You can now loop through each shape in the shapefile and convert each shape in the shapefile into a Bing Maps shape and then add it to the map. When this is all put together, we end up with the complete source code for the MainWindow.xaml.cs file.

using System.Windows;

using System.Windows.Media;

using Catfood.Shapefile;

using Microsoft.Maps.MapControl.WPF;

using Microsoft.Win32;

 

namespace BingMapsShapefileViewer

{

    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window

    {

        #region Constructor

 

        public MainWindow()

        {

            InitializeComponent();

        }

 

        #endregion

 

        #region Private Methods

 

        private void LoadShapefile_Clicked(object sender, RoutedEventArgs e)

        {

            ShapeLayer.Children.Clear();

 

            OpenFileDialog dialog = new OpenFileDialog();

            dialog.Title = "Select an ESRI Shapefile";

            dialog.Filter = "ESRI Shapefile (*.shp) |*.shp;";

           

            bool? valid = dialog.ShowDialog();

 

            if (valid.HasValue && valid.Value)

            {

                using (Shapefile shapefile = new Shapefile(dialog.FileName))

                {

                    //Set the map view for the data set

                    MyMap.SetView(RectangleDToLocationRect(shapefile.BoundingBox));

 

                    foreach (Shape s in shapefile)

                    {

                        RenderShapeOnLayer(s, ShapeLayer);

                    }                                      

                }

            }

        }

 

        private void ClearMap_Clicked(object sender, RoutedEventArgs e)

        {

            ShapeLayer.Children.Clear();

        }

 

        #region Helper Methods

 

        private LocationRect RectangleDToLocationRect(RectangleD bBox)

        {

            return new LocationRect(bBox.Top, bBox.Left, bBox.Bottom, bBox.Right);

        }

 

        private void RenderShapeOnLayer(Shape shape, MapLayer layer)

        {

            switch (shape.Type)

            {

                case ShapeType.Point:

                    ShapePoint point = shape as ShapePoint;

                    layer.Children.Add(new Pushpin()

                    {

                        Location = new Location(point.Point.Y, point.Point.X)

                    });

                    break;

                case ShapeType.PolyLine:

                    ShapePolyLine polyline = shape as ShapePolyLine;

                    for (int i = 0; i < polyline.Parts.Count; i++)

                    {

                        layer.Children.Add(new MapPolyline()

                        {

                            Locations = PointDArrayToLocationCollection(polyline.Parts[i]),

                            Stroke = new SolidColorBrush(Color.FromArgb(150, 255, 0, 0))

                        });

                    }

                    break;

                case ShapeType.Polygon:

                    ShapePolygon polygon = shape as ShapePolygon;

                    if (polygon.Parts.Count > 0)

                    {

                        //Only render the exterior ring of polygons for now.

                        for (int i = 0; i < polygon.Parts.Count; i++)

                        {

                            //Note that the exterior rings in a ShapePolygon have a Clockwise order

                            if (!IsCCW(polygon.Parts[i]))

                            {

                                layer.Children.Add(new MapPolygon()

                                {

                                    Locations = PointDArrayToLocationCollection(polygon.Parts[i]),

                                    Fill = new SolidColorBrush(Color.FromArgb(150, 0, 0, 255)),

                                    Stroke = new SolidColorBrush(Color.FromArgb(150, 255, 0, 0))

                                });

                            }

                        }

                    }

                    break;

                case ShapeType.MultiPoint:

                    ShapeMultiPoint multiPoint = shape as ShapeMultiPoint;

                    for (int i = 0; i < multiPoint.Points.Length; i++)

                    {

                        layer.Children.Add(new Pushpin()

                        {

                            Location = new Location(multiPoint.Points[i].Y, multiPoint.Points[i].X)

                        });

                    }

                    break;

                default:

                    break;

            }

        }

 

        private LocationCollection PointDArrayToLocationCollection(PointD[] points)

        {

            LocationCollection locations = new LocationCollection();

            int numPoints = points.Length;

            for (int i = 0; i < numPoints; i++)

            {

                locations.Add(new Location(points[i].Y, points[i].X));

            }

            return locations;

        }

 

        /// <summary>

        /// Determines if the coordinates in an array are in a counter clockwise order.

        /// </summary>

        /// <returns>A boolean indicating if the coordinates are in a counter clockwise order</returns>

        public bool IsCCW(PointD[] points)

        {

            int count = points.Length;

 

            PointD coordinate = points[0];

            int index1 = 0;

 

            for (int i = 1; i < count; i++)

            {

                PointD coordinate2 = points[i];

                if (coordinate2.Y > coordinate.Y)

                {

                    coordinate = coordinate2;

                    index1 = i;

                }

            }

 

            int num4 = index1 - 1;

 

            if (num4 < 0)

            {

                num4 = count - 2;

            }

 

            int num5 = index1 + 1;

 

            if (num5 >= count)

            {

                num5 = 1;

            }

 

            PointD coordinate3 = points[num4];

            PointD coordinate4 = points[num5];

 

            double num6 = ((coordinate4.X - coordinate.X) * (coordinate3.Y - coordinate.Y)) -

                ((coordinate4.Y - coordinate.Y) * (coordinate3.X - coordinate.X));

 

            if (num6 == 0.0)

            {

                return (coordinate3.X > coordinate4.X);

            }

 

            return (num6 > 0.0);

        }

 

        #endregion

 

        #endregion

    }

}


Here are a few screen shots of the application loading in some different shapefiles.

Example of all the interstate highways in the USA:

ESRI map with HWY bounds

Example with county boundaries being rendered:

ESRI map with country bounds

Getting Data

Here are a couple of good sources for ESRI Shapefiles to test with:

*Note: this data is in OSGB36 projection and need to be re-projected before being used with Bing Maps. These can easily be re-projected using a free tool is called the Geospatial Data Abstraction Library. Here is a quick example of the command needed for converting these files to WGs84 projection:

ogr2ogr -s_srs EPSG:27700 -t_srs EPSG:4326 outputFileInWGS84.shp inputFileInOSGB36.shp

- Ricky Brundritt, EMEA Bing Maps Technology Solution Professional

Comments

  • hi Mr, thanks for nice post. I have some question for you, how to make this map more interactive like election results map? Can you show us the way to add more controls, like navigation, charts, label, etc.? Thanks for ya help.

  • hi Mr, thanks for nice post. I have some question for you, how to make this map more interactive like election results map? Can you show us the way to add more controls, like navigation, charts, label, etc.? Thanks for ya help.

  • hi Mr, thanks for nice post. I have some question for you, how to make this map more interactive like election results map? Can you show us the way to add more controls, like navigation, charts, label, etc.? Thanks for ya help.

  • hi Mr, thanks for nice post. I have some question for you, how to make this map more interactive like election results map? Can you show us the way to add more controls, like navigation, charts, label, etc.? Thanks for ya help.