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 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.
Return to
Idrisi page