/**
* The planting library includes management operations related to artificial and natural regeneration.
*
* Commonalities....
* @class planting
* @memberof abe-lib
*/
lib.planting = {};
/**
* Schedules a general planting activity.
*
* @method general
* @param {Object} options Options for configuring the general planting.
* @param {String} options.id A unique identifier for the activity (default: 'planting').
* @param {Number} options.schedule The planting schedule (default: 1).
* @param {String} options.species The species to plant. **Mandatory**.
* @param {Number} options.spacing The spacing between plants (default: 10).
* @param {Number} options.fraction The fraction of the area to be planted (default: 1).
* @param {Number} options.treeHeight Initial tree height (default: 0.3).
* @param {String|undefined} options.pattern Planting pattern (e.g., "rect2"). (default: undefined).
* @param {Boolean} options.clear Whether to clear existing vegetation before planting (default: false).
* @param {string|undefined} options.sendSignal signal send after planting activity (default: undefined).
* @param {string|undefined} options.constraint constraint (default: undefined).
* @example
* // NOTE: lib.planting is an object, not a class. No need to use 'new'
* lib.planting.general({
* schedule: 2,
* species: 'Pine',
* spacing: 12,
* pattern: 'rect'
* });
*/
lib.planting.general = function(options) {
const defaultOptions = {
id: 'planting',
schedule: 1,
species: undefined,
spacing: 10,
fraction: 1,
treeHeight: 0.3,
pattern: undefined, //"rect2"
clear: false,
sendSignal: undefined,
constraint: undefined
};
const opts = lib.mergeOptions(defaultOptions, options || {});
if (opts.species === undefined)
throw Error("Planting: species is mandatory");
const plant = {
type: 'planting',
id: opts.id,
schedule: opts.schedule,
random: false,
items: [ {
species: opts.species,
fraction: opts.fraction,
height: opts.treeHeight,
pattern: opts.pattern,
spacing: opts.spacing,
clear: opts.clear
}],
onEvaluate: function() { // Space for quitting planting if condition is met
Globals.alert("Planting whoooohoooo");
return true;
},
onExit: function() {
if (opts.sendSignal !== undefined) {
lib.dbg(`Signal: ${opts.sendSignal} emitted.`);
stand.stp.signal(opts.sendSignal);
}
}
}
if (opts.constraint !== undefined) plant.constraint = opts.constraint;
return plant;
}
/*lib.planting.stripwise = function(options) {
const defaultOptions = {
schedule: 1,
species: 'abal',
spacing: 10,
fraction: 1,
treeHeight: 2,
pattern: undefined, //"rect2"
clear: false
};
const opts = mergeOptions(defaultOptions, options || {});
const plant = {
type: 'planting',
schedule: opts.schedule,
random: false,
items: [ {
species: opts.species,
fraction: opts.fraction,
height: opts.treeHeight,
pattern: opts.pattern,
spacing: opts.spacing,
clear: opts.clear}],
onEvaluate: function() { // Space for quitting planting if condition is met
return true
}
}
return plant;
}; */
/*
lib.planting = function(options) {
// 1. Input Validation
// - Validate options.species
// - Validate options.spacing
const defaultOptions = {
spacing: 10,
species: 'pine'
// ... add other default thinning parameters
};
const opts = mergeOptions(defaultOptions, options || {});
// 2. Planting Area Calculation (Hypothetical)
const totalArea = 100; // calculateAvailableArea(options); // Assume you have such a function
// 3. Individual Activity Generation
const plantingActivities = [];
const seedlingsPerActivity = opts.spacing * 2;
for (let areaOffset = 0; areaOffset < totalArea; areaOffset += seedlingsPerActivity) {
plantingActivities.push({
type: 'planting', // Assuming all activities are of type 'planting'
species: opts.species,
spacing: opts.spacing
});
}
return plantingActivities;
}*/
/**
* Dynamically schedules planting activities based on provided options.
*
* @method dynamic
* @param {Object} options Options for configuring the dynamic planting.
* @param {String} options.id A unique identifier for the planting activity (default: 'planting').
* @param {Schedule} options.schedule The planting schedule.
* @param {Object|Function} options.speciesSelectivity Defines the target species and their relative weights.
* This can be an object with species as keys and weights as values, or a function that returns such an object.
* @param {Object} options.speciesDefaults Default settings for species (default: lib.planting.speciesDefaults).
* @param {string} options.patches should planting should happen only on some selected patches (default: undefined).
* @param {string|undefined} options.sendSignal signal send out after final femel harvest activity (default: undefined).
* @param {string|undefined} options.constraint constraint (default: undefined).
*/
lib.planting.dynamic = function(options) {
const defaultOptions = {
id: 'planting',
schedule: undefined,
speciesSelectivity: undefined, ///< species to plant SHOULD WE NAME IT SPECIES LIKE ABOVE?
speciesDefaults: lib.planting.speciesDefaults,
patches: undefined,
sendSignal: undefined,
constraint: undefined
};
const opts = lib.mergeOptions(defaultOptions, options || {});
// dynamic generation of planting items
function buildDynamicItems() {
// read species selectivty from stand meta data....
let species_list = {};
if (opts.speciesSelectivity !== undefined) {
if (typeof opts.speciesSelectivity === 'function')
species_list = opts.speciesSelectivity.call(opts);
else
species_list = opts.speciesSelectivity;
} else if (typeof stand.obj !== undefined && stand.obj.hasOwnProperty('speciesSelectivity')) {
species_list = stand.obj.speciesSelectivity;
} else {
throw new Error('dynamic planting: no speciesSelectivty provided. Either set as option or in stand.obj!')
}
let items=[];
// loop over each species and fetch the default planting item per species
// update the dynamic element (e.g. the number of planting patches)
for (species in species_list) {
let species_default = opts.speciesDefaults[species];
if (species_default === undefined)
throw new Error(`Planting-defaults: no data for species ${species}.`);
const evaluatedItem = {};
for (let key in species_default) {
if (typeof species_default[key] === 'function') {
// Call the method defined in the default object
evaluatedItem[key] = species_default[key].call(species_default, species_list[species]);
// console.log(`function called with ${species_list[species]}: result: ${evaluatedItem[key]} `);
} else {
evaluatedItem[key] = species_default[key];
}
}
if (opts.patches !== undefined) {
evaluatedItem['patches'] = opts.patches;
};
items.push( evaluatedItem );
}
return items;
}
// build the ABE activity
const plant = {
type: 'planting',
id: opts.id,
schedule: opts.schedule,
random: false,
items: [],
onExecute: function() {
// build planting items dynamically and run them
let items = buildDynamicItems();
items.forEach(item => {
fmengine.runPlanting(stand.id, item);
});
//lib.activityLog('planting');
},
buildItems: function() {
// this is a test function to view resulting planting items
let items = buildDynamicItems();
return items;
},
onExit: function() {
lib.dbg(`planting activity done.`)
if (opts.sendSignal !== undefined) {
lib.dbg(`Signal: ${opts.sendSignal} emitted.`);
stand.stp.signal(opts.sendSignal);
}
},
description : `Planting; species and properties are determined dynamically`
}
if (opts.constraint !== undefined) plant.constraint = opts.constraint;
return plant;
}
// defaults per species
/**
* Default planting settings for various species.
*
* @property speciesDefaults
*
* @type {Object}
* @example
* // Accessing default settings for 'piab' (spruce)
* const spruceDefaults = lib.planting.speciesDefaults.piab;
* console.log(spruceDefaults);
* // Output: { species: 'piab', h: 0.3, age: 1, fraction: [Function] }
*/
lib.planting.speciesDefaults = {
// spruce, .... use wall-to-wall planting
'piab': { species: 'piab', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 1 }},
'psme': { species: 'psme', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 1 }},
'abal': { species: 'abal', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 0.8 }},
'fasy': { species: 'fasy', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 0.8 }},
'cabe': { species: 'cabe', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 0.8 }},
'tico': { species: 'tico', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 0.8 }},
'bepe': { species: 'bepe', h: 0.3, age: 1, fraction: function(proportion) {return proportion * 0.8 }},
// for larix, ... use groups
'lade': { species: 'lade', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'quro': { species: 'quro', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'qupe': { species: 'qupe', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'acps': { species: 'acps', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'soau': { species: 'soau', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'saca': { species: 'saca', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'rops': { species: 'rops', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'frex': { species: 'frex', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'casa': { species: 'casa', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
'pisy': { species: 'pisy', h: 0.3, age: 1, pattern: 'circle10', random: true,
n: function(proportion) {return proportion*10000/272; /* 272: area circle10 */} },
};