Example #1
0
 /// <summary>
 /// Compares two <see cref="FuelTaxDetail"/> elements based on their attributes.
 /// </summary>
 /// <param name="detail1">The first detail.</param>
 /// <param name="detail2">The second detail.</param>
 /// <param name="options">The grouping options.</param>
 /// <returns><c>true</c> iff both details share the same attributes, depending on the <see cref="options"/>.</returns>
 static bool AreAttributesEqual(FuelTaxDetail detail1, FuelTaxDetail detail2, dynamic options)
 {
     return(detail1.Jurisdiction == detail2.Jurisdiction &&
            (!options.DriverIdentification || detail1.Driver.Equals(detail2.TollRoad)) &&
            (!options.TollRoadIdentification || detail1.TollRoad == detail2.TollRoad) &&
            (!options.AuthorityIdentification || detail1.Authority == detail2.Authority));
 }
Example #2
0
        /// <summary>
        /// Clips a <see cref="FuelTaxDetail"/> at the nearest hour at or before a given time.
        /// </summary>
        /// <param name="detail">The detail.</param>
        /// <param name="dateTime">The time.</param>
        static void TrimRight(FuelTaxDetail detail, DateTime dateTime)
        {
            var hour     = dateTime.Ticks / HourTicks;
            var exitHour = detail.ExitTime.Ticks / HourTicks;

            if (detail.ExitTime.Ticks % HourTicks > 0)
            {
                exitHour++;
            }
            if (hour < exitHour)
            {
                var hourCount = detail.HourlyOdometer.Count;
                var hourIndex = (int)(hourCount - exitHour + hour);
                detail.ExitTime        = new DateTime(hour * HourTicks, DateTimeKind.Utc);
                detail.ExitOdometer    = detail.HourlyOdometer[hourIndex];
                detail.ExitGpsOdometer = detail.HourlyGpsOdometer[hourIndex];
                detail.ExitLatitude    = detail.HourlyLatitude[hourIndex];
                detail.ExitLongitude   = detail.HourlyLongitude[hourIndex];
                var removeCount = hourCount - hourIndex;
                detail.HourlyOdometer.RemoveRange(hourIndex, removeCount);
                detail.HourlyGpsOdometer.RemoveRange(hourIndex, removeCount);
                detail.HourlyLatitude.RemoveRange(hourIndex, removeCount);
                detail.HourlyLongitude.RemoveRange(hourIndex, removeCount);
            }
        }
Example #3
0
        /// <summary>
        /// Groups successive <see cref="FuelTaxDetail"/> elements by their attributes, depending on the <see cref="options"/>.
        /// </summary>
        /// <param name="details">The details.</param>
        /// <param name="options">The options.</param>
        /// <returns>A list of detail groups.</returns>
        static List <List <FuelTaxDetail> > Group(IList <FuelTaxDetail> details, dynamic options)
        {
            List <List <FuelTaxDetail> > groups = new List <List <FuelTaxDetail> > {
                new List <FuelTaxDetail>()
            };
            List <FuelTaxDetail> group          = groups[0];
            FuelTaxDetail        previousDetail = null;

            foreach (var detail in details)
            {
                if (previousDetail == null || AreAttributesEqual(detail, previousDetail, options))
                {
                    group.Add(detail);
                }
                else
                {
                    group = new List <FuelTaxDetail> {
                        detail
                    };
                    groups.Add(group);
                }
                previousDetail = detail;
            }
            return(groups);
        }
Example #4
0
        /// <summary>
        /// Clips a <see cref="FuelTaxDetail"/> at the nearest hour at or before a given time.
        /// </summary>
        /// <param name="detail">The detail.</param>
        /// <param name="dateTime">The time.</param>
        static void TrimLeft(FuelTaxDetail detail, DateTime dateTime)
        {
            var hour      = dateTime.Ticks / HourTicks;
            var enterHour = detail.EnterTime.Ticks / HourTicks;

            if (hour > enterHour)
            {
                var hourIndex = (int)(hour - enterHour - 1);
                detail.EnterTime        = new DateTime(hour * HourTicks, DateTimeKind.Utc);
                detail.EnterOdometer    = detail.HourlyOdometer[hourIndex];
                detail.EnterGpsOdometer = detail.HourlyGpsOdometer[hourIndex];
                detail.EnterLatitude    = detail.HourlyLatitude[hourIndex];
                detail.EnterLongitude   = detail.HourlyLongitude[hourIndex];
                var removeCount = hourIndex + 1;
                detail.HourlyOdometer.RemoveRange(0, removeCount);
                detail.HourlyGpsOdometer.RemoveRange(0, removeCount);
                detail.HourlyLatitude.RemoveRange(0, removeCount);
                detail.HourlyLongitude.RemoveRange(0, removeCount);
            }
        }
Example #5
0
        /// <summary>
        /// Calculates fuel usage for a collection of fuel tax details, classified by jurisdiction and fuel type.
        /// </summary>
        /// <param name="api">The Geotab API.</param>
        /// <param name="device">The device.</param>
        /// <param name="details">The fuel tax details.</param>
        /// <returns>A list of fuel tax data objects.</returns>
        static Dictionary <string, Dictionary <FuelType, FuelUsage> > GetFuelUsageByJurisdiction(API api, GoDevice device, IList <FuelTaxDetail> details)
        {
            var fuelUsageByJurisdiction = new Dictionary <string, Dictionary <FuelType, FuelUsage> >();

            // Get the details' time interval.
            DateTime fromDate = details[0].EnterTime;
            DateTime toDate   = details[details.Count - 1].ExitTime;

            // Get the fuel transactions within the details' time interval.
            Search fuelTransactionSearch = new FuelTransactionSearch
            {
                VehicleIdentificationNumber = device.VehicleIdentificationNumber,
                FromDate = fromDate,
                ToDate   = toDate
            };
            List <FuelTransaction> fuelTransactions = api.Call <IList <FuelTransaction> >("Get", typeof(FuelTransaction), new { search = fuelTransactionSearch }).ToList();

            fuelTransactions.Sort((transaction1, transaction2) => DateTime.Compare(transaction1.DateTime.Value, transaction2.DateTime.Value));

            // Calculate total purchased fuel by fuel type and jurisdiction.
            Dictionary <FuelType, double> fuelPurchasedByType = new Dictionary <FuelType, double>();
            double totalDistance = 0;

            foreach (var fuelTransaction in fuelTransactions)
            {
                // Locate the fuel transaction within a detail.
                FuelTaxDetail detail = details.Last(x => x.EnterTime <= fuelTransaction.DateTime);

                // Update jurisdiction fuel usage.
                string jurisdiction = detail.Jurisdiction;
                if (jurisdiction != null)
                {
                    if (!fuelUsageByJurisdiction.TryGetValue(jurisdiction, out Dictionary <FuelType, FuelUsage> fuelUsageByType))
                    {
                        fuelUsageByType = new Dictionary <FuelType, FuelUsage>();
                        fuelUsageByJurisdiction.Add(jurisdiction, fuelUsageByType);
                    }
                    FuelTransactionProductType?productType = fuelTransaction.ProductType;
                    FuelType fuelType = productType == null ? FuelType.None : GetFuelType(productType.Value);
                    if (!fuelUsageByType.TryGetValue(fuelType, out FuelUsage fuelUsage))
                    {
                        fuelUsage = new FuelUsage();
                        fuelUsageByType.Add(fuelType, fuelUsage);
                    }
                    double volume = fuelTransaction.Volume.Value;
                    fuelUsage.FuelPurchased += volume;
                    if (!fuelPurchasedByType.ContainsKey(fuelType))
                    {
                        fuelPurchasedByType.Add(fuelType, 0);
                    }
                    fuelPurchasedByType[fuelType] += volume;
                }
            }

            // Resolve fuel type None into the fuel type with the largest purchased volume.
            if (fuelPurchasedByType.TryGetValue(FuelType.None, out double _))
            {
                FuelType topFuelType = FuelType.None;
                double   topVolume   = 0;
                foreach (var fuelTypePurchased in fuelPurchasedByType)
                {
                    FuelType fuelType = fuelTypePurchased.Key;
                    if (fuelType != FuelType.None)
                    {
                        double volume = fuelTypePurchased.Value;
                        if (volume > topVolume)
                        {
                            topVolume   = volume;
                            topFuelType = fuelType;
                        }
                    }
                }
                if (topFuelType != FuelType.None)
                {
                    fuelPurchasedByType[topFuelType] += fuelPurchasedByType[FuelType.None];
                    fuelPurchasedByType.Remove(FuelType.None);
                }
                foreach (var fuelUsageByType in fuelUsageByJurisdiction.Values)
                {
                    if (fuelUsageByType.TryGetValue(FuelType.None, out FuelUsage typeNoneFuelUsage))
                    {
                        fuelUsageByType[topFuelType].FuelPurchased += typeNoneFuelUsage.FuelPurchased;
                        fuelUsageByType.Remove(FuelType.None);
                    }
                }
            }

            // Create fuel usage stumps for jurisdictions where no fuel transactions occurred.
            foreach (var detail in details)
            {
                string jurisdiction = detail.Jurisdiction;
                if (jurisdiction != null)
                {
                    if (!fuelUsageByJurisdiction.TryGetValue(jurisdiction, out Dictionary <FuelType, FuelUsage> fuelUsageByType))
                    {
                        fuelUsageByType = new Dictionary <FuelType, FuelUsage>();
                        fuelUsageByJurisdiction.Add(jurisdiction, fuelUsageByType);
                    }
                    double detailDistance = detail.ExitOdometer - detail.EnterOdometer;
                    foreach (var fuelType in fuelPurchasedByType.Keys)
                    {
                        if (!fuelUsageByType.TryGetValue(fuelType, out FuelUsage fuelUsage))
                        {
                            fuelUsage = new FuelUsage();
                            fuelUsageByType.Add(fuelType, fuelUsage);
                        }
                        fuelUsage.Distance += detailDistance;
                    }
                    totalDistance += detailDistance;
                }
            }

            // Calculate fuel economy for each fuel type. Fill in fuel usage per jurisdiction and fuel type.
            foreach (var typeVolume in fuelPurchasedByType)
            {
                FuelType fuelType    = typeVolume.Key;
                double   totalVolume = typeVolume.Value;
                double   volumePerKm = totalVolume / totalDistance;
                foreach (var fuelUsageByType in fuelUsageByJurisdiction.Values)
                {
                    FuelUsage fuelUsage = fuelUsageByType[fuelType];
                    fuelUsage.FuelUsed    = volumePerKm * fuelUsage.Distance;
                    fuelUsage.FuelEconomy = volumePerKm * 100;
                }
            }
            return(fuelUsageByJurisdiction);
        }