using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; using FarmmapsApi.Models; using FarmmapsApi.Services; using Google.Apis.Upload; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using static FarmmapsApi.Extensions; using static FarmmapsApiSamples.Constants; namespace FarmmapsApiSamples { public class NitrogenService : ITestFlow { private readonly ILogger _logger; private readonly FarmmapsApiService _farmmapsApiService; public NitrogenService(ILogger logger, FarmmapsApiService farmmapsApiService) { _logger = logger; _farmmapsApiService = farmmapsApiService; } public async Task TestFlow(List roots) { var uploadedRoot = roots.SingleOrDefault(r => r.Name == "Uploaded"); if (uploadedRoot == null) { _logger.LogError("Could not find a needed root item"); return; } var myDriveRoot = roots.SingleOrDefault(r => r.Name == "My drive"); if (myDriveRoot == null) { _logger.LogError("Could not find a needed root item"); return; } var cropfieldItem = await GetOrCreateCropfieldItemAsync(myDriveRoot.Code); var dataPath = Path.Combine("Data", "Scan_1_20190605.zip"); var result = await _farmmapsApiService.UploadFile(dataPath, uploadedRoot.Code, progress => _logger.LogInformation($"Status: {progress.Status} - BytesSent: {progress.BytesSent}")); if (result.Progress.Status == UploadStatus.Failed) { _logger.LogError($"Uploading failed {result.Progress.Exception.Message}"); return; } var isariaGeotiffItem = await ProcessIsaria(uploadedRoot.Code,"Scan_1_20190605.zip"); _logger.LogInformation($"Calculating targetN with targetYield: {60}"); var targetN = await CalculateTargetN(cropfieldItem, 60); _logger.LogInformation($"TargetN: {targetN}"); _logger.LogInformation("Calculating nitrogen map"); var nitrogenMapItem = await CalculateNitrogenMap(cropfieldItem, isariaGeotiffItem, targetN); _logger.LogInformation("Downloading nitrogen map"); await _farmmapsApiService.DownloadItemAsync(nitrogenMapItem.Code, $"{nitrogenMapItem.Name}.zip"); } private async Task GetOrCreateCropfieldItemAsync(string parentItemCode) { var cropfieldItems = await _farmmapsApiService.GetItemChildrenAsync(parentItemCode, CROPFIELD_ITEMTYPE); if (cropfieldItems.Count > 0) return cropfieldItems[0]; var currentYear = new DateTime(DateTime.UtcNow.Year, 1, 1); var cropfieldItemRequest = new ItemRequest() { ParentCode = parentItemCode, ItemType = CROPFIELD_ITEMTYPE, Name = "Cropfield for VRA", DataDate = currentYear, DataEndDate = currentYear.AddYears(1), Data = JObject.FromObject(new {startDate = currentYear, endDate = currentYear.AddMonths(3)}), Geometry = JObject.Parse( @"{ ""type"": ""Polygon"", ""coordinates"": [ [ [ 3.40843828875524, 50.638966444680605 ], [ 3.408953272886064, 50.639197789621612 ], [ 3.409242951459603, 50.639469958681836 ], [ 3.409328782148028, 50.639612846807708 ], [ 3.409457528180712, 50.639789755314411 ], [ 3.409639918393741, 50.640014292074966 ], [ 3.409833037442765, 50.640211611372706 ], [ 3.410069071836049, 50.640395321698435 ], [ 3.410380208081761, 50.640572227259661 ], [ 3.410605513638958, 50.640715112034222 ], [ 3.411925160474145, 50.641177783561204 ], [ 3.411935889310142, 50.640728720085136 ], [ 3.412590348309737, 50.63948356709389 ], [ 3.413244807309242, 50.638224772339846 ], [ 3.413400375432099, 50.637901562841307 ], [ 3.413539850300779, 50.637449065809889 ], [ 3.413475477284437, 50.637418445552932 ], [ 3.40999396998362, 50.637449065810451 ], [ 3.409940325803365, 50.638102293212661 ], [ 3.409575545377398, 50.638483338338325 ], [ 3.409060561246574, 50.638707881340494 ], [ 3.40843828875524, 50.638966444680605 ] ] ] }") }; return await _farmmapsApiService.CreateItemAsync(cropfieldItemRequest); } public async Task ProcessIsaria(string parentCode, string zipName) { // get processed isaria data (this can end in an infinite loop...) // need better way of getting new items processed in a context... Item isariaShapeItem = null; await PollTask(TimeSpan.FromSeconds(3), async source => { _logger.LogInformation("Trying to get isaria data"); var uploadedFilesChildren = await _farmmapsApiService.GetItemChildrenAsync(parentCode); var zipItems = uploadedFilesChildren.Where(i => i.Name == zipName); var isariaTasks = new List>>(); foreach (var zipItem in zipItems) { isariaTasks.Add(_farmmapsApiService.GetItemChildrenAsync(zipItem.Code, SHAPE_PROCESSED_ITEMTYPE)); } List[] items = await Task.WhenAll(isariaTasks); if (items.Length > 0) { var nameWithoutExtension = Path.GetFileNameWithoutExtension(zipName); var flatItems = items.SelectMany(i => i).Where(i => i.Name.Contains(nameWithoutExtension)).ToList(); if (flatItems.Count > 0) { isariaShapeItem = flatItems.OrderByDescending(i => i.Created).First(); source.Cancel(); } } }); _logger.LogInformation("Found isaria data"); _logger.LogInformation("Converting shape to geotiff"); // need to transform shape data to geotiff var shapeToGeotiffRequest = new TaskRequest() { TaskType = "vnd.farmmaps.task.shapetogeotiff" }; var taskCode = await _farmmapsApiService.QueueTaskAsync(isariaShapeItem.Code, shapeToGeotiffRequest); await PollTask(TimeSpan.FromSeconds(3), async (tokenSource) => { _logger.LogInformation("Checking shapetogeotiff task status"); var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(isariaShapeItem.Code, taskCode); if (itemTaskStatus.State != ItemTaskState.Processing && itemTaskStatus.State != ItemTaskState.Scheduled) tokenSource.Cancel(); }); _logger.LogInformation("Isaria shape converted to geotiff"); // the parent of the shape item is now the tiff isaria item isariaShapeItem = await _farmmapsApiService.GetItemAsync(isariaShapeItem.Code); return await _farmmapsApiService.GetItemAsync(isariaShapeItem.ParentCode); ; } /// /// Calculates TargetN, makes the assumption the cropfield and user.input(targetn) item have the same parent /// /// The cropfield to base the calculations on /// The target yield input for the TargetN calculation /// The TargetN public async Task CalculateTargetN(Item cropfieldItem, int targetYield) { var targetNItems = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.ParentCode, USERINPUT_ITEMTYPE); Item targetNItem; if (targetNItems.Count == 0) { _logger.LogInformation("Creating targetN item"); var itemRequest = CreateTargetNItemRequest(cropfieldItem.ParentCode); targetNItem = await _farmmapsApiService.CreateItemAsync(itemRequest); } else { targetNItem = targetNItems[0]; } var nbsTargetNRequest = new TaskRequest {TaskType = VRANBS_TASK}; nbsTargetNRequest.attributes["operation"] = "targetn"; nbsTargetNRequest.attributes["inputCode"] = targetNItem.Code; nbsTargetNRequest.attributes["inputType"] = "irmi"; nbsTargetNRequest.attributes["purposeType"] = "consumption"; nbsTargetNRequest.attributes["targetYield"] = targetYield.ToString(); string itemTaskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, nbsTargetNRequest); await PollTask(TimeSpan.FromSeconds(3), async (tokenSource) => { var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode); if (itemTaskStatus.State != ItemTaskState.Processing && itemTaskStatus.State != ItemTaskState.Scheduled) tokenSource.Cancel(); }); var itemTask = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode); if(itemTask.State == ItemTaskState.Error) { _logger.LogError($"Something went wrong with task execution: {itemTask.Message}"); return 0; } var item = await _farmmapsApiService.GetItemAsync(targetNItem.Code); if (item.Data.ContainsKey("TargetN")) return item.Data.Value("TargetN"); return 0; } public async Task CalculateNitrogenMap(Item cropfieldItem, Item inputItem, double targetN) { var nbsNitrogenRequest = new TaskRequest {TaskType = VRANBS_TASK}; nbsNitrogenRequest.attributes["operation"] = "nitrogen"; nbsNitrogenRequest.attributes["inputCode"] = inputItem.Code; nbsNitrogenRequest.attributes["inputType"] = "irmi"; nbsNitrogenRequest.attributes["targetN"] = targetN.ToString(CultureInfo.InvariantCulture); string itemTaskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, nbsNitrogenRequest); await PollTask(TimeSpan.FromSeconds(5), async (tokenSource) => { var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode); if (itemTaskStatus.State != ItemTaskState.Processing && itemTaskStatus.State != ItemTaskState.Scheduled) tokenSource.Cancel(); }); var itemTask = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode); if(itemTask.State == ItemTaskState.Error) { _logger.LogError($"Something went wrong with task execution: {itemTask.Message}"); return null; } var geotiffItems = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.Code, GEOTIFF_PROCESSED_ITEMTYPE); // maybe poll here to, to wait for geotiff task process to be finished? // how to uniquely know which item is really created!? var nitrogenItem = geotiffItems.SingleOrDefault(i => i.Name.Contains("nitrogen") && i.Updated >= itemTask.Finished); if (nitrogenItem == null) { _logger.LogError("Could not find the nitrogen geotiff child item under cropfield"); return null; } return nitrogenItem; } private ItemRequest CreateTargetNItemRequest(string parentItemCode) { return new ItemRequest() { ParentCode = parentItemCode, ItemType = USERINPUT_ITEMTYPE, Name = "TargetN", DataDate = DateTime.UtcNow }; } } }