Maptitude GISDK Help |
The most common pattern is to start Maptitude, TransCAD or TransModeler, then start your python script in a separate process: a terminal window, Visual Studio Code debugger, or a Jupyter notebook. This pattern is called out-of-process: Python and Maptitude are two separate processes. With this pattern, you call GISDK functions from your Python script, using the caliperpy package.
The caliperpy module lets you access not only the GISDK functions, macros and classes, but also the .NET framework via any program written in Python. The caliperpy package works with scientific Python distributions such as Anaconda.
With the caliperpy package, you can write a Python program like this one
import sys, caliperpy
def main():
dk = caliperpy.Maptitude.connect()
tutorial_folder = dk.RunMacro("G30 Tutorial Folder")
table_name = dk.OpenTable("airports","ffb",[ tutorial_folder + "airports.bin",None])
num_rows = dk.GetRecordCount(table_name,None)
dk.SetView(table_name)
dk.SetSelectMax(10)
options = { 'Index Limit': 0}
query = "select * where Domestic > 10000"
num_found = dk.SelectByQuery("high traffic","several",query,options)
In a nutshell, with the Caliper Python module, you can:
Note that "GISDK" with just the "G" capitalized is always used in Python programs.
If you are using Anaconda, follow these instructions:
1. Open Anaconda Prompt via:
This will start a special command window for Anaconda. You can also enable Anaconda Python for all your command windows including Powershell, there are many web sites explaining how to do that.
2. You can use the default environment called base, or you can create a new environment of your choice (myenv here):
conda create --name myenv
3. Check that the environment myenv has been created by listing all the environments:
conda env list
4. Activate the environment of your choice:
conda activate base
Or:
conda activate myenv
5. Install caliperpy in your environment:
python -m pip install "c:\Program Files\Maptitude 2024\GISDK\API\Python"
6. Open python in the same environment: just type python
7. import caliperpy in the python prompt
python
>>import caliperpy
8. You can copy one of the example scripts from the program subfolder Documents\Caliper\Maptitude YYYY\CodeSamples (where YYYY is the year of your Maptitude version) to your own Documents folder. For example, at the Windows command prompt:
copy C:\Users\[Username]\Documents\Caliper\Maptitude YYYY\CodeSamples\Maptitude-example-3.py .
python Maptitude-Example-3.py
A script written in the GISDK programming language can call directly a Python function and fetch the result, in a similar way that the GISDK can call a .NET function written in C#.
The Python function runs embedded in the main hosting process (TransCAD, Maptitude or TransModeler). Your Python function can execute any GISDK function via caliperpy, and use all of the Python packages in your Python environment. This pattern is called in-process, or embedded Python. With this pattern, you call a Python function from the GISDK, and the Python function can also call the GISDK. The following sections explain how it works.
To enable calling Python functions directly from the GISDK, you must indicate to Maptitude what Python distribution to use. Open your Caliper product (e.g. Maptitude), and choose: Tools > GIS Developer's Kit > Register Embedded Python.... A dialog box with a datagrid should appear. The registration process requires Windows Powershell version 7 or later to be installed on your computer.
The program will try its best to detect available Python distributions on your machine. You will be able to choose the distribution and click the Register button next to it to register your preferred Python distribution. You will be prompted to execute the registration steps with elevated Administrator privileges.
The program will install or upgrade all required Python packages, including pip, pywin32 and caliperpy, and register the selected distribution as the one to use for embedded Python. You will need to restart your product to complete the registration.
After you have registered a Python distribution to embed in Maptitude, you can write an embedded Python script, or you can adapt any existing Python script to run either embedded or out-of-process using this simple three-step technique.
To get started, copy the files from the Maptitude program folder GISDK\Python\Samples\embedded-example.py and my_utils.py to a folder you can write to.
The body of the Python script must include a couple of lines in the header and a couple of lines in the footer, as follows.
# first add this header before any local import statements:
import caliperpy
# pylint: disable=undefined-variable
context = caliperpy.ScriptContext(PythonContext if caliperpy.IsInProcess() else None)
# then define your functions in the body, for example:
# local imports must be placed after the context header
import my_utils
def add_two_numbers(a: int, b: int) -> int:
""" returns the sum of the input numbers """
return my_utils.sum_two_numbers(int(a), int(b))
# finally add this footer at the end of your script:
if __name__ in ["__main__", "__ax_main__"]:
context.run(locals())
Test Your Function in the Windows Terminal
You can now execute the function add_two_numbers in a terminal window with this command line:
python embedded-example.py add_two_numbers a=1 b=10
In general, the command line syntax is:
python script_name.py function_name arg1=val1 arg2=val2 arg3=val3 ....
Call Your Function From the GISDK
The GISDK now includes a standard class called “Python”. This class has been designed to execute any function in a Python script, provided the script includes the header and the footer as explained above.
In the GISDK immediate execution toolbar, execute the following two lines, with the “Trace” checkbox enabled:
script = CreateObject("Python","c:/path/to/your/embedded-example.py")
result = script.add_two_numbers({a:3,b:10})
First, we create a “Python” object to hold a reference to the script that contains the function or functions we would like to execute inside Maptitude. Then we execute the function as if it was a public function of this Python script object. In general, the GISDK syntax is:
script = CreateObject("Python","//full/path/to/your/script_file.py")
result = script.function_name({ arg1: val1 , arg2: val2 , arg3: val3 , ... })
The Maptitude runtime executes the Python function in the Maptitude main process, in a similar fashion it executes all the GISDK functions and macros and methods in a .NET class. Your Python function can return any of following types: None, string, number, bool, dictionary, tuple, list, GISDK object and np.array. A dictionary will be translated to an "option array", and an np.array is translated to a GISDK array.
You shoud translate your result to one of these types before returning it from your function. Many Python compound types can be translated to a dictionary (dict) or a list in one statement.
Your Python function can call GISDK functions via caliperpy. If your Python function requires a dataframe as input or returns a dataframe as output, you can use caliperpy functions to translate them to/from dataviews, and return the dataview name in the result. If your Python function returns a compound type that is not supported by the GISDK, the GISDK will throw an error.
Connecting to the GISDK across Python functions
The most common pattern is to use the GISDK across two or more Python functions. To simplify connecting and disconnecting from the GISDK, a new class was added, with two static methods: connect() and disconnect().
import caliperpy
class YourOwnClass(unittest.TestCase):
def test_connecting_to_Maptitude(self):
dk = caliperpy.Maptitude.connect()
// use the GISDK
def test_do_more_processing(self):
dk = caliperpy.Maptitude.connect()
// do some more processing
def test_finally_disconnect_when_done(self):
// disconnect from Maptitude when finished
caliperpy.Maptitude.disconnect()
Fast Translations of GISDK Binary Tables To and From NumPy Panda DataFrames
Two new functions are available to translate GISDK binary files to and from numpy dataframes. The functions are very fast, and let you translate files with hundreds of thousand of rows quickly. The two functions are:
df = dk.GetDataFrameFromBin(bin_file_name,debug_messages)
outputFileName = dk.WriteBinFromDataFrame(df,outputFileName)
These functions are very fast. On a 3.6 Ghz CPU with 64 GB of RAM, translations to and from a data frame with more than 1 million rows and 9 columns of type number, it took less than 1 second. The same table with 9 columns of type string can be translated in 5 seconds.
Simplified RunMacro() Calling Arguments
dk.RunMacro() function now takes the same arguments as the RunMacro() function in the GISDK.
In older versions of our caliperpy package, the first argument of dk.RunMacro() should have been the "compiled UI package":
dk.RunMacro("ui_database","macro name", arg1, arg2, arg3)
It is now NOT necessary to specify the "ui_database" as the first argument of dk.RunMacro()
Instead, the same arguments in the same order can be used in Python as in the GISDK:
dk.RunMacro("macro name",arg1,arg2, arg3)
Please note that if you write your Python code by passing "compiled UI package" as the first argument, it will not work.
In the Python command line, start by importing the package caliperpy and create a connection to the GISDK. The folder GISDK\Samples\Python contains an example Python script that you can copy to your own Documents folder to get started.
import caliperpy
dk = caliperpy.Maptitude.connect()
print(dk)
The connection object can be created with the log file option:
dk = caliperpy .Maptitude.connect( log_file = "python.log")
You can call GISDK functions using the dot notation with the dk object:
folder = dk.RunMacro("G30 Tutorial Folder")
table_name = dk.OpenTable("airports","ffb",[folder + "airports.bin",None])
num_rows = dk.GetRecordCount(table_name,None)
print ("table " + table_name + " has " + str(num_rows) + " rows.")
All of the GISDK functions are available as methods of the Python object dk. The general syntax is:
result = dk.FunctionName(*args)
You can also call macros compiled into a GISDK module with the rscc tool. For example, to execute the macro called "Echo" located in the GISDK package "gis_ui" you write:
res = dk.RunMacro("Echo","string argument",123,{ "key": "value1" , "key2": "value2" })
The Python syntax to call a GISDK macro is:
result = dk.RunMacro(macro_name,*args,**kwargs)
Argument | Type | Contents |
---|---|---|
macro_name | string | Name of the GISDK macro |
args | Non-keyword args | List of parameters to pass to the GISDK macro |
kwargs | Keyword args | Named arguments |
You can pass Python-style named arguments (*kwargs) to the GISDK macro, and they will be translated into an GISDK option array.
dk.SetAlternateInterface("c:\\data\\my_macros.dbd")
dk.RunMacro("Geocode Macro",address="1172 Beacon St",zip_code="02461")
dk.SetAlternateInterface(None)
This script will execute the macro called "Geocode Macro" compiled in the file "c:\\data\\my_macros.dbd", passing as named input arguments address and zip_code.
The "Geocode Macro" source code (e.g. my_macros.rsc) should look like this:
// GISDK code
macro "Geocode Macro" (kwargs)
input_address = kwargs.address
input_zip_code = kwargs.zip_code
// body of the macro here
endmacro
Note that Python uses the keyword None instead of null. You cannot leave it out as in GISDK; instead you have to write None for a null argument.
Note also that arrays and collections are two different objects in Python. Python uses square brackets for arrays:
[folder + "airports.bin",None]
Curly brackets are only used for collections, such as option arrays:
{ "key": "value1" , "key2": "value2" }
When you are done with the GISDK tou can disconnect the process by calling the disconnect method:
caliperpy.Maptitude.disconnect()
Only one Python script can access the GISDK at a time, and that all GISDK method calls must be executed sequentially.
In the latest version of caliperpy we added Python functions to translate between numpy dataframes and GISDK dataviews.
The first three functions work with GISDK views that are open in the current workspace. The functions do not require to close the views before translating them to/from dataframes.
The three functions are: GetDataFrameFromView, GetViewFromDataFrame, and UpdateViewFromDataFrame.
The API is:
GetDataFrameFromView(view_set_name, field_names) -> pd.core.frame.DataFrame
Create a Numpy Dataframe object from an input view and set name, and a list of field names. If the list of field names is missing, it will create a dataframe with all the fields of the input view
column_names = [ "ID1" ,"AB_Time" , "BA_Time" ] data_df = dk.GetDataFrameFromView(link_flows_table,column_names)
GetViewFromDataFrame(input_df,output_view_name) -> str
Copy the input dataframe to a new GISDK view name. Returns the name of the new view.
data_table = dk.GetViewFromDataFrame(data_df[["ID","Distance","Speed"]],"data_table") num_rows = dk.GetRecordCount(data_table,None)
UpdateViewFromDataFrame(input_df, input_view_name) -> dict
Update columns in the existing input_view_name by copying them from the input dataframe.
This first column in the dataframe must contain unique values: it will be used to identify the rows to replace or add to the input view.
The other columns will replace existing columns or be added to the input view.
Returns a dictionary with information about rows added, rows replaced, columns added, and column replaced.
result = dk.UpdateViewFromDataFrame(data_df,link_flows_table)
The following Python script demonstrates how to use the three functions:
""" Test updating a view from a dataframe
"""
dk = caliperpy.Maptitude.connect()
tutorial_folder = dk.RunMacro("G30 Tutorial Folder")
link_flows_table = dk.OpenTable("turn_movements","ffb",[ tutorial_folder + "LinkFlows.bin",None])
num_rows = dk.GetRecordCount(link_flows_table,None)
print(f"table {link_flows_table} has {num_rows} rows.")
all_column_names , _ = dk.GetFields(link_flows_table,"All")
print(f"{link_flows_table} column names are: {all_column_names}
# copy some of the columns to a dataframe
column_names = [ "ID1" ,"AB_Time" , "BA_Time" ]
data_df = dk.GetDataFrameFromView(link_flows_table,column_names)
# Add some data to the dataframe
max_id_value = data_df["ID1"].max()
data_df = data_df.append({"ID1": max_id_value+1, "AB_Time": np.random.rand() ,
"BA_Time": np.random.rand() }, ignore_index=True)
data_df = data_df.append({"ID1": max_id_value+2, "AB_Time": np.random.rand() ,
"BA_Time": np.random.rand() }, ignore_index=True)
data_df["Distance"] = np.random.rand(len(data_df))
data_df["Speed"] = np.random.rand(len(data_df)
print("data_df shape:", data_df.shape)
print("The first 5 rows of data_df:")
print(data_df.head())
# Create a new GISDK view out of some of the columns in the dataframe
data_table = dk.GetViewFromDataFrame(data_df[["ID1","Distance","Speed"]],"data_table")
num_rows = dk.GetRecordCount(data_table,None)
# Add/replace rows in the original link_flows_table with all the rows from data_df
result = dk.UpdateViewFromDataFrame(data_df,link_flows_table)
printf(f"result: {result}")
#result: {'destination view': 'turn_movements', 'rows added': 2,
# 'rows replaced': 5511, 'columns added': 2, 'columns replaced': 2}
Finally, you can copy a dataframe to a binary table that the GISDK can open later via OpenTable. The function is this one:
WriteBinFromDataFrame(data_df, output_file_name) -> str
Writes the input dataframe data_df to the binary table output_file_name. Returns the name of the output file.
bin_file_name = f"{os.getcwd()}/table-name.bin" dk.WriteBinFromDataFrame(data_df,bin_file_name) table_name = dk.OpenTable("table-name","ffb",[bin_file_name,None])
You can use caliperpy to connect to Maptitude from Jupyter Notebooks (scripts with extension .ipynb). The program folder Python\GISDK\Samples\Python\notebooks contains an example notebook called map_tutorial.ipynb. Copy this folder to a folder you can write to. You can open the file map_tutorial.ipynb directly in Visual Studio Code, after you have installed the extensions to run jupyter notebooks, and run your notebook cell-by-cell, then save it to PDF or HTML.
Here is a longer example showing you how to select data from a Maptitude table:
import caliperpy, sys
def main():
dk = caliperpy.Maptitude.connect()
folder = dk.RunMacro("G30 Tutorial Folder")
table_name = dk.OpenTable("airports","ffb",[folder + "airports.bin",None])
num_rows = dk.GetRecordCount(table_name,None)
print ("table " + table_name + " has " + str(num_rows) + " rows.")
dk.SetView(table_name)
dk.SetSelectMax(10)
options = { 'Index Limit': 0}
query = "select * where Domestic > 10000"
num_found = dk.SelectByQuery("high traffic","several",query,options)
if ( num_found > 0 ) :
print (str(num_found) + " records match the query: " + query)
view_set = table_name + "|high traffic"
field_names , field_specs = dk.GetFields(table_name,"All")
print ("field_names: " + str(field_names))
print ("field specs: " + str(field_specs))
sort_order= None
options = None
order = "Row"
i = 1
rh = dk.GetFirstRecord(view_set,None)
for row in dk.GetRecordsValues(view_set,rh,field_names,sort_order,num_found,order,None):
print ("[row " + str(i) + "] " + str(row))
i= i + 1
This prints out the following to the console:
[row 1] (u'PPG', u'PAGO PAGO INTL', u'PAGO PAGO', u'AMERICAN SAMOA', u'AS', u'US', u'51525.*A', None, None, 0, 57835, 57835, u'Western-Pacific', u'HNL', u'Estimated', 30, u'GNC 20', u'Yes', u'CS 05/23', u'NGY', u'Yes')
[row 2] (u'LNY', u'LANAI', u'LANAI CITY', u'MAUI', u'HI', u'US', u'52402.*A', None, None, 0, 84150, 84150, u'Western-Pacific', u'HNL', u'Estimated', 1308, u'HAWAIIAN ISLANDS', None, u'BS 05/73', u'NGY', u'No')
You should be able to write your entire script in Python. caliperpy lets you call all of the GISDK functions and built-in macros in the default "gis_ui" module that ships with the program.
There are some cases when you need to write your own modules directly in the GISDK language. You can access in Python custom GISDK classes and objects. For example, you can write a GISDK program with a class called "Calculator":
// GISDK source code
// test this GISDK class from Python
Class "calculator" (message,args))
init do
self.message = message
self.args = args
enditem
macro "add" (a,b) do
Return(a+b)
enditem
macro "subtract" (a,b) do
Return(a-b)
enditem
macro "macro with named args" (kwargs) do
Return("address:" + kwargs.address + "zip code:" + kwargs.zip_code)
enditem
endClass
Use the GISDK toolbox to compile this code into a GISDK "ui" module, e.g. test.dbd.
In Python3, you can create an instance of the "Calculator" class by creating an object of type GISDKObject:
my_options = { "key": "value1" , "key2": "value2" }
compiled_ui = “c:\\path\\test.dbd”
c = dk.CreateGISDKObject(compiled_ui , "calculator",my_options)
c.message = "this message is from python"
c.args = [ 2 , 3 , 4]
print (c.message)
print (c.args)
print (c.add(3,4))
print (c.subtract(4,5))
You can create an instance of a GISDK class by calling CreateGISDKObject():
compiled_ui = "\\path\\to\\compiled_ui"
GISDK_object = dk.CreateGISDKObject(compiled,class_name,*args,**kwargs)
And you execute methods via:
GISDK_object.MacroName(args,**kwargs)
You access GISDK object properties and execute methods using the dot notation (e.g. c.message)
You should always dispose explicitly of the GISDK object in your Python script when you are done using it. If GISDK_object is the name of your object, then you can either use the statement:
GISDK_object = None
Or you can embed your Python code within a top-level function (e.g., def main()). This will ensure that the objects created by your Python code are properly removed by from the GISDK environment when your script terminates.
If you do have an old GISDK script that does not use classes, for example:
Macro "ProcessingMacro1" (arg1, arg2)
result2 = RunMacro("ProcessingMacro2",arg1,arg2)
result3 = RunMacro("ProcessingMacro3",arg3,arg4)
// etc...
Return(result1)
endMacro
Macro "ProcessingMacro2" (arg3,arg4)
Return(result2)
endMacro
Macro "ProcessingMacro2" (arg3,arg4)
Return(result2)
endMacro
Macro "ProcessingMacro3" (arg3,arg4)
Return(result2)
endMacro
We recommend you write a GISDK class to expose only the macros that you want to call from Python.
Creating a GISDK class exposes a simple, object-oriented API to Python. You can add your own setup code to the constructor and tear down code to the destructor, and add only the public macros that you need to call from Python (ProcessingMacro1), hiding the implementation macros which do not need to be called directly in Python (ProcessingMacro2 and ProcessingMacro3).
Class "My Own Class"(args)
// Constructor
init do
// Setup code
enditem
// Destructor
done do
// Tear down code
enditem
macro "ProcessingMacro1" (args) do
Return(RunMacro("ProcessingMacro1",args))
enditem
endClass
You can now save the GISDK class in a separate resource file, and compile it along with your old resource files into a single "GISDK UI module". Please, refer to the section "To Compile a List of Resource Files into a Custom UI Database" in the Compiling and Testing with the GISDK Toolbar Help Page for details on how to compile multiple resources files into a single "ui" file.
The Python code will then look like this:
import caliperpy
dk = caliperpy.Maptitude.connect()
GISDK_module = "c:\\ProgramData\\Caliper\\My-Own-Class.dbd"
obj = dk.CreateGISDKObject(GISDK_module,"My Own Class",args)
result = obj.ProcessingMacro1(args)
obj = None
In Maptitude and TransModeler, Vectors are a compound data type, very much like 1-dimensional arrays, except that all the elements are of the same type (e.g. string, integer or double).
A GISDK vector is stored efficiently into a single block of memory. In Python you can use the double arrow overloaded operator << to express vector assignment operations.
You can create vectors using the whole set of GISDK functions, combined with the Python range shortcuts:
import sys, traceback, caliperpy
dk = caliperpy.Maptitude.connect()
folder = dk.RunMacro("G30 Tutorial Folder")
view_name = dk.OpenTable("airports","ffb",[folder + "airports.bin",None])
v = dk.GetDataVector(view_name+"|","Name",None)
x = dk.VectorToArray(v)
print (v)
print (x)
x = dk.ArrayToVector.htm"([ 1 , 2, 3 , 4])
dk.ShowArray([ "x" , x ])
v = dk.ArrayToVector.htm"(range(1,10,1))
print (dk.ShowArray([ "v" , v ]))
w = dk.ArrayToVector.htm"(range(101,110,1))
print ( dk.ShowArray([ "w" , w ]) )
z = dk.ArrayToVector.htm"([0]*10)
print (dk.ShowArray([ "z" , z ]))
You can use the GISDK assignment operator in Python "<<" also to work with simple arithmetic operations on vectors:
z << v + w
In a vector assignment statement the vector on the left hand side (z) must exist before you can assign it.
print dk.ShowArray({ "v": v , "w": w, "z": z})
import caliperpy
dk = caliperpy.Maptitude.connect()
GISDK_module = "c:\\ProgramData\\Caliper\\My-Own-Class.dbd"
obj = dk.CreateGISDKObject(GISDK_module,"My Own Class",args)
result = obj.ProcessingMacro1(args)
obj = None
In Maptitude and TransModeler, Vectors are a compound data type, very much like 1-dimensional arrays, except that all the elements are of the same type (e.g. string, integer or double).
A GISDK vector is stored efficiently into a single block of memory. In Python you can use the double arrow overloaded operator << to express vector assignment operations.
You can create vectors using the whole set of GISDK functions, combined with the Python range shortcuts:
import sys, traceback, caliperpy
dk = caliperpy.Maptitude.connect()
folder = dk.RunMacro("G30 Tutorial Folder")
view_name = dk.OpenTable("airports","ffb",[folder + "airports.bin",None])
v = dk.GetDataVector(view_name+"|","Name",None)
x = dk.VectorToArray(v)
print (v)
print (x)
x = dk.ArrayToVector.htm"([ 1 , 2, 3 , 4])
dk.ShowArray([ "x" , x ])
v = dk.ArrayToVector.htm"(range(1,10,1))
print (dk.ShowArray([ "v" , v ]))
w = dk.ArrayToVector.htm"(range(101,110,1))
print ( dk.ShowArray([ "w" , w ]) )
z = dk.ArrayToVector.htm"([0]*10)
print (dk.ShowArray([ "z" , z ]))
You can use the GISDK assignment operator in Python "<<" also to work with simple arithmetic operations on vectors:
z << v + w
In a vector assignment statement the vector on the left hand side (z) must exist before you can assign it.
print dk.ShowArray({ "v": v , "w": w, "z": z})
This prints to the console:
[["z",["vector",[0,0,0,0,0,0,0,0,0,0]]],
["w",["vector",[101,102,103,104,105,106, 107,108,109]]],
["v",["vector",[1,2,3,4,5,6,7,8,9]]]]
Maptitude provides a broad array of GISDK functions for creating and working with matrices and matrix files. Using GISDK functions you can program your own matrix creation, manipulation, and storage. A matrix file can store efficiently one or more matrices called cores of the same type (e.g. integers or doubles).
Like a 1-dimensional GISDK vector, a 2-dimensional GISDK matrix is stored efficiently into a single block of memory, either by row (row major) or by column (column major). In analogy to GISDK vectors, in Python you can use the double arrow operator << to express matrix assignment operations:
dk = caliperpy.Maptitude.connect()
...
mc = dk.GetMatrixEditorCurrency()
v = dk.GetMatrixVector(mc, {"Marginal": "Row Sum" })
mc << mc / v
mc << mc + mc
...
In a matrix assignment statement the matrix core on the left hand side (mc) must exist before you can assign it.
The programming examples in the folder GISDK\Samples\Python demonstrate how to import and export GISDK matrices to and from dataframes and to and from the OMX file format.
The Caliper GISDK provides a simple method called CreateManagedObject that lets you access any .NET assembly installed on your computer. Here is a simple example for the .NET class
System.DateTime:
import sys, traceback, caliperpy
date = dk.CreateManagedObject(None,"System.DateTime",[1980,1,1,0,0,0,0])
is_GISDK_object = dk.IsGISDKObject(date)
if is_GISDK_object:
net_class_info = dk.GetClassInfo(date)
print("" + str(type(net_class_info)) + " " + str(net_class_info))
later_date = date.AddDays(300.00)
print("First date: " + date.ToString())
print("Later date: " + later_date.ToString())
Any COM object is also accessible in the GISDK, via the call to CreateCOMObject(), like this:
xmldoc = dk.CreateComObject("MSXML2.DOMDocument.6.0")
xmldoc.validateOnParse = 0
print(dir(xmldoc))
All of the standard GISDK compound data types are accessible in Python, using the dot notation:
coord = dk.Coord(37757563, -122437162)
lon = coord.Lon
lat = coord.Lat
scope = dk.Scope(coord,100,100,0)
center = scope.Center
w = scope.Width
h = scope.Height
print (dk.ShowArray({ "coord": coord , "lon": lon ,
"lat": lat , "scope": scope ,
"center": center , "w": w, "h": h }))
[["coord",["Coord", "37.757563 -122.437162" ]],
["lon",37757563],
["lat",-122437162],
["scope",["Scope","37.757563 -122.437162 100 100 Miles" ]],
["center",["Coord", "37.757563 -122.437162" ]],
["w",100],
["h",100]]
All runtime messages output by the GISDK function ShowMessage() and exceptions raised by the Maptitude child process are written to the log file python.log located in the Maptitude program folder. To write a message to the python.log file, use the GISDK function ShowMessage():
>>> dk.ShowMessage("message dumped to the python.log file")
content of python.log:
(Thu Sep 18 12:15:49 2008) message dumped to the python.log file :
You can print the internal values of variables passed into the Maptitude process by Python. Use the GISDK function ShowArray(), which in Python returns a string that can be printed to the console:
>>> print (dk.ShowArray([ dk.Scope(dk.Coord(100,-100),20,30, None)]))
[["Scope","0.0001 -0.0001 20 30 Miles" ]]
To dump Maptitude variables to the python.log file, use the short-hand dk.L() which combines a call to ShowMessage() and ShowArray():
>>> dk.L([ "hello" , "world" , dk.Coord(100,-100) ])
content of python.log:
(Thu Sep 18 12:15:49 2008) ["hello","world",["Coord", "0.0001 -0.0001" ]]:
The same script will run out-of-process in a Windows terminal window or in-process inside Maptitude, provided that you insert the header and the footer as explained above.
You should try your script out-of-process first, debuggging it line-by-line in your favorite IDE, e.g. Visual Studio Code with the latest Python extensions.
For example, in Visual Studio Code, open the folder containing your script (e.g. if the file is c:\Projects\embedded-example.py then open the folder C:\projects). In Visual Studio Code, choose Run – Debug – Open Configurations and add the following debug configuration to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
// args: [ "function_name" , "arg1=val1" , "arg2=val2" , ... ]
"args": [
"add_two_numbers",
"first=1",
"second=10"
],
"justMyCode": true
}
]
}
Start Maptitude on your desktop before running your python script in a terminal window or in your debugger IDE.
When a script runs in the terminal window, all print() statements are displayed in the console, and you can debug the script line-by-line.
Running Your Script Inside Maptitude
After you have run successfully the script out-of-process, you can run it in process via the GISDK. At this time, a line-by-line Python debugger is not available in-process with Maptitude.
When running in process, there is no console window, and you can view all the print() statements inside the "Debug View" desktop application by the Windows Sysinternals team, which you can download here:
https://learn.microsoft.com/en-us/sysinternals/downloads/debugview
You can access the GISDK from your Python function via caliperpy and the familiar "dk." statements. The same statements will work out-of-process and in-process, without changes. Here is an example script with a function that reports progress inside Maptitude. You can find the script in the prorgam folder GISDK\Samples\Python\progress.py
# Python
from time import sleep
import caliperpy
# pylint: disable=undefined-variable
context = caliperpy.ScriptContext(PythonContext if caliperpy.IsInProcess() else None
def show_progress(first: int,second: int) -> dict:
""" test progress using caliperpy dk """
dk = caliperpy.Maptitude.connect()
dk.SetErrorLogFileName(None)
first = int(first)
last = int(second)
pbar = dk.CreateGISDKObject("gis_ui","G30 Progress Bar","Status", True,last–first+1)
for n in range(first,last+1):
message = f"the value of n is {n}"
print(message)
pbar.Step(1,message)
sleep(0.4)
return { "a": first, "b" : last , "difference": last - first + 1}
if __name__ in ["__main__","__ax_main__"]:
result = context.run(locals())
// GISDK
script = CreateObject("Python", Full_Path_To_Script)
res = script.show_progress({first: 10, second: 30})
a = res.a
b = res.b
diff = res.difference
©2025 Caliper Corporation | www.caliper.com |