Idrisi's API

Although Idrisi for Windows will accept a list of macro commands in a text file (default .IML extension), the ability to write even relatively simple programs is virtually nonexistent. Fortunately, Clark Labs has made an API available for free download, which programmers in Visual C++/Basic, Delphi, or other Windows-compatible packages may use.

Idrisi's API, while it does have its quirks and bugs, is well worth learning. The ability to develop custom procedures and algorithms is an important feature of any GIS, and Clark Labs has demonstrated their commitment to this. Hopefully, with the release of Version 3.0 in mid-1999, the problems currently encountered will be eliminated.

Currently, Idrisi's API requires Idrisi for Windows Version 2.010; the latest updates to Idrisi 2.0 and the API may be downloaded HERE.

To install the API, all that is necessary is to copy MERCURY.DLL and MERCUR32.DLL to the Windows system directory; the existing DLLs may be renamed to a .BAK extension.

The file "api documentation.rtf" documents the functions that are available. Also included are declaration and/or library files for Visual Basic, Borland Pascal, Delphi, and C++ Builder.

Using the API in Visual C++

Visual C++ compatible .LIB and .H files are not included with the API; therefore, users must link to the DLL explicitly. Because explicit links are somewhat clunky to code, it may be desirable to create a class that handles them. The following header file is an example of a class called Idrapp that handles the minimum functions necessary to check if Idrisi is active, launch Idrisi, set the working directory, register a client, launch a module (macro statement), monitor the progress of a launched module, unregister a client, and close Idrisi. [Click HERE for a more complete version of the Idrapp class, along with some sample code.]

idrapp.h:

#define PROCSTATUS_ACTIVE 1
#define PROCSTATUS_NORMAL_TERMINATE 2
#define PROCSTATUS_ERROR_TERMINATE 3
#define REPORT_TYPE_WORKING 1
#define REPORT_TYPE_PERCENTDONE 2
#define REPORT_TYPE_PASS_X_OF_N 3
#define REPORT_TYPE_PASS_TOTALUNKNOWN 4
#define REPORT_TYPE_COMPLEXPASS 5
#define MONITOR_FROM_CLIENT_ONLY 0
#define MONITOR_FROM_IDRISI 1
#define SHOW_IDRISI_HIDDEN 0
#define SHOW_IDRISI_NORMAL 1
#define SHOW_IDRISI_MINIMIZED 2
#define SHOW_IDRISI_MAXIMIZED 3

struct ProgStruct
{
  short Status;
  short ReportType;
  short PassNum;
  short TotalPasses;
  float PercentDone;
  short ErrorCode;
  char ErrorFile[256];
  char ErrorMessage[256];
  char SubstString1[256];
  char SubstString2[256];
};

class Idrapp
{
protected:
   HINSTANCE hinstLib;
public:
   Idrapp();     // constructor
   short IsIdrisiPresent();
   short LaunchIdrisi(short);
   short SetDataDirectory(char *);
   short RegisterClient();
   short LaunchModule(short, short, char *, char *, char *, char *, short *);
   short GetProgress(short, short, ProgStruct *);
   short UnRegisterClient(short);
   short CloseIdrisi();
   ~Idrapp();    // destructor
};
Although enum would be more elegant, #define is used to declare enumerations because the API calls use short rather than int data types. Also note the class constructor and destructor; these are used to load and unload MERCUR32:

idrapp.cpp:

#include 
#include "idrapp.h"

typedef short (CALLBACK* IsIdrisiPresentFuncType)();
typedef short (CALLBACK* LaunchIdrisiFuncType)(short);
typedef short (CALLBACK* SetDataDirectoryFuncType)(char *);
typedef short (CALLBACK* RegisterClientFuncType)();
typedef short (CALLBACK* LaunchModuleFuncType)(short, short, char *, char *, char *, char *, short *);
typedef short (CALLBACK* GetProgressFuncType)(short, short, ProgStruct *);
typedef short (CALLBACK* UnRegisterClientFuncType)(short);
typedef short (CALLBACK* CloseIdrisiFuncType)();

Idrapp::Idrapp() {
   hinstLib = LoadLibrary("mercur32");
   return;
}

Idrapp::~Idrapp() {
   if (hinstLib == NULL) return;
   BOOL fFreeResult = FreeLibrary(hinstLib);
   return ;
}
The remaining class functions merely provide the front end to the DLL's exported functions, for example:

short Idrapp::GetProgress(short ClientId, short ProcID, ProgStruct * Prog) {
   GetProgressFuncType f;
   if (hinstLib == NULL) return 0;
   f = (GetProgressFuncType) GetProcAddress(hinstLib, "GetProgress");
   if (!f) return 0;
   return f(ClientId, ProcID, Prog);
}
At this point we're ready to develop our first Idrisi application! Let's write a console app that will:

  1. Launch Idrisi if necessary
  2. Set the working directory to "c:\0home\test\idr_vc"
  3. Execute a module command, equivalent to the following macro statement:
    project x 1 westboro spc27ma1 testwb us27tm19 279078 290565.3 4678095 4686651 614 486 0 1
    
test1.cpp:

#include 
#include 
#include "idrapp.h"

Idrapp i;

void main() {

   ProgStruct Prog;
   short result, ClientId, ProcId;
   short PtHinst = 0;
   short show = SHOW_IDRISI_MINIMIZED;
   short mon = MONITOR_FROM_IDRISI;

   char *dir = "c:\\0home\\test\\idr_vc\\";
   char *module = "project";
   char *opt = "1 westboro spc27ma1 testwb us27tm19 279078 290565.3 4678095 4686651 614 486 0 1";
   char *title = "";
   char *units = "";

   if (! i.IsIdrisiPresent())
      result = i.LaunchIdrisi(show);
   i.SetDataDirectory(dir);

   ClientId = i.RegisterClient();
   ProcId = i.LaunchModule(ClientId, mon, module, opt, title, units, &PtHinst);
   do
   {
      result = i.GetProgress(ClientId, ProcId, &Prog);
   }
   while (Prog.Status == PROCSTATUS_ACTIVE);
   result = i.UnRegisterClient(ClientId);
   cout << "Done.\n";
   return;
}
Note that even though the SHOW_IDRISI_MINIMIZED option is specified, Idrisi won't run minimized. This is a known bug that hopefully Clark Labs will fix when Version 3 comes out. Right now, I recommend just keeping a copy of Idrisi active rather than launching it. (Don't forget that you can also add custom commands to Idrisi's menu!)

As you build more advanced applications, you're going to want to take advantage of error reporting, progress reporting (to Idrisi), image/vector documentation, and other functions available in the API. All of these functions are described in the documentation.

Progress Reporting

One thing to watch out for in monitoring progress is that ProgStruct values to be supplied to SetProgress are not necessarily the same as those returned by GetProgress (see also Other Bugs and Quirks below). For example, for REPORT_TYPE_PERCENTDONE GetProgress populates PercentDone with 0.0-100.0 while SetProgress requires 0.0-1.0. (For REPORT_TYPE_COMPLEXPASS, returned values of PassNum and PercentDone are even more bizarre.) The following code is an example of reading the progress of a module and then supplying it to Idrisi.

result = i.GetProgress(ClientId, ProcId, &Prog);
Prog2.Status = Prog.Status;
Prog2.ReportType = Prog.ReportType;
switch (Prog.ReportType)
{
case REPORT_TYPE_WORKING:
   Prog2.PassNum = 1;
   Prog2.TotalPasses = 1;
   Prog2.PercentDone = 0.0;
   break;
case REPORT_TYPE_PERCENTDONE:
   Prog2.PassNum = 1;
   Prog2.TotalPasses = 1;
   Prog2.PercentDone = Prog.PercentDone / 100;
   if (Prog2.PercentDone < 0.0) Prog2.PercentDone = 0.0;
   if (Prog2.PercentDone > 1.0) Prog2.PercentDone = 1.0;
   break;
case REPORT_TYPE_PASS_X_OF_N:
   Prog2.PassNum = Prog.PassNum;
   if (Prog2.PassNum < 0) Prog2.PassNum = 0;
   Prog2.TotalPasses = Prog.TotalPasses;
   Prog2.PercentDone = 0.0;
   break;
case REPORT_TYPE_PASS_TOTALUNKNOWN:
   Prog2.PassNum = Prog.PassNum;
   if (Prog2.PassNum < 0) Prog2.PassNum = 0;
   Prog2.TotalPasses = 0;
   Prog2.PercentDone = 0.0;
   break;
case REPORT_TYPE_COMPLEXPASS:
   calc = (float)(Prog.PassNum - 2080) / 100;
   Prog2.PassNum = (short) calc + 1;
   Prog2.TotalPasses = Prog.TotalPasses;
   if (Prog2.PassNum > Prog2.TotalPasses)
      Prog2.PassNum = Prog2.TotalPasses;
   calc = -207900 - Prog.PercentDone;
   calc = calc - ((Prog2.PassNum - 1) * 9900);
   Prog2.PercentDone = calc / 99.0f;
   if (Prog2.PercentDone < 0.0) Prog2.PercentDone = 0.0;
   if (Prog2.PercentDone > 100.0) Prog2.PercentDone = 100.0;
   break;
}
result2 = i.SetProgress(ClientId2, StatId, &Prog2);

Accessing Idrisi's API from ArcView

Although the MERCUR32 DLLProcs are accessible from ArcView, Avenue cannot manipulate data structures such as ProgStruct. For example, it is not possible to use the SetProgress DLLProc. However, in situations where retrieving extended data is not essential (such as GetProgress), a phony buffer string may be used instead:

Idrisi.Go:

ModName = SELF.Get(0)
Cmdln = SELF.Get(1)
OutTitle = SELF.Get(2)
OutUnits = SELF.Get(3)
theDLL = DLL.Make("c:\windows\system\mercur32.dll".AsFilename)

'register client
RegisterClient = DLLProc.Make(theDLL, "RegisterClient", #DLLPROC_TYPE_INT16,{})
theCID = RegisterClient.Call({})
if (theCID = 0) then
   IsIdrisiPresent = nil
   RegisterClient = nil
   theDLL = nil
   av.PurgeObjects
   return "Could not register client"
end

'launch the process
LaunchModule = DLLProc.Make(theDLL,"LaunchModule",#DLLPROC_TYPE_INT16, {#DLLPROC_TYPE_INT16, #DLLPROC_TYPE_INT16, #DLLPROC_TYPE_STR, #DLLPROC_TYPE_STR, #DLLPROC_TYPE_STR, #DLLPROC_TYPE_STR, #DLLPROC_TYPE_PINT16})
MonOp = 1
thePint = 0
parm_list = {theCID, MonOp, ModName, Cmdln, OutTitle, OutUnits, thePint}
thePID = LaunchModule.Call(parm_list)

'wait until done
Msg = "OK"
dummy_P = String.MakeBuffer(1088)
GetProgress = DLLProc.Make(theDLL,"GetProgress", #DLLPROC_TYPE_INT16, {#DLLPROC_TYPE_INT16, #DLLPROC_TYPE_INT16, #DLLPROC_TYPE_STR})
status = GetProgress.Call({theCID, thePID, dummy_P})
while (status = 1)
   status = GetProgress.Call({theCID, thePID, dummy_P})
end
if (status = 3) then
   Msg = "Process terminated with error"
end

'unregister client and clean up
UnRegisterClient = LLProc.Make(theDLL, "UnRegisterClient", #DLLPROC_TYPE_INT16, {#DLLPROC_TYPE_INT16})
result = UnRegisterClient.Call({theCID})
if (result = 0) then
   if (Msg = "OK") then
      Msg = "Could not unregister client"
   else
      Msg = Msg + NL + "Could not unregister client"
   end
end
RegisterClient = nil
UnRegisterClient = nil
LaunchModule = nil
GetProgress = nil
theDLL = nil
av.PurgeObjects
return Msg
[Click HERE to download a bundle of scripts, including a sample session script, that encapsulates the basic Idrisi API functions.]

Other Bugs and Quirks

Warning Messages

Warning messages, such as those issued by BMPIDRIS and SURFACE, currently cannot be disabled in the API (this will be fixed in Idrisi Version 3.0, due to be released by mid-1999). Attempts to circumvent this by monitoring from the client will lead to "exception eedfadeH" in MERCUR32.DLL if GetProgress is called after LaunchModule.

Other modules that may issue warning messages include:

ARCIDRIS
ASSIGN
BELIEF
DLG
EDIT
FILTER
HISTO
OVERLAY
PERIM
QUERY
RECLASS
RESAMPLE
STANDARD
WMFIDRIS

CloseIdrisi

Idrisi may think that Clients are still active, even after unregistering them.


The following observations are from Stewart Dibbs, President of VYSOR Integration, Inc. and are published with his permission:

LaunchModule

The following illustrates the correct way to run LaunchModule in Visual C 6. Calls to other functions are done in the same fashion: typedef the DLL function and set up the function pointer for the call to GetProcAddress.

HMODULE hMercury32 = NULL;

typedef IDRISI_API (__stdcall *LaunchModule)
(short,short,LPSTR,LPSTR,LPSTR,LPSTR,short*);
LaunchModule fpIdrisiFunction = NULL;
signed short hIdrisiModuleInstance = 0;

hMercury32 = LoadLibrary("MERCUR32.DLL");
if (hMercury32) {
fpIdrisiFunction =
(LaunchModule)GetProcAddress(hMercury32,"LaunchModule");
....

SetDataDirectory

IDRISI wants a trailing '\' on the path.

RegisterClient:

You won't get a ClientID of 7. This appears to be reserved. Not documented. Also, RegisterClient returns -63 on failure, rather than the 0 described in the documentation.

InitProgressTracking

InitProgressTracking takes TWO arguments, not three, as in the documentation.

SetProgress

Setting the PercentDone variable requires different values depending on the ReportMode

   //  WORKING:
   //  anArgBuffer[13] = 1
   //  anArgBuffer[14] = 1
   //  PERCENTDONE:
   //  anArgBuffer[13] = PercentDone
   //  anArgBuffer[14] = 0
   //  PASS_X_OF_N:
   //  anArgBuffer[13] = X
   //  anArgBuffer[14] = N
   //  PASS_TOTALUNKNOWN:
   //  anArgBuffer[13] = Number
   //  anArgBuffer[14] = 0
   //  COMPLEX_PASS:
   //  anArgBuffer[13] = expected Total Passes
   //  anArgBuffer[14] = PercentDone


   switch (anArgBuffer[3]) {
   case REPORT_TYPE_WORKING:
           lpProgStruct->PassNum = iPass = (short) 1;
           lpProgStruct->TotalPasses = (short)1;
           lpProgStruct->PercentDone = (float) 0.0f;
           break;
   case REPORT_TYPE_PERCENTDONE:
           lpProgStruct->PassNum = iPass = (short) 1;
           lpProgStruct->TotalPasses = (short)1;
           if (lpNumVarRec1->nValue < 0) lpNumVarRec1->nValue = 0;
           if (lpNumVarRec1->nValue > 100) lpNumVarRec1->nValue = 100;
           lpProgStruct->PercentDone = 0.001f + ((float)lpNumVarRec1->nValue)/100.0f;
           if (lpProgStruct->PercentDone > 1.0f) lpProgStruct->PercentDone = 1.0f;
           break;
   case REPORT_TYPE_PASS_X_OF_N:
           lpProgStruct->PassNum = iPass = (short)lpNumVarRec1->nValue;
           if (lpNumVarRec2->nValue < 0) lpNumVarRec2->nValue = 0;
           lpProgStruct->TotalPasses = (short)lpNumVarRec2->nValue;
           lpProgStruct->PercentDone = (float) 0.0f;
           break;
   case REPORT_TYPE_PASS_TOTALUNKNOWN:
           if (lpNumVarRec1->nValue < 0) lpNumVarRec1->nValue = 0;
           lpProgStruct->PassNum = iPass = (short)lpNumVarRec1->nValue;
           lpProgStruct->TotalPasses = (short)0;
           lpProgStruct->PercentDone = (float) 0.0f;
           break;
   case REPORT_TYPE_COMPLEXPASS:
           lpProgStruct->PassNum = ++iPass;
           if (lpNumVarRec1->nValue < 0) lpNumVarRec1->nValue = 0;
           lpProgStruct->TotalPasses = (short)lpNumVarRec1->nValue;
           // Note that here we DON'T divide by 100.0f. MERCUR32 inconsistency.
           if (lpNumVarRec2->nValue < 0) lpNumVarRec2->nValue = 0;
           if (lpNumVarRec2->nValue > 100) lpNumVarRec2->nValue = 100;
           lpProgStruct->PercentDone = ((float)lpNumVarRec2->nValue);
           if (lpProgStruct->PercentDone > 100.0f) lpProgStruct->PercentDone = 100.0f;
           break;
   } // end switch

GetProgress

I struggled with the ReportModes here, and SetProgress above, using DEBUG, for two days. The inconsistencies between the Set and Get functions are really annoying. anArgBuffer[n] are in an integer array in my software.

   /* lpNumVarRec1->nValue, lpNumVarRec2->nValue ned to be updated
      with appropriate values.

           //  WORKING:
           //  anArgBuffer[8] = 1 lpProgStruct->PassNum
           //  anArgBuffer[9] = 1
           //  PERCENTDONE:
           //  anArgBuffer[8] = PercentDone eg 0.40
           //  anArgBuffer[9] = 0
           //  PASS_X_OF_N:
           //  anArgBuffer[8] = X
           //  anArgBuffer[9] = N
           //  PASS_TOTALUNKNOWN:
           //  anArgBuffer[8] = Number
           //  anArgBuffer[9] = 0
           //  COMPLEX_PASS:
           //  anArgBuffer[8] = expected Total Passes
           //  anArgBuffer[9] = PercentDone eg 40 note the difference

   */

           switch ((int)lpProgStruct->ReportType) {
           case REPORT_TYPE_WORKING:
                   lpNumVarRec1->nValue = (int)lpProgStruct->PassNum;
                   lpNumVarRec2->nValue = (int)lpProgStruct->TotalPasses;
                   break;
           case REPORT_TYPE_PERCENTDONE:
                   lpNumVarRec1->nValue = (int)(lpProgStruct->PercentDone);
                   break;
           case REPORT_TYPE_PASS_X_OF_N:
                   lpNumVarRec1->nValue = (int)lpProgStruct->PassNum;
                   lpNumVarRec2->nValue = (int)lpProgStruct->TotalPasses;
                   break;
           case REPORT_TYPE_PASS_TOTALUNKNOWN:
                   lpNumVarRec1->nValue = (int)lpProgStruct->PassNum;
                   lpNumVarRec2->nValue = 0;
                   break;
           case REPORT_TYPE_COMPLEXPASS:
                   // get some sensible values. This may change with MERCUR32 version
                   lpNumVarRec1->nValue = (int)lpProgStruct->PassNum / 100 - 59;
                   lpProgStruct->TotalPasses = (short)lpNumVarRec1->nValue;
                   // Note that here we DON'T multiply by 100.0f. MERCUR32 inconsistency.
                   lpNumVarRec2->nValue = (int)lpProgStruct->PercentDone;
                   break;
           } // end switch
There are problems with setting error messages and new error files. The file can exist, but the API an't find it, or ignores the contents and accesses the default IDRISI file, or produces an error -999.

For example, from the docs "if a positive error code (of 1 or greater) is specified, it is understood that this represents a key in a user-defined error file (see the section on ErrorFile below).".

Well. I have tried a positive error code and "My.err" file, and it does not work, and looks in the std error file, so won't find the error code.


One Final Note: Read***File

Stewart also reports that the Read***File functions don't work properly. However, since those functions merely read standard text files, it should be a simple matter to develop one's own routines to handle them. Keep an eye out for future developments!


Return to Idrisi page