Category:Plugins

From Heureka Wiki
Jump to navigation Jump to search

It is possible to replace some of Heureka's built-in functions with your own plugins. You need to program such functions in C#. When you have built a plugin-function, you can place it in My Dcouments > Heureka > Common > Plugins-folder. After that, when you start a Heureka application (StandWise, PlanWise or RegWise), the program will scan this folder for functions that implements a certain so called interface. For example, if there is a plugin (dll) that implements the cost function interface, it will be imported to the application in run-time, and be available as an option the control tables interface.

Plugins are available for the following models:

  • Single-tree growth
  • Stand-level growth
  • Tree volume
  • Tree height
  • Tree height growth
  • Bark thickness
  • Mortality
  • Age to breast height
  • Forwarder and harvester cost (time consumption)

Writing plugins for Heureka

The easiest way to start writing plugins is to use templates that we provide as a starting point PluginTemplateProject.zip. Either open the included project in Visual Studio 2015 or later, or start a new project and include only the required templates. All templates need a reference to 'Slu.Heureka.BaseLayer.dll' and to 'Slu.Heureka.DomainLayer.dll'. This library can be found with all installations of Heureka in the root of the installation directory.

Comments in the templates will tell you what Heureka expects as a return value from the functions. If a plugin needs to read settings that the user has made to, for example, the ProductionModel control table, Heureka will supply this to the plugin constructor if it finds a matching constructor that accepts a single ProductionModelControlTable as an argument. Otherwise the parameterless constructor will be used. At least one of these two constructors MUST be available.

If you want to supply custom arguments to the time consumption model, you can use the property CustomFunctionParameters, which is similar to a C# dictionary consisting of key-value pairs, see below.


IMPORTANT Plugins must have a unique Name string among plugins of the same type. Heureka uses the name to identify which plugin is currently selected.

After building the template project, the plugin .dll files are copied to the solution directory.

Custom cost functions

There are specific functions for different treatments. The plugin interface (ICostCalculator) provided will probably be quite daunting to implement, and a better option is most likely to create a plugin that derives from Heureka's CostCalculatorBase (see below), if you wish to supply custom functions for for example precommercial thinning (cleaning). To add custom functions only for harvesting and forwarding, considered deriving from class SkogforskAdvanced.


ICostCalculator interface

CostCalculatorTemplate code

using System.Collections.Generic;
using Slu.Heureka.BaseLayer;
using Slu.Heureka.DomainLayer.Forest;
using Slu.Heureka.DomainLayer.ValueModel;
 public class CostCalculatorTemplate : ICostCalculator
    {
        //Include this constructor with this signature if the plugin needs to read settings from the control category.
        //If present, this constructor will be used. Otherwise, the empty constructor will be used.
        //public CostCalculatorTemplate(IControlCategory controlCategory)
        //{}

        public CostCalculatorTemplate()
        {
            //Empty constructor required for plugins
        }

        /// <summary>
        /// Get the display name of this plugin
        /// </summary>
        public string Name { get { return "Cost Calculator Template";  } }

        /// <summary>
        /// Calculates the cost of a treatment.
        /// </summary>
        /// <param name="tUnit">Treatment unit that has been treated</param>
        /// <param name="period">Period to calculate cost for</param>
        /// <param name="treatment">Treatment that has been applied</param>
        /// <param name="results">Existing results for treatment unit</param>
        /// <param name="value">Felling value</param>
        /// <returns>Cost of treatment</returns>
        public Cost CalculateCost(TreatmentUnit tUnit, int period, Treatment treatment, IList<Result> results, FellingValue value)
        {
            return null;
        }

        /// <summary>
        /// Optional parameters, displayed in the Cost Control Table
        /// </summary>
        public GenericPropertyArray<double> CustomFunctionParameters { get { return null; } set {} }
    }

CostCalculatorBase

CostCalculatorBase code

<syntaxhighlight2 lang="csharp">

using System;

using System.Collections.Generic; using Slu.Heureka.BaseLayer; using Slu.Heureka.BaseLayer.ConfigurationHandling; using Slu.Heureka.DomainLayer.CommonForestModels; using Slu.Heureka.DomainLayer.Recreation; using Slu.Heureka.DomainLayer.TreatmentModel; using Slu.Heureka.DomainLayer.Forest; using Slu.Heureka.DomainLayer.TreatmentModel.Thinning;

namespace Slu.Heureka.DomainLayer.ValueModel {

   /// <summary>
   /// Provides a base class for CostCalculator classes
   /// </summary>
   public class CostCalculatorBase : ICostCalculator
   {
       Treatment _treatment;
       int _period;
       private TreatmentUnit _treatmentUnit;
       private CostControlTable _configuration;
       private readonly RecreationControlTable _recreationControlTable;
       private readonly TreatmentModelControlTable _treatmentModelControlTable;


       private double _extractedHarvestResidueMass;
       private double _harvestedStumps;
       SoilMoistureType _soilMoistType;
       private SlopeType _slope;
       private SlopeType _terrainRoadSlope;
       private double _terrainTransportDistance;
       private SurfaceType _surface;
       protected SoilMoistureType SoilMoistType
       {
           get { return _soilMoistType; }
       }
       protected TreatmentUnit TreatmentUnit
       {
           get { return _treatmentUnit; }
       }
       /// <summary>
       /// Gets the treatment cost is calculated for
       /// </summary>
       protected Treatment Treatment
       {
           get { return _treatment; }
           set { _treatment = value; }
       }
       ///// <summary>
       ///// Gets cost control table
       ///// </summary>
       //protected CostControlTable configuration
       //{
       //    get { return Configuration; }
       //}
       /// <summary>
       /// Within stand slope 
       /// </summary>
       protected SlopeType Slope
       {
           get { return _slope; }
           set { _slope = value; }
       }
       /// <summary>
       /// Average slope in the terrain road (may include slope of neighboring stands that the forwarder must drive through)
       /// </summary>
       protected SlopeType TerrainRoadSlope
       {
           get { return _terrainRoadSlope; }
           set { _terrainRoadSlope = value; }
       }
       protected double TerrainTransportDistance
       {
           get { return _terrainTransportDistance; }
           set { _terrainTransportDistance = value; }
       }
       protected SurfaceType Surface
       {
           get { return _surface; }
           set { _surface = value; }
       }
       protected CostControlTable Configuration
       {
           get { return _configuration; }
       }
       protected RecreationControlTable RecreationControlTable
       {
           get { return _recreationControlTable; }
       }
       protected TreatmentModelControlTable TreatmentModelControlTable
       {
           get { return _treatmentModelControlTable; }
       }
       public double ExtractedHarvestResidueMass
       {
           get { return _extractedHarvestResidueMass; }
           set { _extractedHarvestResidueMass = value; }
       }
       public double HarvestedStumps
       {
           get { return _harvestedStumps; }
           set { _harvestedStumps = value; }
       }
       /// <summary>
       /// Represents a base class for CostCalculator classes
       /// </summary>
       /// <param name="us"></param>
       internal CostCalculatorBase(IControlCategory us)
       {
           _configuration = (CostControlTable)us.GetControlTable(typeof(CostControlTable));
           if (Configuration == null)
               throw new ArgumentException("Configuration for Cost has not been initialized");
           _recreationControlTable = (Recreation.RecreationControlTable)us.GetControlTable(typeof(Recreation.RecreationControlTable));
           if (RecreationControlTable == null)
               throw new ArgumentException("Configuration for Recreation has not been initialized");
           _treatmentModelControlTable = (TreatmentModelControlTable)us.GetControlTable(typeof(TreatmentModelControlTable));
           if (TreatmentModelControlTable == null)
               throw new ArgumentException("Configuration for TreatmentModel has not been initialized");
       }
       /// <summary>
       /// To enable alternative constructor for tests of derived class
       /// </summary>
       protected CostCalculatorBase()
       {
       }
       /// <summary>
       /// Gets cost of extracting harvest residues after thinning
       /// </summary>
       /// <returns></returns>
       protected virtual double HarvestResidueExtractionCostThinning()
       {
           if (Treatment.CuttingDetails.HarvestResiduesExtracted)
               //return configuration.HarvestResidueThinningCost * _extractedHarvestResidueMass;
               return ExtractedHarvestResidueMass *
                  (Configuration.HarvestResidueExtractionCostThinning.A0 +
                   Configuration.HarvestResidueExtractionCostThinning.A1 * _terrainTransportDistance); // Do not adjust for cost trend factor here, only adjust in methods of type Cost
           return 0.0;
       }
       /// <summary>
       /// Gets cost of extracting harvest residues after final felling
       /// </summary>
       /// <returns></returns>
       protected virtual double HarvestResidueExtractionFinalFelling()
       {
           if (Treatment.CuttingDetails.HarvestResiduesExtracted)
               //return configuration.HarvestResidueFinalFellingCost * _extractedHarvestResidueMass;
               return ExtractedHarvestResidueMass *
                      (Configuration.HarvestResidueExtractionCostFinalFelling.A0 +
                       Configuration.HarvestResidueExtractionCostFinalFelling.A1 * _terrainTransportDistance); // Do not adjust for cost trend factor here, only adjust in methods of type Cost
           return 0.0;
       }
       /// <summary>
       /// Gets cost of extracting stumps
       /// </summary>
       /// <returns></returns>
       protected virtual double StumpExtractionCost()
       {
           if (Treatment.CuttingDetails.HarvestResiduesExtracted)
               return HarvestedStumps *
                      (Configuration.StumpHarvestCostCoefficients.A0 +
                       Configuration.StumpHarvestCostCoefficients.A1 * _terrainTransportDistance); // Do not adjust for cost trend factor here, only adjust in methods of type Cost
           return 0.0;
       }
       /// <summary>
       /// Calulates cost of planting.
       /// </summary>
       /// <returns>Cost of planting, SEK/ha</returns>
       protected virtual Cost PlantingCost()
       {
           double treatedArea = _treatment.TreatedArea.HasValue ? _treatment.TreatedArea.Value : 1.0;
           double corrFactor = Configuration.TreatmentCostTrend[TreatmentCostTrendType.Planting, TreatmentYear];
           // Number of plants not known or price per sapling is not known
           SpeciesGroupCode plantedSpecies = Treatment.RegenerationDetails.SpeciesGroup;
           if (Treatment.RegenerationDetails == null || Treatment.RegenerationDetails.NumberOfSaplings <= double.Epsilon || Configuration.SaplingCostPlant[plantedSpecies] <= double.Epsilon)
               return new Cost(Configuration.PlantCostArea * corrFactor * treatedArea);
           double c = Treatment.RegenerationDetails.NumberOfSaplings * Configuration.SaplingCostPlant[plantedSpecies] * corrFactor * treatedArea;
           return new Cost(c);
       }
       /// <summary>
       /// Calculates cost of fertilization.
       /// </summary>
       /// <returns>Cost of fertilization, SEK/ha</returns>
       protected virtual Cost FertilizationCost()
       {
           if (_treatment.FertilizationDetails.Intensive)
               return new Cost(Configuration.IntensiveFertilizationCostArea);
           if (_treatmentUnit.PredictionUnits.Count == 0)
               return new Cost(0.0);
           double treatedArea = _treatment.TreatedArea.HasValue ? _treatment.TreatedArea.Value : 1.0;
           double cost = treatedArea *
                         (Configuration.FertilizationFixedCost +
                          TreatmentModelControlTable.FertilizerAmount *
                          Configuration.FertilizerUnitCost) *
                         Configuration.TreatmentCostTrend[TreatmentCostTrendType.Fertilization, TreatmentYear];
           return new Cost(cost);
       }
       protected double TreatmentYear
       {
           get { return _treatmentUnit.GetYear(_period) + Treatment.YearOffset; }
       }
       /// <summary>
       /// Calculates cost of burning
       /// </summary>
       /// <returns>Cost of burning, SEK/ha</returns>
       protected virtual Cost BurningCost()
       {
           return new Cost(Configuration.BurningCost * Configuration.TreatmentCostTrend[TreatmentCostTrendType.Burning, TreatmentYear]);
       }
       /// <summary>
       /// Calculates cost of ground preparation
       /// </summary>
       /// <returns>Cost of ground preparation, SEK/ha</returns>
       protected virtual Cost PreparationCost()
       {
           double treatedArea = _treatment.TreatedArea.HasValue ? _treatment.TreatedArea.Value : 1.0;
           return new Cost(Configuration.PreparationCost * Configuration.TreatmentCostTrend[TreatmentCostTrendType.SoilPreparation, TreatmentYear] * treatedArea);
       }


       /// <summary>
       /// Gets cost of sowing
       /// </summary>
       /// <returns></returns>
       protected virtual Cost SowingCost()
       {
           return new Cost(Configuration.SeedCostArea * Configuration.TreatmentCostTrend[TreatmentCostTrendType.Sowing, TreatmentYear]);
       }
       /// <summary>
       /// Retrieves forwarder capacity for thinning
       /// </summary>
       /// <param name="thinNumber">Thinning number</param>
       /// <returns>capacity in ton</returns>
       protected virtual double GetForwarderCapacityThinning(ThinningNumber thinNumber)
       {
           return thinNumber == ThinningNumber.First ? Configuration.FirstThinningCapacity : Configuration.LaterThinningCapacity;
       }
       /// <summary>
       /// Retrieves forwarder capacity when doing final felling
       /// </summary>
       /// <returns>capcity in ton</returns>
       protected virtual double GetForwarderCapacityFinalFelling()
       {
           return Configuration.FinalFellingCapacity;
       }
       /// <summary>
       /// Retrievs thinning number for a TreatmentUnit
       /// </summary>
       /// <param name="tUnit">TreatmentUnit</param>
       /// <param name="currentPeriod">The current period</param>
       /// <returns>ThinningNumber</returns>
       protected ThinningNumber GetThinningNumber(TreatmentUnit tUnit, int currentPeriod)
       {
           switch (tUnit.Treatments.Occurences(currentPeriod, TreatmentCollection.Thinning, TreatmentCollection.Regeneration))
           {
               case 1:
                   return ThinningNumber.First;
               case 2:
                   return ThinningNumber.Second;
               default:
                   return ThinningNumber.Later;
           }
       }
       /// <summary>
       /// Returns the first treatment with cutting treatment
       /// </summary>
       /// <param name="treatmentInfo"></param>
       /// <returns></returns>
       protected int FindFirstCuttingTreatment(object[] treatmentInfo)
       {
           // Find first treatment with cutting treatment
           int index = 0;
           for (int i = 0; i < treatmentInfo.Length; i++)
           {
               TreatmentType tType = ((TreatmentData)treatmentInfo[i]).Treatment;
               if ((tType & TreatmentCollection.Felling) > 0)
               {
                   index = i;
                   break;
               }
           }
           return index;
       }
       /// <summary>
       /// Gets treatment unit data
       /// </summary>
       /// <param name="tUnit"></param>
       /// <param name="period"></param>
       /// <param name="results"></param>
       /// <param name="value">Felling value</param>
       protected virtual void Initialize(TreatmentUnit tUnit, int period, Treatment treatment, IList<Result> results, FellingValue value)
       {
           _soilMoistType = tUnit.SiteData.SoilMoistCode;
           _treatment = treatment;
           _treatmentUnit = tUnit;
           _period = period;

           _terrainTransportDistance = (Configuration.TerrainTransportDistanceDataSource == DataSource.DefaultValue || _treatmentUnit.StandObjectData == null) ?
               Configuration.TerrainTransportDistance :
               _terrainTransportDistance = _treatmentUnit.StandObjectData.TerrainTransportDistance.GetValueOrDefault((int)Configuration.TerrainTransportDistance);
           _surface = (Configuration.SurfaceDataSource == DataSource.DefaultValue || _treatmentUnit.StandObjectData == null) ?
               Configuration.Surface :
               _treatmentUnit.StandObjectData.Surface.GetValueOrDefault(Configuration.Surface);
           if ((int)_surface < 1)
               _surface = Configuration.Surface;
           _slope = (Configuration.SlopeDataSource == DataSource.DefaultValue) ?
               Configuration.Slope :
               _treatmentUnit.SiteData.Slope.GetValueOrDefault(Configuration.Slope);
           if ((int)_slope < 1)
               _slope = Configuration.Slope;
           _terrainRoadSlope = _treatmentUnit.StandObjectData == null ? _slope:
               _terrainRoadSlope = _treatmentUnit.StandObjectData.TerrainRoadSlope.GetValueOrDefault(_slope);
           if ((int)_terrainRoadSlope < 1)
               _terrainRoadSlope = _slope;
           _soilMoistType = _treatmentUnit.SiteData.SoilMoistCode;
       }
       /// <summary>
       /// Calculates the cost of a treatment.
       /// </summary>
       /// <param name="tUnit">Treatment unit that has been treated</param>
       /// <param name="period">Period to calculate cost for</param>
       /// <param name="treatment">Treatment that has been applied</param>
       /// <param name="results">Existing results for treatment unit</param>
       /// <param name="value">Felling value</param>
       /// <returns>Cost of treatment</returns>
       public virtual Cost CalculateCost(TreatmentUnit tUnit, int period, Treatment treatment, IList<Result> results, FellingValue value)
       {
           Initialize(tUnit, period, treatment, results, value);
           var trmtType = treatment.TreatmentType;
           switch (trmtType)
           {
               case TreatmentType.Planting:
                   return PlantingCost();
               case TreatmentType.Sowing:
                   return SowingCost();
               case TreatmentType.Fertilization:
                   return FertilizationCost();
               case TreatmentType.SoilPreparation:
                   return PreparationCost();
               case TreatmentType.Burning:
                   return BurningCost();
               default:
                   return new Cost(0.0);
           }
       }
       public virtual GenericPropertyArray<double> CustomFunctionParameters { get; set; } = null;
       public virtual string Name { get { return string.Empty; } }
   }

}

</syntaxhighlight2>

Creating a plugin that derives from CostCalculatorBase

Creating a plugin that derives from time consumption function SkogforskAdvanced

Below is a code example for a plugin that derives from Heureka's default time consumption function for harvesting and forwarding (SkogforskAdvanced).

Getting Heureka to load the plugins

Plugins are loaded from the current user's \Documents\Heureka\Common\Plugins directory. If successful, the plugin will show up in the drop-down box for that particular feature.

If a plugin failed to load, an error will show up in the 'General' output window with information of what went wrong.

This category currently contains no pages or media.