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: #includeThe remaining class functions merely provide the front end to the DLL's exported functions, for example:#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 ; }
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:
project x 1 westboro spc27ma1 testwb us27tm19 279078 290565.3 4678095 4686651 614 486 0 1
test1.cpp: #includeNote 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!)#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; }
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);
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 modules that may issue warning messages include:
ARCIDRIS
ASSIGN
BELIEF
DLG
EDIT
FILTER
HISTO
OVERLAY
PERIM
QUERY
RECLASS
RESAMPLE
STANDARD
WMFIDRIS
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"); ....
// 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
/* 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 switchThere 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.