From 52172bafcf627a47b93b1e3021c148e71d3232e2 Mon Sep 17 00:00:00 2001 From: Eric Pohl Date: Mon, 23 Jun 2025 10:14:03 -0400 Subject: [PATCH 1/6] Handle null representation code when examining workingdatas --- ISOv4Plugin/ExtensionMethods/ExtensionMethods.cs | 7 +++++++ ISOv4Plugin/Mappers/TimeLogMapper.cs | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ISOv4Plugin/ExtensionMethods/ExtensionMethods.cs b/ISOv4Plugin/ExtensionMethods/ExtensionMethods.cs index 66a5615..0949024 100644 --- a/ISOv4Plugin/ExtensionMethods/ExtensionMethods.cs +++ b/ISOv4Plugin/ExtensionMethods/ExtensionMethods.cs @@ -95,6 +95,13 @@ public static bool ReverseEquals(this string s1, string s2) return true; } + /// + /// Matches NumericRepresentation by code, accounting for null representation code + /// + public static bool ContainsCode(this ApplicationDataModel.Representations.Representation representation, string code) + { + return representation?.Code != null && representation.Code.Contains(code); + } /// /// Looks up unit, converts and loads representation diff --git a/ISOv4Plugin/Mappers/TimeLogMapper.cs b/ISOv4Plugin/Mappers/TimeLogMapper.cs index f0eccf2..4bbbd14 100644 --- a/ISOv4Plugin/Mappers/TimeLogMapper.cs +++ b/ISOv4Plugin/Mappers/TimeLogMapper.cs @@ -431,17 +431,17 @@ protected IEnumerable ImportTimeLog(ISOTask loggedTask, ISOTimeLo private OperationTypeEnum? GetOperationTypeFromWorkingDatas(List workingDatas) { //Harvest/ForageHarvest omitted intentionally to be determined from machine type vs. working data - if (workingDatas.Any(w => w.Representation.Code.Contains("Seed"))) + if (workingDatas.Any(w => w.Representation.ContainsCode("Seed"))) { return OperationTypeEnum.SowingAndPlanting; } - else if (workingDatas.Any(w => w.Representation.Code.Contains("Tillage"))) + else if (workingDatas.Any(w => w.Representation.ContainsCode("Tillage"))) { return OperationTypeEnum.Tillage; } - if (workingDatas.Any(w => w.Representation.Code.Contains("AppRate"))) + if (workingDatas.Any(w => w.Representation.ContainsCode("AppRate"))) { - return OperationTypeEnum.Unknown; //We can't differentiate CropProtection from Fertilizing, but prefer unkonwn to letting implement type set to SowingAndPlanting + return OperationTypeEnum.Unknown; //We can't differentiate CropProtection from Fertilizing, but prefer unknown to letting implement type set to SowingAndPlanting } return null; } From ab75c2f8f78604cdc3a17a7bcd178568049d64ba Mon Sep 17 00:00:00 2001 From: Andrew Vardeman Date: Wed, 30 Jul 2025 13:07:40 -0500 Subject: [PATCH 2/6] Keep valuable operation information from device when possible --- ISOv4Plugin/Mappers/TimeLogMapper.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ISOv4Plugin/Mappers/TimeLogMapper.cs b/ISOv4Plugin/Mappers/TimeLogMapper.cs index 4bbbd14..5c7e1e9 100644 --- a/ISOv4Plugin/Mappers/TimeLogMapper.cs +++ b/ISOv4Plugin/Mappers/TimeLogMapper.cs @@ -409,8 +409,7 @@ protected IEnumerable ImportTimeLog(ISOTask loggedTask, ISOTimeLo operationData.GetDeviceElementUses = x => operationData.DeviceElementUses.Where(s => s.Depth == x).ToList(); operationData.PrescriptionId = prescriptionID; operationData.OperationType = GetOperationTypeFromProductCategory(productIDs) ?? - GetOperationTypeFromWorkingDatas(workingDatas) ?? - GetOperationTypeFromLoggingDevices(time); + OverrideOperationTypeFromWorkingDatas(GetOperationTypeFromLoggingDevices(time), workingDatas); operationData.ProductIds = productIDs; if (!useDeferredExecution) { @@ -428,7 +427,7 @@ protected IEnumerable ImportTimeLog(ISOTask loggedTask, ISOTimeLo return null; } - private OperationTypeEnum? GetOperationTypeFromWorkingDatas(List workingDatas) + private OperationTypeEnum OverrideOperationTypeFromWorkingDatas(OperationTypeEnum deviceOperationType, List workingDatas) { //Harvest/ForageHarvest omitted intentionally to be determined from machine type vs. working data if (workingDatas.Any(w => w.Representation.ContainsCode("Seed"))) @@ -441,9 +440,12 @@ protected IEnumerable ImportTimeLog(ISOTask loggedTask, ISOTimeLo } if (workingDatas.Any(w => w.Representation.ContainsCode("AppRate"))) { - return OperationTypeEnum.Unknown; //We can't differentiate CropProtection from Fertilizing, but prefer unknown to letting implement type set to SowingAndPlanting + if (deviceOperationType != OperationTypeEnum.Fertilizing && deviceOperationType != OperationTypeEnum.CropProtection) + { + return OperationTypeEnum.Unknown; //We can't differentiate CropProtection from Fertilizing, but prefer unknown to letting implement type set to SowingAndPlanting + } } - return null; + return deviceOperationType; } private List> SplitElementsByProductProperties(Dictionary> productAllocations, HashSet loggedDeviceElementIds, ISODevice dvc) From d81d4d888488a6a091275422c3092705a6d73ecc Mon Sep 17 00:00:00 2001 From: Andrew Vardeman Date: Thu, 7 Aug 2025 14:23:33 -0500 Subject: [PATCH 3/6] Two fixes for reading CNH Pro1200 Harvest data: - Stop forcing CategoryEnum.Variety to OperationTypeEnum.SowingAndPlanting (could be Harvesting) - In GetOperationTypeFromLoggingDevices, prefer a non-Unknown operation type --- ISOv4Plugin/Mappers/TimeLogMapper.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ISOv4Plugin/Mappers/TimeLogMapper.cs b/ISOv4Plugin/Mappers/TimeLogMapper.cs index 5c7e1e9..4922ace 100644 --- a/ISOv4Plugin/Mappers/TimeLogMapper.cs +++ b/ISOv4Plugin/Mappers/TimeLogMapper.cs @@ -684,9 +684,6 @@ private void AddProductAllocationsForDeviceElement(Dictionary t.ClientNAMEMachineType >= 2 && t.ClientNAMEMachineType <= 11); + DeviceOperationType deviceType = representedTypes.FirstOrDefault(t => t.ClientNAMEMachineType >= 2 && t.ClientNAMEMachineType <= 11 && + t.OperationType != OperationTypeEnum.Unknown); + if (deviceType != null) { //2-11 represent known types of operations From 5a6d7973c21537b2c0444c60540bd35c97843c98 Mon Sep 17 00:00:00 2001 From: Eric Pohl <31418444+ericpohl@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:58:55 -0400 Subject: [PATCH 4/6] Construct DateTime objects with Local DateTimeKind (#259) * Construct DateTime objects with Local DateTimeKind * Consolidate "first day of 1980" fields --------- Co-authored-by: Eric Pohl --- ISOv4Plugin/Mappers/TimeLogMapper.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ISOv4Plugin/Mappers/TimeLogMapper.cs b/ISOv4Plugin/Mappers/TimeLogMapper.cs index 4922ace..13a39bb 100644 --- a/ISOv4Plugin/Mappers/TimeLogMapper.cs +++ b/ISOv4Plugin/Mappers/TimeLogMapper.cs @@ -33,6 +33,8 @@ internal TimeLogMapper(TaskDataMapper taskDataMapper) : base(taskDataMapper, "TL { } + private static readonly DateTime _firstDayOf1980 = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Local); + #region Export private Dictionary _dataLogValueOrdersByWorkingDataID; public IEnumerable ExportTimeLogs(IEnumerable operationDatas, string dataPath) @@ -150,7 +152,6 @@ private class BinaryWriter { // ATTENTION: CoordinateMultiplier and ZMultiplier also exist in Import\SpatialRecordMapper.cs! private const double CoordinateMultiplier = 0.0000001; private const double ZMultiplier = 0.001; // In ISO the PositionUp value is specified in mm. - private readonly DateTime _januaryFirst1980 = new DateTime(1980, 1, 1); private readonly IEnumeratedValueMapper _enumeratedValueMapper; private readonly INumericValueMapper _numericValueMapper; @@ -193,7 +194,7 @@ private void WriteSpatialRecord(SpatialRecord spatialRecord, List m var millisecondsSinceMidnight = (UInt32)new TimeSpan(0, spatialRecord.Timestamp.Hour, spatialRecord.Timestamp.Minute, spatialRecord.Timestamp.Second, spatialRecord.Timestamp.Millisecond).TotalMilliseconds; memoryStream.Write(BitConverter.GetBytes(millisecondsSinceMidnight), 0, 4); - var daysSinceJanOne1980 = (UInt16)(spatialRecord.Timestamp - (_januaryFirst1980)).TotalDays; + var daysSinceJanOne1980 = (UInt16)(spatialRecord.Timestamp - _firstDayOf1980).TotalDays; memoryStream.Write(BitConverter.GetBytes(daysSinceJanOne1980), 0, 2); //Position @@ -753,8 +754,6 @@ internal static Dictionary ReadImplementGeometryValues(IEnumerable ReadImplementGeometryValues(string filePath, ISOTime templateTime, IEnumerable desiredDLVIndices, int version, IList errors) { Dictionary output = new Dictionary(); From 276efda199f6798e15270efc9e241b4172dacabe Mon Sep 17 00:00:00 2001 From: Kenneth Lausdahl Date: Thu, 30 Oct 2025 14:59:44 +0100 Subject: [PATCH 5/6] Only add boundary with spatial data (#261) * Adding FieldBoundary description from polygon designator if present * only add partfield boundary polygon if spartial data is present --- ISOv4Plugin/Mappers/PartfieldMapper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ISOv4Plugin/Mappers/PartfieldMapper.cs b/ISOv4Plugin/Mappers/PartfieldMapper.cs index 8887a59..12be42f 100644 --- a/ISOv4Plugin/Mappers/PartfieldMapper.cs +++ b/ISOv4Plugin/Mappers/PartfieldMapper.cs @@ -93,8 +93,11 @@ public ISOPartfield ExportField(Field adaptField) FieldBoundary boundary = DataModel.Catalog.FieldBoundaries.SingleOrDefault(b => b.FieldId == adaptField.Id.ReferenceId); if (boundary != null) { - IEnumerable isoPolygons = polygonMapper.ExportMultipolygon(boundary.SpatialData, ISOEnumerations.ISOPolygonType.PartfieldBoundary); - isoField.Polygons.AddRange(isoPolygons); + if (boundary.SpatialData != null) + { + IEnumerable isoPolygons = polygonMapper.ExportMultipolygon(boundary.SpatialData, ISOEnumerations.ISOPolygonType.PartfieldBoundary); + isoField.Polygons.AddRange(isoPolygons); + } } //Guidance From 345ba0639194f6dc0a45ec628662bf81423f2f1e Mon Sep 17 00:00:00 2001 From: Kenneth Lausdahl Date: Thu, 30 Oct 2025 15:09:54 +0100 Subject: [PATCH 6/6] Multiple partfield boundaries (#260) * Adding FieldBoundary description from polygon designator if present * Handle multiple boundaries for fields using the active or first boundary --- ISOv4Plugin/Mappers/PartfieldMapper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ISOv4Plugin/Mappers/PartfieldMapper.cs b/ISOv4Plugin/Mappers/PartfieldMapper.cs index 12be42f..ffa211a 100644 --- a/ISOv4Plugin/Mappers/PartfieldMapper.cs +++ b/ISOv4Plugin/Mappers/PartfieldMapper.cs @@ -90,7 +90,9 @@ public ISOPartfield ExportField(Field adaptField) //Boundary PolygonMapper polygonMapper = new PolygonMapper(TaskDataMapper); - FieldBoundary boundary = DataModel.Catalog.FieldBoundaries.SingleOrDefault(b => b.FieldId == adaptField.Id.ReferenceId); + + var boundaries = DataModel.Catalog.FieldBoundaries.Where(b => b.FieldId == adaptField.Id.ReferenceId).ToList(); + var boundary = boundaries.FirstOrDefault(b=> b.Id.ReferenceId == adaptField.ActiveBoundaryId) ?? boundaries.FirstOrDefault(); if (boundary != null) { if (boundary.SpatialData != null)