Developers Knowledge Base

Read articles and post questions in this comprehensive developer community and support forum.
Showing results for 
Search instead for 
Do you mean 

Example 3: View Image Operator

by Technical Evangelist ‎10-06-2015 11:36 AM - edited ‎06-22-2016 01:55 PM (847 Views)

This example is located in sm_ExternalProcessExample.  It creates a plug-in DLL containing a single operator that simply opens a specified file using either the Windows default application for that file type or Paint (mspaint.exe).


Dissecting Example 3

The View Image operator class

Let’s start by looking at the operator itself.  The code for the operator can be found in ViewImage.h and ViewImage.cpp.  Look at definition of class ViewImage in ViewImage.h.  The first thing we notice is that an operator in Spatial Modeler must be a class that derives from Operator.  Operator is in the Intergraph::SpatialModeler namespace.


class ViewImage : public Intergraph::SpatialModeler::Operator
   virtual ~ViewImage();

There are a few Operator methods that all sub-classed operators must implement.  The Spatial Modeler SDK has macros to help with some of them.


Use the SBLIB_PLUGIN_OBJECT macro to set the namespace and name of a plug-in object.  L”Operator” indicates that this plug-in is an operator.  L”Example” is the namespace for the operator.  This is not necessarily the same as the C++ namespace.  The namespace is intended, as in C++, to further define the operator name and reduce the risk of duplication.  It is used when instantiating the operator using OperatorFactory::Create().  The namespace should not contain spaces.  The operator(s) you write should not use "Example" as the namespace.  L”ViewImage” is the name of the operator.  The name is used when registering the operator—we’ll discuss this below—and when instantiating the operator using OperatorFactory::Create().  The operator name does not have to match the name of the operator class; however, it should not contain spaces.


SBLIB_PLUGIN_OBJECT ( L"Example", L"Operator", L"ViewImage" );

Use the OPERATOR_DISPLAY_INFO macro to set the display information of an operator.  The first parameter is the name of the category for the operator.  The category name is used for grouping operators in the IMAGINE Spatial Modeler Editor Operator list.  The second parameter is the default display name of the operator.  The display name is displayed on the operator in the IMAGINE Spatial Modeler Editor.  It is also used when searching for a specific operator within a model.  The convention is that the display name be the same as the operator name, plus spaces to separate words.  The third parameter is the name of the icon file to be displayed on the operator in the IMAGINE Spatial Modeler Editor.  If the file name is empty, the default icon will be used.  The ETXT_TEXT_TR macro is used to allow these names to be localized in the IMAGINE GUI.


OPERATOR_DISPLAY_INFO ( ETXT_TEXT_TR ( "Example" ), ETXT_TEXT_TR ( "View Image" ), L"" );

Two other methods that must be implemented are Init() and OnExecute()Init() is called when the operator is created.  OnExecute() is called when the model is executed (run).  We’ll discuss these two methods more when we examine ViewImage.cpp.


virtual void Init();
virtual void OnExecute();

The other macro you’ll find in ViewImage.h is OPERATOR_NOT_USABLE_IN_EXPRESSION.  This macro is used to indicate that this operator is not usable in a Spatial Modeler expression (one of the built-in Spatial Modeler operators).  Expressions are textual ways of describing a chain of operators, such as Add ($Input1, $Input2) / Divide ($Input1, $Input2).  An expression must have an output, so in order for an operator to be usable in an expression, it must have an output, and since our example operator here is designed to be used at the end of a chain and does not have an output, it cannot be used in an expression.


ViewImage::Init() Implementation

Now let’s look at how ViewImage is implemented in ViewImage.cpp.  We create the ports for the operator in the Init() method.  ViewImage has two ports:  Filename (a file name) and UsePaint (a boolean).  Both of them are input ports—basically parameters to our operator.  For the Filename we really just need a string that is a file name; however, Spatial Modeler has a special data type for a file name:  File. Having a special data type allows the IMAGINE Spatial Modeler Editor to present the user with a file chooser for selecting the file name.  The FileFilter and Extension attributes—set to L”raster” and L”.tif” respectively—are used by the file chooser to determine what file are shown (by default).  For the Filename port we also are setting the Kind attribute to L”raster” to properly identify the input file type for publishing models in ERDAS Apollo. 


PortPtr filePort = CreatePortWithAttributes ( ETXT_TEXT_TR("Filename"),
FileData::GetDataType(), SB_PORT_INPUT,
   L"FileFilter", Data&colon;:Create<StringData> ( L"raster" ),
   L"Extension", Data&colon;:Create<StringData> ( L"*.tif" ),
   NULL );
filePort->SetDataTypeAttribute<StringData> ( FileData&colon;:GetDataType(), L"Kind",
L"raster" );

The second port is a flag to tell ViewImage whether to open the image using the Windows default application for that file type or Paint.  This input port supports Bool data.  We’re not going to require the user to set a value on this port—we’ll have a default—and we’re even going to hide this port (by default) from the user in the Spatial Modeler Editor GUI.


PortPtr paintPort = CreatePort<BoolData> ( ETXT_TEXT_TR ( "UsePaint" ),
paintPort->SetIsOptional ( true );
paintPort->SetIsHidden ( true );

Notice again the use of the ETXT_TEXT_TR macro to allow the port name to be localized in the IMAGINE GUI.

ViewImage::OnExecute() Implementation

The OnExecute method is called when the model is executed (run).  It does all of the real work of the operator, at least for non-raster operators—we’ll get into raster operators later.  Note that OnExecute() is guaranteed to be called from a single thread, so it does not need to be thread safe.


In this example this is where we’re going to find out what image the user wants us to display and display it in the manner requested.  First we have to get the name of the image.  We get the port for the Filename and request the value that is set on it.  We request it as FileData, and we tell GetDataValue() that the default is an empty string.  If we get an empty string, however, we set an error message on the operator—we have to have a file name to open the image.  SetErrorMessage() throws an exception, so if we do not have a Filename, we’ll exit OnExecute() here.


std::wstring filename = GetPort ( L"Filename" )->GetDataValue<FileData> ( L"" );
if (filename.empty())
   SetErrorMessage ( L"No file name specified." );

Now we get the value of the UsePaint port.  We will default to false.


bool usePaint = GetPort ( L"UsePaint" )->GetDataValue<BoolData> ( false );

The rest of the code in OnExecute() is just the code necessary to implement the algorithm or perform the data operation.  In this case, we just want to open the file.  One tricky little piece with file names, though, is that in the context of IMAGINE file names have UNIX slashes (forward slashes) in the path.  In order to use the file name we got from the Filename port in Windows functions, we need to make sure that slashes are the backward slashes expected by Windows.  That’s what efnp::FileNameInternalToExternal() does for us.  If an error is encountered in OnExecute(), you should call SetErrorMessage() to report the error and abort the processing of the chain.


filename = efnp::FileNameInternalToExternal ( filename );
if ( usePaint )
   hInst = ShellExecute ( NULL, L"open", L"mspaint.exe", filename.c_str(), NULL,
   hInst = ShellExecute ( NULL, L"open", filename.c_str(), NULL, NULL, SW_SHOWNORMAL );
if ( (int)hInst <= 32 )
   SetErrorMessage ( L"Unable to open file." );
Registering the plug-in

A plug-in DLL must register all of the plug-in objects that it contains.  Look in SMExternalProcessExamplePlugin.cpp.  In here we implement GetAvailablePluginProxiesOfType().  When the DLL is discovered at runtime, it is dynamically loaded, and the PluginManager (out of the scope of this documentation) looks for this entry point (so make sure it is exported).  If it is found, it will be called for each plug-in object type:  “Operator”, “Data”, “Conversion”, and “UIProvider”.  Each time it is called, it should return a list of the objects of that type that are implemented in the DLL.  In this example, the only object we’ve implemented is an operator, so we only need to return anything when the type is L”Operator”.  For each object we use the macro SBLIB_PLUGIN_REGISTRATION to create an AvailablePluginProxy.  The first argument to the macro is the name of the object—in this case our operator.  This name must match the name you gave the object in the SBLIB_PLUGIN_OBJECT macro in the class definition.  If it does not, Spatial Modeler will not be able to create an instance of your operator through the OperatorFactory.  The second parameter to the macro is the class that implements the object.  The return value of the macro gets pushed on to the list of plug-ins.


The spatial modeler plugin DLLs also support lazy loading, which will allow a plugin DLL to only be loaded once an operator is actually created.  To use lazy loading in your operator, you must support an empty type for each plugin object registered by your DLL.  This is done through the type == L”” portion of the if-statement below.  Also you need to run a configuration utility as a custom build step.  The command can be seen in the example projects located in the Custom Build Step property.


extern "C" __declspec(dllexport) int
GetAvailablePluginProxiesOfType ( const PluginType& type,
AvailablePluginProxyList& pluginList )
   if ( type == L"Operator" || type == L"" )
      pluginList.push_back ( SBLIB_PLUGIN_REGISTRATION ( L"ViewImage",
      sm_ExternalProcessExample::ViewImage ) );
   return 0;


Previous article:  Creating an operator

Next article:  Example 4: Determine whether pixel is NODATA