diff --git a/FarmmapsApi/FarmmapsApi.csproj b/FarmmapsApi/FarmmapsApi.csproj index 8719220..8f133f8 100644 --- a/FarmmapsApi/FarmmapsApi.csproj +++ b/FarmmapsApi/FarmmapsApi.csproj @@ -5,6 +5,7 @@ + @@ -12,6 +13,7 @@ + diff --git a/FarmmapsApi/Models/Configuration.cs b/FarmmapsApi/Models/Configuration.cs index 72ec057..ee29b7e 100644 --- a/FarmmapsApi/Models/Configuration.cs +++ b/FarmmapsApi/Models/Configuration.cs @@ -4,6 +4,7 @@ { public string Authority { get; set; } public string Endpoint { get; set; } + public string BasePath { get; set; } public string DiscoveryEndpointUrl { get; set; } public string RedirectUri { get; set; } public string ClientId { get; set; } diff --git a/FarmmapsApi/Models/FileRequest.cs b/FarmmapsApi/Models/FileRequest.cs new file mode 100644 index 0000000..0e04088 --- /dev/null +++ b/FarmmapsApi/Models/FileRequest.cs @@ -0,0 +1,45 @@ +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json.Linq; + +namespace FarmmapsApi.Models +{ + public class FileRequest + { + /// + /// Code of the parent + /// + /// 41971e7ea8a446069a817e66b608dcae + public string ParentCode { get; set; } + + /// + /// Geometry meta data + /// + /// {"type": "Point","coordinates": [5.27, 52.10]} + public JObject Geometry { get; set; } + + /// + /// Name of the file to upload + /// + /// MyFile.tiff + [Required] + public string Name { get; set; } + + /// + /// Size of the file to upload + /// + /// 67351 + [Required] + public long Size { get; set; } + + /// + /// If a chunked upload, the size of a single chunk + /// + /// 1048576 + public long ChunkSize { get; set; } + + /// + /// Optional data for the item coupled with the file + /// + public JObject Data { get; set; } + } +} \ No newline at end of file diff --git a/FarmmapsApi/Models/FileResponse.cs b/FarmmapsApi/Models/FileResponse.cs new file mode 100644 index 0000000..4bd7cc5 --- /dev/null +++ b/FarmmapsApi/Models/FileResponse.cs @@ -0,0 +1,17 @@ +namespace FarmmapsApi.Models +{ + public class FileResponse : FileRequest + { + /// + /// Code created for the registered file, use this in subsequent calls + /// + /// a00fbd18320742c787f99f952aef0dbb + public string Code { get; set; } + + /// + /// If a chunked upload, the number of chunks to upload + /// + /// 1 + public long Chunks { get; set; } + } +} \ No newline at end of file diff --git a/FarmmapsApi/Services/FarmmapsApiService.cs b/FarmmapsApi/Services/FarmmapsApiService.cs index b997c26..6d33957 100644 --- a/FarmmapsApi/Services/FarmmapsApiService.cs +++ b/FarmmapsApi/Services/FarmmapsApiService.cs @@ -10,30 +10,31 @@ using System.Text; using System.Threading.Tasks; using System.Web; using FarmmapsApi.Models; +using Google.Apis.Http; +using Google.Apis.Upload; using IdentityModel; using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Winista.Mime; namespace FarmmapsApi.Services { public class FarmmapsApiService { - private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly OpenIdConnectService _openIdConnectService; private readonly Configuration _configuration; - + private HubConnection _hubConnection; public event Action EventCallback; - public FarmmapsApiService(HttpClient httpClient, ILogger logger, + public FarmmapsApiService(HttpClient httpClient, OpenIdConnectService openIdConnectService, Configuration configuration) { - _logger = logger; _httpClient = httpClient; _openIdConnectService = openIdConnectService; _configuration = configuration; @@ -65,10 +66,9 @@ namespace FarmmapsApi.Services private async Task StartEventHub(string accessToken) { - var uri = new Uri(_configuration.Endpoint); - var eventEndpoint = $"{uri.Scheme}://{uri.Host}:{uri.Port}/EventHub"; + var eventEndpoint = $"{_configuration.Endpoint}/EventHub"; _hubConnection = new HubConnectionBuilder() - .WithUrl(eventEndpoint, HttpTransportType.WebSockets, + .WithUrl(eventEndpoint, HttpTransportType.WebSockets, options => options.SkipNegotiation = true) .ConfigureLogging(log => log.AddConsole()) .WithAutomaticReconnect() @@ -76,9 +76,9 @@ namespace FarmmapsApi.Services await _hubConnection.StartAsync(); await AuthenticateEventHub(accessToken); - + _hubConnection.Reconnected += async s => await AuthenticateEventHub(accessToken); - _hubConnection.On("event", (Object @event) => _logger.LogInformation(@event.ToString())); + _hubConnection.On("event", (EventMessage eventMessage) => EventCallback?.Invoke(eventMessage)); } private async Task AuthenticateEventHub(string accessToken) @@ -88,7 +88,8 @@ namespace FarmmapsApi.Services public async Task GetCurrentUserCodeAsync() { - var response = await _httpClient.GetAsync(ResourceEndpoints.CURRENTUSER_RESOURCE); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.CURRENTUSER_RESOURCE}"; + var response = await _httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); @@ -100,7 +101,8 @@ namespace FarmmapsApi.Services public async Task> GetCurrentUserRootsAsync() { - var response = await _httpClient.GetAsync(ResourceEndpoints.MYROOTS_RESOURCE); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.MYROOTS_RESOURCE}"; + var response = await _httpClient.GetAsync(url); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); @@ -109,7 +111,7 @@ namespace FarmmapsApi.Services return JsonConvert.DeserializeObject>(jsonString); } - + public async Task GetItemAsync(string itemCode, string itemType = null, JObject dataFilter = null) { if (dataFilter == null) @@ -118,28 +120,29 @@ namespace FarmmapsApi.Services var items = await GetItemsAsync(itemCode, itemType, dataFilter); return items[0]; } - + public async Task> GetItemsAsync(string itemCode, string itemType = null, JObject dataFilter = null) { - var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); var queryString = HttpUtility.ParseQueryString(string.Empty); - - if(itemType != null) + + if (itemType != null) queryString["it"] = itemType; - - if(dataFilter != null) - queryString["df"] = dataFilter.ToString(Formatting.None); + + if (dataFilter != null) + queryString["df"] = dataFilter.ToString(Formatting.None); resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString; - + var response = await _httpClient.GetAsync(resourceUri); if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NotFound) return null; - + throw new Exception(response.ReasonPhrase); } @@ -149,36 +152,39 @@ namespace FarmmapsApi.Services private async Task GetItemSingleAsync(string itemCode, string itemType = null) { - var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); if (!string.IsNullOrEmpty(itemType)) resourceUri = $"{resourceUri}/{itemType}"; - + var response = await _httpClient.GetAsync(resourceUri); - + if (!response.IsSuccessStatusCode) { if (response.StatusCode == HttpStatusCode.NotFound) return null; - + throw new Exception(response.ReasonPhrase); } var jsonString = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(jsonString); } - - public async Task> GetItemChildrenAsync(string itemCode, string itemType = null, JObject dataFilter = null) + + public async Task> GetItemChildrenAsync(string itemCode, string itemType = null, + JObject dataFilter = null) { - var resourceUri = string.Format(ResourceEndpoints.ITEMS_CHILDREN_RESOURCE, itemCode); - + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_CHILDREN_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); + var queryString = HttpUtility.ParseQueryString(string.Empty); - - if(itemType != null) + + if (itemType != null) queryString["it"] = itemType; - - if(dataFilter != null) - queryString["df"] = dataFilter.ToString(Formatting.None); + + if (dataFilter != null) + queryString["df"] = dataFilter.ToString(Formatting.None); resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString; @@ -195,31 +201,34 @@ namespace FarmmapsApi.Services { var jsonString = JsonConvert.SerializeObject(itemRequest); - var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json); - var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_CREATE_RESOURCE, + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_CREATE_RESOURCE}"; + var content = new StringContent(jsonString, Encoding.UTF8, MediaTypeNames.Application.Json); + var response = await _httpClient.PostAsync(url, content); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); - + var jsonStringResponse = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(jsonStringResponse); } public async Task DeleteItemAsync(string itemCode) { - var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); var response = await _httpClient.DeleteAsync(resourceUri); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); } - + public async Task DeleteItemsAsync(IList itemCodes) { var jsonString = JsonConvert.SerializeObject(itemCodes); - var content = new StringContent(jsonString); - var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_DELETE_RESOURCE, content); + var content = new StringContent(jsonString); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_DELETE_RESOURCE}"; + var response = await _httpClient.PostAsync(url, content); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); @@ -227,10 +236,11 @@ namespace FarmmapsApi.Services public async Task DownloadItemAsync(string itemCode, string filePath) { - var resourceUri = string.Format(ResourceEndpoints.ITEMS_DOWNLOAD_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_DOWNLOAD_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); var response = await _httpClient.GetAsync(resourceUri, HttpCompletionOption.ResponseHeadersRead); - - if(response.IsSuccessStatusCode) + + if (response.IsSuccessStatusCode) { await using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(); await using Stream streamToWriteTo = File.Open(filePath, FileMode.Create); @@ -238,30 +248,32 @@ namespace FarmmapsApi.Services } else { - throw new FileNotFoundException(response.ReasonPhrase); + throw new FileNotFoundException(response.ReasonPhrase); } } public async Task QueueTaskAsync(string itemCode, TaskRequest taskRequest) { - var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); var jsonString = JsonConvert.SerializeObject(taskRequest); - var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json); + var content = new StringContent(jsonString, Encoding.UTF8, MediaTypeNames.Application.Json); var response = await _httpClient.PostAsync(resourceUri, content); if (!response.IsSuccessStatusCode) throw new Exception(response.ReasonPhrase); - + var jsonStringResponse = await response.Content.ReadAsStringAsync(); var json = JsonConvert.DeserializeObject(jsonStringResponse); - + return json["code"].Value(); } public async Task> GetTasksStatusAsync(string itemCode) { - var resourceUri = string.Format(ResourceEndpoints.ITEMTASKS_RESOURCE, itemCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASKS_RESOURCE}"; + var resourceUri = string.Format(url, itemCode); var response = await _httpClient.GetAsync(resourceUri); if (!response.IsSuccessStatusCode) @@ -273,7 +285,8 @@ namespace FarmmapsApi.Services public async Task GetTaskStatusAsync(string itemCode, string itemTaskCode) { - var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_RESOURCE, itemCode, itemTaskCode); + var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASK_RESOURCE}"; + var resourceUri = string.Format(url, itemCode, itemTaskCode); var response = await _httpClient.GetAsync(resourceUri); if (!response.IsSuccessStatusCode) @@ -282,5 +295,85 @@ namespace FarmmapsApi.Services var jsonString = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(jsonString); } + + /// + /// Experimental + /// + /// + /// + /// + /// + /// + public async Task UploadFile(string filePath, string parentItemCode, Action uploadCallback) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException($"File not found {filePath}"); + + var mimeTypes = new MimeTypes(); + var mimeType = mimeTypes.GetMimeTypeFromFile(filePath); + + await using var uploadStream = new FileStream(filePath, FileMode.OpenOrCreate); + + var request = new FileRequest() + { + Name = Path.GetFileName(filePath), + ParentCode = parentItemCode, + Size = uploadStream.Length + }; + + Uri uploadIdentifierUri = null; + using var httpClient = CreateConfigurableHttpClient(_httpClient); + var farmmapsUploader = new FarmmapsUploader(httpClient, uploadStream, request, mimeType.ToString()); + + farmmapsUploader.ProgressChanged += uploadCallback; + farmmapsUploader.UploadSessionData += data => uploadIdentifierUri = data.UploadUri; + + await farmmapsUploader.UploadAsync(); + + return uploadIdentifierUri; + } + + /// + /// Experimental + /// + /// + /// + /// + /// + /// + private async Task ResumeUploadFile(string filePath, Uri location, Action uploadCallback) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException($"File not found {filePath}"); + + await using var uploadStream = new FileStream(filePath, FileMode.OpenOrCreate); + + var request = new FileRequest() + { + Name = Path.GetFileName(filePath), + ParentCode = string.Empty, + Size = uploadStream.Length + }; + + using var httpClient = CreateConfigurableHttpClient(_httpClient); + var farmmapsUploader = new FarmmapsUploader(httpClient, uploadStream, request, string.Empty); + farmmapsUploader.ProgressChanged += uploadCallback; + + await farmmapsUploader.ResumeAsync(location); + + return location; + } + + private ConfigurableHttpClient CreateConfigurableHttpClient(HttpClient parent) + { + var googleHttpClient = new HttpClientFactory().CreateHttpClient(new CreateHttpClientArgs {GZipEnabled = true, ApplicationName = "FarmMaps"}); + googleHttpClient.BaseAddress = parent.BaseAddress; + googleHttpClient.DefaultRequestHeaders.Add("Accept", MediaTypeNames.Application.Json); + + var authHeader = parent.DefaultRequestHeaders.Authorization; + googleHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authHeader.Scheme, authHeader.Parameter); + + return googleHttpClient; + } } } \ No newline at end of file diff --git a/FarmmapsApi/Services/FarmmapsUploader.cs b/FarmmapsApi/Services/FarmmapsUploader.cs new file mode 100644 index 0000000..0bd2945 --- /dev/null +++ b/FarmmapsApi/Services/FarmmapsUploader.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections; +using System.IO; +using System.Net.Http; +using System.Net.Mime; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using FarmmapsApi.Models; +using Google.Apis.Http; +using Google.Apis.Json; +using Google.Apis.Requests; +using Google.Apis.Upload; +using Google.Apis.Util; +using Newtonsoft.Json; + +namespace FarmmapsApi.Services +{ + public class FarmmapsUploader : ResumableUpload + { + /// Payload description headers, describing the content itself. + private const string PayloadContentTypeHeader = "X-Upload-Content-Type"; + + /// Payload description headers, describing the content itself. + private const string PayloadContentLengthHeader = "X-Upload-Content-Length"; + + private const int UnknownSize = -1; + + /// Gets or sets the service. + private HttpClient HttpClient { get; } + + /// + /// Gets or sets the path of the method (combined with + /// ) to produce + /// absolute Uri. + /// + private string Path { get; } + + /// Gets or sets the HTTP method of this upload (used to initialize the upload). + private string HttpMethod { get; } + + /// Gets or sets the stream's Content-Type. + private string ContentType { get; } + + /// Gets or sets the body of this request. + private FileRequest Body { get; } + + private long _streamLength; + + /// + /// Create a resumable upload instance with the required parameters. + /// + /// The stream containing the content to upload. + /// + /// Caller is responsible for maintaining the open until the upload is + /// completed. + /// Caller is responsible for closing the . + /// + public FarmmapsUploader(ConfigurableHttpClient httpClient, Stream contentStream, FileRequest body, string contentType) + : base(contentStream, + new ResumableUploadOptions + { + HttpClient = httpClient, + Serializer = new NewtonsoftJsonSerializer(), + ServiceName = "FarmMaps" + }) + { + httpClient.ThrowIfNull(nameof(httpClient)); + contentStream.ThrowIfNull(nameof(contentStream)); + + _streamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize; + Body = body; + Path = "api/v1/file"; + HttpClient = httpClient; + HttpMethod = HttpConsts.Post; + ContentType = contentType; + } + + /// + public override async Task InitiateSessionAsync(CancellationToken cancellationToken = default) + { + HttpRequestMessage request = CreateInitializeRequest(); + Options?.ModifySessionInitiationRequest?.Invoke(request); + var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + { + throw await ExceptionForResponseAsync(response).ConfigureAwait(false); + } + return response.Headers.Location; + } + + /// Creates a request to initialize a request. + private HttpRequestMessage CreateInitializeRequest() + { + var baseAddres = HttpClient.BaseAddress; + var uri = new Uri($"{baseAddres.Scheme}://{baseAddres.Host}:{baseAddres.Port}"); + var builder = new RequestBuilder() + { + BaseUri = uri, + Path = Path, + Method = HttpMethod, + }; + + SetAllPropertyValues(builder); + + HttpRequestMessage request = builder.CreateRequest(); + if (ContentType != null) + { + request.Headers.Add(PayloadContentTypeHeader, ContentType); + } + + // if the length is unknown at the time of this request, omit "X-Upload-Content-Length" header + if (ContentStream.CanSeek) + { + request.Headers.Add(PayloadContentLengthHeader, _streamLength.ToString()); + } + + var jsonText = JsonConvert.SerializeObject(Body); + request.Content = new StringContent(jsonText, Encoding.UTF8, MediaTypeNames.Application.Json); + + return request; + } + + /// + /// Reflectively enumerate the properties of this object looking for all properties containing the + /// RequestParameterAttribute and copy their values into the request builder. + /// + private void SetAllPropertyValues(RequestBuilder requestBuilder) + { + Type myType = this.GetType(); + var properties = myType.GetProperties(); + + foreach (var property in properties) + { + var attribute = property.GetCustomAttribute(); + if (attribute != null) + { + string name = attribute.Name ?? property.Name.ToLower(); + object value = property.GetValue(this, null); + if (value != null) + { + if (!(value is string) && value is IEnumerable valueAsEnumerable) + { + foreach (var elem in valueAsEnumerable) + { + requestBuilder.AddParameter(attribute.Type, name, Utilities.ConvertToString(elem)); + } + } + else + { + // Otherwise just convert it to a string. + requestBuilder.AddParameter(attribute.Type, name, Utilities.ConvertToString(value)); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/FarmmapsApiSamples/Data/Belgie.zip b/FarmmapsApiSamples/Data/Belgie.zip new file mode 100644 index 0000000..864aa27 Binary files /dev/null and b/FarmmapsApiSamples/Data/Belgie.zip differ diff --git a/FarmmapsApiSamples/Data/Scan_1_20190605.zip b/FarmmapsApiSamples/Data/Scan_1_20190605.zip new file mode 100644 index 0000000..ab00afd Binary files /dev/null and b/FarmmapsApiSamples/Data/Scan_1_20190605.zip differ diff --git a/FarmmapsApiSamples/FarmmapsApiSamples.csproj b/FarmmapsApiSamples/FarmmapsApiSamples.csproj index 4f86bf8..630bed0 100644 --- a/FarmmapsApiSamples/FarmmapsApiSamples.csproj +++ b/FarmmapsApiSamples/FarmmapsApiSamples.csproj @@ -9,6 +9,9 @@ Always + + Always + diff --git a/FarmmapsApiSamples/NbsApp.cs b/FarmmapsApiSamples/NbsApp.cs index b344698..499e24b 100644 --- a/FarmmapsApiSamples/NbsApp.cs +++ b/FarmmapsApiSamples/NbsApp.cs @@ -1,8 +1,11 @@ using System; +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; @@ -12,6 +15,8 @@ namespace FarmmapsApiSamples public class NbsApp : IApp { private const string USERINPUT_ITEMTYPE = "vnd.farmmaps.itemtype.user.input"; + private const string GEOTIFF_ITEMTYPE = "vnd.farmmaps.itemtype.geotiff"; + private const string CROPFIELD_ITEMTYPE = "vnd.farmmaps.itemtype.cropfield"; private const string VRANBS_TASK = "vnd.farmmaps.task.vranbs"; private readonly ILogger _logger; @@ -41,14 +46,33 @@ namespace FarmmapsApiSamples _logger.LogInformation("Authenticated client credentials"); var roots = await _farmmapsApiService.GetCurrentUserRootsAsync(); - var myDriveRoot = roots.SingleOrDefault(r => r.Name == "My drive"); - - if (myDriveRoot != null) + + // upload data to Uploaded + var uploadedRoot = roots.SingleOrDefault(r => r.Name == "Uploaded"); + if (uploadedRoot != null) { - var cropfieldItem = await GetOrCreateCropfieldItem(myDriveRoot.Code); - var targetN = await CalculateTargetN(cropfieldItem, 60); + await _farmmapsApiService.UploadFile(Path.Combine("Data", "Scan_1_20190605.zip"), uploadedRoot.Code, + progress => + { + _logger.LogInformation($"Status: {progress.Status} - BytesSent: {progress.BytesSent}"); + if(progress.Status == UploadStatus.Failed) + _logger.LogError($"Uploading failed {progress.Exception.Message}"); + }); - _logger.LogInformation($"TargetN: {targetN}"); + // need to transform shape data to geotiff + + var myDriveRoot = roots.SingleOrDefault(r => r.Name == "My drive"); + if (myDriveRoot != null) + { + var cropfieldItem = await GetOrCreateCropfieldItem(myDriveRoot.Code); + + _logger.LogInformation($"Calculating targetN with targetYield: {60}"); + var targetN = await CalculateTargetN(cropfieldItem, 60); + _logger.LogInformation($"TargetN: {targetN}"); + + _logger.LogInformation("Calculating nitrogen map"); +// var nitrogenMapItem = CalculateNitrogenMap(cropfieldItem,, targetN); + } } } catch (Exception ex) @@ -60,7 +84,7 @@ namespace FarmmapsApiSamples private async Task GetOrCreateCropfieldItem(string parentItemCode) { var cropfieldItems = await - _farmmapsApiService.GetItemChildrenAsync(parentItemCode, "vnd.farmmaps.itemtype.cropfield"); + _farmmapsApiService.GetItemChildrenAsync(parentItemCode,CROPFIELD_ITEMTYPE); if (cropfieldItems.Count > 0) return cropfieldItems[0]; @@ -70,18 +94,24 @@ namespace FarmmapsApiSamples { ParentCode = parentItemCode, ItemType = "vnd.farmmaps.itemtype.cropfield", - Name = "cropfield for VRA", + Name = "Cropfield for VRA", DataDate = currentYear, DataEndDate = currentYear.AddYears(1), Data = JObject.FromObject(new - {startDate = currentYear, endDate = currentYear.AddYears(1)}), + {startDate = currentYear, endDate = currentYear.AddMonths(3)}), Geometry = JObject.Parse( - @"{""type"":""Polygon"",""coordinates"":[[[6.09942873984307,53.070025028087],[6.09992507404607,53.0705617890585],[6.10036959220086,53.0710679529031],[6.10065149010421,53.0714062774307],[6.10087493644271,53.0716712354474],[6.10091082982487,53.0716936039203],[6.10165087441291,53.0712041549161],[6.10204994718318,53.0709349338005],[6.10263143118855,53.0705789370018],[6.10311578125011,53.0702657538294],[6.10331686552072,53.0701314102389],[6.103326530575,53.070119463569],[6.10309137950343,53.0699829669055],[6.10184241586523,53.0692902201371],[6.10168497998891,53.0691984306747],[6.10092987659869,53.0694894453514],[6.09942873984307,53.070025028087]]]}") + @"{ ""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); } + /// + /// 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 private async Task CalculateTargetN(Item cropfieldItem, int targetYield) { var targetNItems = await @@ -99,8 +129,6 @@ namespace FarmmapsApiSamples targetNItem = targetNItems[0]; } - _logger.LogInformation($"Calculating targetN with targetYield: {targetYield}"); - var nbsTargetNRequest = new TaskRequest {TaskType = VRANBS_TASK}; nbsTargetNRequest.attributes["operation"] = "targetn"; nbsTargetNRequest.attributes["inputCode"] = targetNItem.Code; @@ -111,19 +139,63 @@ namespace FarmmapsApiSamples await PollTask(TimeSpan.FromSeconds(3), async (tokenSource) => { - var itemTask = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode); - - if (itemTask.State != ItemTaskState.Processing && itemTask.State != ItemTaskState.Scheduled) + 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["TargetN"].Value(); - + return 0; } + private 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_ITEMTYPE); + + // how to uniquely know which item is really created!? + var nitrogenItem = geotiffItems.SingleOrDefault(i => i.Name.Contains("nitrogen")); + 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() diff --git a/FarmmapsApiSamples/appsettings.json b/FarmmapsApiSamples/appsettings.json index 337d0e9..c795f28 100644 --- a/FarmmapsApiSamples/appsettings.json +++ b/FarmmapsApiSamples/appsettings.json @@ -1,6 +1,7 @@ { "Authority": "https://accounts.farmmaps.awtest.nl/", - "Endpoint": "https://farmmaps.awtest.nl/api/v1/", + "Endpoint": "http://localhost:8083", + "BasePath": "api/v1", "DiscoveryEndpointUrl": "https://accounts.farmmaps.awtest.nl/.well-known/openid-configuration", "RedirectUri": "http://example.nl/api", "ClientId": "",