Compare commits

...

56 Commits

Author SHA1 Message Date
tamara efb4d6833f Included relevant codelist for dataOperations operationCode and status. 2024-04-11 11:31:56 +02:00
tamara 0a9a83a5a6 updated KPIdefinitions and todolist 2024-04-10 10:29:30 +02:00
tamara fe2bd98589 added target and threshold definitions + changed productionPurposeCode to be functional 2024-03-12 11:42:16 +01:00
Pepijn van Oort ffbb45894a write A1 yield & strawYield to output
update definitions
update TODO.txt
2024-03-11 14:35:03 +01:00
tamara 67014e20f5 updated names of output KPI B1 and B2 + added yieldStraw to output (non functional) 2024-03-07 10:37:27 +01:00
tamara 84f27db16e DataCropfieldCharacteristics as an array containing cropyield and possibly strawyield. If no strawyield is filled in then straw remains in field. 2024-02-27 15:44:07 +01:00
tamara d756f08b4c typos 2024-02-27 13:58:09 +01:00
tamara 77532fee9f add codelist 265. Empty result? 2024-02-27 13:55:51 +01:00
tamara 5d2789a806 updated KPI definitions 2024-02-19 15:07:26 +01:00
Pepijn van Oort dea4f102f5 C1 defs 2024-02-16 15:28:15 +01:00
Pepijn van Oort 39ee3263a7 bit of code simplification 2024-02-16 14:52:48 +01:00
Pepijn van Oort 619cb5e92c small updates on KPIdefinitions.csv & TODO.txt 2024-02-16 14:36:10 +01:00
tamara 5399fd66df added detailed output for KPI C1 effective organic matter supply 2024-02-16 12:58:00 +01:00
tamara 5d02370f56 details about N & P balance written to output file 2024-02-12 15:43:00 +01:00
Pepijn van Oort e4087adc77 add miliebelastingspunten (mbp) and checks on whether KPI task is finished or not yet (if not yet then wait and call again) 2023-11-28 15:16:44 +01:00
Pepijn van Oort 4761166472 new Task<Configuration> GetConfiguration 2023-11-28 15:12:51 +01:00
Pepijn van Oort 9d691dfed1 read & write KPI "E1" = milieu belastingspunten ("mbp") 2023-11-22 16:16:59 +01:00
Pepijn van Oort d616fd3dfc in KPIApplication add bofek to cropfield, because KPI E1 (milieubelastingspunten) only calculated if soil is known 2023-11-21 14:03:14 +01:00
Pepijn van Oort 3f66676914 specify how many seconds to wait for the KPItask to be completed 2023-11-15 16:49:46 +01:00
Pepijn van Oort aa18393102 two example inputs for testing garden pea (doperwt) for whole year of part of growing period 2023-11-08 13:51:30 +01:00
Pepijn van Oort 0b0ec8f767 StartDate & EndDate of cropfield. Is relevant for N fixation by leguminose crops where currently N fixation depends on day between these two dates. If not provided a whole year is assumed which leads to high N fixation 2023-11-08 13:50:37 +01:00
Pepijn van Oort aef8c7c446 update KPIdefinitions.csv & TODO.txt 2023-11-08 13:48:01 +01:00
Pepijn van Oort f1707c432f More testing & demo's in KPIInput.json:
* pesticide
* effect of soiltype: yes, different target & threshold for nitrogen (OK)
* effect of productionpurpose: none?
* effect of location: different nitrogen balance due to different atmospheric deposition (OK)

And update TODO.txt
2023-11-01 13:05:49 +01:00
Pepijn van Oort cfba86cf75 Get polygon area from geometry and give to operations if operation was on whole field
Time tracker for how much time the KPI calculations take
2023-11-01 13:02:54 +01:00
Pepijn van Oort da808362c3 Added codelist CL251: Codering teeltdoelen (CropProductionPurposeCode) 2023-11-01 12:32:05 +01:00
Pepijn van Oort 847ff75845 download codelist CL405, soil types 2023-10-24 13:47:58 +02:00
Pepijn van Oort 6d903c3e9b error catching and console input to ask which KPIinput file to use 2023-10-24 13:06:36 +02:00
Pepijn van Oort 6eb36b2b4f remove author name from code 2023-10-23 12:48:28 +02:00
Pepijn van Oort 7f3b87ae38 and of course also add & commit this one ... 2023-10-18 17:35:34 +02:00
Pepijn van Oort 90f285a0d1 update todo list 2023-10-18 17:33:55 +02:00
Pepijn van Oort 546ce131e4 also commit this file 2023-10-18 17:33:32 +02:00
Pepijn van Oort d8c2ecb88b new project to download latest version of editeelt codelists 2023-10-18 17:32:38 +02:00
Pepijn van Oort 2d588026e2 also write totalNapplied to csv file with KPI outputs per cropfield 2023-10-16 15:34:07 +02:00
Pepijn van Oort 1a9d5af5f6 don't write duplicate KPIOutput items to output csv 2023-10-16 13:55:12 +02:00
Pepijn van Oort 8771e47b28 TODO 2023-10-13 16:38:27 +02:00
Pepijn van Oort 4102ed628e farmmapsKPI receives minimum necessary input and writes relevant KPI output 2023-10-13 16:37:50 +02:00
Pepijn van Oort 1cc36422a4 start & end date & geometry added to operations item and to cropfield characteristics item 2023-10-11 17:00:25 +02:00
Pepijn van Oort fedd363075 in KPIApplication possible to (1) add an operation, e.g. applying fertilizer and (2) add a cropfield characteristic containing cropyield. 2023-10-04 17:21:05 +02:00
Pepijn van Oort f04cc239d5 Extra note to Clone the git repository to a local drive. DON'T clone to a network drive like "My Documents". Latter may cause authentication problems. 2023-10-04 10:43:44 +02:00
Pepijn van Oort 8a770a1762 handles existing cropfielditemcode 2023-06-27 14:08:27 +02:00
Abel Hoeven, van b99191b7b1 niewste versie 2023-06-27 12:58:36 +02:00
Pepijn van Oort 9be9eb3145 update TargetFramework of all projects to netcoreapp3.1. Except FarmmapsAPi & Secrets which remain 2.1 2023-06-13 12:05:30 +02:00
Abel Hoeven, van 641e09f833 Merge branch 'master' of https://git.akkerweb.nl/abel/FarmMapsApiClient_KB34_MAST 2023-05-02 10:55:43 +02:00
Abel Hoeven, van 8c5a0aed95 de niet verplichte inputs verwijderd van de input file. Wel wordt er dus de input file gebruikt die in de C:\Users\hoeve065\Documents\hoofdmap_werk\project_KB34MAST nutrienten DB\API04.03\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\KPIInput.json staat, en niet bij het KPI programma. 2023-05-02 10:55:24 +02:00
Abel Hoeven, van 59f9ef4743 nu kan er van de gegeven input een cropfield wordern gemaakt, en hier kunnen vervolgens KPIs over berekend worden. Volgende stap is het strippen van de KPIinput om te kijken wat het minimale is dat nodig is om de kpis te berekenen. verder nu eem aantal inputs die niet nodig waren verwijdertInput aangepast, 2023-04-25 16:33:57 +02:00
Abel Hoeven, van 8bb5c6f581 Input aangepast, nu kan er van de gegeven input een cropfield wordern gemaakt, en hier kunnen vervolgens KPIs over berekend worden. Volgende stap is het strippen van de KPIinput om te kijken wat het minimale is dat nodig is om de kpis te berekenen. 2023-04-25 16:14:43 +02:00
Abel Hoeven, van 7149086ce2 KPI items opslaan werkt. alle KPI items worden nu naar een JSON file in de dowload folder geschreven 2023-04-25 12:00:08 +02:00
abel 55f00da9d0 KPI items ophalen voor een cropfield werkt nu. wel dus een bestaand cropfield ingeven bij de settings in de BIN. Alle KPIs worden opgehaald na een minuut wachten. 2023-04-07 14:33:49 +02:00
abel 3d7bf186e7 de properties voor de KPI task worden nu correct doorgegeven denk ik 2023-04-06 16:11:37 +02:00
abel 8f8f857bf0 ook in in de andere KPIinout toegevoegd 2023-04-03 13:05:00 +02:00
abel b7bedf182c aggegrate KPI = false toegevoegd aan inputs 2023-04-03 13:03:10 +02:00
abel be22d631bc geupdate runKPIitemtask, helaas nog geen KPI data gevonden als child item 2023-03-31 11:13:51 +02:00
abel 7dda21dd77 probeersel om een runKPItask te maken 2023-03-31 11:02:06 +02:00
abel c79e0d062c Merge branch 'master' of https://git.akkerweb.nl/abel/FarmMapsApiClient_KB34_MAST 2023-03-22 14:48:34 +01:00
abel 32138bef80 blah test 2023-03-22 14:48:13 +01:00
Pepijn van Oort e66820bd87 test 2023-03-22 14:41:41 +01:00
30 changed files with 2500 additions and 24 deletions

View File

@ -12,7 +12,12 @@ namespace FarmmapsApiSamples
public const string SHAPE_ITEMTYPE = "vnd.farmmaps.itemtype.shape";
public const string GEOJSON_ITEMTYPE = "vnd.farmmaps.itemtype.geojson";
public const string BLIGHT_ITEMTYPE = "vnd.farmmaps.itemtype.blight";
public const string CROPREC_ITEMTYPE = "vnd.farmmaps.itemtype.crprec.operation";
public const string CROPREC_ITEMTYPE = "vnd.farmmaps.itemtype.crprec";
public const string CROPOP_ITEMTYPE = "vnd.farmmaps.itemtype.crprec.operation";
public const string CROPCHAR_ITEMTYPE = "vnd.farmmaps.itemtype.edicrop.characteristic";
public const string CROPSCHEME_ITEMTYPE = "vnd.farmmaps.itemtype.croppingscheme"; // deze toegevoegd, misschien is het type van de KPI task wel een croppingscheme
//public const string KPI_ITEM = "vnd.farmmaps.itemtype.kpi.data"; //PO20231004: originally with .data
public const string KPICONTAINER_ITEM = "vnd.farmmaps.itemtype.kpi.data.container"; //PO20231004: originally without .container
public const string VRANBS_TASK = "vnd.farmmaps.task.vranbs";
public const string VRAHERBICIDE_TASK = "vnd.farmmaps.task.vraherbicide";
@ -24,8 +29,10 @@ namespace FarmmapsApiSamples
public const string TASKMAP_TASK = "vnd.farmmaps.task.taskmap";
public const string WORKFLOW_TASK = "vnd.farmmaps.task.workflow";
public const string BOFEK_TASK = "vnd.farmmaps.task.bofek";
public const string CROPREC_TASK = "vnd.farmmaps.task.crprec";
public const string SHADOW_TASK = "vnd.farmmaps.task.shadow";
public const string AHN_TASK = "vnd.farmmaps.task.ahn";
public const string WATBAL_TASK = "vnd.farmmaps.task.watbal";
public const string KPI_TASK = "vnd.farmmaps.task.kpi"; // dus nieuwe taak om de KPIs te berekenen
}
}

View File

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>

View File

@ -46,6 +46,10 @@ namespace FarmmapsApi.Services
}
}
public async Task<Configuration> GetConfiguration()
{
return _configuration;
}
public async Task AuthenticateAsync()
{
if (_httpClient.DefaultRequestHeaders.Authorization != null &&

View File

@ -22,14 +22,20 @@ namespace FarmmapsApi.Services
}
public async Task<Item> CreateCropfieldItemAsync(string parentItemCode, string name, int year,
string fieldGeomJson, string data = "{}") {
var currentYear = new DateTime(year, 1, 1);
string fieldGeomJson, string data = "{}", DateTime startDate = new DateTime(), DateTime endDate = new DateTime() )
{
//If user provides no startDate or endDate, then set startDate and endDate to full year provided through 'year'
if (startDate == new DateTime() || endDate == new DateTime())
{
startDate = new DateTime(year, 1, 1);
endDate = new DateTime(year, 12, 31);
}
var cropfieldItemRequest = new ItemRequest() {
ParentCode = parentItemCode,
ItemType = CROPFIELD_ITEMTYPE,
Name = name,
DataDate = currentYear,
DataEndDate = currentYear.AddYears(1).AddDays(-1),
DataDate = startDate,
DataEndDate = endDate,
Data = JObject.Parse(data),
Geometry = JObject.Parse(fieldGeomJson)
};
@ -37,6 +43,55 @@ namespace FarmmapsApi.Services
return await _farmmapsApiService.CreateItemAsync(cropfieldItemRequest);
}
public async Task<Item> CreateOperationItemAsync(string cropRecordingItemCode, int year,
string fieldGeomJson, string data = "{}", DateTime startDate = new DateTime(), DateTime endDate = new DateTime())
{
//If user provides no startDate or endDate, then set startDate and endDate to full year provided through 'year'
if (startDate == new DateTime() || endDate == new DateTime())
{
startDate = new DateTime(year, 1, 1);
endDate = new DateTime(year, 12, 31);
}
JObject jdata = JObject.Parse(data);
string name = string.Format($"CrpRec Operation, {jdata.GetValue("name")}");
ItemRequest operationItemRequest = new ItemRequest()
{
ParentCode = cropRecordingItemCode,
ItemType = CROPOP_ITEMTYPE,
Name = name,
DataDate = startDate,
DataEndDate = endDate,
Data = jdata,
Geometry = JObject.Parse(fieldGeomJson)
};
return await _farmmapsApiService.CreateItemAsync(operationItemRequest);
}
public async Task<Item> CreateCropfieldCharacteristicItemAsync(string cropfieldItemCode, int year,
string fieldGeomJson, string data = "{}", DateTime startDate = new DateTime(), DateTime endDate = new DateTime())
{
//If user provides no startDate or endDate, then set startDate and endDate to full year provided through 'year'
if (startDate == new DateTime() || endDate == new DateTime())
{
startDate = new DateTime(year, 1, 1);
endDate = new DateTime(year, 12, 31);
}
// not sure if here we also need to specify DataDate, DataEndDate & Geometry. Do it just in case
string name = "Cropfield characteristic";
ItemRequest cropfieldCharactericsticItemRequest = new ItemRequest()
{
ParentCode = cropfieldItemCode,
ItemType = CROPCHAR_ITEMTYPE,
Name = name,
DataDate = startDate,
DataEndDate = endDate,
Data = JObject.Parse(data),
Geometry = JObject.Parse(fieldGeomJson)
};
return await _farmmapsApiService.CreateItemAsync(cropfieldCharactericsticItemRequest);
}
public async Task<Item> UploadDataAsync(UserRoot root, string itemType, string filePath, string itemName, string geoJsonString = null)
{
var startUpload = DateTime.UtcNow.AddSeconds(-3);
@ -247,6 +302,39 @@ namespace FarmmapsApi.Services
return dataItem;
}
public async Task<Item> RunCropRecordingTask(Item cropfieldItem)
{
var cropRecordingRequest = new TaskRequest { TaskType = CROPREC_TASK };
string itemTaskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, cropRecordingRequest);
await PollTask(TimeSpan.FromSeconds(5), async (tokenSource) => {
var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode);
_logger.LogInformation($"Waiting on RunCropRecordingTask; status: {itemTaskStatus.State}");
if (itemTaskStatus.IsFinished)
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;
}
//the CropRecording data is a child of the cropfield
var itemName = "Crprec";
var cropRecordingItem = await FindChildItemAsync(cropfieldItem.Code,
CROPREC_ITEMTYPE, itemName);
if (cropRecordingItem == null)
{
_logger.LogError("Could not find the CropRecording data as a child item under the cropfield");
return null;
}
return cropRecordingItem;
}
public async Task<Item> RunBofekTask(Item cropfieldItem) {
var taskmapRequest = new TaskRequest { TaskType = BOFEK_TASK };
@ -277,6 +365,51 @@ namespace FarmmapsApi.Services
return bofekItem;
}
public async Task<List<Item>> GetKpiItemsForCropField(Item cropfieldItem, int waitsecs = 6)
{
TaskRequest kpiRequest = new TaskRequest { TaskType = KPI_TASK };
kpiRequest.attributes["processAggregateKpi"] = "false";
int year = cropfieldItem.DataDate.Value.Year;
kpiRequest.attributes["year"] = year.ToString();
int ms = waitsecs * 1000;
string itemTaskCode = await _farmmapsApiService.QueueTaskAsync(cropfieldItem.Code, kpiRequest);
await PollTask(TimeSpan.FromSeconds(5), async (tokenSource) => {
var itemTaskStatus = await _farmmapsApiService.GetTaskStatusAsync(cropfieldItem.Code, itemTaskCode);
_logger.LogInformation($"Waiting on retreiving KPI data; status: {itemTaskStatus.State}");
if (itemTaskStatus.IsFinished)
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;
}
_logger.LogInformation($"Taking {waitsecs} seconds to look up all KPI items. The longer we wait the more KPI's we get ..." +
$"");
await Task.Delay(ms); // wait 60000ms = 60 secs for task to be completed
//After the task is completed we have 1 kpiContainerItem with a code and with no data
//Because the data are in the children of this kpiContainerItem. The container will have a list of children each with an "id" aand data,
//The ids' are "a1", "b1", "b2", "c1","d1","d3","d5"
//with following meanings:
//| A1 | Opbrengst | Yield |
//| B1 | Stikstofoverschot | Nitrogen surplus |
//| B2 | Fosfaatoverschot | Phosphate surplus |
//| C1 | Effectieve Organischestof aanvoer| Effective Organic Matter supply |
//| D1 | Gebruik bestrijdingsmiddelen | Use of pesticides|
//| D3 | Gewasdiversiteit(randdichtheid) | Crop diversity(edge density) |
//| D5 | Percentage rustgewassen | Percentage of rest crops |
List <Item> kpiContainerItem = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.Code, KPICONTAINER_ITEM);
string kpiContainerItemCode = kpiContainerItem[0].Code;
List<Item> kpiItems = await _farmmapsApiService.GetItemChildrenAsync(kpiContainerItemCode);
return kpiItems;
}
public async Task<Item> RunAhnTask(Item cropfieldItem) {
var taskmapRequest = new TaskRequest { TaskType = AHN_TASK };

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30320.27
# Visual Studio Version 17
VisualStudioVersion = 17.4.33122.133
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsNbs", "FarmmapsNbs\FarmmapsNbs.csproj", "{E08EF7E9-F09E-42D8-825C-164E458C78F4}"
EndProject
@ -31,56 +31,122 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Secrets", "Secrets\Secrets.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsCleanUp", "FarmmapsCleanUp\FarmmapsCleanUp.csproj", "{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsKPI", "FarmmapsKPI\FarmmapsKPI.csproj", "{14575235-9867-4CE5-A22F-3F9FE002FF42}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsDownloadCL", "FarmmapsDownloadCL\FarmmapsDownloadCL.csproj", "{63E69101-D804-4DBC-B2E4-A33771CD5C5F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Debug|x64.Build.0 = Debug|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|Any CPU.Build.0 = Release|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|x64.ActiveCfg = Release|Any CPU
{E08EF7E9-F09E-42D8-825C-164E458C78F4}.Release|x64.Build.0 = Release|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|x64.ActiveCfg = Debug|x64
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Debug|x64.Build.0 = Debug|x64
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|Any CPU.Build.0 = Release|Any CPU
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|x64.ActiveCfg = Release|x64
{1FA9E50B-F45E-4534-953A-37C783D03C74}.Release|x64.Build.0 = Release|x64
{731A88CD-9DC4-4969-86F2-2315830A6998}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Debug|Any CPU.Build.0 = Debug|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Debug|x64.ActiveCfg = Debug|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Debug|x64.Build.0 = Debug|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Release|Any CPU.ActiveCfg = Release|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Release|Any CPU.Build.0 = Release|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Release|x64.ActiveCfg = Release|Any CPU
{731A88CD-9DC4-4969-86F2-2315830A6998}.Release|x64.Build.0 = Release|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Debug|x64.ActiveCfg = Debug|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Debug|x64.Build.0 = Debug|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Release|Any CPU.Build.0 = Release|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Release|x64.ActiveCfg = Release|Any CPU
{DFA89D0B-5400-4374-B824-8367B76B4B6E}.Release|x64.Build.0 = Release|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Debug|x64.ActiveCfg = Debug|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Debug|x64.Build.0 = Debug|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Release|Any CPU.Build.0 = Release|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Release|x64.ActiveCfg = Release|Any CPU
{AAFAB03A-6F5C-4D91-991F-867B7898F981}.Release|x64.Build.0 = Release|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Debug|Any CPU.Build.0 = Debug|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Debug|x64.ActiveCfg = Debug|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Debug|x64.Build.0 = Debug|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Release|Any CPU.ActiveCfg = Release|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Release|Any CPU.Build.0 = Release|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Release|x64.ActiveCfg = Release|Any CPU
{892E0932-5D11-4A37-979E-CEDB39C2E181}.Release|x64.Build.0 = Release|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Debug|x64.ActiveCfg = Debug|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Debug|x64.Build.0 = Debug|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Release|Any CPU.Build.0 = Release|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Release|x64.ActiveCfg = Release|Any CPU
{91A58C4A-4A80-4079-B43D-9B851206194F}.Release|x64.Build.0 = Release|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Debug|x64.ActiveCfg = Debug|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Debug|x64.Build.0 = Debug|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Release|Any CPU.Build.0 = Release|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Release|x64.ActiveCfg = Release|Any CPU
{32ED9500-AAAB-4030-9C7A-F611A85DF890}.Release|x64.Build.0 = Release|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Debug|x64.ActiveCfg = Debug|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Debug|x64.Build.0 = Debug|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Release|Any CPU.Build.0 = Release|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Release|x64.ActiveCfg = Release|Any CPU
{772DBDCD-9FAA-40A7-8551-2C1620C4AB67}.Release|x64.Build.0 = Release|Any CPU
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Debug|x64.ActiveCfg = Debug|x64
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Debug|x64.Build.0 = Debug|x64
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Release|Any CPU.Build.0 = Release|Any CPU
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Release|x64.ActiveCfg = Release|x64
{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}.Release|x64.Build.0 = Release|x64
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Debug|x64.ActiveCfg = Debug|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Debug|x64.Build.0 = Debug|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Release|Any CPU.Build.0 = Release|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Release|x64.ActiveCfg = Release|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.Release|x64.Build.0 = Release|Any CPU
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Debug|x64.ActiveCfg = Debug|x64
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Debug|x64.Build.0 = Debug|x64
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Release|Any CPU.Build.0 = Release|Any CPU
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Release|x64.ActiveCfg = Release|x64
{14575235-9867-4CE5-A22F-3F9FE002FF42}.Release|x64.Build.0 = Release|x64
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Debug|x64.ActiveCfg = Debug|x64
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Debug|x64.Build.0 = Debug|x64
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Release|Any CPU.Build.0 = Release|Any CPU
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Release|x64.ActiveCfg = Release|x64
{63E69101-D804-4DBC-B2E4-A33771CD5C5F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,6 +1,6 @@
[
{
"UseCreatedCropfield": true,
"UseCreatedCropfield": false,
"outputFileName": "TestData",
"fieldName": "TestField",
"DownloadFolder": "Downloads", //"C:\\workdir\\groenmonitor\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using FarmmapsApi;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using FarmmapsDownloadCL.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static FarmmapsApiSamples.Constants;
namespace FarmmapsDownloadCL
{
public class DownloadCLApplication : IApplication
{
private readonly ILogger<DownloadCLApplication> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
private readonly DownloadCLService _DownloadCLService;
private readonly GeneralService _generalService;
public readonly Dictionary<string, List<string>> _dictCl;
string _itemcode;
public DownloadCLApplication(ILogger<DownloadCLApplication> logger, FarmmapsApiService farmmapsApiService,
GeneralService generalService, DownloadCLService DownloadCLService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
_generalService = generalService;
_DownloadCLService = DownloadCLService;
CodelistsClasses clc = new CodelistsClasses();
_itemcode = clc.itemcode;
_dictCl = clc.dictCl;
}
public async Task RunAsync()
{
var fieldsInputJson = File.ReadAllText("DownloadCLInput.json");
DownloadCLInput clInput = JsonConvert.DeserializeObject<DownloadCLInput>(fieldsInputJson);
// !! this call is needed the first time an api is called with a fresh clientid and secret !!
await _farmmapsApiService.GetCurrentUserCodeAsync();
var roots = await _farmmapsApiService.GetCurrentUserRootsAsync();
//Where to write the output
string downloadFolder = clInput.DownloadFolder;
if (string.IsNullOrEmpty(downloadFolder))
{
downloadFolder = "Downloads";
}
if (!Directory.Exists(downloadFolder))
Directory.CreateDirectory(downloadFolder);
//Get the most recent codelists
foreach (string codelist in clInput.codelists)
{
try
{
await Process(roots, codelist, downloadFolder);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
}
}
private async Task Process(List<UserRoot> roots, string codelistname, string downloadFolder)
{
List<Item> codelist;
string itemtype;
string className;
string body;
string header;
string value;
string[] aboutArray = null;
string[] headerArray = null;
PropertyInfo[] props;
PropertyInfo prop;
List<string> dataList;
string clJson = Path.Combine(downloadFolder, $"{codelistname}.json");
string clCsv = Path.Combine(downloadFolder, $"{codelistname}.csv");
try
{
itemtype = _dictCl[codelistname][0];
className = _dictCl[codelistname][1];
codelist = await _farmmapsApiService.GetItemChildrenAsync(_itemcode, itemtype);
//Write full codelist in json format to clJson.
body = JsonConvert.SerializeObject(codelist);
File.WriteAllText(clJson, body);
_logger.LogInformation($"Downloaded file {clJson}");
//Write full list in csv format to clCsv.
StreamWriter sw = new StreamWriter(clCsv);
//Write metadata in top: when downloaded
sw.WriteLine($"Editeelt codelist {codelistname} downloaded on {DateTime.Now} with the FarmmapsDownloadCL application");
//Generic, for any Codelist as long as it is also defined in FarmmapsDownloadCL.Models in
string typerequest = "FarmmapsDownloadCL.Models." + className + ", FarmmapsDownloadCL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
Type objType = Type.GetType(typerequest);
object clitem = Activator.CreateInstance(objType);
props = clitem.GetType().GetProperties();
//Lookup about info and write to top
foreach (PropertyInfo pi in props)
{
if (pi.Name == "about")
{
aboutArray = (string[])pi.GetValue(clitem);
foreach (string about in aboutArray)
{
//Add quotes
value = "\"" + about + "\"";
//Write each about to a separate line in the csv file
sw.WriteLine(value);
}
}
}
//Write an empty line
sw.WriteLine();
//Look up the headerArray and write the header comma separated
int i = 0;
while (headerArray == null)
{
prop = props[i];
if (prop.Name == "headers")
{
headerArray = (string[])prop.GetValue(clitem);
}
i++;
}
header = string.Join(",", headerArray);
sw.WriteLine(header);
//Add quotes because a value for field 'description' could be '21% Cl, 16.5%CaO, 1330 g/l' and without the quotes that
//would show up as separate columns. So per definition we will add quotes, except if we have a fieldname in the listNotToQuote, then value for that fieldname will not be given quotes
//e.g. not code "3" but code 3.
List<string> listNotToQuote = new List<string>() { "code", "n", "p", "k", "quantity", "cropGroupCode", "cultivationGroupCode" };
//Loop through all items in the codelist
foreach (Item item in codelist)
{
dataList = new List<string> { };
clitem = JsonConvert.DeserializeObject(item.Data.ToString(), objType);
//Add values of the cLitem to the dataList in the same order as the header
foreach (string h in headerArray)
{
value = (string)clitem.GetType().GetProperty(h).GetValue(clitem, null);
if(listNotToQuote.Contains(h) == false)
value = "\"" +value + "\"";
dataList.Add(value);
}
string dataLine = string.Join(",", dataList);
sw.WriteLine(string.Join(",", dataList));
}
sw.Close();
_logger.LogInformation($"Downloaded file {clCsv}");
}
catch
{
_logger.LogWarning($"Missing lookup information on codelist {codelistname}");
}
}
}
}

View File

@ -0,0 +1,5 @@
{
"codeLists": [ "CL022", "CL127", "CL263", "CL265", "CL405", "CL251", "CL042" ]
//"codeLists": [ "CL263", "CL265"]
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
}

View File

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using FarmmapsDownloadCL.Models;
using Microsoft.Extensions.Logging;
using static FarmmapsApi.Extensions;
using static FarmmapsApiSamples.Constants;
namespace FarmmapsDownloadCL
{
public class DownloadCLService
{
private readonly ILogger<DownloadCLService> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
private readonly GeneralService _generalService;
public DownloadCLService(ILogger<DownloadCLService> logger, FarmmapsApiService farmmapsApiService,
GeneralService generalService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
_generalService = generalService;
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Collections" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FarmmapsApi\FarmmapsApi.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="DownloadCLInput.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace FarmmapsDownloadCL.Models
{
public class CodelistsClasses
{
public CodelistsClasses()
{
this.itemcode = "938c0aec4efc46e88783fd029f17309e%3ASYSTEM_DATA";
this.dictCl = new Dictionary<string, List<string>>();
//Add here once you have defined a new CLxxxitem below
//To add a new item, look it up in https://test.farmmaps.eu/swagger/index.html. Download the item you are interested in (like "vnd.farmmaps.itemtype.codelist.cl263")
//then below create a new CLxxxitem with fieldnames as you see them in the just downloaded new codelist
this.dictCl.Add("CL022", new List<string> { "vnd.farmmaps.itemtype.codelist.cl022", "CL022item" });
this.dictCl.Add("CL127", new List<string> { "vnd.farmmaps.itemtype.codelist.cl127", "CL127item" });
this.dictCl.Add("CL251", new List<string> { "vnd.farmmaps.itemtype.codelist.cl251", "CL251item" });
this.dictCl.Add("CL263", new List<string> { "vnd.farmmaps.itemtype.codelist.cl263", "CL263item" });
this.dictCl.Add("CL265", new List<string> { "vnd.farmmaps.itemtype.codelist.cl265", "CL265item" });
this.dictCl.Add("CL405", new List<string> { "vnd.farmmaps.itemtype.codelist.cl405", "CL405item" });
}
public string itemcode;
//Define here the codelist names, their item type and the class defining the contents of the data type (and defined in the Models.CodelistsClasses)
public Dictionary<string, List<string>> dictCl;
}
public class CL022item
{
public CL022item()
{
this.headers = new string[] { "codelist", "code", "description", "type", "n", "p", "k", "composition" };
this.codelist = "CL022";
this.about = new string[] { "EDI-Crop, coderingslijst meststoffen", "type MOR = organische meststof; MAN = anorganische meststof" };
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string k { get; set; }
public string n { get; set; }
public string p { get; set; }
public string code { get; set; }
public string type { get; set; }
public string composition { get; set; }
public string description { get; set; }
}
public class CL127item
{
public CL127item()
{
this.headers = new string[] { "codelist", "code", "description", "description_nl", "culturalPracticeLevel1", "culturalPracticeLevel2" };
this.codelist = "CL127";
}
public string[] headers { get; }
public string codelist { get; }
public string code { get; set; }
public string description { get; set; }
public string description_nl { get; set; }
public string culturalPracticeLevel1 { get; set; }
public string culturalPracticeLevel2 { get; set; }
}
public class CL251item
{
public CL251item()
{
this.headers = new string[] { "codelist", "code", "rvo", "active", "primary", "description", "description_fr", "description_nl" };
this.codelist = "CL251";
this.about = new string[] { "Codering teeltdoelen (CropProductionPurposeCode)", "primary: 1 = primary; 2 = secondary", "rvo: 1 = yes; 0 = no" };
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string code { get; set; }
public string rvo { get; set; }
public string active { get; set; }
public string primary { get; set; }
public string description { get; set; }
public string description_nl { get; set; }
public string description_fr { get; set; }
}
public class CL263item
{
public CL263item()
{
this.headers = new string[] { "codelist", "code", "eppoCode", "botanicName", "description", "description_fr", "description_nl", "cropGroupCode", "cropGroupName", "subCropGroupCode", "subCropGroupName", "cultivationGroupCode", "cultivationGroupName" };
this.codelist = "CL263";
this.about = new string[] { "EDI-Crop, coderingslijst gewassoorten" };
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string code { get; set; }
public string eppoCode { get; set; }
public string botanicName { get; set; }
public string description { get; set; }
public string description_nl { get; set; }
public string description_fr { get; set; }
public string cropGroupCode { get; set; }
public string cropGroupName { get; set; }
public string subCropGroupCode { get; set; }
public string subCropGroupName { get; set; }
public string cultivationGroupCode { get; set; }
public string cultivationGroupName { get; set; }
public string composition { get; set; } //?
}
public class CL265item
{
public CL265item()
{
this.headers = new string[] { "Code", "Formaat", "Eenheid", "Waarde" };
this.codelist = "CL265";
this.about = new string[] { "EDI-Crop CL265: FarmCharacterictics, FieldCharacteristics, CropFieldCharacteristics, TreatmentCharacteristics",
"Betreft codelijst eigenschappen teelt en teelthandelingen CL265.Daar waar in kolom 'Waarde' wordt verwezen naar CL.., wordt de codelijst bedoeld die onder het betreffende nummer bekend is.",
"In het EDI-Crop bericht wordt de code gebruikt onder resp. CropFieldCharacteristic c.q.TreatmentCharacteristic middels de dataelementen PropertyVariableCode (invullen vanuit CL265) en PropertyVariableValue(voor de waarde)"};
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string Code { get; set; }
public string Formaat { get; set; }
public string Eenheid { get; set; }
public string Waarde { get; set; }
}
public class CL405item
{
public CL405item()
{
this.headers = new string[] { "codelist", "code", "description", "description_fr", "description_nl", "hoofdgroep" };
this.codelist = "CL405";
this.about = new string[] { };
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string code { get; set; }
public string hoofdgroep { get; set; }
public string description { get; set; }
public string description_fr { get; set; }
public string description_nl { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using Newtonsoft.Json.Linq;
namespace FarmmapsDownloadCL.Models
{
public class DownloadCLInput
{
public string[] codelists { get; set; }
public string DownloadFolder { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System.Threading.Tasks;
using FarmmapsApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FarmmapsDownloadCL
{
class Program : FarmmapsProgram<DownloadCLApplication>
{
private static async Task Main(string[] args)
{
await new Program().Start(args);
}
protected override void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddLogging()
.AddTransient<DownloadCLService>();
}
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PlatformTarget>x64</PlatformTarget>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FarmmapsApi\FarmmapsApi.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="KPIInput.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,759 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using FarmmapsApi;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using FarmmapsKPI.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static FarmmapsApiSamples.Constants;
namespace FarmmapsKPI
{
public class KPIApplication : IApplication
{
//private const string DownloadFolder = "Downloads";
private const string SettingsFile = "settings.json";
private readonly ILogger<KPIApplication> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
private readonly KPIService _kpiService;
private readonly GeneralService _generalService;
private Settings _settings;
public KPIApplication(ILogger<KPIApplication> logger, FarmmapsApiService farmmapsApiService,
GeneralService generalService, KPIService kpiService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
_generalService = generalService;
_kpiService = kpiService;
}
public async Task RunAsync()
{
KPIInput input;
string fnKPIinput;
//Console.WriteLine("Type name of input json file. Example: KPIinput.json (in same directory as FarmmapsKPI.exe) or for example like this: C:/temp/KPIinputChemieTmp.json");
//fnKPIinput = Console.ReadLine();
//if (string.IsNullOrEmpty(fnKPIinput))
//{
fnKPIinput = "KPIinput.json";
//}
var fieldsInputJson = File.ReadAllText(fnKPIinput);
List<KPIInput> fieldsInputs = JsonConvert.DeserializeObject<List<KPIInput>>(fieldsInputJson);
// !! this call is needed the first time an api is called with a fresh clientid and secret !!
await _farmmapsApiService.GetCurrentUserCodeAsync();
var roots = await _farmmapsApiService.GetCurrentUserRootsAsync();
//Where to write the output
string downloadFolder = fieldsInputs[0].DownloadFolder;
if (string.IsNullOrEmpty(downloadFolder))
{
downloadFolder = "Downloads";
}
if (!Directory.Exists(downloadFolder))
Directory.CreateDirectory(downloadFolder);
//Write the same info to a single csv file. Note this means existing file will be overwritten!
StreamWriter sw;
string KPIItemCsv = Path.GetFileNameWithoutExtension(fnKPIinput) + "_Items.csv";
string KPIItemPathCsv = Path.Combine(downloadFolder, KPIItemCsv);
List<string> headerList = new List<string> { "parentName", "cropfieldcode", "area_ha", "cropTypeCode", "cropTypeName", "KPIid", "KPIvariable", "KPIvalue", "KPIunit", "KPItargetvalue", "KPIthresholdValue",
"mbp_productCode","mbp_productName","mbp_quantity","mbp_unitCode","mbp_date","mbp_KPIvariable","mbp_KPIvalue"};
//Create a new csv file. Means if existing then overwritten !!!
sw = new StreamWriter(KPIItemPathCsv);
Configuration cf = await _farmmapsApiService.GetConfiguration();
string endPoint = cf.Endpoint;
sw.WriteLine($"Using FarmmapsKPI application in FarmmapsApSamples.sln. Input file: '{fnKPIinput}'. Download DateTime: '{DateTime.Now}'. Calculations on: '{endPoint}'");
sw.WriteLine();
sw.WriteLine(string.Join(",", headerList));
// For each input download all KPI's. Keep track to time, important when doing bulk calculations
var watch = System.Diagnostics.Stopwatch.StartNew();
TimeSpan tsSofar = new TimeSpan();
TimeSpan tsRemaining;
TimeSpan tsTotalEstimated;
for (int i = 0; i < fieldsInputs.Count; i++)
//for (int i = 10; i < 12; i++)
{
watch.Restart();
input = fieldsInputs[i];
_logger.LogInformation(string.Format($"// FarmmapsKPI: Downloading KPI's for field {i + 1} out of {fieldsInputs.Count} to single csv file {KPIItemPathCsv}"));
try
{
await Process(roots, input, sw);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
watch.Stop();
tsSofar = tsSofar + watch.Elapsed;
tsTotalEstimated = tsSofar / (i + 1) * fieldsInputs.Count;
tsRemaining = tsTotalEstimated - tsSofar;
_logger.LogInformation(string.Format($"// Time (hh:mm:ss): this field: {strTime(watch.Elapsed)}. Sofar: {strTime(tsSofar)}. Estimated total: {strTime(tsTotalEstimated)}. Remaining: {strTime(tsRemaining)}"));
}
//Close the csv file, write message to screen
sw.Close();
_logger.LogInformation(string.Format($"// FarmmapsKPI:"));
_logger.LogInformation($"Done! Written all KPI for all fields in '{fnKPIinput}' to output file '{KPIItemPathCsv}'");
}
private async Task Process(List<UserRoot> roots, KPIInput input, StreamWriter sw)
{
List<Item> cropfieldChildren;
List<Item> crprecChildren;
KPIOutput kpio;
KPIOutput kpioPrevious = new KPIOutput(); //creates a new empty
//If KPI E1 is calculated, write these sub kpi's to output
string[] mbp_KPIvariables = new string[] { "aquaticLife", "groundWater", "soilLife" };
string mbp_KPIvalue;
int targetKPIitemsCount; //if we know we should be getting 8 KPI items (A1,B1,B2,C1,D1,E1,F1,F2)
int maxtries = 5; // but don't keep on trying forever; there is a maximum number of tries
int trycnt;
string downloadFolder = input.DownloadFolder;
if (string.IsNullOrEmpty(downloadFolder)) {
downloadFolder = "Downloads";
}
if (!Directory.Exists(downloadFolder))
Directory.CreateDirectory(downloadFolder);
// !!specify if you are using an already created cropfield:
bool useExistingCropfieldWithChildren = input.UseExistingCropfieldWithChildren;
int cropYear = input.CropYear;
string fieldName = input.fieldName;
string fieldGeom = input.GeometryJson.ToString(Formatting.None);
//Settings
string settingsfile = $"Settings_{fieldName}.json";
LoadSettings(settingsfile);
var uploadedRoot = roots.SingleOrDefault(r => r.Name == "USER_IN");
if (uploadedRoot == null)
{
_logger.LogError("Could not find a needed root item");
return;
}
var myDriveRoot = roots.SingleOrDefault(r => r.Name == "USER_FILES");
if (myDriveRoot == null)
{
_logger.LogError("Could not find a needed root item");
return;
}
// Use already created cropfield or create new one, added a Data input, with field specific data for the KPI calculation
Item cropfieldItem;
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && input.CropfieldItemCode = "" or absent -> read from settings json
//3 useExistingCropfieldWithChildren = true && input.CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> use this one
if (useExistingCropfieldWithChildren == false)
{
_logger.LogInformation($"Creating cropfield with name '{fieldName}'");
cropfieldItem = await _generalService.CreateCropfieldItemAsync(myDriveRoot.Code,
$"{fieldName}", cropYear, input.GeometryJson.ToString(Formatting.None), input.DataCropfield.ToString(Formatting.None), input.StartDate, input.EndDate);
_settings.CropfieldItemCode = cropfieldItem.Code;
SaveSettings(settingsfile);
}
else if (string.IsNullOrEmpty(input.CropfieldItemCode))
{
_logger.LogInformation("reading CropfieldItemCode from settings file");
cropfieldItem = await _farmmapsApiService.GetItemAsync(_settings.CropfieldItemCode);
}
else
{
_logger.LogInformation("reading CropfieldItemCode from KPIinput json");
cropfieldItem = await _farmmapsApiService.GetItemAsync(input.CropfieldItemCode);
SaveSettings(settingsfile);
}
// The cropfieldCharacteristicItem is used to enter crop yields and/or straw yields
Item cropfieldCharacteristicItem;
string dataCropfieldCharacteristic;
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && input.CropfieldCharacteristicItemCode = "" or absent -> read from settings json
//3 useExistingCropfieldWithChildren = true && input.CropfieldCharacteristicItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> use this one
if (useExistingCropfieldWithChildren == false)
{
for (int i = 0; i < input.DataCropfieldCharacteristics.Length; i++)
{
dataCropfieldCharacteristic = input.DataCropfieldCharacteristics[i].ToString(Formatting.None);
dynamic data = JObject.Parse(dataCropfieldCharacteristic);
_logger.LogInformation($"CreateCropfieldCharacteristicItemAsync ... for cropfieldCharacteristic {i}: '{data.label}', value '{data.value}'");
cropfieldCharacteristicItem = await _generalService.CreateCropfieldCharacteristicItemAsync(cropfieldItem.Code, cropYear, fieldGeom, dataCropfieldCharacteristic);
_settings.CropfieldCharacteristicItemCode = cropfieldCharacteristicItem.Code;
SaveSettings(settingsfile);
}
}
else if (string.IsNullOrEmpty(input.CropfieldItemCode))
{
_logger.LogInformation("reading CropfieldCharacteristicItemCode from settings file");
cropfieldCharacteristicItem = await _farmmapsApiService.GetItemAsync(_settings.CropfieldCharacteristicItemCode);
}
else
{
_logger.LogInformation("reading CropfieldCharacteristicItemCode from KPIinput json");
cropfieldCharacteristicItem = await _farmmapsApiService.GetItemAsync(input.CropfieldCharacteristicItemCode);
SaveSettings(settingsfile);
}
// Now we can do a first KPI calculation and get the polygon area from the KPI output (where it is based on geometry)
// We need that because for operations, you need to provide the area on which the operation was applied
// And if we put that to the crop area, then we neatly get everything on a per ha basis
_logger.LogInformation($"Getting polygon area (ha))");
List<Item> KPIItems = await _generalService.GetKpiItemsForCropField(cropfieldItem, 3);
trycnt = 1;
targetKPIitemsCount = 3; // here for the area we need at least 3, but not more than that
while (KPIItems.Count < targetKPIitemsCount & trycnt < maxtries)
{
KPIItems = await _generalService.GetKpiItemsForCropField(cropfieldItem,3);
_logger.LogInformation($"Found {KPIItems.Count} KPI items");
trycnt++;
}
kpio = JsonConvert.DeserializeObject<KPIOutput>(KPIItems[0].Data.ToString());
string area_ha = kpio.data.area;
// turn the area into a JObject for later merging with operation data;
string strJarea = JsonConvert.SerializeObject(new { area = area_ha });
JObject Jarea = JObject.Parse(strJarea);
if (useExistingCropfieldWithChildren == false)
{
//Retreiving BOFEK. A cropfield has 1 soil
//Have a look at the cropfieldChildren before and after running this task, see one child (i.e. soil) has been added)
//cropfieldChildren = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.Code);
_logger.LogInformation("Get BOFEK for field");
Item bofekItem = await _generalService.RunBofekTask(cropfieldItem);
if (bofekItem == null)
{
_logger.LogError("Something went wrong while obtaining the BOFEK data");
return;
}
//cropfieldChildren = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.Code);
}
else
{
_logger.LogInformation("For existing cropfield we assume it already has BOFEK soil data");
}
// A cropfield has 1 crop recording and the crop recording has 0:many operations
//So first at the crop recording
Item crprecItem;
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && input.CropRecordingItemCode = "" or absent -> read from settings json
//3 useExistingCropfieldWithChildren = true && input.CropRecordingItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> use this one
if (useExistingCropfieldWithChildren == false)
{
_logger.LogInformation("RunCropRecordingTask ...");
crprecItem = await _generalService.RunCropRecordingTask(cropfieldItem);
_settings.CropRecordingItemCode = crprecItem.Code;
SaveSettings(settingsfile);
}
else if (string.IsNullOrEmpty(input.CropfieldItemCode))
{
_logger.LogInformation("reading CropRecordingItemCode from settings file");
crprecItem = await _farmmapsApiService.GetItemAsync(_settings.CropRecordingItemCode);
}
else
{
_logger.LogInformation("reading CropRecordingItemCode from KPIinput json");
crprecItem = await _farmmapsApiService.GetItemAsync(input.CropRecordingItemCode);
SaveSettings(settingsfile);
}
// Now add the operations
List<Item> crpOperationItems = new List<Item> { };
List<string> crpOperationItemCodes = new List<string> { };
Item crpOperationItem;
string dataOperation;
string codeOperation;
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && input.OperationItemCode = "" or absent -> read from settings json
//3 useExistingCropfieldWithChildren = true && input.OperationItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> use this one
if (useExistingCropfieldWithChildren == false)
{
for (int i = 0; i < input.DataOperations.Length; i++)
{
dataOperation = input.DataOperations[i].ToString(Formatting.None);
dynamic data = JObject.Parse(dataOperation);
_logger.LogInformation($"CreateOperationItemAsync ... for operation {i}: '{data.name}', on date '{data.from}'");
// Now check if the operation has a field called area
string? opArea = data["area"];
if (string.IsNullOrEmpty(opArea))
{
// if not having field area: add it to dataOperation ...
input.DataOperations[i].Merge(Jarea);
// ... and update the string
dataOperation = input.DataOperations[i].ToString(Formatting.None);
}
else
{
// if yes having field area: compare with polygon area. If not the same, throw warning
if (data.area != area_ha)
{
double differencePercent = 100.0*(Convert.ToDouble(area_ha) / Convert.ToDouble(data.area) - 1.0);
_logger.LogWarning($"cropfield has area {area_ha}, but in your KPIinput.json or file like that, you have an operation with area {data.area}" +
$" Difference is {area_ha} / {data.area} - 100% = {differencePercent}%." +
$" Is that correct? Example if operation was applied in part of field, e.g. in case of variable rate (VRA) application." +
$" Or did you accidentally fill in area in the KPIinput json? To use cropfield area, omit field 'area' from json" +
$" then the KPI applicataion will fill in area calculated from geometry");
}
}
//Now after optionally adding the area, add the Operation to the crop recording
crpOperationItem = await _generalService.CreateOperationItemAsync(crprecItem.Code, cropYear, fieldGeom, dataOperation);
crpOperationItems.Add(crpOperationItem);
crpOperationItemCodes.Add(crpOperationItem.Code);
}
_settings.OperationItemCodes = crpOperationItemCodes.ToArray();
SaveSettings(settingsfile);
}
else if (string.IsNullOrEmpty(input.CropfieldItemCode))
{
_logger.LogInformation("reading OperationItemCode from settings file");
for (int i = 0; i < _settings.OperationItemCodes.Length; i++)
{
codeOperation = _settings.OperationItemCodes[i];
crpOperationItem = await _farmmapsApiService.GetItemAsync(codeOperation);
crpOperationItems.Add(crpOperationItem);
crpOperationItemCodes.Add(crpOperationItem.Code);
}
}
else
{
_logger.LogInformation("reading OperationItemCodes from KPIinput json");
for (int i = 0; i < input.OperationItemCodes.Length; i++)
{
codeOperation = input.OperationItemCodes[i];
crpOperationItem = await _farmmapsApiService.GetItemAsync(codeOperation);
crpOperationItems.Add(crpOperationItem);
crpOperationItemCodes.Add(crpOperationItem.Code);
}
_settings.OperationItemCodes = crpOperationItemCodes.ToArray();
SaveSettings(settingsfile);
}
// Inspect the children and grandchildren. If all is well, cropfield will have:
// one crprec, with 0-many operations as children. And the Data of an operation will have specification of how much fertilizer / cropprotection agent was applied
// one edicrop.characteristic (with yield in the data)
cropfieldChildren = await _farmmapsApiService.GetItemChildrenAsync(cropfieldItem.Code);
crprecChildren = await _farmmapsApiService.GetItemChildrenAsync(crprecItem.Code);
//Now get the KPIs for this cropfield, mounted with operations & cropyield
//Note sometimes the KPIItems.Count is already for some crazy reason greater than or equal to targetKPIitemsCount
//But that would have strange results, since that was from above before adding the crop recordings. We want to do at least one new call -> 'while (trycnt == 0 || '
_logger.LogInformation($"GetKpiItemsForCropField('{cropfieldItem.Code}')");
//Pesticide KPI's D1 and E1 are retreived from API's of CTBG and CLM and may take a bit longer to retrieve
targetKPIitemsCount = 8; //if we know we should be getting 8 KPI items (A1,B1,B2,C1,D1,E1,F1,F2)
trycnt = 0;
bool boolAquaticLife = false;
_logger.LogInformation($"Firing calls GetKpiItemsForCropField() until we have {targetKPIitemsCount} KPIitems, but don't keep firing forever, stop after {maxtries} calls");
_logger.LogInformation($"Before we start:");
_logger.LogInformation($"* KPIItems.Count = {KPIItems.Count}");
_logger.LogInformation($"* trycnt = {trycnt}");
_logger.LogInformation($"* boolAquaticLife = {boolAquaticLife}");
//additional criterion for while loop: check if it really contains the E1 mbp elements.
//while (trycnt == 0 || ((KPIItems.Count < targetKPIitemsCount || boolAquaticLife == false) & trycnt < maxtries))
//normal while loop
targetKPIitemsCount = 8; //if we know we should be getting 8 KPI items (A1,B1,B2,C1,D1,E1,F1,F2)
while (trycnt == 0 || (KPIItems.Count < targetKPIitemsCount & trycnt < maxtries))
{
_logger.LogInformation($"Call nr {trycnt + 1}");
KPIItems = await _generalService.GetKpiItemsForCropField(cropfieldItem, 3); //number after comma is how many seconds per try
_logger.LogInformation($"Found {KPIItems.Count} KPI items");
//boolAquaticLife = GetBoolAquaticLife(KPIItems);
trycnt ++;
}
if (KPIItems.Count < targetKPIitemsCount) {
_logger.LogWarning($"Found {KPIItems.Count} KPIItems while you were aiming for {targetKPIitemsCount} KPIItems");
}
//Download KPI's into a json output file for this specific cropfield (with unique cropfieldItem.Code)
string KPIItemPathJson = Path.Combine(downloadFolder, $"KPIItems_{cropfieldItem.Code}.json");
var count = 0;
foreach (Item item in KPIItems)
{
//Console.WriteLine($"KPI item #{count}: {item.Name}");
File.AppendAllText(KPIItemPathJson, item.Data + Environment.NewLine);
count++;
}
_logger.LogInformation($"Downloaded file {KPIItemPathJson}");
_logger.LogInformation($"");
//Write to the csv file that collects all KPI's for all the crop fields
List<string> dataList;
foreach (Item item in KPIItems)
{
kpio = JsonConvert.DeserializeObject<KPIOutput>(item.Data.ToString());
if (kpio.id != null)
{
if (kpio.id != kpioPrevious.id)
{
KPIelementsOfBalance kPIelementsOfBalance = kpio.data.values;
if (kpio.id == "A1") //TtD
{
//Make a new dataList = new line to be written
//Fill the datalist with this kpi
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
kpio.quantity, // in KPI output quantity is what we call KPIvariable in headerlist of csv file
kpio.value,
kpio.unit,
kpio.targetValue,
kpio.thresholdValue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
foreach (string elementName in kpio.A1elements)
{
// get A1element from the element called values
// TtD 20240311: note elements of A1 are in the data structure elements of kpio.data and not as below for B1, B2, etc as kpio.data.values
string elementValue = (string)kpio.data.GetType().GetProperty(elementName).GetValue(kpio.data, null);
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
elementName, // specific output variable name for A1 element
elementValue, // specific output value name for A1 element
kpio.unit,
"",
""
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
else if (kpio.id == "B1")
{
//Make a new dataList = new line to be written
//Fill the datalist with this kpi
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
kpio.quantity, // in KPI output quantity is what we call KPIvariable in headerlist of csv file
kpio.value,
kpio.unit,
kpio.targetValue,
kpio.thresholdValue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
foreach (string elementName in kpio.B1elements)
{
// get B1element from the element called values
string elementValue = (string)kPIelementsOfBalance.GetType().GetProperty(elementName).GetValue(kPIelementsOfBalance, null);
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
elementName, // specific output variable name for B1 element
elementValue, // specific output value name for B1 element
kpio.unit,
"",
""
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
else if (kpio.id == "B2")
{
//Make a new dataList = new line to be written
dataList = new List<string>
{
//Fill the datalist with this kpi
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
kpio.quantity, // in KPI output quantity is what we call KPIvariable in headerlist of csv file
kpio.value,
kpio.unit,
kpio.targetValue,
kpio.thresholdValue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
foreach (string elementName in kpio.B2elements)
{
// get B1element from the element called values
string elementValue = (string)kPIelementsOfBalance.GetType().GetProperty(elementName).GetValue(kPIelementsOfBalance, null);
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
elementName, // specific output variable name for B2 element
elementValue, // specific output value name for B2 element
kpio.unit,
"",
""
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
else if (kpio.id == "C1") //TtD
{
//Make a new dataList = new line to be written
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
kpio.quantity, // in KPI output quantity is what we call KPIvariable in headerlist of csv file
kpio.value,
kpio.unit,
kpio.targetValue,
kpio.thresholdValue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
foreach (string elementName in kpio.C1elements)
{
// get C1element from the element called values
string elementValue = (string)kPIelementsOfBalance.GetType().GetProperty(elementName).GetValue(kPIelementsOfBalance, null);
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
elementName, // specific output variable name for C1 element
elementValue, // specific output value name for C1 element
kpio.unit,
"",
""
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
else if (kpio.id == "E1")
{
//for E1, environmentMeasureData is an array of elements with 1 per pesticide. Inside each element there are multiple mbp_KPIvariables to be written
foreach (KPIenvironmentMeasureData e in kpio.data.environmentMeasureData)
{
foreach (string mbp_KPIvariable in mbp_KPIvariables)
{
if (mbp_KPIvariable == "aquaticLife") { mbp_KPIvalue = e.aquaticLife; } else if (mbp_KPIvariable == "groundWater") { mbp_KPIvalue = e.groundWater; } else if (mbp_KPIvariable == "soilLife") { mbp_KPIvalue = e.soilLife; } else { mbp_KPIvalue = ""; }
//Make a new dataList = new line to be written
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id, //"E1"
kpio.quantity, // "mbp"
"", // not here the value
"", // not here KPIunit for this indicator
"", // not here KPItargetvalue for this indicator
"", // not here KPIthresholdValue for this indicator
e.productCode,
e.productName,
e.quantity,
e.unitCode,
e.date,
mbp_KPIvariable,
mbp_KPIvalue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
}
else
{
//Any other KPI, example A1 or D1, with just 1 record to write
dataList = new List<string>
{
kpio.parentName,
cropfieldItem.Code,
kpio.data.area,
kpio.data.cropTypeCode,
kpio.data.cropTypeName,
kpio.id,
kpio.quantity,
kpio.value,
kpio.unit,
kpio.targetValue,
kpio.thresholdValue
};
//Write the datalist to a line to the streamwrieter sw for the output csv file
sw.WriteLine(string.Join(",", dataList));
}
}
}
kpioPrevious = kpio;
}
////Total N applied from input
//double totalNapplied = 0.0;
//double operationNapplied;
//for (int i = 0; i < crpOperationItemCodes.Count; i++)
//{
// codeOperation = crpOperationItemCodes[i];
// crpOperationItem = await _farmmapsApiService.GetItemAsync(codeOperation);
// dynamic data = JObject.Parse(crpOperationItem.Data.ToString(Formatting.None));
// if (data.n != null)
// {
// operationNapplied = crpOperationItem.Data.GetValue("n").ToObject<double>();
// totalNapplied = totalNapplied + operationNapplied;
// }
//}
////Also add totalNapplied to the csv
//dataList = new List<string> { };
//dataList.Add(kpioPrevious.parentName);
//dataList.Add(cropfieldItem.Code);
//try { dataList.Add(kpioPrevious.data.area); } catch { dataList.Add(""); };
//try { dataList.Add(kpioPrevious.data.cropTypeCode); } catch { dataList.Add(""); };
//try { dataList.Add(kpioPrevious.data.cropTypeName); } catch { dataList.Add(""); };
//dataList.Add("");
//dataList.Add("totalNapplied");
//dataList.Add(totalNapplied.ToString());
//dataList.Add("kg/ha");
//dataList.Add("");
//dataList.Add("");
//sw.WriteLine(string.Join(",", dataList));
//Clean up. Only newly created fields
//Look up instruction in Swagger / api / v1 / items /{ code}
if (useExistingCropfieldWithChildren == false && input.DeleteNewlyCreatedAfterCalc == true)
{
// Tested and found that if I delete the parent (cropfieldItem) then
// automatically deletes children (crprecItem, cropfieldCharacteristicItem, KPIcontainer)
// and automatically deletes grand children (crprecItem: operations, KPIcontainer: KPI items)
await _farmmapsApiService.DeleteItemAsync(cropfieldItem.Code);
//Do we need to wait here for the task to be completed? Or does it continue in the background?
//await Task.Delay(60000); // wait 60 secs for task to be completed
//Before the GetItem you will see cropfieldItem, crprecItem etc exist (with data).
//After the GetItem these cropfieldItem, crprecItem etc are null which means they have been succesfully deleted.
//// Check if below works, i.e. is item really deleted?
//cropfieldItem = await _farmmapsApiService.GetItemAsync(cropfieldItem.Code);
//// Check if croprecording has also been deleted. If not also delete it
//crprecItem = await _farmmapsApiService.GetItemAsync(crprecItem.Code);
//// Check if 1 operation has also been deleted. If not also delete it
//crpOperationItem = await _farmmapsApiService.GetItemAsync(crpOperationItemCodes[0]);
//// Check if 1 cropfieldCharacteristicItem has also been deleted. If not also delete it
//cropfieldCharacteristicItem = await _farmmapsApiService.GetItemAsync(cropfieldCharacteristicItem.Code);
}
}
// Functions to save previously created cropfields
private void LoadSettings(string file)
{
if (File.Exists(file))
{
var jsonText = File.ReadAllText(file);
_settings = JsonConvert.DeserializeObject<Settings>(jsonText);
}
else
{
_settings = new Settings();
}
}
private void SaveSettings(string file)
{
if (_settings == null)
return;
var json = JsonConvert.SerializeObject(_settings);
File.WriteAllText(file, json);
}
private void SaveInfo(string file)
{
if (_settings == null)
return;
var json = JsonConvert.SerializeObject(_settings);
File.WriteAllText(file, json);
}
private string strTime(TimeSpan ts)
{
return String.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds);
}
private bool GetBoolAquaticLife(List<Item> KPIItems)
{
bool found = false;
foreach (Item item in KPIItems)
{
KPIOutput kpio = JsonConvert.DeserializeObject<KPIOutput>(item.Data.ToString());
if (kpio.id == "E1")
{
try
{
foreach (KPIenvironmentMeasureData e in kpio.data.environmentMeasureData)
{
if (e.aquaticLife != null)
{
found = true;
_logger.LogInformation($"Found e.aquaticLife = {e.aquaticLife}");
_logger.LogInformation("");
}
}
}
catch (Exception ex)
{
_logger.LogWarning($"NOT Found e.aquaticLife");
_logger.LogError(ex.Message);
}
}
}
return found;
}
}
}

View File

@ -0,0 +1,817 @@
[
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "test_Potato_ZeroFertilizer_Consumption_Sand",
//Note when you run the FarmmapsKPI project, somewhere to your C:\git\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\ directory, files named like this are written:
//'Settings_aardappelveld_test_Potato_ZeroFertilizer.json', where 'aardappelveld_test_Potato_ZeroFertilizer.json' is your fieldName (see below)
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && CropfieldItemCode = "" or absent -> read fieldName (see below) -> read all codes from settings json file
//3 useExistingCropfieldWithChildren = true && CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> if you have in your account already an existing cropfield then use that
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
//And then if useExistingCropfieldWithChildren = true, this would be used.
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
"soilCode": "1", //From codelist CL405. Can be omitted if unknown
"soilName": "Sand", //From codelist CL405
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
"productionPurposeCode": "3", //From codelist CL251. For testing, see case with consumption & starch potato
"productionPurposeName": "consumption" //From codelist CL251
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [], //leaving the operations empty means no fertilizer is applied
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
//PO20231029: not different yields from different parts of fields?
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "test_Potato_ZeroFertilizer_Consumption_Clay",
//Note when you run the FarmmapsKPI project, somewhere to your C:\git\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\ directory, files named like this are written:
//'Settings_aardappelveld_test_Potato_ZeroFertilizer.json', where 'aardappelveld_test_Potato_ZeroFertilizer.json' is your fieldName (see below)
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && CropfieldItemCode = "" or absent -> read fieldName (see below) -> read all codes from settings json file
//3 useExistingCropfieldWithChildren = true && CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> if you have in your account already an existing cropfield then use that
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
//And then if useExistingCropfieldWithChildren = true, this would be used.
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
"soilCode": "7", //From codelist CL405. Can be omitted if unknown
"soilName": "Clay", //From codelist CL405
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
"productionPurposeCode": "3", //From codelist CL251. For testing, see case with consumption & starch potato
"productionPurposeName": "consumption" //From codelist CL251
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [], //leaving the operations empty means no fertilizer is applied
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
//PO20231029: not different yields from different parts of fields?
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "test_Potato_ZeroFertilizer_Starch_Sand",
//Note when you run the FarmmapsKPI project, somewhere to your C:\git\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\ directory, files named like this are written:
//'Settings_aardappelveld_test_Potato_ZeroFertilizer.json', where 'aardappelveld_test_Potato_ZeroFertilizer.json' is your fieldName (see below)
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && CropfieldItemCode = "" or absent -> read fieldName (see below) -> read all codes from settings json file
//3 useExistingCropfieldWithChildren = true && CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> if you have in your account already an existing cropfield then use that
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
//And then if useExistingCropfieldWithChildren = true, this would be used.
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
"soilCode": "1", //From codelist CL405. Can be omitted if unknown
"soilName": "Sand",
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
"productionPurposeCode": "2", //From codelist CL251. For testing, see case with consumption & starch potato
"productionPurposeName": "starch" //From codelist CL251
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [], //leaving the operations empty means no fertilizer is applied
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
//PO20231029: not different yields from different parts of fields?
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "aardappelveld_test_Potato_cattlemanure144kgnha_plusUrea92kgNha",
"CropfieldItemCode": "",
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
//"soilCode": "5", //From codelist CL405. Can be omitted if unknown
//"soilName": "Loam", //From codelist CL405.
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
//"rootDepthMax": 45, //not needed for KPI calculation, but shown here to know this is a possible property
//"emergenceDate": "2022-05-16T00:00:00", //not needed for KPI calculation, but shown here to know this is a possible property
"productionPurposeCode": "3", //From codelist CL251. Can be omitted if unknown
"productionPurposeName": "consumption" //From codelist CL251. Can be omitted if unknown
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [
{
//"area": "0.08", //if leave empty then write code to fill in value from geometry (operation applied to whole field). Fill in different value if operation applied to part of the field (e.g. in case of variable rate application, VRA)
"contractor": false,
"designator": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"from": "2022-05-23T11:34:00",
"method": "70400", //refers to codelist CL127 with operation methods
"n": "92", //refers to codelist CL022 with fertilizer types & npk contents
"name": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"product": "7360", //refers to codelist CL022 with fertilizer types
"quantity": "200",
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "7", // refers to codelist CL018 consisting of main catergorie of operations. Code 7 stands for fertilization
"to": "2022-05-23T12:34:00",
"unit": "kg/ha",
"unitCode": "KGMHAR"
},
{
"k": "201.6",
"n": "144",
"p": "60.8",
"to": "2022-04-19T09:19:00",
//"area": "0.08", //if leave empty then write code to fill in value from geometry (operation applied to whole field). Fill in different value if operation applied to part of the field (e.g. in case of variable rate application, VRA)
"from": "2022-04-19T08:19:00",
"name": "Injecteren", //refers to codelist CL127 with operation methods
"unit": "kg/ha",
"method": "70700", //refers to codelist CL127 with operation methods
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "7", // refers to codelist CL018 consisting of main catergorie of operations. Code 7 stands for fertilization
"product": "2329", //refers to codelist CL022 with fertilizer types
"quantity": "32000",
"unitCode": "KGMHAR",
"contractor": false,
"designator": "Injecteren"
}
],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "aardappelveld_test_Potato_Urea92kgNha",
"CropfieldItemCode": "",
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
//"soilCode": "5", //From codelist CL405. Can be omitted if unknown
//"soilName": "Loam", //From codelist CL405.
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
//"rootDepthMax": 45, //not needed for KPI calculation, but shown here to know this is a possible property
//"emergenceDate": "2022-05-16T00:00:00", //not needed for KPI calculation, but shown here to know this is a possible property
"productionPurposeCode": "3", //From codelist CL251. Can be omitted if unknown
"productionPurposeName": "consumption" //From codelist CL251. Can be omitted if unknown
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [
{
//"area": "0.08", //if leave empty then write code to fill in value from geometry (operation applied to whole field). Fill in different value if operation applied to part of the field (e.g. in case of variable rate application, VRA)
"contractor": false,
"designator": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"from": "2022-05-23T11:34:00",
"method": "70400", //refers to codelist CL127 with operation methods
"n": "92",
"name": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"product": "7360", //refers to codelist CL022 with fertilizer types
"quantity": "200",
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "7", // refers to codelist CL018 consisting of main catergorie of operations. Code 7 stands for fertilization
"to": "2022-05-23T12:34:00",
"unit": "kg/ha",
"unitCode": "KGMHAR"
}
],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "aardappelveld_test_Potato_Urea92kgNha_ZuidLimburg",
"CropfieldItemCode": "",
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
//"soilCode": "5", //From codelist CL405. Can be omitted if unknown
//"soilName": "Loam", //From codelist CL405.
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
//"rootDepthMax": 45, //not needed for KPI calculation, but shown here to know this is a possible property
//"emergenceDate": "2022-05-16T00:00:00", //not needed for KPI calculation, but shown here to know this is a possible property
"productionPurposeCode": "3", //From codelist CL251. Can be omitted if unknown
"productionPurposeName": "consumption" //From codelist CL251. Can be omitted if unknown
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [
{
//"area": "0.08", //if leave empty then write code to fill in value from geometry (operation applied to whole field). Fill in different value if operation applied to part of the field (e.g. in case of variable rate application, VRA)
"contractor": false,
"designator": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"from": "2022-05-23T11:34:00",
"method": "70400", //refers to codelist CL127 with operation methods
"n": "92",
"name": "Kunstmest strooien", //refers to codelist CL127 with operation methods
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "7", // refers to codelist CL018 consisting of main catergorie of operations. Code 7 stands for fertilization
"product": "7360", //refers to codelist CL022 with fertilizer types
"quantity": "200",
"to": "2022-05-23T12:34:00",
"unit": "kg/ha",
"unitCode": "KGMHAR"
}
],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.900,
50.800
],
[
5.901,
50.800
],
[
5.901,
50.801
],
[
5.900,
50.801
],
[
5.900,
50.800
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "caseQuoteNnotProvidedInDataOperations",
"CropfieldItemCode": "",
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
//"soilCode": "5", //From codelist CL405. Can be omitted if unknown
//"soilName": "Loam", //From codelist CL405.
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
//"rootDepthMax": 45, //not needed for KPI calculation, but shown here to know this is a possible property
//"emergenceDate": "2022-05-16T00:00:00", //not needed for KPI calculation, but shown here to know this is a possible property
"productionPurposeCode": "3", //From codelist CL251. Can be omitted if unknown
"productionPurposeName": "consumption" //From codelist CL251. Can be omitted if unknown
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [
{
//"area": "0.08", //if leave empty then write code to fill in value from geometry (operation applied to whole field). Fill in different value if operation applied to part of the field (e.g. in case of variable rate application, VRA)
"contractor": false,
"designator": "Kunstmest strooien", //refers to codelist CL127 with operation methods
"from": "2022-05-23T11:34:00",
"method": "70400", //refers to codelist CL127 with operation methods
//"n": "92",
"name": "Kunstmest strooien", //refers to codelist CL127 with operation methods
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "7", // refers to codelist CL018 consisting of main catergorie of operations. Code 7 stands for fertilization
"product": "7360", //refers to codelist CL022 with fertilizer types
"quantity": "200",
"to": "2022-05-23T12:34:00",
"unit": "kg/ha",
"unitCode": "KGMHAR"
}
],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "examplePesticide_11767N",
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
"dataCropfield": {
"final": true, //always true
"cropTypeCode": "1010101", //From codelist CL263
"cropTypeName": "Potato", //From codelist CL263
"productionPurposeCode": "3", //From codelist CL251. Can be omitted if unknown
"productionPurposeName": "consumption" //From codelist CL251. Can be omitted if unknown
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [
{
"to": "2022-07-13T17:27:00",
"from": "2022-07-13T16:27:00",
"name": "Volveld spuiten (3/3)", //refers to codelist CL127 with operation methods
"method": "80200", //refers to codelist CL127 with operation methods
//"status": "3", // refers to codelist CL256. where 3 stand for completed. The status does not matter and is not used in the KPI calculations
"operationCode": "8", // refers to codelist CL018 consisting of main catergorie of operations. Code 8 stands for crop protection
"products": [
{
"product": "11767 N", //CTBG code, lookup via CTBG API. Important: with space N",
"quantity": "1.25",
"unitCode": "LTRHAR"
//"designator": "Amistar"
}
]
}
],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619",
"label": "cropyield",
"value": "48.01"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "doperwt_heleJaar",
"CropfieldItemCode": "",
"dataCropfield": {
"final": true, //always true
"cropTypeCode": "1030101", //From codelist CL263
"cropTypeName": "Garden pea" //From codelist CL263
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "9.58" //CBS: https://www.cbs.nl/nl-nl/cijfers/detail/37738, 2022: 40.4 Mkg / 4216 ha = 40400 / 4216
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//"StartDate": "2022-04-15", //PO20231108: StartDate & EndDate important for legumes, where Nitrogen fixation depends on number of days from start to end. More days = more N fixation
//"EndDate": "2022-07-30", // if StartDate & EndDate left empty then whole year from cropYear
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "doperwt_groeiperiode",
"CropfieldItemCode": "",
"dataCropfield": {
"final": true, //always true
"cropTypeCode": "1030101", //From codelist CL263
"cropTypeName": "Garden pea" //From codelist CL263
},
"CropRecordingItemCode": "",
"OperationItemCodes": [],
"dataOperations": [],
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "9.58" //CBS: https://www.cbs.nl/nl-nl/cijfers/detail/37738, 2022: 40.4 Mkg / 4216 ha = 40400 / 4216
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_WURtest\FarmmapsDataDownload\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
"StartDate": "2022-04-15", //PO20231108: StartDate & EndDate important for legumes, where Nitrogen fixation depends on number of days from start to end. More days = more N fixation
"EndDate": "2022-07-30", // if StartDate & EndDate left empty then whole year from cropYear
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "test_WinterWheat_DefaultStrawRemainsInField",
//Note when you run the FarmmapsKPI project, somewhere to your C:\git\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\ directory, files named like this are written:
//'Settings_aardappelveld_test_Potato_ZeroFertilizer.json', where 'aardappelveld_test_Potato_ZeroFertilizer.json' is your fieldName (see below)
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && CropfieldItemCode = "" or absent -> read fieldName (see below) -> read all codes from settings json file
//3 useExistingCropfieldWithChildren = true && CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> if you have in your account already an existing cropfield then use that
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
//And then if useExistingCropfieldWithChildren = true, this would be used.
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
"soilCode": "1", //From codelist CL405. Can be omitted if unknown
"soilName": "Sand", //From codelist CL405
"cropTypeCode": "1020101", //From codelist CL263
"cropTypeName": "Winter wheat" //From codelist CL263
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [], //leaving the operations empty means no fertilizer is applied
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
//PO20231029: not different yields from different parts of fields?
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "10.0"
}
],
//"DownloadFolder": "Downloads", //"C:\\hugoschrererdir\\kpidir\\", // "Downloads", -> if you just put "Downloads" the program will download to somewhere in ..\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\Downloads\
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
},
{
"useExistingCropfieldWithChildren": false,
"deleteNewlyCreatedAfterCalc": true,
"fieldName": "test_WinterWheat_StrawRemovedFromField",
//Note when you run the FarmmapsKPI project, somewhere to your C:\git\FarmMapsApiClient_KB34_MAST\FarmmapsKPI\bin\Debug\netcoreapp3.1\ directory, files named like this are written:
//'Settings_aardappelveld_test_Potato_ZeroFertilizer.json', where 'aardappelveld_test_Potato_ZeroFertilizer.json' is your fieldName (see below)
//1 useExistingCropfieldWithChildren = false -> create new
//2 useExistingCropfieldWithChildren = true && CropfieldItemCode = "" or absent -> read fieldName (see below) -> read all codes from settings json file
//3 useExistingCropfieldWithChildren = true && CropfieldItemCode like "deb48a74c5b54299bb852f17288010e9" in KPIinput -> if you have in your account already an existing cropfield then use that
"CropfieldItemCode": "", // could contain for example this: "abae97f89f3c4ac08953b1b8bea9f076" if this is an exisiting CropfieldItemCode in your account.
//And then if useExistingCropfieldWithChildren = true, this would be used.
"dataCropfield": {
//"area": 4.22, //Leave empty, KPI app will calculate it from geometry
"final": true, //always true
"soilCode": "1", //From codelist CL405. Can be omitted if unknown
"soilName": "Sand", //From codelist CL405
"cropTypeCode": "1020101", //From codelist CL263
"cropTypeName": "Winter wheat" //From codelist CL263
},
"CropRecordingItemCode": "", // could contain for example this: "314aa1b6480b493ba28a5f55039a01d4" if this is the CropRecordingItemCode of the provided CropfieldItemCode. And then if useExistingCropfieldWithChildren = true, this would be used
"OperationItemCodes": [], // could contain for example this: ["4d1a6f28ea3f414180663baa4b08c60f"] or ["4d1a6f28ea3f414180663baa4b08c60f","21e08386f0454ad79acfebf78649d59b"] if these are the operations of the provided CropRecordingItemCode. And then if useExistingCropfieldWithChildren = true, these would be used
"dataOperations": [], //leaving the operations empty means no fertilizer is applied
"CropfieldCharacteristicItemCode": "",
"DataCropfieldCharacteristics": [
{
//PO20231029: not different yields from different parts of fields?
"code": "860619", //From codelist CL265. 860619 refers to cropyield in ton/ha
"label": "cropyield",
"value": "10.0"
},
{
"code": "fm009", //TODO: which codelist?
"label": "strawyield",
"value": "6.8" //was 5.9
}
],
"CropYear": 2022,
//geometryJson = polygon of location of the cropfield, coordinates in LON,LAT (LON: decimal degrees East, LAT: decimal degrees North)
"geometryJson": {
"type": "Polygon",
"coordinates": [
[
[
5.5945257993548765,
52.57080744107003
],
[
5.598645994070678,
52.571540800206236
],
[
5.599381743127071,
52.57012773140724
],
[
5.595408698222548,
52.56968054825188
],
[
5.5945257993548765,
52.57080744107003
]
]
]
}
}
]

View File

@ -0,0 +1,27 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using FarmmapsKPI.Models;
using Microsoft.Extensions.Logging;
using static FarmmapsApi.Extensions;
using static FarmmapsApiSamples.Constants;
namespace FarmmapsKPI
{
public class KPIService
{
private readonly ILogger<KPIService> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
private readonly GeneralService _generalService;
public KPIService(ILogger<KPIService> logger, FarmmapsApiService farmmapsApiService,
GeneralService generalService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
_generalService = generalService;
}
}
}

View File

@ -0,0 +1,23 @@
KPIid,KPIvariable,Description,KPItargetvalue,KPIthresholdValue
A1,yield,observed yield (user input),target value as in benchmark value for same crop in same region. E.g. cropyield target is from Central Bureau of Statistics (CBS) for same crop in region in which the cropfield (geometry) is located.,No threshold for this KPI.
A1,strawYield,straw yield (user input; value 0 or not filled in = straw remains in field; value > 0 means straw was exported),there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,nitrogen,Nitrogen surplus = N artifical fertilizer + N manure+ N atmospheric deposition + N fixation by crop + N from seed potatoes - N removal through harvested product. Calculated KPI internal model & parameters and from user input: yield and fertilizer/manure applications.,The target value depends on the soil type. To calculate the threshold and target values data from CBS and RIVM are used. The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw).,The threshold value depends on soil type and groundwater levels. To calculate the threshold and target values data from CBS and RIVM are used. The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw).
B1,nHarvestedKgHa, N removal through harvested product. Calculated KPI internal model & parameters and from user input: yield.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,nHarvestedStrawKgHa, N removal through harvested product. Calculated KPI internal model & parameters and from user input: yieldStraw.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,nFixationNKgHa, N fixated by crop (legumes only). Calculated KPI internal model & parameters and from user input: planting date,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,nDepositionNKgHa, N deposition based on RIVM maps. ,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,nFertilizerNKgHa, N from artifical fertilizers and manure.Calculated KPI internal model & parameters and from user input: fertilizer/manure applications.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B1,sowPlantingNKgHa, N from seed potatoes (potatoes only). Calculated KPI internal model & parameters and from user input: amount of planted seed potatoes,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B2,phosphate,Phosphate surplus = P fertilizer + P from seed potatoes - P removal through harvested product. Calculated KPI internal model & parameters and from user input: yield and fertilizer applications ,The phosphate target value is set at 20 kg/ha ,No threshold for this KPI.
B2,pHarvestedKgHa, P2O5 removal through harvested product. Calculated KPI internal model & parameters and from user input: yield,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B2,pHarvestedStrawKgHa, P2O5 removal through harvested product. Calculated KPI internal model & parameters and from user input: yieldStraw,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B2,pFertillizerPKgHa, P2O5 from artifical fertilizers and manure.Calculated KPI internal model & parameters and from user input: fertilizer/manure applications.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
B2,sowPlantingPKgHa, P2O5 from seeds potatoes (potatoes only). Calculated KPI internal model & parameters and from user input: amount of planted seed potatoes,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
C1,effective organic matter supply,"Organic matter supply = Organic matter from manure + Crop residues to soil + organic matter from green manure. Calculated KPI internal model & parameters and from user input: yield and manure applications. ""Effective"" means netto, after correction for carbon respiration by soil microbial activity",The target value for this KPI is the amount of EOM that companies with Goede Landbouwpraktijk supply (2500 kg EOM/ha/year). ,The threshold value is 2000 kg EOM/ha/year. This value comes from van Doorn et al. (2022) which states that on average 2000 kg of EOM/ha degrades on Dutch agricultural land. This is the also the threshold value from biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw)
C1,fertilizerEom,"Effective organic matter added through fertilizer (animal manure, compost, etc). Calculated KPI internal model & parameters and from user input: manure applications.",there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
C1,greenManureEom,Effective organic matter added through green manure (also called cover crop). Calculated KPI internal model & parameters and from user input: cover crop.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
C1,cropLeftoversEom,Effective organic matter added through crop residues of main product and (when relevant) straw. Calculated KPI internal model & parameters.,there are no target values for individual compenets of KPI's,there are no threshold values for individual compenets of KPI's
D1,pesticides,"kg/ha of active ingredient. Sum over kg a.i. of the different crop protection agents applied, with concentration (kg a.i. / kg or litre) of crop protection agents retrieved from CTBG",The target value fort his KPI is to reduce pesticide use to 50% of the amount used in the Netherlands during 2020. Data for 2020 is obtained from CBS statline ,No threshold for this KPI.
E1,mpb,"milieubelastingspunten. For each pesticide applied show 3 sub-mbp's: aquaticLife, groundWater, soilLife","0 applications with exceedances of either the target MBP or risks. MBP's consist of three categories (groundwater, soil life, and aquatic life). An value of <100 in either or these categories counts as exceedance. Each application of pesticides cannot have more then one exceedance for MBP. Risks consist of two categories (natural enemies and pollinators), a value of B or C in either category counts as exceedance. Each application of pesticides cannot have more then one exceedance for risks. Values and methodology come from Environmental Yardstick for Pesticides (https://www.milieumeetlat.nl/). The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw).","0 applications with exceedances of either the target MBP or risks. MBP's consist of three categories (groundwater, soil life, and aquatic life). An value of <10 in either or these categories counts as exceedance. Each application of pesticides cannot have more then one exceedance for MBP. Risks consist of two categories (natural enemies and pollinators), a value of B or C in either category counts as exceedance. Each application of pesticides cannot have more then one exceedance for risks. Values and methodology come from Environmental Yardstick for Pesticides (https://www.milieumeetlat.nl/). The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw)."
F1,greenness,time of year that the field was covered. Based on NDVI data from AgroDataCube.,Target value is 75%.,Threshold value is 25%.
F2,rotationindex,index of diversity of crops over time. Calculated in AgroDataCube,Target value is 75%.,Threshold value is 25%.
1 KPIid KPIvariable Description KPItargetvalue KPIthresholdValue
2 A1 yield observed yield (user input) target value as in benchmark value for same crop in same region. E.g. cropyield target is from Central Bureau of Statistics (CBS) for same crop in region in which the cropfield (geometry) is located. No threshold for this KPI.
3 A1 strawYield straw yield (user input; value 0 or not filled in = straw remains in field; value > 0 means straw was exported) there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
4 B1 nitrogen Nitrogen surplus = N artifical fertilizer + N manure+ N atmospheric deposition + N fixation by crop + N from seed potatoes - N removal through harvested product. Calculated KPI internal model & parameters and from user input: yield and fertilizer/manure applications. The target value depends on the soil type. To calculate the threshold and target values data from CBS and RIVM are used. The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw). The threshold value depends on soil type and groundwater levels. To calculate the threshold and target values data from CBS and RIVM are used. The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw).
5 B1 nHarvestedKgHa N removal through harvested product. Calculated KPI internal model & parameters and from user input: yield. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
6 B1 nHarvestedStrawKgHa N removal through harvested product. Calculated KPI internal model & parameters and from user input: yieldStraw. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
7 B1 nFixationNKgHa N fixated by crop (legumes only). Calculated KPI internal model & parameters and from user input: planting date there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
8 B1 nDepositionNKgHa N deposition based on RIVM maps. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
9 B1 nFertilizerNKgHa N from artifical fertilizers and manure.Calculated KPI internal model & parameters and from user input: fertilizer/manure applications. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
10 B1 sowPlantingNKgHa N from seed potatoes (potatoes only). Calculated KPI internal model & parameters and from user input: amount of planted seed potatoes there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
11 B2 phosphate Phosphate surplus = P fertilizer + P from seed potatoes - P removal through harvested product. Calculated KPI internal model & parameters and from user input: yield and fertilizer applications The phosphate target value is set at 20 kg/ha  No threshold for this KPI.
12 B2 pHarvestedKgHa P2O5 removal through harvested product. Calculated KPI internal model & parameters and from user input: yield there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
13 B2 pHarvestedStrawKgHa P2O5 removal through harvested product. Calculated KPI internal model & parameters and from user input: yieldStraw there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
14 B2 pFertillizerPKgHa P2O5 from artifical fertilizers and manure.Calculated KPI internal model & parameters and from user input: fertilizer/manure applications. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
15 B2 sowPlantingPKgHa P2O5 from seeds potatoes (potatoes only). Calculated KPI internal model & parameters and from user input: amount of planted seed potatoes there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
16 C1 effective organic matter supply Organic matter supply = Organic matter from manure + Crop residues to soil + organic matter from green manure. Calculated KPI internal model & parameters and from user input: yield and manure applications. "Effective" means netto, after correction for carbon respiration by soil microbial activity The target value for this KPI is the amount of EOM that companies with ‘ Goede Landbouwpraktijk’ supply (2500 kg EOM/ha/year). The threshold value is 2000 kg EOM/ha/year. This value comes from van Doorn et al. (2022) which states that on average 2000 kg of EOM/ha degrades on Dutch agricultural land. This is the also the threshold value from biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw)
17 C1 fertilizerEom Effective organic matter added through fertilizer (animal manure, compost, etc). Calculated KPI internal model & parameters and from user input: manure applications. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
18 C1 greenManureEom Effective organic matter added through green manure (also called cover crop). Calculated KPI internal model & parameters and from user input: cover crop. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
19 C1 cropLeftoversEom Effective organic matter added through crop residues of main product and (when relevant) straw. Calculated KPI internal model & parameters. there are no target values for individual compenets of KPI's there are no threshold values for individual compenets of KPI's
20 D1 pesticides kg/ha of active ingredient. Sum over kg a.i. of the different crop protection agents applied, with concentration (kg a.i. / kg or litre) of crop protection agents retrieved from CTBG The target value fort his KPI is to reduce pesticide use to 50% of the amount used in the Netherlands during 2020. Data for 2020 is obtained from CBS statline No threshold for this KPI.
21 E1 mpb milieubelastingspunten. For each pesticide applied show 3 sub-mbp's: aquaticLife, groundWater, soilLife 0 applications with exceedances of either the target MBP or risks. MBP's consist of three categories (groundwater, soil life, and aquatic life). An value of <100 in either or these categories counts as exceedance. Each application of pesticides cannot have more then one exceedance for MBP. Risks consist of two categories (natural enemies and pollinators), a value of B or C in either category counts as exceedance. Each application of pesticides cannot have more then one exceedance for risks. Values and methodology come from Environmental Yardstick for Pesticides (https://www.milieumeetlat.nl/). The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw). 0 applications with exceedances of either the target MBP or risks. MBP's consist of three categories (groundwater, soil life, and aquatic life). An value of <10 in either or these categories counts as exceedance. Each application of pesticides cannot have more then one exceedance for MBP. Risks consist of two categories (natural enemies and pollinators), a value of B or C in either category counts as exceedance. Each application of pesticides cannot have more then one exceedance for risks. Values and methodology come from Environmental Yardstick for Pesticides (https://www.milieumeetlat.nl/). The same calculation method is also used in biodiversiteitsmonitoring akkerbouw (https://www.bo-akkerbouw.nl/kennis-en-innovatie/pps-biodiversiteitsmonitor-akkerbouw).
22 F1 greenness time of year that the field was covered. Based on NDVI data from AgroDataCube. Target value is 75%. Threshold value is 25%.
23 F2 rotationindex index of diversity of crops over time. Calculated in AgroDataCube Target value is 75%. Threshold value is 25%.

View File

@ -0,0 +1,26 @@
using System;
using Newtonsoft.Json.Linq;
namespace FarmmapsKPI.Models
{
public class KPIInput
{
public bool UseExistingCropfieldWithChildren { get; set; }
public bool DeleteNewlyCreatedAfterCalc { get; set; }
public string fieldName { get; set; }
public string CropfieldItemCode { get; set; }
public JObject DataCropfield { get; set; }
public string CropRecordingItemCode { get; set; }
public JObject DataCropRecording { get; set; }
public string[] OperationItemCodes { get; set; }
public JObject[] DataOperations { get; set; }
public string CropfieldCharacteristicItemCode { get; set; }
public JObject[] DataCropfieldCharacteristics { get; set; }
public string DownloadFolder { get; set; }
public int CropYear { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public JObject GeometryJson { get; set; }
}
}

View File

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace FarmmapsKPI.Models
{
public class KPIOutput
{
public string id { get; set; }
public KPIOutputData data { get; set; }
public string type { get; set; }
public string unit { get; set; }
public string value { get; set; }
public string duration { get; set; }
public string quantity { get; set; }
public string parentName { get; set; }
public string targetValue { get; set; }
public string thresholdValue { get; set; }
public List<string> A1elements; //TtD
public List<string> B1elements;
public List<string> B2elements;
public List<string> C1elements;
public KPIOutput()
{
this.A1elements = new List<string>() { "yield", "strawYield" }; //TtD
this.B1elements = new List<string>() { "nHarvestedKgHa", "nHarvestedStrawKgHa", "nFixationNKgHa", "nDepositionNKgHa","nFertilizerNKgHa","sowPlantingNKgHa" }; //TtD
this.B2elements = new List<string>() { "pHarvestedKgHa", "pHarvestedStrawKgHa", "pFertillizerPKgHa", "sowPlantingPKgHa" }; //TtD
this.C1elements = new List<string>() { "fertilizerEom", "greenManureEom", "cropLeftoversEom" }; //TtD to do rename
}
}
public class KPIOutputData
{
public string area { get; set; }
public string cropTypeCode { get; set; }
public string cropTypeName { get; set; }
public string yield { get; set; }
public string strawYield { get; set; }
public KPIelementsOfBalance values { get; set; }
public KPIenvironmentMeasureData[] environmentMeasureData { get; set; }
}
public class KPIenvironmentMeasureData
{
public string date { get; set; }
public string productCode { get; set; }
public string productName { get; set; }
public string quantity { get; set; }
public string unitCode { get; set; }
public string soilLife { get; set; }
public string aquaticLife { get; set; }
public string groundWater { get; set; }
}
public class KPIelementsOfBalance
{
public string yield { get; set; } //TtD
public string strawYield { get; set; } //TtD
public string nHarvestedKgHa { get; set; } //TtD
public string nHarvestedStrawKgHa { get; set; } //TtD
public string nFixationNKgHa { get; set; }
public string nDepositionNKgHa { get; set; }
public string nFertilizerNKgHa { get; set; }
public string sowPlantingNKgHa { get; set; }
public string pHarvestedKgHa { get; set; } //TtD
public string pHarvestedStrawKgHa { get; set; } //TtD
public string pFertillizerPKgHa { get; set; } //TtD
public string sowPlantingPKgHa { get; set; } //TtD
public string fertilizerEom { get; set; } //TtD
public string greenManureEom { get; set; } //TtD
public string cropLeftoversEom { get; set; } //TtD tamara: rename
}
}

View File

@ -0,0 +1,11 @@
namespace FarmmapsKPI
{
public class Settings
{
public string CropfieldItemCode { get; set; }
public string CropRecordingItemCode { get; set; }
public string[] OperationItemCodes { get; set; }
public string CropfieldCharacteristicItemCode { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System.Threading.Tasks;
using FarmmapsApi;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FarmmapsKPI
{
class Program : FarmmapsProgram<KPIApplication>
{
private static async Task Main(string[] args)
{
await new Program().Start(args);
}
protected override void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddLogging()
.AddTransient<KPIService>();
}
}
}

View File

@ -0,0 +1,66 @@
TODO.txt
###################################################################################################
# 1. DONE
###################################################################################################
Laat de farmmapsKPI zelf maar uitzoeken wat de n-p gehaltes zijn:
a. We willen als input opgeven we hebben 400 kg/ha urea gegeven, of 32000 kg/ha runderdrijfmest.
Daarvoor wel de juiste gewas code opgeven -> zie onder, FarmmapsEditeeltList 263 gewassen
Daarvoor wel de juiste meststof code opgeven -> zie onder, FarmmapsEditeeltList 022 meststoffen
b. Check of de operation data een element “n” bevat. Zoniet of als waarde 0, dan
i. Zoek onder de motorkap op: de omreken factor (bijv 200 kg/ha urea met 46% n -> 200*0.46 = 92 kg N/ha)
ii. Voeg die 92 toe aan de operation data ("n": 92)
c. Reken N surplus uit (KPI item id b1)
GEPARKEERD:
Want voor veel meststoffen is geen content bekend -> automatisch opzoeken zal in veel gevallen niet goed werken
Laat de gebruiker zelf maar de codelist CL022 raadplegen en omrekenen.
Nieuw project: FarmmapsEditeeltLists DONE
a. Why? Because
i. This is (1) so that we can at any time retrieve the most recent up-to-date list
ii. so that you can look up crops and fertilizer types and fill in KPIinput.json correctly
b. Download full list of items from vnd.farmmaps.itemtype.codelist.cl263 DONE
c. Write them to a csv file DONE
d. In any case following lists: 127 operations DONE; 022 meststoffen (fertilizer products) DONE; 263 gewassen DONE; 405 soilcode DONE
in which list the productionPurposeCode 251 DONE
Testing:
Effect of soiltype (e.g. different N concents in crop depending on soiltype? (CL405))
DONE (using KPIinput.json): same N balance, different target & threshold
Effect of location (e.g. different atmospheric N deposition).
DONE (using KPIinput.json): slightly different N balance
"DataCropfieldCharacteristic": {
"code": "860619", //PO20231004: so what does this code mean? Can we see the code list somewhere? COMPLETED code comes from codelist CL265. 860619 refers to cropyield in ton/ha
productionPurposeCode: different result depending on starch or consumption potato (different N contents in tubers)?
nContentFactor seems not to be adjusted by productionPurposeCode. Tamara check with Francisco
idem for pContentFactor
DONE (using KPIinput.json): surprisingly no effect?, format of the productionPurposeCode was wrong i.e was '002' instead of '2'. Format has been adjusted.
dataOperations: "operationCode": "7" & "8": DONE
a. which codelist? What does it mean? this refers to CL018 (bewerkinghoofdcaterogie), which contains information about the caterogie of the crop operations.
7 stands for fertilization while 8 refers to crop protection
b. using KPIinput.json: does it have an effect on KPI outcomes? YES
dataOperations: "status": "3": DONE
a. which codelist? What does it mean? this refers to CL256 (statusBewerking), which contains information about a crop operation has been planned, completed, cancelled etc. 3 refers to completed
b. using KPIinput.json: does it have an effect on KPI outcomes? NO, even without the status the crop operations is included in the KPI calculation.
As the status has no effect on the KPI calculations I have changed the lines about the status in the KPIinput.json file to comment
KPIelementsOfBalance: Tamara
a. define elements for C1 organic matter supply
b. for nitrogen B1 rename disposal to nHarvestedNKgHa COMPLETED
c. for phosporous B2 rename disposal to pHarvestedPKgHa COMPLETED
d. for phosporous B2 rename from pFertilizerNKgHa to pFertilizerPKgHa COMPLETED
e. idem sowPlantingNKgHa COMPLETED
f. add straw for N & P in output COMPLETED
g. add 2 examples in KPIinput.json: straw yes/no removed. Bijv. nStrawLeftInField EN nStrawRemoved COMPLETED
h. add amount of removed straw in A1 COMPLETED
i. update KPIdefinitions.csv COMPLETED
###################################################################################################
# 2. Pending
###################################################################################################
Testing:
KPIelementsOfBalance: Tamara
j. for C1 rename 'cropLeftoversEom' to 'cropResiduesEom' GEPARKEERD

View File

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -1,6 +1,7 @@
##### NOT PRODUCTION READY CODE, JUST AN EXAMPLE
Put your clientId and clientSecret or your Farmmaps-account data (username and password) in a newly created appsettings.secrets.json file inside the root of the Secrets project.
Clone the git repository to a local drive. DON'T clone to a network drive like "My Documents". Latter may cause authentication problems.
**appsettings.secrets.json**
```

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>

View File

@ -2,11 +2,11 @@
//There are three farmmaps environments, uncomment the environemnt you want to use with this sample client
//test environment
"Authority": "https://accounts.test.farmmaps.eu/",
"Endpoint": "https://test.farmmaps.eu/",
"BasePath": "api/v1",
"DiscoveryEndpointUrl": "https://accounts.test.farmmaps.eu/.well-known/openid-configuration",
"GrantClientId": "farmmapstesteu",
//"Authority": "https://accounts.test.farmmaps.eu/",
//"Endpoint": "https://test.farmmaps.eu/",
//"BasePath": "api/v1",
//"DiscoveryEndpointUrl": "https://accounts.test.farmmaps.eu/.well-known/openid-configuration",
//"GrantClientId": "farmmapstesteu",
////acceptance environment
//"Authority": "https://accounts.acc.farmmaps.eu/",
@ -16,11 +16,11 @@
//"GrantClientId": "farmmapsacceu",
////production environment
//"authority": "https://accounts.farmmaps.eu/",
//"endpoint": "https://farmmaps.eu/",
//"basepath": "api/v1",
//"discoveryendpointurl": "https://accounts.farmmaps.eu/.well-known/openid-configuration",
//"GrantClientId": "farmmaps",
"authority": "https://accounts.farmmaps.eu/",
"endpoint": "https://farmmaps.eu/",
"basepath": "api/v1",
"discoveryendpointurl": "https://accounts.farmmaps.eu/.well-known/openid-configuration",
"GrantClientId": "farmmaps",
//overige info
"RedirectUri": "http://example.nl/api",