Merge pull request 'master' (#16) from abel/FarmMapsApiClient_KB34_MAST:master into master

Reviewed-on: #16
This commit is contained in:
Mark [extern] 2025-01-10 15:39:26 +00:00
commit 56b337aebf
33 changed files with 2724 additions and 263 deletions

View File

@ -12,20 +12,27 @@ 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";
public const string VRAHAULMKILLING_TASK = "vnd.farmmaps.task.vrahaulmkilling";
public const string VRAPLANTING_TASK = "vnd.farmmaps.task.vrapoten";
public const string VRAZONERING_TASK = "vnd.farmmaps.task.vrazonering";
public const string SATELLITE_TASK = "vnd.farmmaps.task.sentinelhub"; //"vnd.farmmaps.task.satellite";
public const string SATELLITE_TASK = "vnd.farmmaps.task.sentinelhub";
public const string VANDERSAT_TASK = "vnd.farmmaps.task.vandersat";
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, 0, 0, 0, DateTimeKind.Utc);
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
@ -29,58 +29,114 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsBulkSatDownload", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Secrets", "Secrets\Secrets.csproj", "{C4EE5ECA-253A-4B71-9F67-D231AC4517D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FarmmapsCleanUp", "FarmmapsCleanUp\FarmmapsCleanUp.csproj", "{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}"
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
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E4387F9-5953-4A9B-BCA5-DF3964EED3CB}.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
{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

@ -17,6 +17,8 @@ using System.Text;
namespace FarmmapsBulkSatDownload
{
//To run this app, first go to farmmaps datastore at https://farmmaps.eu/en/editor/plan (or on test)
//goto 'Apps and Data', goto 'Data', buy (or get for free?): 'SATELLITE'
public class BulkSatDownloadApplication : IApplication
{
private readonly ILogger<BulkSatDownloadApplication> _logger;

View File

@ -1,143 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using FarmmapsApi;
using FarmmapsApi.Models;
using FarmmapsApi.Services;
using FarmmapsBulkSatDownload.Models;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Npgsql;
using Newtonsoft.Json.Linq;
using static FarmmapsApiSamples.Constants;
namespace FarmmapsCleanup
{
public class CleanupApplication : IApplication
{
private readonly ILogger<CleanupApplication> _logger;
private readonly FarmmapsApiService _farmmapsApiService;
private readonly CleanupService _cleanupService;
private readonly GeneralService _generalService;
public CleanupApplication(ILogger<CleanupApplication> logger, FarmmapsApiService farmmapsApiService,
GeneralService generalService, CleanupService cleanupService)
{
_logger = logger;
_farmmapsApiService = farmmapsApiService;
_generalService = generalService;
_cleanupService = cleanupService;
}
public async Task RunAsync()
{
// !! 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();
//Establish a database connection. Expecting you are using same database server as in project FarmmapsBulkSatDownload
DB dbparcels = JsonConvert.DeserializeObject<DB>(File.ReadAllText("DBsettings.secrets.json"));
string schemaname = "bigdata";
string parceltablename = "parcel_bollenrevolutie_tulips2020"; //"parcelsijbrandij" "parcel"; "parcel_flowerbulbs"; "parcel_disac"; ""parcel_bollenrevolutie_tulips2020""
//Query to get lists of items to delete from FarmmapsDatabase and from parceltablename
string cropfielditemcode;
string satellitetaskcode;
List<string> cropfieldItemCodes = new List<string>();
List<string> satellitetaskCodes = new List<string>();
string connectionString = dbparcels.GetConnectionString();
string readSql = string.Format(
@"
SELECT pt.cropfielditemcode, pt.satellitetaskcode
FROM {0}.{1} pt
WHERE
pt.arbid IN(1,2)
ORDER BY pt.arbid
;", schemaname, parceltablename);
string updateCropfieldItemCodesSql = string.Format(
@"
UPDATE {0}.{1}
SET cropfielditemcode=NULL
WHERE arbid IN(1,2)
;", schemaname, parceltablename); //Same WHERE AS above
string updateSatellitetaskCodesSql = string.Format(
@"
UPDATE {0}.{1}
SET satellitetaskcode=NULL
WHERE arbid IN(1,2)
;", schemaname, parceltablename); //Same WHERE AS above
using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
{
connection.Open();
// Read data (run query) = build a list of fields for which to download images
NpgsqlCommand command = connection.CreateCommand();
command.CommandText = readSql;
NpgsqlDataReader dr = command.ExecuteReader();
while (dr.Read())
{
cropfielditemcode = dr.GetString(0);
satellitetaskcode = dr.GetString(1);
if(!string.IsNullOrEmpty(cropfielditemcode))
cropfieldItemCodes.Add(cropfielditemcode);
if (!string.IsNullOrEmpty(satellitetaskcode))
satellitetaskCodes.Add(satellitetaskcode);
}
connection.Close();
}
_logger.LogWarning($"// FarmmapsCleanUp: WARNING: you are about to delete {cropfieldItemCodes.Count} cropfieldItemCodes and {satellitetaskCodes.Count} satellitetaskCodes from the FarmMaps database and your own table {schemaname}.{parceltablename}");
_logger.LogInformation($"// Nice of you to clean up after the work is done. You would typically do this for cropfieldItemCodes used only once.");
_logger.LogInformation($"// You do NOT want to do this if you think you may later on still want to use these items.");
_logger.LogInformation($"// Please carefully check the SQL queries 'readSql' 'updateSql' in CleanUpApplication.cs before proceeding.");
_logger.LogInformation($"// FarmmapsCleanUp: delete selected cropfieldItemCodes from FarmMaps database and table {schemaname}.{parceltablename}? 0 = no, 1 = yes");
int i;
i = Int32.Parse(Console.ReadLine());
if (i == 1)
{
await _farmmapsApiService.DeleteItemsAsync(cropfieldItemCodes);
//TODO _farmmapsApiService.DeleteItemsAsync throws an error: {StatusCode: 415, ReasonPhrase: 'Unsupported Media Type', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers: {...}}
//what is wrong with cropfieldItemCodes?
//and shouldn't we be telling _farmmapsApiService.DeleteItemsAsync what item type to delete?
using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
{
connection.Open();
NpgsqlCommand updateCmd = connection.CreateCommand();
updateCmd.CommandText = updateCropfieldItemCodesSql;
int r = updateCmd.ExecuteNonQuery();
if (r == -1)
throw new Exception("// FarmmapsCleanUp: Update cropfielditemcode Failed");
connection.Close();
}
}
_logger.LogInformation($"// FarmmapsCleanUp: delete selected satellitetaskCodes from FarmMaps database and table {schemaname}.{parceltablename}? 0 = no, 1 = yes");
i = Int32.Parse(Console.ReadLine());
if (i == 1)
{
await _farmmapsApiService.DeleteItemsAsync(satellitetaskCodes);
//TODO _farmmapsApiService.DeleteItemsAsync throws an error: {StatusCode: 415, ReasonPhrase: 'Unsupported Media Type', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers: {...}}
//what is wrong with satellitetaskCodes?
//and shouldn't we be telling _farmmapsApiService.DeleteItemsAsync what item type to delete?
using (NpgsqlConnection connection = new NpgsqlConnection(connectionString))
{
connection.Open();
NpgsqlCommand updateCmd = connection.CreateCommand();
updateCmd.CommandText = updateSatellitetaskCodesSql;
int r = updateCmd.ExecuteNonQuery();
if (r == -1)
throw new Exception("// FarmmapsCleanUp: Update cropfielditemcode Failed");
connection.Close();
}
}
_logger.LogInformation($"// FarmmapsCleanUp: done! Hit any key to exit ...");
Console.ReadKey();
}
}
}

View File

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql" Version="5.0.5" />
</ItemGroup>
<ItemGroup>
<None Update="Data\**\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FarmmapsApi\FarmmapsApi.csproj" />
<ProjectReference Include="..\FarmmapsBulkSatDownload\FarmmapsBulkSatDownload.csproj" />
</ItemGroup>
</Project>

View File

@ -15,6 +15,8 @@ using static FarmmapsApiSamples.Constants;
namespace FarmmapsDataDownload
{
//To run this app, first go to farmmaps datastore at https://farmmaps.eu/en/editor/plan (or on test)
//goto 'Apps and Data', goto 'Data', buy (or get for free?): 'SATELLITE'
public class DataDownloadApplication : IApplication
{
//private const string DownloadFolder = "Downloads";

View File

@ -1,41 +1,38 @@
# ShowGeotiff.r
# Have a look at a downloaded satellite image and check if stats are correctly calculated
# I downloaded and calculated the stats for the polygon defined in C:\git\FarmMapsApiClient_WURtest\FarmmapsDataDownload\DataDownloadInput.json
# in which I set "SatelliteBand": "wdvi" and in which in the console I requested the image for date '2022-03-08'
# downloaded and calculated the stats for the polygon defined in C:\git\FarmMapsApiClient\FarmmapsDataDownload\DataDownloadInput.json
# and in which in the console I requested the image for date '2022-08-25'
library(raster)
library(sf)
library(rgdal)
setwd("C:/git/FarmMapsApiClient_WURtest/FarmmapsDataDownload/bin/Debug/netcoreapp3.1/Downloads")
library(terra)
setwd("C:/git/FarmMapsApiClient/FarmmapsDataDownload/bin/Debug/netcoreapp3.1/Downloads")
# FarmmapsDataDownload
fileGeotiff <- "sentinelhub_test_BvdTFieldlabG92_20220308.tif"
fileGeotiff <- "sentinelhub_test_BvdTFieldlabG92_20220825.tif"
lenfilename <- nchar(fileGeotiff)
year <- substr(fileGeotiff,lenfilename-11,lenfilename-8)
imgdate <- substr(fileGeotiff,lenfilename-11,lenfilename-4)
stk.sentinelhub <- stack(x=fileGeotiff)
# plot(stk.sentinelhub) shows 6 plots (6 bands)
r.sentinelhub <- rast(x=fileGeotiff)
# plot(r.sentinelhub) shows 6 plots (6 bands)
# 1. ndvi
# 2. wdvi Note wdvi-red
# 3. ci-red
# 4. natural: red
# 5. natural: green
# 6. natural: blue
names(stk.sentinelhub) <- c("ndvi","wdvired","ci-red","red","green","blue")
plot(stk.sentinelhub)
crs(stk.sentinelhub)
names(r.sentinelhub) <- c("ndvi","wdvired","ci-red","red","green","blue")
# crs(r.sentinelhub)
# CRS arguments: +proj=longlat +datum=WGS84 +no_defs
stk.sentinelhub.rd <- projectRaster(stk.sentinelhub, crs = CRS('+init=EPSG:28992'))
crs(stk.sentinelhub)
r.sentinelhub.rd <- project(r.sentinelhub,'+init=EPSG:28992')
# crs(r.sentinelhub.rd)
r.sentinelhub.rd.wdvi <- subset(stk.sentinelhub.rd,2)
dev.off()
r.sentinelhub.rd.wdvi <- subset(r.sentinelhub.rd,2)
# dev.off()
plot(r.sentinelhub.rd.wdvi,main=paste("wdvi",imgdate),xlab="RDX",ylab="RDY")
cellStats(r.sentinelhub.rd.wdvi,'mean') # 0.2252725
global(r.sentinelhub.rd.wdvi, fun="mean")
# Convert the .rd.wdvi raster to WGS84
r.sentinelhub.wgs84.wdvi <- projectRaster(r.sentinelhub.rd.wdvi, crs = CRS('+init=EPSG:4326'))
r.sentinelhub.wgs84.wdvi <- project(r.sentinelhub.rd.wdvi, '+init=EPSG:4326')
# Draw a polygon on top of the raster
# Polygon pol from C:\git\FarmMapsApiClient_WURtest\FarmmapsDataDownload\DataDownloadInput.json
@ -48,67 +45,38 @@ pol <- data.frame(id = 1, wkt = gsub("\n","",'POLYGON((
))'))
pol.wgs84 <- st_as_sf(pol, wkt = 'wkt', crs = CRS('+init=EPSG:4326'))
pol.rd <- st_transform(pol.wgs84, "+init=epsg:28992")
#Calculate approximate middle of polygon
res <- as.data.frame(do.call("rbind", lapply(st_geometry(pol.wgs84), st_bbox)))
res$latmid <- (res$ymax+res$ymin)/2.0
res$lonmid <- (res$xmax+res$xmin)/2.0
res
# xmin ymin xmax ymax latmid lonmid
# 1 5.563472 52.54048 5.567917 52.54773 52.5441 5.565695
pol.wgs84 <- vect(pol$wkt,crs='+init=EPSG:4326')
pol.rd <- project(pol.wgs84, "+init=epsg:28992")
# Have a look at both polygons
# wg84
dev.off()
plot(r.sentinelhub.wgs84.wdvi,main=paste("wdvi",imgdate),xlab="LON",ylab="LAT")
plot(pol.wgs84,add=TRUE, col="transparent",border="red")
# RD
dev.off()
plot(r.sentinelhub.rd.wdvi,main=paste("wdvi",imgdate),xlab="RDX",ylab="RDY")
plot(pol.rd,add=TRUE, col="transparent",border="red")
# Clip the polygon from the full rectangle figure
r.sentinelhub.rd.wdvi.pol <- mask(r.sentinelhub.rd.wdvi,pol.rd)
r.sentinelhub.wgs84.wdvi.pol <- mask(r.sentinelhub.wgs84.wdvi,pol.wgs84)
dev.off()
plot(r.sentinelhub.wgs84.wdvi.pol,main=paste("wdvi",imgdate),xlab="LON",ylab="LAT")
plot(pol.wgs84,add=TRUE, col="transparent",border="red")
#That's what we want!
# Now compare the stats
cellStats(r.sentinelhub.wgs84.wdvi,'mean') # [1] 0.2250987 # Stats from rectangle, WGS84
cellStats(r.sentinelhub.rd.wdvi,'mean') # [1] 0.2252725 # Stats from rectangle, RD. Almost but not exactly same as above
cellStats(r.sentinelhub.wgs84.wdvi.pol,'mean') # [1] 0.2275067 # Stats from raster clipped by polygon, WGS84
cellStats(r.sentinelhub.rd.wdvi.pol,'mean') # [1] 0.2275073 # Stats from raster clipped by polygon, RD. Almost but not exactly same as above
# file satelliteStats_test_BvdTFieldlabG92_20220308.csv
# "wdvi" "mean": 0.22744397204465
global(r.sentinelhub.wgs84.wdvi,'mean') # [1] 0.1733634 # Stats from rectangle, WGS84
global(r.sentinelhub.rd.wdvi,'mean') # [1] 0.173531 # Stats from rectangle, RD. Almost but not exactly same as above
global(r.sentinelhub.wgs84.wdvi.pol,'mean',na.rm=TRUE) # [1] 0.1740224 # Stats from raster clipped by polygon, WGS84
global(r.sentinelhub.rd.wdvi.pol,'mean',na.rm=TRUE) # [1] 0.1738386 # Stats from raster clipped by polygon, RD. Almost but not exactly same as above
# file satelliteStats_test_BvdTFieldlabG92_20220825.csv
# "wdvi" "mean": 0.173905644768987
# Mean in csv corresponds with cellStats calculated from clipped tif!
# So while the tif returned is a non-clipped image, the downloaded statistics are from the clipped image
# Exactly as we wanted.
cellStats(r.sentinelhub.wgs84.wdvi.pol,'median') # Error in .local(x, stat, ...) : invalid 'stat'. Should be sum, min, max, sd, mean, or 'countNA'
global(r.sentinelhub.wgs84.wdvi.pol,'median', na.rm=TRUE) # Error in fun(values(x[[i]]), ...) : could not find function "fun"
r.sentinelhub.wgs84.wdvi.vals <- values(r.sentinelhub.wgs84.wdvi)
median(r.sentinelhub.wgs84.wdvi.vals) # [1] NA
median(r.sentinelhub.wgs84.wdvi.vals,na.rm=TRUE) # [1] 0.2318
median(r.sentinelhub.wgs84.wdvi.vals) # [1] 0.1459627
median(r.sentinelhub.wgs84.wdvi.vals,na.rm=TRUE) # [1] 0.1459627
r.sentinelhub.wgs84.wdvi.pol.vals <- values(r.sentinelhub.wgs84.wdvi.pol)
median(r.sentinelhub.wgs84.wdvi.pol.vals) # [1] NA
median(r.sentinelhub.wgs84.wdvi.pol.vals,na.rm=TRUE) # [1] 0.2338
# file satelliteStats_test_BvdTFieldlabG92_20220308.csv
# "wdvi" "mean": 0.233799993991851
# Median is same as for median(r.sentinelhub.wgs84.wdvi.pol.vals,na.rm=TRUE)
# in csv corresponds with cellStats calculated from clipped tif!
# So while the tif returned is a non-clipped image, the downloaded statistics are from the clipped image
# Exactly as we wanted.
cellStats(r.sentinelhub.wgs84.wdvi,'countNA') # [1] 27896
ncell(r.sentinelhub.wgs84.wdvi) # [1] 272718
cellStats(r.sentinelhub.wgs84.wdvi,'countNA') / ncell(r.sentinelhub.wgs84.wdvi) # [1] 0.1022888 # 10% no data? doesn't show in the plot?
cellStats(r.sentinelhub.wgs84.wdvi.pol,'countNA') # [1] 57625
summary(r.sentinelhub.wgs84.wdvi.pol.vals) # shows the same: NA's: 57625
ncell(r.sentinelhub.wgs84.wdvi.pol) # [1] 272718
populationCount = ncell(r.sentinelhub.wgs84.wdvi.pol) - cellStats(r.sentinelhub.wgs84.wdvi.pol,'countNA')
populationCount # [1] 215093
# file satelliteStats_test_BvdTFieldlabG92_20220308.csv
# "wdvi" "populationCount": 214688
# similar but not same
median(r.sentinelhub.wgs84.wdvi.pol.vals,na.rm=TRUE) # [1] 0.1453323

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", "CL104" ]
//"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,169 @@
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")
//do this under Items <- get/api/v1/items, use the it field. Depending on the size of the list retrieving the list can take a few minutes.
//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("CL104", new List<string> { "vnd.farmmaps.itemtype.codelist.cl104", "CL104item" });
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 CL104item
{
public CL104item()
{
this.headers = new string[] { "codelist", "code", "description" };
this.codelist = "CL104";
this.about = new string[] { "EDI-crop, coderingslijst groenbemesters" };
}
public string[] headers { get; }
public string codelist { get; }
public string[] about { get; }
public string code { 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,895 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
using System.Transactions;
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";
fnKPIinput = "C:\\git\\FarmMapsApiClient_KB34_MAST\\FarmmapsKPI\\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> { "run","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;
//Per default just 1 run per field. For debugging check if when we run multiple times do we get (should be) always the same output?
int nrun = 1;
for (int run = 1; run <= nrun; run++) {
for (int i = 0; i < fieldsInputs.Count; i++)
//for (int i = 3; i < 4; i++) // for testing
{
watch.Restart();
input = fieldsInputs[i];
_logger.LogInformation(string.Format($"// FarmmapsKPI: Run {run}: Downloading KPI's for field {i + 1} out of {fieldsInputs.Count} to single csv file {KPIItemPathCsv}"));
try
{
await Process(roots, input, sw, run);
}
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, int run)
{
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;
//double totalNferiliserInput = 0;
//double totalNferiliserCropfield = 0;
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}'");