A key aspect of Envision's architecture is the ability to define "plug-in" modules that extend Envision's functionality. This is a primary mechanism for incorporating models of landscape change into an Envision application. Plug-ins are generally written in C++ and are coded as Dynamic Link Libraries (DLLs), a mechanism allowing Windows programs to incorporate new functionality into an application at run time. Most Windows-based compilers support the creation of DLLs. Creating Envision-compliant DLL's is a fairly straight-forward process, but does require coding in a language supporting creation of DLL's.


Below, we provide a walk-though of the step required to create an Envision plug-in.  We will be using the Microsoft Visual Studio compiler in the examples below.
To facilitate writing Envision plug-ins, an SDK is available in the “Downloads” section of this site. This SDK includes required include and library files, as well as a set of example files and C++ classes designed to make it relatively easy to write plug-ins. The steps required to create a basic plug-in are described below:

Envision currently supports three types of plug-ins: landscape change models (autonomous processes), evaluators, and visualizers. There are described in the following table:
Landscape Change Models
Landscape change models are the most common type of plug-in in Envision, and are used to implement models the modify the landscape representation in the IDU coverage, and to generate output data collected by Envision during the course of a simulation. They have full access to Envision internals, and can expose input variables (also called scenario variables) that can be set in scenario-specific ways if desired.
Evaluators
Evaluators are very similar to Landscape Change Models, but perform one additional task - returning a set of evaluation metrics that are collected by Envision during the course of a run.
Visualizers
Visualizers are plug-ins whose sole purpose is to provide dynamic visualization of spatial or aspatial data during an Envision simulation run.

In the walk-though below, we will create a basic Envision plug-in that demonstrates the basics of developing your own plug-in, including setting up the approriate entry functions, accessing data available in the IDU coverage, and writing your own data to the IDU coverage, as well as exposing non-spatial data outputs to Envision's runtime data collection system. We will develop a simple Landscape Change Model that takes as input a mapping of fuel load by vegetation type, examines the IDU coverage for current vegetation patterns, populates a corresponding IDU attribute for fire risk, and generates a metric of total fuel load on the landscape for capture by Envision. We will demonstrate the basics of setting up a plug-in, accessing data, and exposing output variables to Envision.

[top]

To complete the walk-through below, you will need the following:

  1. A current version of Envision, installed on our local machine. You can download Envision from this link.
  2. A recent version of the Microsoft Visual Studio C++ Compiler. A free version of this compiler is available from Microsoft at  https://visualstudio.microsoft.com/vs/ - Select the "Community" version for download and install on your local machine.  The walk-through below assumes you have Visual Studio installed on your local machine.
  3. The Envision Software Development Kit (SDK) - available in the “Downloads” section of the Envision web site. This needs to be installed locally on your development machine.
  4. A set of Envision project files. Below, we will be using a project from the Envision tutorials. A setup program for these files is available for download from this link. After downloading, run the setup program to install the necessary tutorial input files.

[top]

In Visual Studio, create a “New Project” of type MFC DLL. In the "Name" input box, put the name of your plug-in. You can adjust the "location" as needed for setup. When you click OK, you will be asked to set the “Application Settings”. Indicate “MFC Extension DLL” and click Finish. The Visual Studio App Wizard will create a skeleton DLL for you, placing the resulting code files in the location you specified above. In particular, note the following files: dllmain.cpp, myproject.cpp, and myproject.def, where myproject is the name of the DLL you are creating. We will modify these files next.


[top]

Visual Studio will generate a 'dllmain.cpp' code file; we will replace this VS-generated file with the one from the Envision SDK. Go to the location where you installed the Envision SDK, and copy the SDK-version of dllmain.cpp to your project directory, overwriting the dllmain.cpp generated by VS. The "new" dllmain.cpp is can be seen at this link.

Because we are only creating a landscape change model plug-in, we can simplify this file substantially. We do this be deleting the portions of the file that are not related to this type of plug-in, specifically all references to evaluators and visualizers. Additionally, note the sections in the oringal file marked "TODO:" - we have made appropriate changes where noted to reflect this specific plug-in.

The resulting dllmain.cpp is shown below. This will be the dllmain.cpp that we use for our plug-in, which we will call "FuelLoad". Copy the code below into your dllmain.cpp file, replacing the existing content. This will be the version we use in our FuelLoad plug-in project.

// dllmain.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include <afxwin.h>
#include <afxdllx.h>
#include <EnvEngine\EnvContext.h>   // point this to your location
#include "FuelLoad.h"    // We will create this file shortly

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

/**** indicate model/process instance pointer ****/
FuelLoad  *theModel = NULL;

/**** indicate DLL Name ****/
static AFX_EXTENSION_MODULE FuelLoadDLL = { NULL, NULL };

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	// Remove this if you use lpReserved
	UNREFERENCED_PARAMETER(lpReserved);

	if (dwReason == DLL_PROCESS_ATTACH)
	{
        // this code runs when the DLL is first loaded

        /**** update trace string with module name ****/
		TRACE0("FuelLoad.DLL Initializing!\n");
		
		// Extension DLL one-time initialization
        if (!AfxInitExtensionModule(FuelLoadDLL, hInstance))
			return 0;

        /**** Create instance of Fuel Model ****/
		new CDynLinkLibrary(FuelLoadDLL);
     
        /*** TODO:  instantiate any models/processes ***/
        ASSERT( theModel == NULL );
        theModel = new FuelLoad;
	}
    
	else if (dwReason == DLL_PROCESS_DETACH)
	{
        // this code runs when the DLL is unleaded on exiting Envision

        /**** Update module name in trace string ****/
		TRACE0("FuelLoad.DLL Terminating!\n");

        /**** Delete instantiated model ****/
        if ( theModel != NULL)
            delete theModel;

		// Terminate the library before destructors are called
		AfxTermExtensionModule(FuelLoadDLL);
	}
	return 1;   // ok
}

//////////////////////////
// API function prototypes
//////////////////////////

extern "C" void PASCAL EXPORT GetExtInfo( ENV_EXT_INFO *pInfo );

// for landscape change model (autonomous process)
extern "C" BOOL PASCAL EXPORT APInit( EnvContext*, LPCTSTR initStr );           // called when DLL loaded
extern "C" BOOL PASCAL EXPORT APInitRun( EnvContext*, bool useInitialSeed );    // called when starting a simulation run
extern "C" BOOL PASCAL EXPORT APRun( EnvContext* );                             // called at each time step (year)
extern "C" int  PASCAL EXPORT APInputVar( int modelID, MODEL_VAR** modelVar );  // only needed if inputs exposed
extern "C" int  PASCAL EXPORT APOutputVar( int modelID, MODEL_VAR** modelVar ); // only needed if outputs exposed
extern "C" BOOL PASCAL EXPORT APEndRun( EnvContext* );                          // optional
extern "C" BOOL PASCAL EXPORT APSetup( EnvContext*, HWND hWnd );                // optional

/////////////////////////////////////////////////////////////////////////////////////
// API Implementations
/////////////////////////////////////////////////////////////////////////////////////

void PASCAL GetExtInfo( ENV_EXT_INFO *pInfo ) 
   { 
   pInfo->types = EET_AUTOPROCESS;
   pInfo->description = "Fuel Load Model";
   }

/////////////////////////////////////////////////////////////////////////////////////
// Landscape Change Model Interfaces - These are just "C" "wrappers" around the C++
// implementation
/////////////////////////////////////////////////////////////////////////////////////

BOOL PASCAL APInit( EnvContext *pEnvContext, LPCTSTR initStr )    { return theModel->Init( pEnvContext, initStr ); }
BOOL PASCAL APInitRun( EnvContext *pEnvContext, bool useInitSeed ){ return theModel->InitRun( pEnvContext, useInitSeed ); }
BOOL PASCAL APRun( EnvContext *pEnvContext )                      { return theModel->Run( pEnvContext );  }
BOOL PASCAL APEndRun( EnvContext *pEnvContext )                   { return theModel->EndRun( pEnvContext );  }
int  PASCAL APInputVar( int id, MODEL_VAR** modelVar )            { return theModel->InputVar( id, modelVar ); }
int  PASCAL APOutputVar( int id, MODEL_VAR** modelVar )           { return theModel->OutputVar( id, modelVar ); }
BOOL PASCAL EXPORT APSetup( EnvContext *pContext, HWND hWnd )     { return theModel->Setup( pContext, hWnd ); }

[top]

Visual Studio will generate a 'FuelLoad.def' file that defines what functions are exported from the DLL. Again, we will replace this VS-generated file with the one from the Envision SDK. Go to the location where you installed the Envision SDK, and copy the SDK-version of EnvExtExample.def to your project directory, renaming it FuelLoad.def. Alternatively, copy the text below into your existing FuelLoad.def file.

; FuelLoad.def : Declares exported functions

LIBRARY      "FuelLoad"

EXPORTS
; Explicit exports can go here
GetExtInfo  @1
APInit      @2
APInitRun   @3
APRun       @4
APEndRun    @5
APInputVar  @6
APOutputVar @7
APSetup     @8

Note that any function defined in dllmain.cpp above should have an entry in the .def file. The @ numbers in the 'def' file must be unique for each function.


[top]

Right-click on your project in Visual Studio’s Solution Pane and select 'Properties'. This allows you to set various project settings. Change the following, being sure to select 'All Configurations' and 'All Platforms' as the configuration targets (at the top of the 'Properties' dialog box.

Required Project Settings ('All Configurations'/'All Platforms')
  1. General->Character Set: Not Set
  2. C/C++ ->General->Additional Include Directories: C:\Envision\SDK\include; (Note: the location may vary depending on where you installed the SDK)
  3. C/C++ ->Preprocessor->Preprocessor Definitions: add "__EXPORT__=__declspec( dllimport )"; Be sure to include the quotes!
  4. Linker->General->Additional Library Directories: C:\Envision\SDK\libs; (Note: the location may vary depending on where you installed the SDK)
  5. Linker->Input->Additional Dependencies: libs.lib
  6. Build Events->Post Build Events: copy $(TargetPath) C:\Envision (Note: the location specified in the second path may vary depending on where you installed the Envision)
Note that your setup may vary slightly based on your directory structure.

Additionally, we will need ad the EnvExtension.h and EnvExtension.cpp files to the project. These are available in the directory you installed the Envision SDK into. Copy these files into your project directory and add them to your Visual Studio project via the Solution Explorer.


Finally, we will want to create a 64-bit version of the DLL, so be to sure to indicate the build platform is "x64".

[top]

The SDK provides two C++ classes, EnvEvalModel and EnvAutoProcess, to facilitate the creation of Envision Plug-ins. They subclass from EnvExtension. The classes provide several capabilities: 1) The provide default implemenations for all interface functions, 2) the manage input and output variables exposed by the models/processes to Envision, and 3) the provide a wrapper to facilitate making changes to the underlying map layers.

To create a evaluator:
  1. Derive a subclass from EnvEval Model
To create a landscape change model (autonous process):
  1. Derive a subclass from EnvAutoProcess

For both evaluators and landscape change models:

  1. Override any of EnvExtension::Init(), InitRun(), and Run() as needed.  Other overrides are optional and depend on the needs of the plug-in.
  2. In the Init() method of the class, call EnvExtension::AddInputVar() and EnvExtension::AddOutputVar for all input and output variables exposed by the plug-in;
  3. When accessing the IDU map layer during processes, get the MapLayer pointer from the EnvContext object passed to the DLL; When making changes to the Map, DO NOT call the MapLayer directly; instead, use EnvExtension::UpdateIDU() (documented below) to make modifiction to the map.

Putting the concepts above to use, we will next develop the actual model captured in this plug-in. To facilitate this process, we will take advantage of classes defined in the SDK. Because this is a landscape change model (autonomous process), we will subclass our model from the EnvAutoProcess class provided by Envision. Alternatively, if we where developing an Evaluative Model, we would derived our model from EnvEvalModel class.

For our model, we will only implement the Init, InitRun, and Run methods, and rely on the default implementations provided in the EnvAutoProcess class for the remaining API functions.

The header file for our model class is provided below. This is pretty minimalist, but it shows the basics of a model subclass implementing the Envision interfaces. Note that Visual Studio will create an empty .cpp file for the project, but not a corresponding .h file. For this walk-through, you can create this header file using one of the following methods:

  1. in the Visual Studio IDE, right click on the FuelLoad project and select the "Add/Header File (.h) option, naming the file FuelLoad.h, and adding the code below to your new file,
  2. get the file from this link and save it to your project directory
#pragma once

#include 

// this privides a basic class definition for this
// plug-in module.
// Note that we want to override the parent methods
// for Init, InitRun, and Run.

class FuelLoad : public EnvAutoProcess
   {
   public:
      // constructor
      ~FuelLoad(void);

      // override API Methods
      BOOL Init(EnvContext *pEnvContext, LPCTSTR initStr);
      BOOL InitRun(EnvContext *pEnvContext, bool useInitialSeed);
      BOOL Run(EnvContext *pContext);
    
   protected:
      // we'll add model code here as needed
   };

This provides the basics of defining a class that contains declarations for three of the Envision APIs (Init, InitRun, and Run), but no implementations. To get a start on the implementation of these functions, open the FuelLoad.cpp that Visual Studio created when you created the project, and paste the code below into this file:
// FuelLoad.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "FuelLoad.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// constructor
FuelLoad::~FuelLoad(void)
   { }

// override API Methods
BOOL FuelLoad::Init(EnvContext *pEnvContext, LPCTSTR initStr)
   {
   return TRUE;
   }

BOOL FuelLoad::InitRun(EnvContext *pEnvContext, bool useInitialSeed)
   {
   return TRUE;
   }

BOOL FuelLoad::Run(EnvContext *pContext)
   {
   return TRUE;
   }

At this point, your should be able to successfully compile your DLL. It does not yet perform any useful functions, but at least we have a compilable plug-in and will add functionailty next.


[top]

The FuelLoad model is intended to perform a simple task - scanning the IDU land use/land cover (LULC) information and assigning an appropriate fuel load to each IDU based on the IDU's land cover class. (Note that we could easily accomplish this same functionality using the standard "Sync" plug-in, but that's somewhat beside the point!) To make the model as general as possible, we will store mappings between and IDU land cover class and the corresponding fuel load in an external XML file, making it easy to modify the mappings without having to make code changes or recompile the model DLL. We will take advantage of classes available in the Envision SDK that support reading and parsing XML files to implement the functionaility of loading and storing the mappings. Our XML input file looks like this:

<?xml version="1.0" encoding="utf-8"?>

<fuel_loads lulc_col="LULC_A" fuel_col="FUEL_LOAD">
  <fuel_load lulc="1" load="3.06" />
  <fuel_load lulc="2" load="5.32" />
  <fuel_load lulc="3" load="12.3" />
</fuel_loads>

This first (outer) <fuel_loads> tag allows us to specify which columns in the IDU database contains LULC and fuel load information, respectively. The child <fuel_load> tags define a set of mappings between specific lulc classes and corresponding fuel loads. Our model will need to do several things:

  1. During initialization, when the plug-in is first loaded, it will need to read, parse, and store the information in the XML configuration file above. We will implement this functionality in the "Init()" method.
  2. At each time step when a simulation is running, it will need to scan the IDUs and assign an appropriate fuel load. We will implement this functionality in the "Run()" method.

Additionally, we will compute and expose as an output variable the average fuel load density on the landscape. We will do this in the Init() method; alternatively, we could do this in the FuelLoad constructor. We will define a variable holding the current average (area-weighted) fuel load in our FuelLoad class.

Let's start with our header file. We will make three modifications.

  1. Add a data structure, in this case a map of LULC/Fuel Load pairs, that we will use to store the mappings defined in our input XMl files,
  2. Add a variable for storing the average fuel load that we will export,
  3. Adding a function "LoadXml()" that will load the input XML file, parse it's contents, and store the mappings in our class object.
#pragma once

#include <EnvExtension.h>
#include <map>

// this provides a basic class definition for this plug-in module.
// Note that we want to override the parent methods for Init, InitRun, and Run.

class FuelLoad : public EnvAutoProcess
   {
   public:
      // constructor
      FuelLoad(void);

      // override API Methods
      BOOL Init(EnvContext *pEnvContext, LPCTSTR initStr);
      BOOL InitRun(EnvContext *pEnvContext, bool useInitialSeed);
      BOOL Run(EnvContext *pContext);

   protected:
      // store the LULC, FuelLoad field indexes
      int m_colLULC;
      int m_colFuelLoad;

      // output variables
      float m_avgFuelLoad;  // area-weighted average fuel load

      // define a map that maps lulc codes into corresponding fuel loads
      std::map<int,float> m_mappings;  // key=lulc, value=fuel load

      // function for loading map pairs from XML input file
      bool LoadXml(LPCTSTR filename, EnvContext*);
   };

Our class implementation file (FuelLoad.cpp) will be updated to include implementations for our API functions and additional supporting code.

// FuelLoad.cpp : Defines the initialization routines for the DLL.
//

#include "stdafx.h"
#include "FuelLoad.h"

#include <PathManager.h>
#include <Report.h>
#include <tixml.h>


#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// constructor
FuelLoad::FuelLoad(void)
   : m_colLULC(-1)
   , m_colFuelLoad(-1)
   , m_avgFuelLoad(0)
   { }

// override API Methods
// Init() gets called when the DLL is first loaded.  We will load the XML input
// file (name specified in the .envx file) and populate the initial fuel loads 
// based on the initial IDU lulc values.  Additionally, we will 'expose' our output variable
BOOL FuelLoad::Init(EnvContext *pEnvContext, LPCTSTR initStr)
   {
   // note that initStr (defined in the .envx file) contains the name of the 
   // XML input file
   bool result = LoadXml(initStr, pEnvContext);    // load the XML input file.

   // expose the output variable
   AddOutputVar("Area-Weighted Avg Fuel Load", m_avgFuelLoad, "");

   Run(pEnvContext);     // populate initial fuel distributions
   return TRUE;
   }

// InitRun() gets called befor each simulation run. There is nothing special 
// needed at beginning of a simulation run, so just return
BOOL FuelLoad::InitRun(EnvContext *pEnvContext, bool useInitialSeed)
   {
   return TRUE;
   }

// Run() is called at each time step.  We will iterate through the IDUs,
// get the lulc value and populating the fuel load field if a mapping
// if found.  Additionally, we will calculate the area-weighted fuel load
// across the landscape
BOOL FuelLoad::Run(EnvContext *pEnvContext)
   {
   MapLayer *pLayer = (MapLayer*) pEnvContext->pMapLayer;  // get a ptr to the IDU coverage

   int colArea = pLayer->GetFieldCol("AREA");
   float totalArea = 0;
   float totalFuelLoad = 0;

   // iterate through the IDU polygons, writing fuel loads if defined
   for (MapLayer::Iterator idu = pLayer->Begin(); idu < pLayer->End(); idu++)
      {
      int lulc = -1;
      pLayer->GetData(idu, m_colLULC, lulc);

      float area = 0;
      pLayer->GetData(idu, colArea, area);

      float fuelLoad = 0;

      // if mapping for this IDU's lulc class exists, get associated fuel load 
      try
         {
         fuelLoad = m_mappings.at(lulc);
         }
      catch (...) {}

      // update the IDU
      UpdateIDU(pEnvContext, idu, m_colFuelLoad, fuelLoad);

      // update avg fuel load stats
      totalArea += area;
      totalFuelLoad += fuelLoad * area;
      }

   // compute final output variable value
   m_avgFuelLoad = totalFuelLoad / totalArea;

   return TRUE;
   }

// Function for loading XML file
bool FuelLoad::LoadXml(LPCTSTR filename, EnvContext *pEnvContext)
   {
   // does the file somewhere on the path?
   CString _filename;
   if (PathManager::FindPath(filename, _filename) < 0) //  return value: > 0 = success; < 0 = failure (file not found), 0 = path fully qualified and found 
      {
      CString msg;
      msg.Format("FuelLoad: Input file '%s' not found - this process will be disabled", filename);
      Report::ErrorMsg(msg);
      return false;
      }

   // Load the xml document from disk
   TiXmlDocument doc;
   bool ok = doc.LoadFile(filename);

   if (!ok)
      {
      Report::ErrorMsg(doc.ErrorDesc());
      return false;
      }

   // Get the root node attributes
   TiXmlElement *pXmlRoot = doc.RootElement();  // <fuel_loads>

   LPTSTR lulcCol = NULL, fuelLoadCol = NULL;
   XML_ATTR attrs[] = {
         // attr        type        address       isReq checkCol
         { "lulc_col",  TYPE_STRING, &lulcCol,     true,    0 },
         { "fuel_col",  TYPE_STRING, &fuelLoadCol, true,    0 },
         { NULL,        TYPE_NULL,    NULL,        false,   0 } };

   ok = TiXmlGetAttributes(pXmlRoot, attrs, filename);

   if (!ok)
      {
      Report::ErrorMsg("FuelLoad:  Missing required attribute(s) in <fuel_loads> tag (should contain 'lulc_col' and 'fuel_col' attributes)");
      return false;
      }

   // make sure the columns exist in the IDU coverage
   MapLayer *pLayer = (MapLayer*)pEnvContext->pMapLayer;  // get a ptr to the IDU coverage
   if (this->CheckCol(pLayer, m_colLULC, lulcCol, TYPE_INT, CC_MUST_EXIST) == false)
      {
      std::string str("FuelLoad: Missing required column ");
      str += lulcCol;
      Report::ErrorMsg(str.c_str());
      }

   // if the Fuel Load field doesn't exist, automatically add it.
   this->CheckCol(pLayer, m_colFuelLoad, fuelLoadCol, TYPE_FLOAT, CC_AUTOADD);

   // iterate though <fuel_load> tags, storing each pair in our map
   TiXmlElement *pXmlFuelLoad = pXmlRoot->FirstChildElement( "fuel_load");  // <fuel_load>
   while (pXmlFuelLoad != NULL)
      {
      int lulc = -1;
      float fuelLoad = 0;
      XML_ATTR attrs[] = {
         // attr        type       address    isReq checkCol
            { "lulc",  TYPE_INT,   &lulc,     true,    0 },
            { "load",  TYPE_FLOAT, &fuelLoad, true,    0 },
            { NULL,    TYPE_NULL,   NULL,     false,   0 } };

      ok = TiXmlGetAttributes(pXmlFuelLoad, attrs, filename );
      if (!ok)
         {
         Report::ErrorMsg("FuelLoad:  Missing required attribute(s) in <fuel_load> tag (should contain 'lulc' and 'load' attributes");
         return false;
         }

      // add the pair to the map
      m_mappings[lulc] = fuelLoad;
   
      // get next <fuel_load> tag and repeat
      pXmlFuelLoad = pXmlFuelLoad->NextSiblingElement("fuel_load");
      }

   // all done!
   return true;
   }

We now have a fully-capable Envision plug-in. Make sure the DLL is copied to the directory where your Envision binaries are located, and we are ready to incorporate this plug-in into an Envision application.


[top]

The final step is to add your new model DLL to your Envision project. To do so, we will add an entry to the project's .envx file, in the <models> section of the project file. The code below show the entry that is needed in the <models> section of your project (.envx) file. 'name' is an arbitrary label for this model, 'path' indicates the path to the DLL containing the model, 'id' is unique identifier for this model (not really needed for this DLL, since it contains only a single model), 'use=1' indicates the model should be included in the simulations, and 'timing=0' indicates this model should run before actor decision-making occurs within a time step.

    <model
		  name         ='Fuel Load Model'
		  path         ='FuelLoad.dll'
		  id           ='0'
		  use          ='1'
		  timing       ='0'
	/>
If you save the project (.envx) file with the model entry above added into the <models> section, you should now be able to run a simulation that includes this model.