-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPlugIn.cs
322 lines (284 loc) · 15.5 KB
/
PlugIn.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
// Author: Clément Hardy
using Landis.Library.UniversalCohorts;
using Landis.Core;
using Landis.SpatialModeling;
using System.Collections.Generic;
using System.IO;
using Landis.Library.Metadata;
using System;
using System.Diagnostics;
namespace Landis.Extension.ForestRoadsSimulation
{
public class PlugIn
: ExtensionMain
{
// Properties of the Plugin class : type (disturbance), name (Forest Roads Simulation),
// and several other used for the output of data and the reading of its parameters.
// The PlugIn class needs 4 functions : a constructor, LoadParameters, Initialize et Run
public static readonly ExtensionType ExtType = new ExtensionType("disturbance:roads");
public static readonly string ExtensionName = "Forest Roads Simulation";
private bool harvestExtensionDetected = false;
private List<RelativeLocation> skiddingNeighborhood;
private List<RelativeLocation> minLoopingNeighborhood;
private List<RelativeLocation> maxLoopingNeighborhood;
public static MetadataTable<RoadLog> roadConstructionLog;
public static string errorToGithub = " If you cannot solve the issue, please post it on the Github repository and I'll try to help : https://github.com/Klemet/LANDIS-II-Forest-Roads-Simulation-module";
// Properties to contain the parameters
private static IInputParameters parameters;
// Properties to contain the "Core" object of LANDIS-II to reference it in other functions.
private static ICore modelCore;
//---------------------------------------------------------------------
// Constructor of the PlugIn class. Heritates from the construction of the "ExtensionMain" class.
// It just fills the properties containing the type of the extension and its name : nothing else.
public PlugIn()
: base(ExtensionName, ExtType)
{
}
//---------------------------------------------------------------------
// Properties to get the Model Core in read-only
public static ICore ModelCore
{
get
{
return modelCore;
}
}
//---------------------------------------------------------------------
// Property to contain the parameters in read-only
public static IInputParameters Parameters
{
get
{
return parameters;
}
}
//---------------------------------------------------------------------
// Function launched at the beginning of the LANDIS-II simulation to initialize the parameters of the extension.
// It requires a reference to the .txt file where the parameters are.
public override void LoadParameters(string dataFile, ICore mCore)
{
modelCore = mCore;
// We initialize the site variables object
SiteVars.Initialize();
// We read the parameters in the .txt file
InputParameterParser parser = new InputParameterParser();
parameters = Landis.Data.Load<IInputParameters>(dataFile, parser);
modelCore.UI.WriteLine(" Parameters of the Forest Roads Simulation Extension are loaded");
// For debugging purposes
// InputParameterParser.DisplayParameters();
}
//---------------------------------------------------------------------
// Function launched before the simulation starts properly. Used to initialize other things.
// In the case of this module, it's use to initialize the road network by checking different things.
public override void Initialize()
{
modelCore.UI.WriteLine(" Initialization of the Forest Roads Simulation Extension...");
Timestep = parameters.Timestep;
modelCore.UI.WriteLine(" Reading the rasters...");
// We read all of the maps.
MapManager.ReadAllMaps();
// Testing if a harvest extension is included in the scenario and thus initialized
if (modelCore.GetSiteVar<int>("Harvest.TimeOfLastEvent") != null) { this.harvestExtensionDetected = true; modelCore.UI.WriteLine("Harvest extension correctly detected."); }
else
{
modelCore.UI.WriteLine(" FOREST ROAD SIMULATION WARNING : NO HARVEST EXTENSION DETECTED");
modelCore.UI.WriteLine(" Without a harvest extension, no roads will be created by this extension. Please include a harvest extension in your scenario, or this extension will be quite useless.");
}
// We check if some roads are not connected to sawmills or to a main public network.
modelCore.UI.WriteLine(" Checking if the wood has somewhere to go...");
if (!MapManager.IsThereAPlaceForTheWoodToGo(ModelCore))
{
throw new Exception(" FOREST ROAD SIMULATION WARNING : There is no site to which the road can flow to (sawmill or main road network). " +
" Please put at least one in the input raster containing the initial road network.");
}
// If some exist, we initialize the roadnetwork to indicate which road is connected to them, and to connect the roads that might be isolated.
else
{
// We initialize the "cost raster" on which the path of our roads will be based.
MapManager.CreateCostRaster();
modelCore.UI.WriteLine(" Cost raster created. It can be visualised in the output folder of the extension.");
// We initialize the initial road network
modelCore.UI.WriteLine(" Initializing the road network...");
RoadNetwork.Initialize(ModelCore, parameters.HeuristicForNetworkConstruction);
// We initialize the relative locations that will have to be checked to see if their is a road in it at skidding distance from a site.
skiddingNeighborhood = MapManager.CreateSearchNeighborhood(parameters.SkiddingDistance, modelCore);
modelCore.UI.WriteLine(" Skidding neighborhood initialized. It contains " + skiddingNeighborhood.Count + " relative locations.");
// If the loop behavior is activated, we will create a search neighborhood to create loops
if (parameters.LoopingBehavior)
{
minLoopingNeighborhood = MapManager.CreateSearchNeighborhood(parameters.LoopingMinDistance, modelCore);
maxLoopingNeighborhood = MapManager.CreateSearchNeighborhood(parameters.LoopingMaxDistance, modelCore);
modelCore.UI.WriteLine(" Smaller looping neighborhood initialized. It contains " + minLoopingNeighborhood.Count + " relative locations.");
modelCore.UI.WriteLine(" Bigger looping neighborhood initialized. It contains " + maxLoopingNeighborhood.Count + " relative locations.");
}
// We initialize the metadatas
MetadataHandler.InitializeMetadata();
// If we are going to simulate the wood flux, we initialize objects important for it.
if (parameters.SimulationOfWoodFlux)
{
RoadNetwork.fluxPathCatalogue = new List<FluxPath>();
RoadNetwork.fluxPathDictionary = new Dictionary<Site, FluxPath>();
}
// We output the map at timestep 0. Can be usefull.
MapManager.WriteMap(parameters.OutputsOfRoadNetworkMaps, modelCore);
}
modelCore.UI.WriteLine(" Initialization of the Forest Roads Simulation Extension is done");
}
// Function called at every time step where the extension is activated.
// Contains the effects of the extension on the landscape.
public override void Run()
{
// We give a warning back to the user if no harvest extension is detected
if (!this.harvestExtensionDetected)
{
modelCore.UI.WriteLine(" FOREST ROAD SIMULATION WARNING : NO HARVEST EXTENSION DETECTED");
modelCore.UI.WriteLine(" Without a harvest extension, no roads will be created by this extension. Please include a harvest extension in your scenario, or this extension will be quite useless.");
}
// If not, we do what the extension have to do at its timestep : for each recently harvested site, we'll try to build a road that lead to it if needed.
else if (this.harvestExtensionDetected)
{
// 1) We age all of the roads in the landscape, if aging is simulated. Those that are too old will be considered destroyed.
List<Site> listOfSitesWithRoads;
if (parameters.SimulationOfRoadAging)
{
listOfSitesWithRoads = MapManager.GetSitesWithRoads(ModelCore);
foreach (Site siteWithRoad in listOfSitesWithRoads)
{
SiteVars.RoadsInLandscape[siteWithRoad].agingTheRoad(siteWithRoad);
}
// 2) We update the status of all the roads concerning their connection to an exit point (sawmill or main road network); so that the pathfinding algorithms can now when to stop afterward.
ModelCore.UI.WriteLine(" Looking to see if the roads can go to a exit point (sawmill, main road network)...");
listOfSitesWithRoads = MapManager.GetSitesWithRoads(ModelCore);
RoadNetwork.UpdateConnectionToExitPointStatus();
}
// 3) We get all of the sites for which a road must be constructed
modelCore.UI.WriteLine(" Getting sites recently harvested...");
List<Site> listOfHarvestedSites = MapManager.GetAllRecentlyHarvestedSites(ModelCore, Timestep);
// 4) We shuffle the list according to the heuristic given by the user.
modelCore.UI.WriteLine(" Shuffling sites according to the heuristic...");
listOfHarvestedSites = MapManager.ShuffleAccordingToHeuristic(ModelCore, listOfHarvestedSites, parameters.HeuristicForNetworkConstruction);
// 5) We reset the wood flux objects and data for the current timestep, if wood flux is sumlated
if (parameters.SimulationOfWoodFlux)
{
modelCore.UI.WriteLine(" Reseting wood flux data...");
RoadNetwork.RestTimestepWoodFluxData();
}
// 6) We initialize some UI elements because this step takes time, and set the cost of construction/repairs at this timestep to 0.
modelCore.UI.WriteLine(" Number of recently harvested sites : " + listOfHarvestedSites.Count);
modelCore.UI.WriteLine(" Generating roads to harvested sites...");
var progressBar = modelCore.UI.CreateProgressMeter(listOfHarvestedSites.Count);
var watch = System.Diagnostics.Stopwatch.StartNew();
int roadConstructedAtThisTimestep = 0;
RoadNetwork.costOfConstructionAndRepairsAtTimestep = 0;
// 7) We construct the roads to each harvested site
foreach (Site harvestedSite in listOfHarvestedSites)
{
// We construct the road only if the cell is at more than the given skidding distance by the user from an existing road.
if (!MapManager.IsThereANearbyRoad(skiddingNeighborhood, harvestedSite))
{
// If the looping behavior is activated, we will check if we should do a loop.
if (parameters.LoopingBehavior)
{
// To create a normal road if one of the conditions fail
bool conditionsForLoop = true;
// We don't create a loop if there are roads that are too close
int roadsInSmallNeighborhood = MapManager.HowManyRoadsNearby(minLoopingNeighborhood, harvestedSite);
if (roadsInSmallNeighborhood > 0) { conditionsForLoop = false; }
else
{
// We don't create a loop if there are no roads close enough, at least 2 to make a loop
int roadsInLargeNeighborhood = MapManager.HowManyRoadsNearby(maxLoopingNeighborhood, harvestedSite);
if (roadsInLargeNeighborhood < 2) { conditionsForLoop = false; }
else
{
// We don't create a loop if there are too many roads nearby
double percentageOfRoadsAround = (double)((double)roadsInLargeNeighborhood * 100.0) / (double)maxLoopingNeighborhood.Count;
if (percentageOfRoadsAround > parameters.LoopingMaxPercentageOfRoads) { conditionsForLoop = false; }
else
{
// Now, we let the dijkstra function for the loop try to create a loop.
// However, if the loop is too costly to build or if the probability isn't right, the loop will not be constructed (see inside this function)
int numberOfRoadsCreated = DijkstraSearch.DijkstraLeastCostPathWithLooping(ModelCore, harvestedSite, minLoopingNeighborhood, parameters.LoopingMaxCost);
roadConstructedAtThisTimestep += numberOfRoadsCreated;
}
}
}
// If one of the conditions to make the loop has failed, we create a normal road.
if (!conditionsForLoop)
{
DijkstraSearch.DijkstraLeastCostPathToClosestConnectedRoad(ModelCore, harvestedSite);
roadConstructedAtThisTimestep++;
}
}
// If no looping behavior, we just create the least-cost road to the site.
else
{
DijkstraSearch.DijkstraLeastCostPathToClosestConnectedRoad(ModelCore, harvestedSite);
roadConstructedAtThisTimestep++;
}
}
progressBar.IncrementWorkDone(1);
}
watch.Stop();
modelCore.UI.WriteLine(" At this timestep, " + roadConstructedAtThisTimestep + " roads were built");
modelCore.UI.WriteLine(" The construction took " + (watch.ElapsedMilliseconds / 1000) + " seconds.\n");
// 8) If woodflux is simulated, We add the woodflux from the harvested area to the closest road, and we then used a dijkstra
// search that only goes through roads in order to reach an exit point for the wood. Every road used will see its flux value
// for the timestep augment by one. The quantity of woodflux is based on the number of cohorts harvested in the cell.
if (parameters.SimulationOfWoodFlux)
{
modelCore.UI.WriteLine(" Fluxing the wood to exit points...");
progressBar = modelCore.UI.CreateProgressMeter(listOfHarvestedSites.Count);
int sitesFluxedAtThisTimestep = 0;
watch.Restart();
ISiteVar<int> numberOfCohortsDamaged = modelCore.GetSiteVar<int>("Harvest.CohortsDamaged");
foreach (Site harvestedSite in listOfHarvestedSites)
{
int siteFlux = numberOfCohortsDamaged[harvestedSite];
Site siteWithClosestRoad = MapManager.GetClosestSiteWithRoad(skiddingNeighborhood, harvestedSite);
// Case of the siteWithClosestRoad being a fluxpath : we just flux this path.
if (RoadNetwork.fluxPathDictionary.ContainsKey(siteWithClosestRoad))
{
RoadNetwork.fluxPathDictionary[siteWithClosestRoad].FluxPathFromSite(siteWithClosestRoad, siteFlux);
}
// If not, we create a flux path.
else
{
DijkstraSearch.DijkstraWoodFlux(PlugIn.ModelCore, siteWithClosestRoad, siteFlux);
}
sitesFluxedAtThisTimestep++;
progressBar.IncrementWorkDone(1);
}
modelCore.UI.WriteLine(" At this timestep, the wood of " + sitesFluxedAtThisTimestep + " sites was fluxed.");
modelCore.UI.WriteLine(" The fluxing took " + (watch.ElapsedMilliseconds / 1000) + " seconds.\n");
}
// 9) We finish by upgrading the types of the roads if they have to be according to the woodflux for the timestep, and the parameters given by the user, if woodflux is simulated.
if (parameters.SimulationOfWoodFlux)
{
listOfSitesWithRoads = MapManager.GetSitesWithRoads(ModelCore);
foreach (Site siteWithRoad in listOfSitesWithRoads)
{
SiteVars.RoadsInLandscape[siteWithRoad].UpdateAccordingToWoodFlux(siteWithRoad);
}
}
// 10) We write the output maps
MapManager.WriteMap(parameters.OutputsOfRoadNetworkMaps, modelCore);
if (parameters.SimulationOfWoodFlux) { MapManager.WriteMap(parameters.OutputsOfRoadNetworkMaps, modelCore, "WoodFlux"); }
// 11) We write the log, and we're done for this timestep.
roadConstructionLog.Clear();
RoadLog roadLog = new RoadLog();
roadLog.Timestep = modelCore.CurrentTime;
roadLog.NumberOfHarvestedSitesToConnect = listOfHarvestedSites.Count;
roadLog.NumberOfRoadsConstructed = roadConstructedAtThisTimestep;
roadLog.TimeTaken = (int)watch.ElapsedMilliseconds / (1000 * 60);
roadLog.CostOfConstructionAndRepairs = RoadNetwork.costOfConstructionAndRepairsAtTimestep;
roadConstructionLog.AddObject(roadLog);
roadConstructionLog.WriteToFile();
} // End of if harvest extension detected
} // End of run function
public override void AddCohortData()
{
return;
}
} // End of PlugIn class
} // End of namespace