Skip to content

Commit

Permalink
Merge pull request #191 from ADAPT/develop
Browse files Browse the repository at this point in the history
Merge to Master for next release
  • Loading branch information
Stuart Rhea authored Sep 14, 2022
2 parents 12f72f9 + 0c9732c commit 877f4bb
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 26 deletions.
5 changes: 5 additions & 0 deletions ISOv4Plugin/Mappers/GuidancePatternMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using AgGateway.ADAPT.ISOv4Plugin.ISOEnumerations;
using AgGateway.ADAPT.ISOv4Plugin.ISOModels;
using AgGateway.ADAPT.Representation.UnitSystem;
using AgGateway.ADAPT.ISOv4Plugin.ExtensionMethods;

namespace AgGateway.ADAPT.ISOv4Plugin.Mappers
{
Expand Down Expand Up @@ -237,6 +238,10 @@ public GuidancePattern ImportGuidancePattern(ISOGuidancePattern isoGuidancePatte
LineStringMapper lineStringMapper = new LineStringMapper(TaskDataMapper);
PointMapper pointMapper = new PointMapper(TaskDataMapper);
var isoLineString = isoGuidancePattern.LineString ?? new ISOLineString();
if (isoLineString.LineStringWidth.HasValue)
{
pattern.SwathWidth = ((int)isoLineString.LineStringWidth.Value).AsNumericRepresentationValue("mm");
}
switch (isoGuidancePattern.GuidancePatternType)
{
case ISOGuidancePatternType.AB:
Expand Down
5 changes: 5 additions & 0 deletions ISOv4Plugin/Mappers/LineStringMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using AgGateway.ADAPT.ApplicationDataModel.Shapes;
using AgGateway.ADAPT.ISOv4Plugin.ISOEnumerations;
using AgGateway.ADAPT.ISOv4Plugin.ISOModels;
using AgGateway.ADAPT.ISOv4Plugin.ExtensionMethods;

namespace AgGateway.ADAPT.ISOv4Plugin.Mappers
{
Expand Down Expand Up @@ -73,6 +74,10 @@ public ISOLineString ExportGuidancePattern(GuidancePattern adaptGuidancePattern)
{
ISOLineString lineString = new ISOLineString(TaskDataMapper.Version);
lineString.LineStringType = ISOLineStringType.GuidancePattern;
if (adaptGuidancePattern.SwathWidth != null)
{
lineString.LineStringWidth = (uint?)adaptGuidancePattern.SwathWidth.AsConvertedInt("mm");
}

PointMapper pointMapper = new PointMapper(TaskDataMapper);

Expand Down
4 changes: 3 additions & 1 deletion ISOv4Plugin/Mappers/ProductMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,9 @@ public Product ImportProduct(ISOProduct isoProduct)
product.Form = _manufacturer?.GetProductForm(isoProduct) ?? product.Form;

// Category
product.Category = _manufacturer?.GetProductCategory(isoProduct) ?? CategoryEnum.Unknown;
product.Category = _manufacturer?.GetProductCategory(isoProduct) ?? (product.ProductType == ProductTypeEnum.Variety
? CategoryEnum.Variety
: CategoryEnum.Unknown);

// Update ProductType
if (product.ProductType == ProductTypeEnum.Generic && product.Category != CategoryEnum.Unknown)
Expand Down
165 changes: 140 additions & 25 deletions ISOv4Plugin/Mappers/TimeLogMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using AgGateway.ADAPT.ApplicationDataModel.Common;
using AgGateway.ADAPT.ApplicationDataModel.Equipment;
using AgGateway.ADAPT.ApplicationDataModel.LoggedData;
using AgGateway.ADAPT.ApplicationDataModel.Products;
using AgGateway.ADAPT.ApplicationDataModel.Shapes;
using AgGateway.ADAPT.ISOv4Plugin.ExtensionMethods;
using AgGateway.ADAPT.ISOv4Plugin.ISOEnumerations;
Expand Down Expand Up @@ -355,34 +356,44 @@ protected IEnumerable<OperationData> ImportTimeLog(ISOTask loggedTask, ISOTimeLo
List<OperationData> operationDatas = new List<OperationData>();
foreach (ISODevice dvc in loggedDeviceElementsByDevice.Keys)
{
OperationData operationData = new OperationData();

//Determine products
Dictionary<string, List<ISOProductAllocation>> productAllocations = GetProductAllocationsByDeviceElement(loggedTask, dvc);
List<int> productIDs = GetDistinctProductIDs(TaskDataMapper, productAllocations);

//This line will necessarily invoke a spatial read in order to find
//1)The correct number of CondensedWorkState working datas to create
//2)Any Widths and Offsets stored in the spatial data
IEnumerable<DeviceElementUse> sections = sectionMapper.Map(time,
isoRecords,
operationData.Id.ReferenceId,
loggedDeviceElementsByDevice[dvc],
productAllocations);

var workingDatas = sections != null ? sections.SelectMany(x => x.GetWorkingDatas()).ToList() : new List<WorkingData>();

operationData.GetSpatialRecords = () => spatialMapper.Map(isoRecords, workingDatas, productAllocations);
operationData.MaxDepth = sections.Count() > 0 ? sections.Select(s => s.Depth).Max() : 0;
operationData.GetDeviceElementUses = x => sectionMapper.ConvertToBaseTypes(sections.Where(s => s.Depth == x).ToList());
operationData.PrescriptionId = prescriptionID;
operationData.OperationType = GetOperationTypeFromLoggingDevices(time);
operationData.ProductIds = productIDs;
if (!useDeferredExecution)
Dictionary<string, List<ISOProductAllocation>> deviceProductAllocations = GetProductAllocationsByDeviceElement(loggedTask, dvc);

//Create a separate operation for each product form (liquid, granular or solid).
List<List<string>> deviceElementGroups = SplitElementsByProductForm(deviceProductAllocations, loggedDeviceElementsByDevice[dvc], dvc);

foreach (var deviceElementGroup in deviceElementGroups)
{
operationData.SpatialRecordCount = isoRecords.Count(); //We will leave this at 0 unless a consumer has overridden deferred execution of spatial data iteration
OperationData operationData = new OperationData();

Dictionary<string, List<ISOProductAllocation>> productAllocations = deviceProductAllocations
.Where(x => deviceElementGroup.Contains(x.Key))
.ToDictionary(x => x.Key, x => x.Value);
List<int> productIDs = GetDistinctProductIDs(TaskDataMapper, productAllocations);

//This line will necessarily invoke a spatial read in order to find
//1)The correct number of CondensedWorkState working datas to create
//2)Any Widths and Offsets stored in the spatial data
IEnumerable<DeviceElementUse> sections = sectionMapper.Map(time,
isoRecords,
operationData.Id.ReferenceId,
deviceElementGroup,
productAllocations);

var workingDatas = sections != null ? sections.SelectMany(x => x.GetWorkingDatas()).ToList() : new List<WorkingData>();

operationData.GetSpatialRecords = () => spatialMapper.Map(isoRecords, workingDatas, productAllocations);
operationData.MaxDepth = sections.Count() > 0 ? sections.Select(s => s.Depth).Max() : 0;
operationData.GetDeviceElementUses = x => sectionMapper.ConvertToBaseTypes(sections.Where(s => s.Depth == x).ToList());
operationData.PrescriptionId = prescriptionID;
operationData.OperationType = GetOperationTypeFromProductCategory(productIDs) ?? GetOperationTypeFromLoggingDevices(time);
operationData.ProductIds = productIDs;
if (!useDeferredExecution)
{
operationData.SpatialRecordCount = isoRecords.Count(); //We will leave this at 0 unless a consumer has overridden deferred execution of spatial data iteration
}
operationDatas.Add(operationData);
}
operationDatas.Add(operationData);
}

//Set the CoincidentOperationDataIds property identifying Operation Datas from the same TimeLog.
Expand All @@ -393,6 +404,81 @@ protected IEnumerable<OperationData> ImportTimeLog(ISOTask loggedTask, ISOTimeLo
return null;
}

private List<List<string>> SplitElementsByProductForm(Dictionary<string, List<ISOProductAllocation>> productAllocations, HashSet<string> loggedDeviceElementIds, ISODevice dvc)
{
//This function splits device elements logged by single TimeLog into groups based
//on product form referenced by these elements. This is done using following logic:
// - determine used products forms and list of device element ids for each form
// - for each product form determine device elements from all other forms
// - remove these device elements and their children from a copy of device hierarchy elements
// - this gives a list of device elements to keep for a product form
var deviceElementIdsByProductForm = productAllocations
.SelectMany(x => x.Value.Select(y => new { Form = GetProductFormByProductAllocation(y), Id = x.Key }))
.Where(x => x.Form.HasValue)
.GroupBy(x => x.Form, x => x.Id)
.Select(x => x.Distinct().ToList())
.ToList();

List<List<string>> deviceElementGroups = new List<List<string>>();
if (deviceElementIdsByProductForm.Count > 1)
{
var deviceHierarchyElement = TaskDataMapper.DeviceElementHierarchies.Items[dvc.DeviceId];

var idsWithProduct = deviceElementIdsByProductForm.SelectMany(x => x).ToList();
foreach (var deviceElementIds in deviceElementIdsByProductForm)
{
var idsToRemove = idsWithProduct.Except(deviceElementIds).ToList();
var idsToKeep = FilterDeviceElementIds(deviceHierarchyElement, idsToRemove);

deviceElementGroups.Add(loggedDeviceElementIds.Intersect(idsToKeep).ToList());
}
}
else
{
deviceElementGroups.Add(loggedDeviceElementIds.ToList());
}

return deviceElementGroups;
}

private ProductFormEnum? GetProductFormByProductAllocation(ISOProductAllocation pan)
{
var adaptProductId = TaskDataMapper.InstanceIDMap.GetADAPTID(pan.ProductIdRef);
var adaptProduct = TaskDataMapper.AdaptDataModel.Catalog.Products.FirstOrDefault(x => x.Id.ReferenceId == adaptProductId);

// Add an error if ProductAllocation is referencing non-existent product
if (adaptProduct == null)
{
TaskDataMapper.AddError($"ProductAllocation referencing Product={pan.ProductIdRef} skipped since no matching product found");
}
return adaptProduct?.Form;
}

private List<string> FilterDeviceElementIds(DeviceHierarchyElement deviceHierarchyElement, List<string> idsToRemove)
{
var elementIdsToKeep = new List<string>();
if (!idsToRemove.Contains(deviceHierarchyElement.DeviceElement.DeviceElementId))
{
//By default we need to keep this element - covers scenario of no children elements
bool addThisElement = true;
if (deviceHierarchyElement.Children != null && deviceHierarchyElement.Children.Count > 0)
{
foreach (var c in deviceHierarchyElement.Children)
{
elementIdsToKeep.AddRange(FilterDeviceElementIds(c, idsToRemove));
}
//Keep this element if at least one child element is kept
addThisElement = elementIdsToKeep.Count > 0;
}

if (addThisElement)
{
elementIdsToKeep.Add(deviceHierarchyElement.DeviceElement.DeviceElementId);
}
}
return elementIdsToKeep;
}

protected virtual ISOTime GetTimeElementFromTimeLog(ISOTimeLog isoTimeLog)
{
return isoTimeLog.GetTimeElement(this.TaskDataPath);
Expand Down Expand Up @@ -500,6 +586,35 @@ private void AddProductAllocationsForDeviceElement(Dictionary<string, Dictionary
}
}

private OperationTypeEnum? GetOperationTypeFromProductCategory(List<int> productIds)
{
var productCategories = productIds
.Select(x => TaskDataMapper.AdaptDataModel.Catalog.Products.FirstOrDefault(y => y.Id.ReferenceId == x))
.Where(x => x != null && x.Category != CategoryEnum.Unknown)
.Select(x => x.Category)
.ToList();

switch (productCategories.FirstOrDefault())
{
case CategoryEnum.Variety:
return OperationTypeEnum.SowingAndPlanting;

case CategoryEnum.Fertilizer:
case CategoryEnum.NitrogenStabilizer:
case CategoryEnum.Manure:
return OperationTypeEnum.Fertilizing;

case CategoryEnum.Fungicide:
case CategoryEnum.Herbicide:
case CategoryEnum.Insecticide:
case CategoryEnum.Pesticide:
return OperationTypeEnum.CropProtection;

default:
return null;
}
}

private OperationTypeEnum GetOperationTypeFromLoggingDevices(ISOTime time)
{
HashSet<DeviceOperationType> representedTypes = new HashSet<DeviceOperationType>();
Expand Down

0 comments on commit 877f4bb

Please sign in to comment.