Geo
19th October 2020
by Christoph Perger

The third part of this blog series is going into more detail and will show a couple of real life examples how to extract data from a File Geodatabase using data sampling strategies of different complexities.

gdb-ua2012.png

Walkthrough

I am happy to finally provide you with the last part of this series. 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.

Access methods

In the following chapters we have to dig a bit deeper into the domain of the project from some years ago. One of the main features was to draw samples from uploaded datasets to look at them in detail and evaluate their correctness by comparing them to other data sources. We implemented a couple of different sampling strategies and the following chapters will use some very simplified version of this to illustrate how to handle concrete workflows in file geodatabases.

Random features

This sampling strategy selects a random feature from the whole list of features, like picking a row from a table. Each feature is treated equal, so chances of selecting a particular feature are 1:n (number of features).

private static void SelectRandomFeatures(string dataSetPath, int sampleSize)
{
    var result = new List<dynamic>();
    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);

    var layer = dataSource.GetLayerByIndex(0);
    var spatialReference = layer.GetSpatialRef();

    // A list of all the feature indices in the layer from 0 to featureCount-1
    var featureList = Enumerable.Range(0, (int)layer.GetFeatureCount(0)).ToList();
    var random = new Random();

    for (var i = 0; i < sampleSize; i++)
    {
        var index = random.Next(0, featureList.Count);

        // Get the feature at the selected index position
        var feature = layer.GetFeature(featureList[index]);
        featureList.RemoveAt(index);

        result.Add(new
        {
            Classification = feature.GetFieldAsString("CODE2012"),
            FeatureGeometry = Tools.GetSampleItemGeometriesPolygon(feature, spatialReference)
        });
    }

    Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}

Result for 3 random features:

[
  {
    "FeatureId": 19910,
    "Classification": "11210",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((16.5528410727562 48.6921164527596,16.5528463230742 48.6921158649348,16.5528516432864 48.692115735041,16.5528569423564 48.6921160657437,16.5529771647074 48.6921288440874,16.5529889081126 48.6920361521343,16.5529395274899 48.6918698637146,16.5528171685314 48.691786277691,16.5526782228099 48.6918564450468,16.5523759790451 48.6920555970197,16.5522131081335 48.6922870975742,16.5524126791115 48.6922407831797,16.5526059621735 48.6921550972548,16.5526101572912 48.6921534680155,16.5526146156941 48.6921521806094,16.55261927295 48.6921512532782,16.5528410727562 48.6921164527596)))",
        "GeometryWKT": "MULTIPOLYGON (((4802872.8226 2863521.3173,4802873.2132 2863521.2865,4802873.6045 2863521.3069,4802873.9898 2863521.3782,4802882.6803 2863523.5804,4802884.4368 2863513.3861,4802882.4231 2863494.6369,4802874.26 2863484.5746,4802863.3954 2863491.4411,4802839.3127 2863511.5324,4802825.1356 2863536.1197,4802840.2143 2863532.2927,4802855.2124 2863524.0619,4802855.5357 2863523.9088,4802855.875 2863523.7953,4802856.2254 2863523.723,4802872.8226 2863521.3173)))"
      }
    ]
  },
  {
    "FeatureId": 70398,
    "Classification": "23000",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((16.9460725960462 47.7657062559634,16.9461493557534 47.7655658710898,16.9461735461869 47.7655174067735,16.9461960207055 47.7654687200579,16.9462156060474 47.7654218967546,16.9462322839855 47.7653747010005,16.9462461115095 47.7653228953568,16.9462564423694 47.7652705912512,16.9462645097616 47.7652179595698,16.9462848777388 47.7650595179663,16.9462932120793 47.7650069677489,16.9463037150832 47.7649547971846,16.9463389665482 47.7648143389711,16.9463467643855 47.7647673797262,16.946349673505 47.7647205541777,16.9463486243097 47.7646733823403,16.9463448469911 47.7646259341391,16.9463393783782 47.7645782863699,16.946313817146 47.7643808916014,16.9463056030587 47.7643312110995,16.9462943792672 47.7642831370699,16.9462783834349 47.7642374859524,16.946255431605 47.7641953439212,16.9462256893448 47.7641598442059,16.9461891671279 47.764129100424,16.9461472519002 47.7641038534843,16.9461009383643 47.7640852909814,16.9460508728813 47.7640751139457,16.9459980587094 47.7640746421876,16.9459427394151 47.7640820088293,16.9458855466559 47.7640951164181,16.9458231987422 47.7641136280255,16.9457602307732 47.7641357932798,16.9457001148558 47.7641597993254,16.9456405704558 47.7641858732798,16.9455869933158 47.7642111525447,16.9455348740052 47.7642378924991,16.9454849841866 47.7642663881944,16.945438235642 47.7642970883402,16.9453919298768 47.7643332305096,16.945349344821 47.7643717986044,16.9453094572345 47.7644120954217,16.9452714132124 47.764453565623,16.9451614469346 47.7645808696083,16.9450962881358 47.7646543948591,16.9449790475617 47.7647814202937,16.9448338801932 47.7649455085018,16.9447435045967 47.7650410841867,16.944696863061 47.7650875907243,16.9446062425223 47.765172937774,16.9445214638327 47.7652470336974,16.944470792233 47.7652879015386,16.9444698497007 47.7652992765742,16.9445324415933 47.7653186496368,16.9444336190776 47.7654367462633,16.9441702237391 47.7657515117572,16.9441034807143 47.7658312717528,16.9440698907799 47.7658206051059,16.9440507689017 47.7658608689329,16.9439782423132 47.7659974491591,16.9439512190126 47.7660528174427,16.9439266442215 47.7661099555977,16.9439070060811 47.7661669869189,16.9438948345383 47.7662220365624,16.94389179296 47.7662681297565,16.9438953068913 47.7663096557433,16.9439030086291 47.7663478089506,16.9439311329433 47.7664622052754,16.9439362967905 47.7665005359749,16.9439362219262 47.766547497329,16.943927014318 47.7666426963198,16.9439250589019 47.7666907886358,16.9439290351027 47.7667401306138,16.9439388704662 47.7667892014949,16.9439540154386 47.7668374374271,16.9439744347787 47.7668841642144,16.9440006169286 47.7669285380186,16.9440335992042 47.7669694841525,16.9440750322246 47.7670056189296,16.9441188460045 47.7670320781776,16.9441687739829 47.7670546963394,16.9442232478012 47.7670743081876,16.9442810118518 47.7670915254257,16.9443447616364 47.7671077057632,16.9444099379671 47.767122312512,16.9445471353914 47.7671494726167,16.9446175995908 47.7671609418039,16.9446863402016 47.7671686975711,16.9447526381552 47.7671708095916,16.9448155903047 47.7671648110446,16.944866094495 47.7671516135382,16.9449137947251 47.7671319869736,16.9449590055357 47.7671073202286,16.9450018979322 47.7670786753423,16.9450391188793 47.7670498489419,16.9450751866932 47.7670191622265,16.945316590424 47.7668015614473,16.945405274266 47.766719140298,16.9454369831893 47.7666882797693,16.9454804640705 47.7666432508435,16.9455216803455 47.7665972947075,16.9455606300411 47.7665504954793,16.9455971142303 47.7665028486436,16.9456297277964 47.766456272308,16.9456895861549 47.7663637858968,16.9457461876166 47.7662702890096,16.9458281141009 47.7661304042963,16.9460725960462 47.7657062559634)))",
        "GeometryWKT": "MULTIPOLYGON (((4841131.9713 2763572.9641,4841139.1226 2763557.9598,4841141.4191 2763552.7637,4841143.5898 2763547.5309,4841145.526 2763542.4841,4841147.249 2763537.3756,4841148.806 2763531.7366,4841150.1071 2763526.0178,4841151.2426 2763520.2468,4841154.3685 2763502.8463,4841155.5231 2763497.0862,4841156.8357 2763491.3834,4841160.8901 2763476.0789,4841161.948 2763470.9341,4841162.6397 2763465.7697,4841163.0395 2763460.5391,4841163.2385 2763455.2587,4841163.3133 2763449.9443,4841163.4063 2763427.9074,4841163.2968 2763422.3486,4841162.9464 2763416.9465,4841162.2153 2763411.7791,4841160.9295 2763406.9513,4841159.0696 2763402.8112,4841156.6555 2763399.15,4841153.7832 2763396.0595,4841150.5149 2763393.6782,4841146.8816 2763392.199,4841142.9448 2763391.7751,4841138.7416 2763392.2015,4841134.3404 2763393.2504,4841129.4997 2763394.8614,4841124.5757 2763396.8726,4841119.8459 2763399.1077,4841115.1378 2763401.5758,4841110.8831 2763403.9979,4841106.7224 2763406.592,4841102.7103 2763409.3962,4841098.9103 2763412.4666,4841095.0882 2763416.1427,4841091.5192 2763420.1136,4841088.134 2763424.2949,4841084.8745 2763428.6191,4841075.3776 2763441.9414,4841069.7697 2763449.6242,4841059.7328 2763462.8645,4841047.2362 2763480.0122,4841039.523 2763489.9592,4841035.5709 2763494.7806,4841027.9431 2763503.5933,4841020.8653 2763511.2013,4841016.6696 2763515.37,4841016.484 2763516.6229,4841020.9589 2763519.2084,4841012.3872 2763531.5897,4840989.5409 2763564.5899,4840983.7518 2763572.952,4840981.3531 2763571.5346,4840979.5181 2763575.8584,4840972.7217 2763590.4714,4840970.144 2763596.4121,4840967.7311 2763602.566,4840965.6877 2763608.7428,4840964.2216 2763614.7527,4840963.5276 2763619.8351,4840963.3691 2763624.4579,4840963.5573 2763628.7367,4840964.4971 2763641.6014,4840964.4941 2763645.882,4840964.0127 2763651.0814,4840962.361 2763661.5578,4840961.7278 2763666.8692,4840961.5246 2763672.3607,4840961.7614 2763677.8634,4840962.4029 2763683.311,4840963.4533 2763688.6286,4840964.9576 2763693.7262,4840967.0041 2763698.4921,4840969.73 2763702.7847,4840972.7316 2763706.0227,4840976.2284 2763708.8784,4840980.0949 2763711.4332,4840984.2312 2763713.746,4840988.8247 2763715.9861,4840993.5406 2763718.062,4841003.504 2763722.0346,4841008.6463 2763723.8003,4841013.6976 2763725.1427,4841018.6238 2763725.843,4841023.3825 2763725.6217,4841027.2852 2763724.5157,4841031.0438 2763722.6781,4841034.6677 2763720.2649,4841038.1589 2763717.3949,4841041.2287 2763714.4649,4841044.2313 2763711.3208,4841064.4517 2763688.9249,4841071.9052 2763680.4226,4841074.5843 2763677.2286,4841078.2855 2763672.5486,4841081.8271 2763667.75,4841085.2081 2763662.8421,4841088.4137 2763657.823,4841091.3196 2763652.8952,4841096.724 2763643.0756,4841101.8956 2763633.1212,4841109.4273 2763618.2086,4841131.9713 2763572.9641)))"
      }
    ]
  },
  {
    "FeatureId": 181319,
    "Classification": "11230",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((14.3015336283012 48.386460421807,14.3017708776038 48.3845313830004,14.301488529269 48.3845943246825,14.3013259094684 48.3846305757316,14.3013153995041 48.3847971568055,14.3012234976607 48.3862537841722,14.3012118137018 48.3864389651321,14.3013677184772 48.3864493610929,14.3014081892386 48.3864520593614,14.3015336283012 48.386460421807)))",
        "GeometryWKT": "MULTIPOLYGON (((4639528.2661 2817395.0517,4639558.0106 2817181.9564,4639536.7311 2817187.7239,4639524.4751 2817191.0457,4639522.6445 2817209.4905,4639506.6374 2817370.7766,4639504.6024 2817391.2809,4639516.0664 2817393.1078,4639519.0423 2817393.582,4639528.2661 2817395.0517)))"
      }
    ]
  }
]

Random feature weighted

This sampling strategy again returns a random feature from the layer, but contrary to the previous method it takes the size of the feature into account. This means that bigger features have a higher probability of being selected than smaller ones. In order to allow this, we have to go back and refactor the Spatial Indexing method a little and perform the following tasks:

  1. We do not add the size of each feature to the total size of features with a particular classification, but store each feature separately.
  2. We introduce a second level of relative size. We not only calculate the percentage of each distinc classification but within each classification, we store the percentage for each individual feature.
  3. We do not store the individual percentage but an accumulated percentage, so in the end we have a scale from 0 to 1 with each class marking its accumulated share of that scale. Eg. we have 3 classes with a distribution on 30%, 50% and 20%, the aggregated percentages would be 30%, 80% (30+50), 100% (80+20).
  4. When we want to sample a random feature, we first generate a random number between 0 and 1 and see in which classification interval this number falls. Looking at our previous example, a random number of 0.4 would mean we select the second class because of its range from 30-80%.
  5. Within this classification we find another scale of aggregated percentages for each feature in the layer. We just repeat the previous step and select the feature with the interval that covers the generated random number.

Steps 1-3 require a new data structure to store the accumulated percentage for each feature FeatureScale. The ProbabilityLimit marks the upper value of the range. The lower limit is defined by the upper limit of is predecessor.

Then we need the accumulated percentage for each classification value with its ProbabilityLimit and the collection of features. We call it ColumnValueScale.

The collection of all the classifications with their limits is stored in Scale. We provide a simple AddFeature method, a method to calculate the aggregated percentages from feature sizes and make this class generic since the datatype of the classification could be different from string.

public class FeatureScale
{
    public int FeatureId { get; set; }

    public double ProbabilityLimit { get; set; }

    public double Size { get; set; }
}

public class ColumnValueScale
{
    public List<FeatureScale> Features { get; set; } = new List<FeatureScale>();

    public double ProbabilityLimit { get; set; }

    public double TotalSize { get; set; }

    public void CalculateProbabilities()
    {
        TotalSize = Features.Sum(x => x.Size);
        var limit = 0d;
        foreach (var val in Features)
        {
            limit += val.Size / TotalSize;
            val.ProbabilityLimit = limit;
        }
    }
}

public class ColumnScale<T>
{
    public double TotalSize { get; set; }

    public Dictionary<T, ColumnValueScale> ColumnValueScales { get; set; } = new Dictionary<T, ColumnValueScale>();

    public void AddFeature(T classification, FeatureScale feature)
    {
        if (!ColumnValueScales.ContainsKey(classification))
        {
            ColumnValueScales.Add(classification, new ColumnValueScale());
        }

        ColumnValueScales[classification].Features.Add(feature);
    }

    public void CalculateProbabilities()
    {
        foreach (T key in ColumnValueScales.Keys)
        {
            // First calculate the probabilities per classification (key)
            ColumnValueScales[key].CalculateProbabilities();
        }

        TotalSize = ColumnValueScales.Values.Sum(x => x.ColumnValueSize);
        double limit = 0d;

        foreach (T key in ColumnValueScales.Keys)
        {
            limit += (double)ColumnValueScales[key].ColumnValueSize / TotalSize;
            ColumnValueScales[key].ProbabilityLimit = limit;
        }
    }
}

Our spatial indexing method looks very similar to before but uses the new data structure to store the features:

private static ColumnScale<string> SpatialIndexAdvanced(string dataSetPath)
{
    var columnScale = new ColumnScale<string>();

    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);
    var layer = dataSource.GetLayerByIndex(0);

    var feature = layer.GetNextFeature();
    while (feature != null)
    {
        var classification = feature.GetFieldAsString("CODE2012");
        var geometry = feature.GetGeometryRef();
        var size = geometry.Area();

        columnScale.AddFeature(classification,
            new FeatureScale() { FeatureId = (int)feature.GetFID(), Size = size });

        feature = layer.GetNextFeature();
    }

    columnScale.CalculateProbabilities();

    foreach (var keyValuePair in columnScale.ColumnValueScales)
    {
        Console.WriteLine($"Value: {keyValuePair.Key} has an aggregated scale limit of {keyValuePair.Value.ProbabilityLimit,7:P}.");
    }

    return columnScale;
}

Output:

Classification 11100 has an aggregated scale limit of   0,27 %.
Classification 11210 has an aggregated scale limit of   1,75 %.
Classification 11220 has an aggregated scale limit of   3,71 %.
Classification 11230 has an aggregated scale limit of   5,09 %.
Classification 11240 has an aggregated scale limit of   5,49 %.
Classification 11300 has an aggregated scale limit of   6,16 %.
Classification 12100 has an aggregated scale limit of   7,86 %.
Classification 12300 has an aggregated scale limit of   7,91 %.
Classification 12400 has an aggregated scale limit of   8,03 %.
Classification 13100 has an aggregated scale limit of   8,31 %.
Classification 13300 has an aggregated scale limit of   8,35 %.
Classification 13400 has an aggregated scale limit of   8,48 %.
Classification 14100 has an aggregated scale limit of   8,93 %.
Classification 14200 has an aggregated scale limit of   9,39 %.
Classification 21000 has an aggregated scale limit of  40,04 %.
Classification 22000 has an aggregated scale limit of  41,47 %.
Classification 23000 has an aggregated scale limit of  56,56 %.
Classification 91000 has an aggregated scale limit of  56,64 %.
Classification 31000 has an aggregated scale limit of  91,72 %.
Classification 32000 has an aggregated scale limit of  95,65 %.
Classification 33000 has an aggregated scale limit of  95,97 %.
Classification 40000 has an aggregated scale limit of  96,22 %.
Classification 50000 has an aggregated scale limit of  97,85 %.
Classification 12220 has an aggregated scale limit of  99,56 %.
Classification 12230 has an aggregated scale limit of  99,80 %.
Classification 12210 has an aggregated scale limit of 100,00 %.

Now we can use the new index results for our sampling method and continue with step 4 and 5 of our methodology.

We generate a random number and take the first columnValueScale with a bigger probabilityLimit, so we fall into its range. Then we do the same for the features inside the columnValueScale:

private static void SelectRandomFeatureWeighted(string dataSetPath, int sampleSize)
{
    var columnScale = SpatialIndexAdvanced(dataSetPath);

    var result = new List<dynamic>();
    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);

    var layer = dataSource.GetLayerByIndex(0);
    var spatialReference = layer.GetSpatialRef();

    var featureIds = new List<int>();
    var random = new Random();

    for (var i = 0; i < sampleSize; i++)
    {
        var randomValue = random.NextDouble();
        var columnValueScale = columnScale.ColumnValueScales
            .Where(x => x.Value.ProbabilityLimit > randomValue)
            .Where(x => x.Value.Features.Any(y => !featureIds.Contains(y.FeatureId)))
            .OrderBy(x => x.Value.ProbabilityLimit)
            .FirstOrDefault();
        if (columnValueScale.Value != null)
        {
            FeatureScale featureScale;
            do
            {
                randomValue = random.NextDouble();

                featureScale = columnValueScale.Value.Features.Where(x => x.ProbabilityLimit > randomValue)
                    .Where(x => !featureIds.Contains(x.FeatureId))
                    .OrderBy(x => x.ProbabilityLimit)
                    .FirstOrDefault();
            }
            while (featureScale != null && featureIds.Contains(featureScale.FeatureId));

            var feature = layer.GetFeature(featureScale.FeatureId);
            featureIds.Add(featureScale.FeatureId);

            result.Add(new
            {
                FeatureId = feature.GetFID(),
                Classification = feature.GetFieldAsString("CODE2012"),
                FeatureGeometry = Tools.GetSampleItemGeometriesPolygon(feature, spatialReference)
            });
        }
    }

    Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}

Output for 3 random features, and as we expected there is at least two huge geometry in the results:

[
  {
    "FeatureId": 298550,
    "Classification": "31000",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((...", // 26665 characters removed for clarity
        "GeometryWKT": "MULTIPOLYGON (((..." // 20386 characters removed for clarity
      }
    ]
  },
  {
    "FeatureId": 295172,
    "Classification": "23000",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((...", // 34430 characters removed for clarity
        "GeometryWKT": "MULTIPOLYGON (((..." // 24794 characters removed for clarity
      }
    ]
  },
  {
    "FeatureId": 114204,
    "Classification": "11230",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((...", // 3009 characters removed for clarity
        "GeometryWKT": "MULTIPOLYGON (((..." // 2290 characters removed for clarity
      }
    ]
  }
]

Features at specific locations

Our last sampling strategy is used to return feature that are located at predefined coordinates. It was used to augment a random sample with some known control points to support the verification of the results.

We are just iterating over the list of known locations (x-y coordinates) and intersecting each feature of the layer with it to find a match.

private static void SelectFeatureAtLocations(string dataSetPath, IEnumerable<Coordinate> locations)
{
    var result = new List<dynamic>();
    var fileGdbDriver = Ogr.GetDriverByName("OpenFileGDB");
    var dataSource = fileGdbDriver.Open(dataSetPath, 0);

    var layer = dataSource.GetLayerByIndex(0);
    var spatialReference = layer.GetSpatialRef();

    foreach (var location in locations)
    {
        var point = new Geometry(wkbGeometryType.wkbPoint);
        point.AddPoint(location.X, location.Y, 0);

        var feature = layer.GetNextFeature();
        while (feature != null)
        {
            var geometry = feature.GetGeometryRef();
            if (geometry.Intersects(point))
            {
                break;
            }

            feature = layer.GetNextFeature();
        }

        if (feature != null)
        {
            result.Add(new
            {
                FeatureId = feature.GetFID(),
                Classification = feature.GetFieldAsString("CODE2012"),
                FeatureGeometry = Tools.GetSampleItemGeometriesPolygon(feature, spatialReference)
            });
        }
    }

    Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}

Result for the center coordinate of the Vienna Town Hall:

[
  {
    "FeatureId": 44600,
    "Classification": "12100",
    "FeatureGeometry": [
      {
        "GeometryWGS84WKT": "MULTIPOLYGON (((16.3580228389648 48.2099719963204,16.358010724554 48.2099459075204,16.3572532374359 48.2100421091592,16.3572519701942 48.2100422562983,16.357112145914 48.2100569437736,16.3570219121667 48.2100697915283,16.357021444924 48.2100698563968,16.356744820641 48.210106958705,16.356181767655 48.2101824759725,16.35618027249 48.2102469152279,16.3561802406984 48.2102476281849,16.3561742472971 48.2103431390072,16.3561830565523 48.2104137194517,16.3561985481351 48.2104927275165,16.3563370258221 48.2108718967356,16.3563372573704 48.2108725665207,16.3563373999398 48.21087302305,16.3565389250694 48.2115490996368,16.3565672055931 48.2116439727291,16.3565958562308 48.2117202775197,16.3566242348945 48.211763936907,16.3569475733456 48.2117171590909,16.3575201398736 48.2116343231361,16.3575221015599 48.2116340808665,16.3575227227936 48.211634005304,16.3577088875615 48.2116133850507,16.3580247048753 48.2115771869343,16.3584792494095 48.2115128762921,16.3584759806193 48.2114717286618,16.358475929277 48.2114707741353,16.3584759349828 48.2114696827545,16.3584770405147 48.2114372393459,16.3584780232702 48.2114083893257,16.3584780232472 48.2113623964076,16.3584780702822 48.2113611077137,16.3584782822413 48.2113593483521,16.3584786657475 48.211357599844,16.3584789369615 48.2113566862591,16.3584972383066 48.211300186526,16.3584975209306 48.2112993742582,16.3584982429826 48.2112976756953,16.3584989612963 48.2112963013247,16.3585133626455 48.2112710012081,16.3585135300245 48.2112707108868,16.358514576576 48.2112690901531,16.3585157818695 48.2112675180122,16.3585171360805 48.2112660022507,16.358517815904 48.211265319567,16.3585440155909 48.2112398202837,16.3585448382313 48.2112390490383,16.3585464776703 48.2112376639093,16.3585482496241 48.2112363550578,16.3585501462732 48.2112351247401,16.3585521576804 48.2112339798469,16.3585523920896 48.2112338571201,16.3586088925906 48.2112042569139,16.3586107770892 48.211203326907,16.3586112151199 48.2112031265475,16.358657073545 48.2111824449076,16.3587249066593 48.2111516832674,16.3587955435011 48.2111156880053,16.3588496743154 48.2110824278869,16.3588498449386 48.2110823241831,16.3588505575946 48.2110819022121,16.3588830506087 48.2110630803788,16.3589049675591 48.2110411654617,16.3589175404469 48.2110237616122,16.3589185800589 48.2110224183239,16.3589186437768 48.2110223451575,16.3589424755995 48.2109938013496,16.3589763770266 48.2109336421456,16.3589978185356 48.210884631761,16.3590110358634 48.2108389866758,16.3590203181832 48.2107811664977,16.3590233584241 48.2107345125154,16.359012936086 48.2106409073832,16.358986266243 48.2105906459735,16.358956531889 48.2105376393664,16.35893117437 48.2104931635465,16.3589041018448 48.2104494400969,16.3588754361821 48.2104106105069,16.3588462323966 48.2103773304088,16.3588098971864 48.2103429224286,16.3587615894523 48.2103002407382,16.3587480348067 48.2102902579988,16.3587179271419 48.2102681797212,16.3586728950451 48.2102411774381,16.3586283765247 48.2102213406596,16.358587554446 48.2102070462535,16.3585359895049 48.2101951525006,16.3584269689901 48.2101744583161,16.3584256994672 48.2101742013065,16.3584252493454 48.2101741018507,16.358343449775 48.2101559017369,16.3583414117128 48.2101554084309,16.3583394549255 48.210154852677,16.3582882526737 48.2101392532084,16.3582877859581 48.210139107783,16.3582869369144 48.210138832817,16.3582238344611 48.2101177313079,16.3582223322922 48.2101172034562,16.3582200639819 48.2101162974865,16.3582178902028 48.2101152949135,16.358216041866 48.2101143240914,16.3581614386906 48.2100838916218,16.3581046102032 48.2100568941247,16.3581039903218 48.2100565941978,16.3581019195754 48.2100554981385,16.3580999592382 48.2100543135839,16.3580981195987 48.2100530471597,16.3580964725777 48.2100517551779,16.3580587702048 48.2100202550516,16.3580587081215 48.2100202017814,16.358057130344 48.2100187839726,16.3580556942927 48.2100173011291,16.3580544088027 48.2100157590582,16.3580540468326 48.2100152763897,16.3580251456226 48.2099758772343,16.3580243760238 48.209974763825,16.3580234008444 48.2099731226222,16.3580228389648 48.2099719963204)))",
        "GeometryWKT": "MULTIPOLYGON (((4793095.8844 2808834.7304,4793095.2307 2808831.762,4793038.2484 2808837.5774,4793038.1532 2808837.5856,4793027.6635 2808838.3189,4793020.8627 2808839.1655,4793020.8275 2808839.1697,4793000.0 2808841.512,4792957.6069 2808846.2796,4792956.8955 2808853.4106,4792956.8865 2808853.4894,4792955.5524 2808864.0347,4792955.5467 2808871.9121,4792955.9572 2808880.7661,4792962.6755 2808923.6676,4792962.6864 2808923.7433,4792962.6927 2808923.7948,4792971.3109 2809000.0,4792972.5203 2809010.6938,4792973.9302 2809019.3324,4792975.6243 2809024.3518,4793000.0 2809021.236,4793043.1645 2809015.7186,4793043.312 2809015.7043,4793043.3587 2809015.6999,4793057.3344 2809014.6056,4793081.0547 2809012.6144,4793115.3084 2809008.3955,4793115.4501 2809003.815,4793115.4552 2809003.7089,4793115.4658 2809003.588,4793115.8502 2809000.0,4793116.192 2808996.8094,4793116.6209 2808991.7129,4793116.6364 2808991.5704,4793116.6685 2808991.3768,4793116.7132 2808991.1855,4793116.7418 2808991.086,4793118.6237 2808984.9423,4793118.6522 2808984.8541,4793118.7215 2808984.6705,4793118.7875 2808984.5228,4793120.0897 2808981.8114,4793120.1048 2808981.7803,4793120.1974 2808981.6074,4793120.3013 2808981.4409,4793120.4157 2808981.2816,4793120.4724 2808981.2103,4793122.65 2808978.5523,4793122.7181 2808978.4721,4793122.8524 2808978.3291,4793122.9958 2808978.1954,4793123.1477 2808978.0712,4793123.3073 2808977.9572,4793123.3258 2808977.9451,4793127.7851 2808975.0265,4793127.9333 2808974.9355,4793127.9676 2808974.9161,4793131.5558 2808972.9177,4793136.865 2808969.9429,4793142.4306 2808966.4061,4793146.7486 2808963.0668,4793146.7622 2808963.0564,4793146.8189 2808963.0142,4793149.4002 2808961.1364,4793151.2273 2808958.8482,4793152.3205 2808957.0001,4793152.41 2808956.8579,4793152.4154 2808956.8502,4793154.4461 2808953.8397,4793157.5172 2808947.3903,4793159.5618 2808942.0966,4793160.9661 2808937.1232,4793162.1926 2808930.7755,4793162.8528 2808925.6252,4793162.9541 2808915.1861,4793161.4482 2808909.446,4793159.741 2808903.3821,4793158.2783 2808898.2915,4793156.6816 2808893.2733,4793154.9213 2808888.7872,4793153.0694 2808884.9126,4793150.7 2808880.8674,4793147.5213 2808875.8288,4793146.6108 2808874.6359,4793144.5875 2808871.9968,4793141.5051 2808868.7166,4793138.3939 2808866.2337,4793135.5047 2808864.3886,4793131.7977 2808862.7408,4793123.9187 2808859.7503,4793123.8271 2808859.7137,4793123.7947 2808859.6998,4793117.9079 2808857.1598,4793117.7616 2808857.0921,4793117.6219 2808857.018,4793113.9763 2808854.9619,4793113.9431 2808854.9428,4793113.8828 2808854.9069,4793109.4074 2808852.165,4793109.3011 2808852.0969,4793109.1416 2808851.982,4793108.99 2808851.857,4793108.8622 2808851.7376,4793105.1031 2808848.0161,4793101.1472 2808844.661,4793101.1041 2808844.6238,4793100.961 2808844.4891,4793100.8269 2808844.3453,4793100.7025 2808844.1932,4793100.5926 2808844.0395,4793098.0948 2808840.3078,4793098.0907 2808840.3015,4793097.9871 2808840.1343,4793097.8946 2808839.9608,4793097.8138 2808839.7817,4793097.7915 2808839.7259,4793096.019 2808835.1752,4793095.9724 2808835.0469,4793095.9155 2808834.8588,4793095.8844 2808834.7304)))"
      }
    ]
  }
]

Sampled polyon of vienna town hall

Helper methods

In our access methods examples we have always shown the output by printing the feature geometries in their WKT (well-known text) representation both in the layer's spatial reference system as well as in WGS84 aka. GPS coordinates. Here is how the GetSampleItemGeometriesPolygon method works.

We just access the geometry from the feature and export in to Wkt (using a ToText extension method):

public static class Tools
{
    public static List<SampleItemGeometry> GetSampleItemGeometriesPolygon(Feature feature, OSGeo.OSR.SpatialReference spatialReference)
    {
        var sampleItemGeometries = new List<SampleItemGeometry>();

        // TODO: If the spatialReference is WGS84, only one geometry should be stored
        var geometry = feature.GetGeometryRef();
        var geometryWGS84 = GeometryToWGS84(geometry, spatialReference);

        sampleItemGeometries.Add(new SampleItemGeometry
        {
            GeometryWKT = geometry.ToText(),
            GeometryWGS84WKT = geometryWGS84.ToText(),
        });

        return sampleItemGeometries;
    }

    public static string ToText(this Geometry geometry)
    {
        geometry.ExportToWkt(out var wkt);

        return wkt;
    }
}

Finally how are we reprojecting the geometry from its spatial reference to WGS84? We create a copy of the geometry, define the desination reference system and transform. Here is the GeometryToWGS84 method:

public static Geometry GeometryToWGS84(Geometry geom, OSGeo.OSR.SpatialReference spatialReference)
{
    geom.ExportToWkt(out var wkt);
    var result = Geometry.CreateFromWkt(wkt);

    var dest = new OSGeo.OSR.SpatialReference(string.Empty);
    dest.ImportFromEPSG(4326);

    // GDAL 3 swapped coordinates. See https://github.com/OSGeo/gdal/issues/1546
    dest.SetAxisMappingStrategy(OSGeo.OSR.AxisMappingStrategy.OAMS_TRADITIONAL_GIS_ORDER);

    var transformer = new OSGeo.OSR.CoordinateTransformation(spatialReference, dest);
    result.Transform(transformer);

    return result;
}

Note: We have to specify the right axis mapping strategy. This is necessary due to some change in GDAL 3, where coordinates have been swapped from x/y to lat/lon (= y/x).

Conclusion

After completing the entire walkthrough, you should have gained a solid understanding how to handle geospatial data stored in a File Geodatabase. Numerous examples illustrate the concepts of reading metadata, accessing attributive information and dealing with spatial data in an our demo layer. The series is concluded by some real-world examples coming from a previous project which implemented different concepts of extracting sample data from a layer.

Don't forget to take a look into the sample code repo. We hope you enjoyed these guides and please don't hesitate contacting us if you have any questions or feedback.