Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process Nutzungsflaechen #51

Merged
merged 32 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bfd8ac2
Process nutzungsflaechen
tschumpr Apr 26, 2024
525d1a5
Merge remote-tracking branch 'origin/gdal-processing' into implement-…
tschumpr Apr 26, 2024
e246806
Remove unnecessary import
tschumpr Apr 26, 2024
82e6201
Remove comment
tschumpr Apr 26, 2024
ebdc425
Use consistent names
tschumpr Apr 26, 2024
b37d7aa
Set spatial reference for final layer
tschumpr Apr 29, 2024
dec20c7
Fix encoding
tschumpr Apr 29, 2024
7878bf6
Merge remote-tracking branch 'origin/gdal-processing' into implement-…
tschumpr Apr 29, 2024
08a1358
Extend timeout
tschumpr Apr 29, 2024
49b9a3f
Merge remote-tracking branch 'origin/gdal-processing' into implement-…
tschumpr Apr 29, 2024
42df9c5
Merge branch 'gdal-processing' into implement-nutzungsflaechenprocessor
tschumpr Apr 29, 2024
fa3737e
Add test for nutzungsflaechen
tschumpr Apr 29, 2024
06ffa08
Fix order of expectation and result
tschumpr Apr 29, 2024
4f3ab07
Add break to for-loops
tschumpr Apr 30, 2024
21a77a7
Merge branch 'gdal-processing' into implement-nutzungsflaechenprocessor
tschumpr Apr 30, 2024
e2c19b0
Remove unused import
tschumpr Apr 30, 2024
edc50f0
Replace dispose with using
tschumpr Apr 30, 2024
56a23fd
Add constants for layer names
tschumpr Apr 30, 2024
aa9355a
Remove unused import
tschumpr Apr 30, 2024
8b266f2
Fix test
tschumpr Apr 30, 2024
a71768f
Merge branch 'main' into implement-nutzungsflaechenprocessor
tschumpr Apr 30, 2024
1116584
Use PascalCase for Nutzungsart catalog
tschumpr Apr 30, 2024
1031c5f
Merge branch 'implement-nutzungsflaechenprocessor' of github.com:blw-…
tschumpr Apr 30, 2024
b21caec
Use auto-property
tschumpr Apr 30, 2024
123faad
Replace dispose with using
tschumpr Apr 30, 2024
fb981f5
Set correct culture info
tschumpr Apr 30, 2024
d669499
Add comments to explain processings steps
tschumpr Apr 30, 2024
c32d6f5
Fix spacing
tschumpr Apr 30, 2024
64fad2a
Set correct culture info
tschumpr Apr 30, 2024
cbb02eb
Log general processing exception
tschumpr Apr 30, 2024
51ccf10
Add log for deserialization error
tschumpr Apr 30, 2024
ccb98a3
Use single to get localized text
tschumpr Apr 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Geodatenbezug.Test/GdalAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal static void AssertFieldType(Layer resultLayer, string fieldName, FieldT
{
var resultLayerDefn = resultLayer.GetLayerDefn();
var fieldIndex = resultLayerDefn.GetFieldIndex(fieldName);
Assert.AreEqual(resultLayerDefn.GetFieldDefn(fieldIndex).GetFieldType(), expectedFieldType);
Assert.AreEqual(expectedFieldType, resultLayerDefn.GetFieldDefn(fieldIndex).GetFieldType());
}

/// <summary>
Expand All @@ -41,8 +41,8 @@ internal static void AssertFieldType(Layer resultLayer, string fieldName, FieldT
{
var resultLayerDefn = resultLayer.GetLayerDefn();
var fieldIndex = resultLayerDefn.GetFieldIndex(fieldName);
Assert.AreEqual(resultLayerDefn.GetFieldDefn(fieldIndex).GetFieldType(), expectedFieldType);
Assert.AreEqual(resultLayerDefn.GetFieldDefn(fieldIndex).GetSubType(), expectedFieldSubType);
Assert.AreEqual(expectedFieldType, resultLayerDefn.GetFieldDefn(fieldIndex).GetFieldType());
Assert.AreEqual(expectedFieldSubType, resultLayerDefn.GetFieldDefn(fieldIndex).GetSubType());
}

/// <summary>
Expand Down
76 changes: 76 additions & 0 deletions Geodatenbezug.Test/Processors/NutzungsflaechenProcessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
using Geodatenbezug.Models;
using Microsoft.Extensions.Logging;
using Moq;
using OSGeo.OGR;

namespace Geodatenbezug.Processors;

[TestClass]
[DeploymentItem("testdata/lwb_nutzungsflaechen_v2_0_lv95_NE_202404191123.gpkg", "testdata")]
[DeploymentItem("testdata/lwb_bewirtschaftungseinheit_v2_0_lv95_NE_202404191123.gpkg", "testdata")]
public class NutzungsflaechenProcessorTest
{
private readonly Topic topic = new ()
Expand Down Expand Up @@ -67,4 +69,78 @@ public async Task PrepareDataAsync()
geodiensteApiMock.Verify(api => api.StartExportAsync(bewirtschaftungseinheitTopic), Times.Once);
geodiensteApiMock.Verify(api => api.CheckExportStatusAsync(bewirtschaftungseinheitTopic), Times.Once);
}

[TestMethod]
public async Task RunGdalProcessingAsync()
{
loggerMock.Setup(LogLevel.Information, $"Starte GDAL-Prozessierung von Thema {topic.TopicTitle} ({topic.Canton})...");
loggerMock.Setup(LogLevel.Information, $"Lade Nutzungsart-Katalog von https://models.geo.admin.ch/BLW/LWB_Nutzungsflaechen_Kataloge_V2_0.xml...");

processor.InputDataPath = "testdata\\lwb_nutzungsflaechen_v2_0_lv95_NE_202404191123.gpkg";
processor.BewirtschaftungseinheitDataPath = "testdata\\lwb_bewirtschaftungseinheit_v2_0_lv95_NE_202404191123.gpkg";
await processor.RunGdalProcessingAsync();

var layerName = "nutzungsflaechen";

var inputSource = Ogr.Open(processor.InputDataPath, 0);
var inputLayer = inputSource.GetLayerByName(layerName);

var resultSource = Ogr.Open(processor.InputDataPath.Replace(".gpkg", ".gdb", StringComparison.InvariantCulture), 0);
Assert.AreEqual(1, resultSource.GetLayerCount());

var resultLayer = resultSource.GetLayerByName(layerName);

var expectedLayerFields = new List<string>
{
"t_id",
"bezugsjahr",
"lnf_code",
"code_programm",
"programm",
"nutzungsidentifikator",
"bewirtschaftungsgrad",
"flaeche_m2",
"kanton",
"ist_bff_qi",
"hauptkategorie_de",
"hauptkategorie_fr",
"hauptkategorie_it",
"nutzung_de",
"nutzung_fr",
"nutzung_it",
"bewe_betriebsnummer",
};
GdalAssert.AssertLayerFields(resultLayer, expectedLayerFields);

GdalAssert.AssertFieldType(resultLayer, "t_id", FieldType.OFTInteger);
GdalAssert.AssertFieldType(resultLayer, "bezugsjahr", FieldType.OFTDateTime);
GdalAssert.AssertFieldType(resultLayer, "ist_bff_qi", FieldType.OFTInteger, FieldSubType.OFSTInt16);

GdalAssert.AssertOnlyValidLnfCodes(resultLayer);

GdalAssert.AssertOnlySinglePartGeometries(resultLayer);

var firstInputFeature = inputLayer.GetNextFeature();
resultLayer.ResetReading();
var firstResultFeature = resultLayer.GetNextFeature();

Assert.AreEqual(firstInputFeature.GetFID(), firstResultFeature.GetFieldAsInteger("t_id"));
GdalAssert.AssertDateTime(firstInputFeature, firstResultFeature, "bezugsjahr");
Assert.AreEqual(firstInputFeature.GetFieldAsInteger("lnf_code"), firstResultFeature.GetFieldAsInteger("lnf_code"));
Assert.AreEqual(firstInputFeature.GetFieldAsString("code_programm"), firstResultFeature.GetFieldAsString("code_programm"));
Assert.AreEqual(firstInputFeature.GetFieldAsString("programm"), firstResultFeature.GetFieldAsString("programm"));
Assert.AreEqual(firstInputFeature.GetFieldAsString("nutzungsidentifikator"), firstResultFeature.GetFieldAsString("nutzungsidentifikator"));
Assert.AreEqual(firstInputFeature.GetFieldAsInteger("bewirtschaftungsgrad"), firstResultFeature.GetFieldAsInteger("bewirtschaftungsgrad"));
Assert.AreEqual(firstInputFeature.GetFieldAsInteger("flaeche_m2"), firstResultFeature.GetFieldAsInteger("flaeche_m2"));
Assert.AreEqual(firstInputFeature.GetFieldAsString("kanton"), firstResultFeature.GetFieldAsString("kanton"));
Assert.AreEqual(0, firstResultFeature.GetFieldAsInteger("ist_bff_qi"));
Assert.AreEqual("Ackerfläche", firstResultFeature.GetFieldAsString("hauptkategorie_de"));
Assert.AreEqual("Terres cultivées", firstResultFeature.GetFieldAsString("hauptkategorie_fr"));
Assert.AreEqual("Superficie coltiva", firstResultFeature.GetFieldAsString("hauptkategorie_it"));
Assert.AreEqual("Sommergerste", firstResultFeature.GetFieldAsString("nutzung_de"));
Assert.AreEqual("Orge de printemps", firstResultFeature.GetFieldAsString("nutzung_fr"));
Assert.AreEqual("Orzo primaverile", firstResultFeature.GetFieldAsString("nutzung_it"));
Assert.AreEqual("NE65020007", firstResultFeature.GetFieldAsString("bewe_betriebsnummer"));
GdalAssert.AssertGeometry(firstInputFeature, firstResultFeature);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Geodatenbezug.Models;
using MaxRev.Gdal.Core;
using Microsoft.Extensions.Logging;
using Moq;
using OSGeo.OGR;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Geodatenbezug.Models;
using MaxRev.Gdal.Core;
using Microsoft.Extensions.Logging;
using Moq;
using OSGeo.OGR;
Expand Down
60 changes: 44 additions & 16 deletions Geodatenbezug.Test/Processors/TopicProcessorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ public class TopicProcessorTest
private Mock<ILogger<Processor>> loggerMock;
private Mock<IGeodiensteApi> geodiensteApiMock;
private Mock<IAzureStorage> azureStorageMock;
private RebbaukatasterProcessor processor;
private Mock<RebbaukatasterProcessor> processorMock;

private RebbaukatasterProcessor Processor => processorMock.Object;

[TestInitialize]
public void Initialize()
{
loggerMock = new Mock<ILogger<Processor>>(MockBehavior.Strict);
geodiensteApiMock = new Mock<IGeodiensteApi>(MockBehavior.Strict);
azureStorageMock = new Mock<IAzureStorage>(MockBehavior.Strict);
processor = new RebbaukatasterProcessor(geodiensteApiMock.Object, azureStorageMock.Object, loggerMock.Object, topic);
processorMock = new Mock<RebbaukatasterProcessor>(geodiensteApiMock.Object, azureStorageMock.Object, loggerMock.Object, topic)
{
CallBase = true,
};
}

[TestCleanup]
Expand All @@ -53,7 +58,7 @@ public async Task ExportTopic()
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"status\":\"success\", \"info\":\"Data ready to be downloaded. Provide your credentials to download the data.\", \"download_url\":\"test.com/data.zip\", \"exported_at\":\"2022-03-24T09:31:05.508\"}"), });
loggerMock.Setup(LogLevel.Information, $"Exportiere {topic.TopicTitle} ({topic.Canton})...");

var result = await processor.ExportTopicAsync(topic);
var result = await Processor.ExportTopicAsync(topic);

Assert.AreEqual("test.com/data.zip", result);
geodiensteApiMock.Verify(api => api.StartExportAsync(topic), Times.Once);
Expand All @@ -77,7 +82,7 @@ public async Task ExportTopicOnlyOneExportIn24h()
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"status\":\"success\", \"info\":\"Data ready to be downloaded. Provide your credentials to download the data.\", \"download_url\":\"test.com/data.zip\", \"exported_at\":\"2022-03-24T09:31:05.508\"}"), });
loggerMock.Setup(LogLevel.Information, $"Exportiere {topic.TopicTitle} ({topic.Canton})...");

var result = await processor.ExportTopicAsync(topic);
var result = await Processor.ExportTopicAsync(topic);

Assert.AreEqual("test.com/data.zip", result);
geodiensteApiMock.Verify(api => api.StartExportAsync(topic), Times.Once);
Expand All @@ -101,10 +106,10 @@ public async Task ExportTopicStartExportFails()
loggerMock.Setup(LogLevel.Information, $"Exportiere {topic.TopicTitle} ({topic.Canton})...");
loggerMock.Setup(LogLevel.Error, $"Fehler beim Starten des Exports für Thema {topic.TopicTitle} ({topic.Canton}): {HttpStatusCode.NotFound} - Data export information not found. Invalid token?");

await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, processor.ProcessingResult.Info);
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await Processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, Processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, Processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, Processor.ProcessingResult.Info);
geodiensteApiMock.Verify(api => api.StartExportAsync(topic), Times.Once);
}

Expand All @@ -128,10 +133,10 @@ public async Task ExportTopicCheckExportStatusFailed()
loggerMock.Setup(LogLevel.Information, $"Exportiere {topic.TopicTitle} ({topic.Canton})...");
loggerMock.Setup(LogLevel.Error, $"Fehler bei der Statusabfrage des Datenexports für Thema {topic.TopicTitle} ({topic.Canton}): An unexpected error occurred. Please try again by starting a new data export.");

await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, processor.ProcessingResult.Info);
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await Processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, Processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, Processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, Processor.ProcessingResult.Info);
geodiensteApiMock.Verify(api => api.StartExportAsync(topic), Times.Once);
geodiensteApiMock.Verify(api => api.CheckExportStatusAsync(topic), Times.Once);
}
Expand All @@ -156,11 +161,34 @@ public async Task ExportTopicCheckExportStatusError()
loggerMock.Setup(LogLevel.Information, $"Exportiere {topic.TopicTitle} ({topic.Canton})...");
loggerMock.Setup(LogLevel.Error, $"Fehler bei der Statusabfrage des Datenexports für Thema {topic.TopicTitle} ({topic.Canton}): {HttpStatusCode.NotFound} - Data export information not found. Invalid token?");

await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, processor.ProcessingResult.Info);
await Assert.ThrowsExceptionAsync<InvalidOperationException>(async () => await Processor.ExportTopicAsync(topic), "Export failed");
Assert.AreEqual(processingResult.Code, Processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, Processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, Processor.ProcessingResult.Info);
geodiensteApiMock.Verify(api => api.StartExportAsync(topic), Times.Once);
geodiensteApiMock.Verify(api => api.CheckExportStatusAsync(topic), Times.Once);
}

[TestMethod]
public async Task PrepareDataFails()
{
var processingResult = new ProcessingResult
{
Code = HttpStatusCode.InternalServerError,
Reason = "Something happened",
Info = "Inner exception details",
TopicTitle = topic.TopicTitle,
Canton = topic.Canton,
};

processorMock.Setup(p => p.PrepareDataAsync())
.Throws(new InvalidOperationException("Something happened", new InvalidOperationException("Inner exception details")));
loggerMock.Setup(LogLevel.Information, $"Verarbeite Thema {topic.TopicTitle} ({topic.Canton})...");
loggerMock.Setup(LogLevel.Error, $"Fehler beim Verarbeiten des Themas {topic.TopicTitle} ({topic.Canton}): Something happened");

await Processor.ProcessAsync();
Assert.AreEqual(processingResult.Code, Processor.ProcessingResult.Code);
Assert.AreEqual(processingResult.Reason, Processor.ProcessingResult.Reason);
Assert.AreEqual(processingResult.Info, Processor.ProcessingResult.Info);
}
}
74 changes: 74 additions & 0 deletions Geodatenbezug/Models/NutzungsflaechenCatalog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Xml.Serialization;

namespace Geodatenbezug.Models;

[XmlRoot("TRANSFER", Namespace = "http://www.interlis.ch/INTERLIS2.3")]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable SA1600 // Elements should be documented
public class Transfer
{
[XmlElement("DATASECTION")]
required public DataSection DataSection { get; set; }
}

public class DataSection
{
[XmlElement("LWB_Nutzungsflaechen_V2_0.LNF_Kataloge")]
required public LnfKataloge LnfKataloge { get; set; }
}

public class LnfKataloge
{
[XmlElement("LWB_Nutzungsflaechen_V2_0.LNF_Kataloge.LNF_Katalog_Nutzungsart")]
required public List<LnfKatalogNutzungsart> LnfKatalogNutzungsart { get; set; }
}

public class LnfKatalogNutzungsart
{
[XmlElement("LNF_Code")]
required public int LnfCode { get; set; }

[XmlElement("Nutzung")]
required public Nutzung Nutzung { get; set; }

[XmlElement("Hauptkategorie")]
required public Hauptkategorie Hauptkategorie { get; set; }

[XmlElement("Ist_BFF_QI")]
required public bool IstBFFQI { get; set; }
}

public class Nutzung
{
[XmlElement("LocalisationCH_V1.MultilingualText")]
required public LocalisationCHV1MultilingualText LocalisationCHV1MultilingualText { get; set; }
}

public class Hauptkategorie
{
[XmlElement("LocalisationCH_V1.MultilingualText")]
required public LocalisationCHV1MultilingualText LocalisationCHV1MultilingualText { get; set; }
}

public class LocalisationCHV1MultilingualText
{
[XmlElement("LocalisedText")]
required public LocalisedText LocalisedText { get; set; }
}

public class LocalisedText
{
[XmlElement("LocalisationCH_V1.LocalisedText")]
required public List<LocalisationCHV1LocalisedText> LocalisationCHV1LocalisedText { get; set; }
}

public class LocalisationCHV1LocalisedText
{
[XmlElement("Language")]
required public string Language { get; set; }

[XmlElement("Text")]
required public string Text { get; set; }
}
#pragma warning restore SA1600 // Elements should be documented
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Geodatenbezug.Processors;
public class BewirtschaftungseinheitProcessor(IGeodiensteApi geodiensteApi, IAzureStorage azureStorage, ILogger logger, Topic topic) : TopicProcessor(geodiensteApi, azureStorage, logger, topic)
{
/// <inheritdoc/>
protected override Task ProcessTopic()
protected override Task ProcessTopicAsync()
{
using var bezugsjahrFieldDefinition = new FieldDefn("bezugsjahr", FieldType.OFTDateTime);
var fieldTypeConversions = new Dictionary<string, FieldDefn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Geodatenbezug.Processors;
public class BiodiversitaetsfoerderflaechenProcessor(IGeodiensteApi geodiensteApi, IAzureStorage azureStorage, ILogger logger, Topic topic) : TopicProcessor(geodiensteApi, azureStorage, logger, topic)
{
/// <inheritdoc/>
protected override Task ProcessTopic()
protected override Task ProcessTopicAsync()
{
using var bezugsjahrFieldDefinition = new FieldDefn("bezugsjahr", FieldType.OFTDateTime);
using var schnittzeitpunktFieldDefinition = new FieldDefn("schnittzeitpunkt", FieldType.OFTDateTime);
Expand Down
33 changes: 3 additions & 30 deletions Geodatenbezug/Processors/GdalLayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class GdalLayer
/// <summary>
/// Initializes a new instance of the <see cref="GdalLayer"/> class.
/// </summary>
public GdalLayer(Layer inputLayer, Layer processingLayer, Dictionary<string, FieldDefn> fieldTypeConversions, string[] fieldsToDrop)
public GdalLayer(Layer inputLayer, Layer processingLayer, Dictionary<string, FieldDefn> fieldTypeConversions, List<string> fieldsToDrop)
{
this.inputLayer = inputLayer;
this.processingLayer = processingLayer;
Expand Down Expand Up @@ -119,41 +119,14 @@ public void CopyFeatures()
/// </summary>
public void FilterLnfCodes()
{
processingLayer.ResetReading();
for (var i = 0; i < processingLayer.GetFeatureCount(1); i++)
{
var feature = processingLayer.GetNextFeature();
var lnfCode = feature.GetFieldAsInteger("lnf_code");
if ((lnfCode >= 921 && lnfCode <= 928) || lnfCode == 950 || lnfCode == 951)
{
processingLayer.DeleteFeature(feature.GetFID());
}
}
processingLayer.FilterLnfCodes();
}

/// <summary>
/// Convert multipart geometries to singlepart geometries.
/// </summary>
public void ConvertMultiPartToSinglePartGeometry()
{
processingLayer.ResetReading();
for (var i = 0; i < processingLayer.GetFeatureCount(1); i++)
{
var feature = processingLayer.GetNextFeature();
var geometry = feature.GetGeometryRef();

if (geometry.GetGeometryCount() > 1)
{
for (var j = 0; j < geometry.GetGeometryCount(); j++)
{
using var newFeature = feature.Clone();
newFeature.SetFID(-1);
newFeature.SetGeometry(geometry.GetGeometryRef(j));
processingLayer.CreateFeature(newFeature);
}

processingLayer.DeleteFeature(feature.GetFID());
}
}
processingLayer.ConvertMultiPartToSinglePartGeometry();
}
}
Loading