Using Python and MapInfo with Callbacks


The other day I posted an entry about using MapInfo with Python and Qt (see https://woostuff.wordpress.com/2011/03/05/mapinfo-map-control-into-qt-python-form/), one big thing that I missed was support for callbacks, which if you want to do anything related to integrated mapping is a must for map tool support.

Turns out it is pretty easy, and today I worked out how.

You will need to create a class in python that looks something like this:

class Callback():
    _public_methods_ = ['SetStatusText']
    _reg_progid_ = "MapInfo.PythonCallback"
    _reg_clsid_ = "{14EF8D30-8B00-4B14-8891-36B8EF6D51FD}"
    def SetStatusText(self,status):
        print status

This will be our callback object that we will need to create for MapInfo.

First I will explain what some of the funny stuff is:

  • _public_methods_ is a Python array of all the methods that you would like to expose to COM eg MapInfo in this case. This attribute is a must for creating a COM object.
  • _reg_progid_ is the name of your COM application or object.  This can be anything you would like.
  • _reg_clsid_ is the GUID, or unique id, for the object or app.  Do not use the one I have, call the following in a Python shell to create your own.
             import pythoncom
             pythoncom.CreateGuid()
             
  • SetStatusText is the MapInfo callback method that is called when the status bar changes in MapInfo.

In order to use the class as a COM object we have two more steps to complete, one is registering the COM object and the other is creating it.

First, in oder to register the object we call the following code from our main Python method:

if __name__ == "__main__":
    print "Registering COM server..."
    import win32com.server.register
    win32com.server.register.UseCommandLine(Callback)
    main()

This will register the COM object which will mean it can then be creating for use by MapInfo.

In order to create our callback in Python we call:

callback = win32com.client.Dispatch("MapInfo.PythonCallback")

and set it as our callback object for MapInfo:

mapinfo.SetCallback(callback)

So after all that the final code looks like this:

def main():
    from PyQt4.QtCore import *
    from PyQt4.QtGui import *
    from win32com.client import Dispatch
    import sys

    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_NativeWindows,True)
    wnd = QMainWindow()
    wnd.resize(400, 400)
    widget = QWidget()
    wnd.setCentralWidget(widget)
    wnd.show()

    handle = int(widget.winId())
    mapinfo = Dispatch("MapInfo.Application")
    callback = win32com.client.Dispatch("MapInfo.PythonCallback")
    mapinfo.SetCallback(callback)
    mapinfo.do('Set Next Document Parent %s Style 1' % handle)
    mapinfo.do('Open Table "D:\GIS\MAPS\Property.TAB"')
    mapinfo.do('Map from Property')

    app.exec_()

class Callback():
    """ Callback class for MapInfo """
    _public_methods_ = ['SetStatusText']
    _reg_progid_ = "MapInfo.PythonCallback"
    _reg_clsid_ = "{14EF8D30-8B00-4B14-8891-36B8EF6D51FD}"
    def SetStatusText(self,status):
        print status

if __name__ == "__main__":
    print "Registering COM server..."
    import win32com.server.register
    win32com.server.register.UseCommandLine(Callback)
    main()

and the result is a map window and information printed to the console.

Information from MapInfo callback

I think Python could be a good language to prototype MapInfo based app, or even build a whole app itself. If you do end up making something of it let me know I am quite interested with what people could come up with.

MapInfo map control into Qt Python form


Tonight for a bit of fun, or shits and jiggles as we say here, I thought I would try and embed a MapInfo map control into a Qt python widget (although I should be studying, but it’s Saturday night) .

Turns out it is pretty easy!

pls send me teh codez? OK here you go.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from win32com.client import Dispatch
import sys

app = QApplication(sys.argv)
app.setAttribute(Qt.AA_NativeWindows,True)
wnd = QMainWindow()
wnd.resize(400, 400)
widget = QWidget()
wnd.setCentralWidget(widget)
wnd.show()

handle = int(widget.winId())
mapinfo = Dispatch("MapInfo.Application")
mapinfo.do('Set Next Document Parent %s Style 1' % handle)
mapinfo.do('Open Table "D:\GIS\MAPS\Property.TAB"')
mapinfo.do('Map from Property')

app.exec_()

The above code will load MapInfo and open the property layer into the Qt Widget control, with the result below.

MapInfo map in python Qt based form

So this means you don’t “always” have to write your MapInfo based apps in C# or C++; of course I already knew this as anything that can use OLE and provide a native window handle to MapInfo will work, I just never tried it.

MapInfo .Net Wrapper – The End…


Well it was a good project while it lasted, but I’m sorry to say that I will not be continuing work on it. For now anyway. Very sorry to those who were waiting for me to release v2.

Over the last couple of months, the amount of time I have spent on the project has decreased dramatically due to full-time work, part-time uni, other projects and family life.

My reasons for ending the project are: (1) I don’t have to the time to spend on it. My current work has me doing less development and more surveying and map work. (2) full-time work and part-time uni leaves very little time for anything else. (3) Having recently rediscovered QGIS, I feel my programming efforts are better spent on that project and helping develop and promote it more especially in Australia .  (4) Being a lone developer is hard, especially on a large library type project.  I was not able to keep the code coming, examples clean and up to date, and docs written.

I will however be continuing my other MapInfo based applications which can be found on: http://code.google.com/p/nathansmapinfoprojects/ I have no intentions of ending those.  I know a lot [few] of people use them so I am quite happy to continue support and features.

So, again, very sorry that it has ended but everything must come to an end at some stage.   The code is up on http://code.google.com/p/mapinfodotnetwrapper/ if anyone else would like to continue with it I am quite happy to give you full write access to the repository.

Creating an instance of a MapInfo COM object in .NET – Speed Tests


A while ago I posted about how to create an instance of MapInfo in .Net, If you missed those posts then they can be found here.  In these posts I outlined how you can create a instance using three different methods, in the reflection based post I said that one of the disadvantages of doing it this way was that it was slower.   I said this due to just my observations but I thought it would be a good idea to put it to the test and show the speed difference.

I created a simple project to test and show me the results of three different things: Speed of complied MBX, calling a MapBasic function though the Do and via the interface (see posts 1 & 3) and calling Do and Eval via reflection (see post 2)

The code is simple, and is posted at the bottom of this post, the command “Fetch Next From {Table}” is called a number of times: 100;200;300;500;1000;2000;5000 and each block is timed.

After running each iteration set 3 times, these are the results :

SpeedTests

Behold my fancy graph making skills….or lack there of.  The time is in seconds, so the 0.032 in the Interface pass 1 is 0.032 seconds for 200 iterations which is still pretty quick.  You’ll notice that using the reflection based method starts to really take its toll when you are doing 5000 iterations, mind you ~1 second is still pretty quick.

The Mapbasic code I used:

Declare Sub Main
Declare Function Time(count as Integer) as Float
Declare Function GetTickCount Lib "kernel32" () As Integer

Sub Main
   Dim pass as Integer
   pass = 0
	Dim a,b,c,d,e,f,g as Integer
   a = 100
   b = 200
   c = 300
   d = 500
   e = 1000
   f = 2000
   g = 5000

	Do While pass <= 2
       Print "====PASS " + pass + "========="
       Print Time(a)
       Print Time(b)
       Print Time(c)
       Print Time(d)
       Print Time(e)
       Print Time(f)
       Print Time(g)
		pass = pass + 1
   Loop
End Sub

Function Time(count as Integer) as Float
    Dim a as Integer
    Dim b as Integer
	Dim i as Integer
	a = GetTickCount()
	For i = 0 to count
       Fetch Next From Untitled
    Next
	b = GetTickCount()
	Time = (b - a) / 1000
End Function

and the C# code:

class ReflectionMapInfo
    {
        private readonly object mapinfo;

        public ReflectionMapInfo(object mapinfo)
        {
            this.mapinfo = mapinfo;
        }

        public void Do(string commandstring)
        {
            this.mapinfo.GetType().InvokeMember("Do", BindingFlags.InvokeMethod,
                                                                null, this.mapinfo,
                                                                new[] {commandstring});
        }
    }

    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern int GetTickCount();
        static MapInfoApplication mapinfo = new MapInfoApplication();
        static ReflectionMapInfo reflectionmapinfo = new ReflectionMapInfo(mapinfo);

        static void Main(string[] args)
        {
            mapinfo.Do(@"Open Table ""C:\Users\Woo\Documents\Untitled.TAB""");

            int pass = 0;
            while (pass <= 2)
            {
                Console.WriteLine("Interface Test, Pass " + pass + "Count 100 " + Time(100));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 200 " + Time(200));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 300 " + Time(300));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 500 " + Time(500));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 1000 " + Time(1000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 2000 " + Time(2000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 5000 " + Time(5000));
                pass++;
            }

            pass = 0;
            while (pass <= 4)
            {
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 100 " + ReflectionTime(100));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 200 " + ReflectionTime(200));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 300 " + ReflectionTime(300));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 500 " + ReflectionTime(500));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 1000 " + ReflectionTime(1000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 2000 " + ReflectionTime(2000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 5000 " + ReflectionTime(5000));
                pass++;
            }
            Console.ReadLine();
        }

        public static double Time(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                mapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            mapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }

        public static double ReflectionTime(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                reflectionmapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            reflectionmapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }
    }

Creating an instance of a MapInfo COM object in .NET – Speed Tests


Blog has now moved to <a href="http://nathanw.net&quot;A while ago I posted about how to create an instance of MapInfo in .Net, If you missed those posts then they can be found here.  In these posts I outlined how you can create a instance using three different methods, in the reflection based post I said that one of the disadvantages of doing it this way was that it was slower.   I said this due to just my observations but I thought it would be a good idea to put it to the test and show the speed difference.

I created a simple project to test and show me the results of three different things: Speed of complied MBX, calling a MapBasic function though the Do and via the interface (see posts 1 & 3) and calling Do and Eval via reflection (see post 2)

The code is simple, and is posted at the bottom of this post, the command “Fetch Next From {Table}” is called a number of times: 100;200;300;500;1000;2000;5000 and each block is timed.

After running each iteration set 3 times, these are the results :

SpeedTests

Behold my fancy graph making skills….or lack there of.  The time is in seconds, so the 0.032 in the Interface pass 1 is 0.032 seconds for 200 iterations which is still pretty quick.  You’ll notice that using the reflection based method starts to really take its toll when you are doing 5000 iterations, mind you ~1 second is still pretty quick.

The Mapbasic code I used:

Declare Sub Main
Declare Function Time(count as Integer) as Float
Declare Function GetTickCount Lib "kernel32" () As Integer

Sub Main
   Dim pass as Integer
   pass = 0
	Dim a,b,c,d,e,f,g as Integer
   a = 100
   b = 200
   c = 300
   d = 500
   e = 1000
   f = 2000
   g = 5000

	Do While pass <= 2
       Print "====PASS " + pass + "========="
       Print Time(a)
       Print Time(b)
       Print Time(c)
       Print Time(d)
       Print Time(e)
       Print Time(f)
       Print Time(g)
		pass = pass + 1
   Loop
End Sub

Function Time(count as Integer) as Float
    Dim a as Integer
    Dim b as Integer
	Dim i as Integer
	a = GetTickCount()
	For i = 0 to count
       Fetch Next From Untitled
    Next
	b = GetTickCount()
	Time = (b - a) / 1000
End Function

and the C# code:

class ReflectionMapInfo
    {
        private readonly object mapinfo;

        public ReflectionMapInfo(object mapinfo)
        {
            this.mapinfo = mapinfo;
        }

        public void Do(string commandstring)
        {
            this.mapinfo.GetType().InvokeMember("Do", BindingFlags.InvokeMethod,
                                                                null, this.mapinfo,
                                                                new[] {commandstring});
        }
    }

    class Program
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern int GetTickCount();
        static MapInfoApplication mapinfo = new MapInfoApplication();
        static ReflectionMapInfo reflectionmapinfo = new ReflectionMapInfo(mapinfo);

        static void Main(string[] args)
        {
            mapinfo.Do(@"Open Table ""C:UsersWooDocumentsUntitled.TAB""");

            int pass = 0;
            while (pass <= 2)
            {
                Console.WriteLine("Interface Test, Pass " + pass + "Count 100 " + Time(100));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 200 " + Time(200));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 300 " + Time(300));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 500 " + Time(500));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 1000 " + Time(1000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 2000 " + Time(2000));
                Console.WriteLine("Interface Test, Pass " + pass + "Count 5000 " + Time(5000));
                pass++;
            }

            pass = 0;
            while (pass <= 4)
            {
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 100 " + ReflectionTime(100));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 200 " + ReflectionTime(200));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 300 " + ReflectionTime(300));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 500 " + ReflectionTime(500));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 1000 " + ReflectionTime(1000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 2000 " + ReflectionTime(2000));
                Console.WriteLine("Reflection Test, Pass " + pass + "Count 5000 " + ReflectionTime(5000));
                pass++;
            }
            Console.ReadLine();
        }

        public static double Time(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                mapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            mapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }

        public static double ReflectionTime(int count)
        {
            int start = GetTickCount();
            for (int i = 0; i < count; i++)
            {
                reflectionmapinfo.Do("Fetch Next From Untitled");
            }
            int end = GetTickCount();
            reflectionmapinfo.Do("Fetch First From Untitled");
            return (end - start) / 1000d;
        }
    }

MapInfo Window Manager Version 1 – Coming Soon!


Since the release of version 0.5 of my MapInfo Window Manager I have been working on the new version which will be Version 1.0 and to be released soon. It has some new features, bug fixes and a improved UI.

Some Screenshots and feature run down:

Windows Tab: (Shows Open Windows)

Features:

  • Double click to bring window to the front.
  • F2 turns on rename function for the selected window.
  • Supports all windows types, even info and stat windows. (Version 0.5 just supported Maps and Layouts)
  • Groups can be sorted alphabetically in ascending or descending order.  (A and D buttons)
  • Groups can be sorted individually.
  • All windows can be cloned except for Special windows.
  • Shows count of windows open in each group.
  • Windows can be locked to stop accidental closing.
  • Fancy icons :)

Closed Windows Tab: (shows closed windows allows them to be reopened)

Credit to Peter Horsboll Moller of PBBI MapInfo for this idea, which came from his MapBasic Window Helper tool.

Features:

  • Records all windows as they are closed, letting you restore the window later. (Handy for accidentally closing a legend window)
  • Double click to reopen window.

Settings tab: (Pretty boring just lets you change some settings)

Features:

  • Allows the program to be auto loaded and the tool to be shown when MapInfo loads.
  • Allows the program to store the position of itself.

About Tab: (Pretty boring also, just some info about the app and where to contact me)

Features:

  • It’s an about page, what features can it have!? :)

I have been testing my Window Manager at home and at work, I use it almost everyday, and with some people that I know.

However I would like to get some more testing done by some other people to make sure that Version 1.0 is good and stable.  I am also still look out for anymore ideas that I could throw into Version 1.0.

If you would like to give the beta a test and do some bug hunting for me, or likewise if you have a feature you would like to see rolled into Version 1.0, please contact me on here or by email (which you can see in the about tab page screenshot above)

Just for fun:

Here is a comparison screenshot of the old and new one:

Unofficial MapInfo feature request list – Updated Again!


EDIT:

So nothing official was decided however I really like the layout and usability of http://getsatisfaction.com/mapinfopro and Bill Theon added my wish list to the header of MapInfo-l so I guess we can use it for a while until something better comes along.   I am looking a buying some paid hosting and moving my blog and creating a better wish list, something that I have more control over.  Something like http://www.opensourcerails.com/projects/1785-OpenMind but money is tight so I don’t think that will happen for a while.

The other day I was looking around some GIS websites and found that ESRI have a place where people can go and post feature requests,bugs,ideas about all the different ESRI products.  Looking around I wasn’t able to find anything like this for any MapInfo products, so I thought it might be a good idea to start one.

A lot of information flows though the global MapInfo-l message board, things that would be nice to have down as feature requests or bugs so that people can comment and PBBI can really see what people would like in their products.  I am aware that you can send feature requests though the MapInfo Professional software however I think having a community effort would be much better as you can quickly see what other people
have thought of and if you support that idea also, which in turn lets PBBI see what the community wants.

I have trailed a few different feedback sites and I have gone with a site called getsatisfaction.com, it is user friendly, lets you post comments (even what mood the request or bug puts you in, handy to see what really bugs people). The only thing I wanted and it didn’t have is a vote up and vote down feature, it has it in the paid version put I am only using the free version because I don’t have a spare $20 a month.  My thoughts were, if you would like to see a feature or a bug fixed post a comment on the item and use the smiley face (you’ll see what I mean on the site) to show you support the idea.

I have added two new items which I would like to see, please feel free to add more.  Hopefully we can start building up a list of featuresthat we would like see, or bugs that we find.