diff --git a/FarmmapsApi/Models/ItemTaskStatus.cs b/FarmmapsApi/Models/ItemTaskStatus.cs index 60069b7..ba9c426 100644 --- a/FarmmapsApi/Models/ItemTaskStatus.cs +++ b/FarmmapsApi/Models/ItemTaskStatus.cs @@ -15,8 +15,10 @@ namespace FarmmapsApi.Models public string TaskType { get; set; } public string Code { get; set; } public string Message { get; set; } - public ItemTaskState State { get; set; } + public ItemTaskState State { get; set; } = ItemTaskState.Scheduled; public DateTime? Started { get; set; } public DateTime? Finished { get; set; } + + public bool IsFinished => State == ItemTaskState.Error || State == ItemTaskState.Ok; } } \ No newline at end of file diff --git a/FarmmapsApiSamples/Data/Lutum.zip b/FarmmapsApiSamples/Data/Lutum.zip deleted file mode 100644 index 906467b..0000000 Binary files a/FarmmapsApiSamples/Data/Lutum.zip and /dev/null differ diff --git a/FarmmapsApiSamples/Data/Scan_1_20190605.zip b/FarmmapsApiSamples/Data/Scan_1_20190605.zip deleted file mode 100644 index ab00afd..0000000 Binary files a/FarmmapsApiSamples/Data/Scan_1_20190605.zip and /dev/null differ diff --git a/FarmmapsApiSamples/Data/vd_born_lutum.zip b/FarmmapsApiSamples/Data/vd_born_lutum.zip new file mode 100644 index 0000000..a4b5b94 Binary files /dev/null and b/FarmmapsApiSamples/Data/vd_born_lutum.zip differ diff --git a/FarmmapsApiSamples/Data/vd_born_om.zip b/FarmmapsApiSamples/Data/vd_born_om.zip new file mode 100644 index 0000000..4bab7b2 Binary files /dev/null and b/FarmmapsApiSamples/Data/vd_born_om.zip differ diff --git a/FarmmapsApiSamples/HerbicideService.cs b/FarmmapsApiSamples/HerbicideService.cs index 46a7a9f..44db5c0 100644 --- a/FarmmapsApiSamples/HerbicideService.cs +++ b/FarmmapsApiSamples/HerbicideService.cs @@ -21,6 +21,7 @@ namespace FarmmapsApiSamples private readonly FarmmapsApiService _farmmapsApiService; private HerbicideAgent liberatorTarweAgent; + private readonly string _downloadFolder = "Downloads"; public HerbicideService(ILogger logger, FarmmapsApiService farmmapsApiService) { @@ -49,10 +50,15 @@ namespace FarmmapsApiSamples public async Task TestFlow(List roots) { - var downloadFolder = "Downloads"; - if (!Directory.Exists(downloadFolder)) - Directory.CreateDirectory(downloadFolder); - + if (!Directory.Exists(_downloadFolder)) + Directory.CreateDirectory(_downloadFolder); + +// await SingleLutumTiffFlow(roots); + await MultiVanDenBorneShapeFlow(roots); + } + + private async Task SingleLutumTiffFlow(List roots) + { var myDrive = roots.SingleOrDefault(r => r.Name == "My drive"); if (myDrive == null) { @@ -68,28 +74,93 @@ namespace FarmmapsApiSamples } _logger.LogInformation("Creating cropfield"); - var cropfieldItem = await CreateCropfieldItemAsync(myDrive.Code); + var cropfieldItem = await CreateCropfieldSingleLutumItemAsync(myDrive.Code); _logger.LogInformation("Uploading data"); - var dataItem = await UploadData(uploadedRoot, GEOTIFF_PROCESSED_ITEMTYPE, Path.Combine("Data", "Lutum.tiff")); - if(dataItem == null) + var inputItem = await UploadDataAsync(uploadedRoot, GEOTIFF_PROCESSED_ITEMTYPE, Path.Combine("Data", "Lutum.tiff"), "Lutum.tiff"); + if(inputItem == null) { _logger.LogError($"Failed to upload data"); return; } _logger.LogInformation("Calculating appliance map"); - var applianceMapItem = await CalculateApplianceMap(cropfieldItem, dataItem, liberatorTarweAgent); + var applianceMapItem = await CalculateApplianceMapAsync(cropfieldItem, liberatorTarweAgent, inputItem); if (applianceMapItem == null) { return; } _logger.LogInformation("Downloading appliance map"); - await _farmmapsApiService.DownloadItemAsync(applianceMapItem.Code, Path.Combine(downloadFolder, $"{applianceMapItem.Name}.zip")); + await _farmmapsApiService.DownloadItemAsync(applianceMapItem.Code, Path.Combine(_downloadFolder, + $"{applianceMapItem.Name}_{Guid.NewGuid():N}.zip")); } - private async Task CreateCropfieldItemAsync(string parentItemCode) + private async Task MultiVanDenBorneShapeFlow(List roots) + { + var myDrive = roots.SingleOrDefault(r => r.Name == "My drive"); + if (myDrive == null) + { + _logger.LogError("Could not find a needed root item"); + return; + } + + var uploadedRoot = roots.SingleOrDefault(r => r.Name == "Uploaded"); + if (uploadedRoot == null) + { + _logger.LogError("Could not find a needed root item"); + return; + } + + _logger.LogInformation("Creating cropfield"); + var cropfieldItem = await CreateCropfieldVanDenBorneAsync(myDrive.Code); + + // upload data + _logger.LogInformation("Uploading lutum data"); + var inputItem = await UploadZipWithShapeAsync(uploadedRoot, Path.Combine("Data", "vd_born_lutum.zip"), "Lutum"); + if(inputItem == null) + { + _logger.LogError($"Failed to upload lutum data"); + return; + } + + _logger.LogInformation("Uploading organic matter data"); + var inputExtraItem = await UploadZipWithShapeAsync(uploadedRoot, Path.Combine("Data", "vd_born_om.zip"), "OS"); + if(inputExtraItem == null) + { + _logger.LogError($"Failed to upload organic matter data"); + return; + } + + // transform shape to geotiff as nbstask only supports tiff input item + var tiffInputItem = await ShapeToGeotiff(inputItem); + if (tiffInputItem == null) + { + _logger.LogError($"Failed to convert shape to tiff for inputItem"); + return; + } + + var tiffInputExtraItem = await ShapeToGeotiff(inputExtraItem); + if (tiffInputExtraItem == null) + { + _logger.LogError($"Failed to convert shape to tiff for inputExtraItem"); + return; + } + + // create appliance map + _logger.LogInformation("Calculating appliance map"); + var applianceMapItem = await CalculateApplianceMapAsync(cropfieldItem, liberatorTarweAgent, tiffInputItem, tiffInputExtraItem); + if (applianceMapItem == null) + { + return; + } + + _logger.LogInformation("Downloading appliance map"); + await _farmmapsApiService.DownloadItemAsync(applianceMapItem.Code, Path.Combine(_downloadFolder, + $"{applianceMapItem.Name}_{Guid.NewGuid():N}.zip")); + } + + private async Task CreateCropfieldSingleLutumItemAsync(string parentItemCode) { var currentYear = new DateTime(DateTime.UtcNow.Year, 1, 1); var cropfieldItemRequest = new ItemRequest() @@ -106,8 +177,26 @@ namespace FarmmapsApiSamples return await _farmmapsApiService.CreateItemAsync(cropfieldItemRequest); } + + private async Task CreateCropfieldVanDenBorneAsync(string parentItemCode) + { + var currentYear = new DateTime(DateTime.UtcNow.Year, 1, 1); + var cropfieldItemRequest = new ItemRequest() + { + ParentCode = parentItemCode, + ItemType = CROPFIELD_ITEMTYPE, + Name = "Cropfield VRA Herbicide van den borne", + DataDate = currentYear, + DataEndDate = currentYear.AddMonths(3), + Data = JObject.Parse("{}"), + Geometry = JObject.Parse( + @"{ ""type"": ""Polygon"", ""coordinates"": [ [ [ 5.833813406930386, 52.662044400144573 ], [ 5.835448040282574, 52.662663232992713 ], [ 5.836017878606523, 52.662878934715643 ], [ 5.836051608552347, 52.66284538659815 ], [ 5.836752490453029, 52.662168665385018 ], [ 5.837926180228544, 52.662604908046319 ], [ 5.839506445610824, 52.661074514977884 ], [ 5.839509138399547, 52.661071906335934 ], [ 5.83609382583743, 52.659780657270744 ], [ 5.834864415345847, 52.661000863063798 ], [ 5.833813406930386, 52.662044400144573 ] ] ] }") + }; - private async Task UploadData(UserRoot root, string itemType, string filePath) + return await _farmmapsApiService.CreateItemAsync(cropfieldItemRequest); + } + + private async Task UploadDataAsync(UserRoot root, string itemType, string filePath, string itemName) { var startUpload = DateTime.UtcNow; var result = await _farmmapsApiService.UploadFile(filePath, root.Code, @@ -116,27 +205,69 @@ namespace FarmmapsApiSamples if (result.Progress.Status == UploadStatus.Failed) return null; - var fileName = Path.GetFileNameWithoutExtension(filePath); - return await FindItem(root.Code, itemType, fileName, + return await FindChildItemAsync(root.Code, itemType, itemName, i => i.Created >= startUpload && - i.Name.ToLower().Contains(fileName.ToLower())); + i.Name.ToLower().Contains(itemName.ToLower())); } - private async Task CalculateApplianceMap(Item cropfieldItem, Item inputItem, HerbicideAgent agent) + private async Task UploadZipWithShapeAsync(UserRoot root, string filePath, string itemName) { + var startUpload = DateTime.UtcNow; + var result = await _farmmapsApiService.UploadFile(filePath, root.Code, + progress => _logger.LogInformation($"Status: {progress.Status} - BytesSent: {progress.BytesSent}")); + + if (result.Progress.Status == UploadStatus.Failed) + return null; + + var zipName = Path.GetFileNameWithoutExtension(filePath); + Item shapeItem = null; + await PollTask(TimeSpan.FromSeconds(3), async source => + { + var uploadedFilesChildren = await _farmmapsApiService.GetItemChildrenAsync(root.Code); + var zipItems = uploadedFilesChildren.Where(i => i.Name.Contains(zipName)); + var childrenTasks = new List>>(); + foreach (var zipItem in zipItems) + { + childrenTasks.Add(_farmmapsApiService.GetItemChildrenAsync(zipItem.Code, + SHAPE_PROCESSED_ITEMTYPE)); + } + + List[] items = await Task.WhenAll(childrenTasks); + if (items.Length > 0) + { + var flatItems = items.SelectMany(i => i).Where(i => i.Name.Contains(itemName)).ToList(); + if (flatItems.Count > 0) + { + shapeItem = flatItems.Where(i => i.Created >= startUpload).OrderByDescending(i => i.Created).First(); + source.Cancel(); + } + } + }); + + return shapeItem; + } + + private async Task CalculateApplianceMapAsync(Item cropfieldItem, HerbicideAgent agent, params Item[] inputItem) + { + if (inputItem.Length == 0) + return null; + var taskRequest = new TaskRequest() { TaskType = "vnd.farmmaps.task.vraherbicide" }; - taskRequest.attributes["inputCode"] = inputItem.Code; + taskRequest.attributes["inputCode"] = inputItem[0].Code; taskRequest.attributes["agent"] = JsonConvert.SerializeObject(agent); + if (inputItem.Length > 1) + taskRequest.attributes["extraInputCode"] = inputItem[1].Code; + var taskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, taskRequest); await PollTask(TimeSpan.FromSeconds(3), async (tokenSource) => { _logger.LogInformation("Checking vraherbicide task status"); var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, taskCode); - if (itemTaskStatus.State == ItemTaskState.Error || itemTaskStatus.State == ItemTaskState.Ok) + if (itemTaskStatus.IsFinished) tokenSource.Cancel(); }); @@ -148,7 +279,7 @@ namespace FarmmapsApiSamples } var itemName = $"VRAHerbicide {agent.Middel}"; - var applianceMapItem = await FindItem(cropfieldItem.Code, + var applianceMapItem = await FindChildItemAsync(cropfieldItem.Code, GEOTIFF_PROCESSED_ITEMTYPE, itemName, i => i.Updated >= itemTask.Finished.GetValueOrDefault(DateTime.UtcNow) && i.Name.ToLower().Contains(itemName.ToLower())); @@ -162,7 +293,30 @@ namespace FarmmapsApiSamples return applianceMapItem; } - private async Task FindItem(string parentCode, string itemType, string containsName, + private async Task ShapeToGeotiff(Item shapeItemCode) + { + var shapeToGeotiffRequest = new TaskRequest() + { + TaskType = "vnd.farmmaps.task.shapetogeotiff" + }; + var taskCode = await _farmmapsApiService.QueueTaskAsync(shapeItemCode.Code, shapeToGeotiffRequest); + + await PollTask(TimeSpan.FromSeconds(3), async (tokenSource) => + { + _logger.LogInformation("Checking shapetogeotiff task status"); + var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(shapeItemCode.Code, taskCode); + if (itemTaskStatus.IsFinished) + tokenSource.Cancel(); + }); + + _logger.LogInformation("Data shape converted to geotiff"); + + // the parent of the shape item is now the tiff item + shapeItemCode = await _farmmapsApiService.GetItemAsync(shapeItemCode.Code); + return await _farmmapsApiService.GetItemAsync(shapeItemCode.ParentCode); + } + + private async Task FindChildItemAsync(string parentCode, string itemType, string containsName, Func filter = null, int maxTries = 10) { Item dataItem = null; diff --git a/FarmmapsApiSamples/NitrogenService.cs b/FarmmapsApiSamples/NitrogenService.cs index 2599e8a..d58f629 100644 --- a/FarmmapsApiSamples/NitrogenService.cs +++ b/FarmmapsApiSamples/NitrogenService.cs @@ -159,7 +159,7 @@ namespace FarmmapsApiSamples // the parent of the shape item is now the tiff isaria item isariaShapeItem = await _farmmapsApiService.GetItemAsync(isariaShapeItem.Code); - return await _farmmapsApiService.GetItemAsync(isariaShapeItem.ParentCode); ; + return await _farmmapsApiService.GetItemAsync(isariaShapeItem.ParentCode); } ///