Geo
5th August 2020
by Christoph Perger

The first part of this blog series is providing some background information and lays the basis for working with the ESRI File Geodatabase data format for geospatial data.

gdb-ua2012.png

About

Working with geospatial data can be tricky without proper tools and guidelines. Years ago we have been working on a project with the task to analyze and handle raster and vector datasets. Back then the only supported vector-format was ESRI Shapefile, so the .NET library DotSpatial looked quite promising and was working well until now. However this package was not updated in the last few years, also new requirements like .NET Core, potentially Linux support and File Geodatabase support required some rethinking.

We are focussing now on the File Geodatabase support here. File Geodatabase is data format developed by ESRI and more or less replaces the older Personal Geodatabase format, based on Microsoft Access. It is a container format for storing both vector as well as raster data. Typically this format is represented as a folder with the .gdb suffix, but it can also be compressed in a zip-archive.

Walkthrough

This tutorial series is split into 3 separate posts and gives you some insights into how to handle File Geodatabase in your C# solutions.

Note: As test data for the following examples, we are using the Urban Atlas 2012 for Austria, which you also find in the sample code of this blog post. Any sceenshots or output we are showing here will visualize the output of this particular file geodatabase, but the results should look very similar for any other dataset as well.

Related tools

GDAL/OGR

GDAL/OGR is the de-facto standard open source libary for reading and writing geospatial data formats. The Geospatial Data Abstraction Library (GDAL) is split into 3 main components:

  • GDAL - for raster data formats
  • OGR - for vector data formats
  • PROJ - for projections and transformations

.NET Core bindings

GDAL is written in C, so we need a wrapper to use it in our .NET application. Searching for NuGet packages results in two potential solutions:

  • Gdal.Core from jgoday which is using GDAL 2.3 and wasn't updated in the last 18 months
  • MaxRev.Gdal.Core from MaxRev that is using the quite recent GDAL 3.0.1 and maintains the active Github repo gdal.netcore, which is why we choose it for our purpose

Installation & Setup

We start with a new .NET Core Console App and install the Gdal.Core NuGet Packages (select the runtime based on your target OS):

dotnet add package MaxRev.Gdal.Core
dotnet add package MaxRev.Gdal.WindowsRuntime.Minimal

Next step is to add the using statement and run the ConfigureAll method:

namespace FileGeodatabaseSample
{
    using MaxRev.Gdal.Core;

    public class Program
    {
        public static void Main(string[] args)
        {
            GdalBase.ConfigureAll();

            // GDAL is ready.
        }
    }
}

Opening and examining the File Geodatabase

In the following chapters we are going to perform a few common tasks when handling file geodatabases, starting from simply opening and examining its content over more in-depth analysis regarding individual layers, attributes and geometric properties to a number of specific and applied tasks.

Open

To open a file geodatabase we have to select the appropriate OGR driver. There are two options:

  • the OpenFileGDB driver, which is built into OGR by default but supports only read access
  • the ESRI FileGDB driver, which requires the additional FileGDB API library, hence requires some more preparatory work but supports also editing of data

Since we are only reading and analyzing data in this post we will go with the simpler, built-in option and leave the latter option for a potential future blog post.

After we selected the right driver, we can simply open the GDB. Note: There is no need to extract the zipped geodatabase. The driver works with both the zipped or unzipped version.

After that we can iterate over the contained layers and print their names in the console.

private static void Open(string dataSetPath)
{
    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);

    var layerCount = dataSource.GetLayerCount();

    for (var i = 0; i < layerCount; i++)
    {
        var layer = dataSource.GetLayerByIndex(i);

        Console.WriteLine($"Layer: {layer.GetName()}");
    }
}

In our case the ouput will look like this:

Layer: UA2012_AT_Merge

Layer details

Now that we opened the file geodatabase and successfully identified the contained layers, we can have a closer look at the layer. In the following example we will:

  • identify the shape type (e.g. Point, Line, Polygon, Multi-*)
  • get its spatial properties (projection and dataset extent)
  • get the total number of features
  • and the attributes (name and type)
private static void Details(string dataSetPath)
{
    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);
    var layer = dataSource.GetLayerByIndex(0);

    var shapeType = layer.GetGeomType().ToString("G").Substring(3);
    Console.WriteLine($"Shape Type: {shapeType}");

    var spatialReference = layer.GetSpatialRef();
    var projectionName = spatialReference.GetName();
    Console.WriteLine($"Projection: {projectionName}");

    var extent = new Envelope();
    layer.GetExtent(extent, 0);
    var dataSetExtent = new
    {
        XMin = extent.MinX,
        XMax = extent.MaxX,
        YMin = extent.MinY,
        YMax = extent.MaxY,
    };

    Console.WriteLine($"Extent: {JsonSerializer.Serialize(dataSetExtent, new JsonSerializerOptions { WriteIndented = true })}");

    var featureCount = (int)layer.GetFeatureCount(0);
    Console.WriteLine($"Feature Count: {featureCount}");

    var columns = new List<dynamic>();

    var layerDefinition = layer.GetLayerDefn();
    for (var j = 0; j < layerDefinition.GetFieldCount(); j++)
    {
        var field = layerDefinition.GetFieldDefn(j);
        columns.Add(new
        {
            Name = field.GetName(),
            DataType = field.GetFieldTypeName(field.GetFieldType()),
        });
    }

    Console.WriteLine($"Columns: {JsonSerializer.Serialize(columns, new JsonSerializerOptions { WriteIndented = true })}");
}

Output:

Shape Type: MultiPolygon
Projection: ETRS89 / LAEA Europe
Extent: {
  "XMin": 4382010.8362,
  "XMax": 4854633.2937,
  "YMin": 2595132.8378,
  "YMax": 2879685.3701
}
Feature Count: 304122
Columns: [
  {
    "Name": "COUNTRY",
    "DataType": "String"
  },
  {
    "Name": "CITIES",
    "DataType": "String"
  },
  {
    "Name": "FUA_OR_CIT",
    "DataType": "String"
  },
  {
    "Name": "CODE2012",
    "DataType": "String"
  },
  {
    "Name": "ITEM2012",
    "DataType": "String"
  },
  {
    "Name": "PROD_DATE",
    "DataType": "String"
  },
  {
    "Name": "IDENT",
    "DataType": "String"
  },
  {
    "Name": "Shape_Leng",
    "DataType": "Real"
  },
  {
    "Name": "Shape_Length",
    "DataType": "Real"
  },
  {
    "Name": "Shape_Area",
    "DataType": "Real"
  }
]

Next steps

Since we are now able to open a File Geodatabase, iterate over its layers and extract some metadata, it is time to look at the actual data in the layer. This will be done in part 2 of our series.