Display Shapefiles in WPF

Shapefiles are a great way to store geospatial information: it's file-based, fairly compact, and portable. Increasingly, however, it has become harder and harder to create applications that allow you to use your data in the format of your choosing without having to first upload it to some other company's server and use their proprietary format.

In this article, we will cover how to create an application that shows you how easy it is to display shapefiles in WPF using ThinkGeo Desktop Maps. The data that will be used in this article is sourced from Natural Earth, which has an awesome collection of freely available shapefile data. Here is the application in action using their land data:

shapefile-viewer-app.png

Project Setup

We'll start off by creating a new project in Visual Studio 2019. You can use the first few steps in Microsoft's guide to Create a WPF Application on their documentation website. Once the project is opened in Visual Studio, you are ready to move on to the next step.

Install NuGet Packages

In order to display our shapefile data on a map, we will use ThinkGeo's WPF NuGet package that will provide us with a map control and a full suite of GIS tools to use.

Open the NuGet Package Manager by right-clicking on your project in the Solution Explorer and select Manage NuGet Packages. Search for ThinkGeo.UI.Wpf and add it to your project.

install-thinkgeo-wpf-nuget-package2.png

Or if you prefer, you can install the package using the dotnet CLI:

dotnet add package ThinkGeo.UI.WPF

After the installation is complete, you'll see several packages installed. The most important of which being:

  • ThinkGeo.UI.WPF - WPF controls for displaying maps, interactive spatial drawing, and more.
  • ThinkGeo.Core - GIS classes and functions for layers, styles, and performing geospatial operations.

NOTE

In order to use ThinkGeo products in your application, you will need to create a ThinkGeo Account and install the license on your development environment.

Follow ThinkGeo's guide to getting started with ThinkGeo Desktop Maps to install a free 30 day trial license.

Add the Map Control

Now that our application is setup, we can start building. We'll start with adding a map to the window. In order to use ThinkGeo's map control, we'll need to add the ThinkGeo namespace in the Window control in MainWindow.xaml:


 xmlns:tg="clr-namespace:ThinkGeo.UI.Wpf;assembly=ThinkGeo.UI.Wpf"

With the namespace added, we can now add the MapView control to the Grid:


<Grid>
    <!-- Map -->
    <tg:MapView x:Name="map" MapUnit="DecimalDegree" Loaded="Map_Loaded"/>
</Grid>

We'll name the MapView control to "map" so that we can reference it in the code-behind. We also need to set the map's unit of measurement to match the measurement of the data. In this case, we are using data from Natural Earth, so we will use DecimalDegree as the map unit to match. Finally, a Map_Loaded event handler is added to the map so that we can load the data as soon as the map is ready.

NOTE

GIS data in different units of measurement from the map will have to be reprojected on the fly from its unit type to the map's unit type in order to be displayed properly. We'll cover the nuances of reprojection in a future blog article.

Display a Shapefile on the Map

With the MapView added to the application's window, we can begin to display the shapefile in the map control.

Create the Shapefile Layer

Download Natural Earth's land data, create a ShapeFileFeatureLayer in Map_Loaded method and style it.


private void Map_Loaded(object sender, RoutedEventArgs e)
{
    // Create a shapefileLayer, passing in the path of a shape file. 
    ShapeFileFeatureLayer shapefileLayer = new ShapeFileFeatureLayer("Data/ne_110m_land.shp");

    // Set the default area style and apply it to all 20 ZoomLevels
    shapefileLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = new AreaStyle(GeoPens.Black);
    shapefileLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
}

As you can see, styles are added to what is called a ZoomLevelSet. A ZoomLevelSet, by default, contains 20 zoom levels, or geometric scale breakpoints, that allow you to design how your layer looks depending on how zoomed in the user is on the map. You can then assign multiple styles at each ZoomLevel and apply those styles across other scales.

In our case, we are just assigning a simple AreaStyle with a black outline on all 20 zoom levels.

Add the Layer to a LayerOverlay

Now that the layer is styled, we need to add a LayerOverlay that will contain our shapefile layer. An Overlay is essentially a container that holds a collection of objects. There are several types overlays that contain things such as map adornments, feature layers, and editable features. In this case, we need a LayerOverlay to contain Layer objects for our shapefile. We'll then add that overlay to the map so that it will be visible.


private void Map_Loaded(object sender, RoutedEventArgs e)
{
	// Create a shapefileLayer, passing in the path of a shape file. 
	[...]
    
	// Set the default area style and apply it to all 20 ZoomLevels
	[...]
    
	// Create an Overlay to store the layer and add it to the map.
	LayerOverlay overlay = new LayerOverlay();
	overlay.Layers.Add(shapefileLayer);
	map.Overlays.Add(overlay);
}

With that, we should now be able to see our layer on the map. But depending on the data, it may be hard to see. So, in the next section, we will automatically adjust the map's view to show the entire shapefile when the application loads.

Set the Map's Extent

When the application loads, we want to make sure we can see the shapefile data. In order to do this, we can simply make a query on the shapefile to find the bounding box of all its data. We can then set the Map's view to match that:


private void Map_Loaded(object sender, RoutedEventArgs e)
{
	// Create a shapefileLayer, passing in the path of a shape file. 
	[...]
    
	// Set the default area style and apply it to all 20 ZoomLevels
	[...]
    
	// Add the layer to the ShapefileOverlay
	[...]
    
	// Set the map's extent to the bounding box of the shapefile
	ShapeFileFeatureLayer.BuildIndexFile("Data/ne_110m_land.shp");
	shapefileLayer.Open();
	map.CurrentExtent = shapefileLayer.GetBoundingBox();
	map.Refresh();
}

We did something a little sneaky here. Not all shapefiles come with an index file. Without this file, query and drawing performance drops dramatically. So, if our application doesn't find one, we'll just create one for them using the ShapeFileFeatureLayer.BuildIndexFile static method. This can impact the initial load of the layer while the index is being created, but once created, subsequent loads will be many times faster.

Now that we have ensured an index exists, we can efficiently perform queries on the layer. We do this by first opening the layer. Here, we are using a very common query of GetBoundingBox and setting the Map's CurrentExtent property to it. Finally, we call Refresh on the Map to indicate that we are ready for it to redraw.

With that, our application is complete. Here is the gitlab repo where you can find the complete source code.

Run the Application

When you run the application, you will see the shapefile displayed on the map. Below is how the application looks when I load up Natural Earth's basic land data:

shapefile-viewer-app.png

Closing Remarks

Thank you for sticking with me through this tutorial. Be sure to check out our other education blog articles for more of what ThinkGeo can do for you. Let us know if there are any subjects that you would like to see covered in future blog articles!

Check out ThinkGeo's Desktop Maps product page for more information on what features you'll get from our products!

Previous
Previous

Display Maps in .NET 5 WPF Using VS Code

Next
Next

ThinkGeo UI 12.3 Released