Extending MV Basic by Calling Python
One of the compelling things about using Python is the ability to extend the capability of the MultiValue platform. Perhaps you are thinking that means Python having access into the MultiValue environment to read / write data files, run commands and call Basic subroutines. Certainly that can be done but more than that, Python programs can be called from within the MV environment.
I call this ‘bi directional’ connectivity: Python can call into the MV environment or the MV environment can call outside to run Python programs. In this blog post, I’ll build an example of how MV Basic can be extended by calling out and running a Python function.
OK, this example doesn’t actually need Python as it could be done with MV Basic. However, it’s an interesting sample that is not too trivial but not complicated either. So, on that basis it serves to show how to call Python for those situations where it can offer real world assistance by extending MV Basic.
What we will do is calculate the distance between two points on earth using a function that implements the Haversine formula for calculating distances on a sphere.
Here is the source code of the Python function (distance_between_points) that does the distance calculation:
import math from decimal import Decimal def distance_between_points(lat1, long1, lat2, long2): lat1 = Decimal(lat1) lat2 = Decimal(lat2) long1 = Decimal(long1) long2 = Decimal(long2) # Convert latitude and longitude to # spherical coordinates in radians. radius = 6378 # kilometres or 3960 miles dlat = math.radians(lat2-lat1) dlon = math.radians(long2-long1) a = math.sin(dlat/2)*math.sin(dlat/2) + math.cos(math.radians(lat1)) \ * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) d = radius * c return d
Note: I did not write the function distance_between_points displayed above but rather I adapted it from the DISTANCEBTWNPOINTS.PY program in the PP file of the XDEMO account, which comes with Universe 11.3.1.
This is written in Python, so it may look a little unfamiliar but hopefully you can read it well enough. One of the things I like about Python is that the syntax is simple and easy to read.
To help read it, comments in Python start with the hash (#) character https://docs.python.org/3/tutorial/introduction.html.
As you can see, the function takes four parameters, being the latitude and longitude for each of the two points and it returns the distance between those points in kilometres.
Testing the Function
Let’s test our function by measuring the distance between the Australian cities of Sydney and Melbourne. I used the site https://www.gps-coordinates.net/ to get the GPS co-ordinates for the Sydney Opera House and Federation Square in Melbourne, so we will measure the distance between those two points:
Sydney Opera House: Bennelong Point, Sydney NSW 2000, Australia
Federation Square: Swanston St & Flinders St, Melbourne VIC 3000, Australia
The function is in a module called DistanceCalcs (saved in a file called DistanceCalcs.py) and I’ll import that module to make the distance_between_points function available.
First, let’s test it from Python. The following output shows two things:
- First, the DistanceCalcs module is imported and given the identifier of dc. This is done with the Python import statement.
- Second, we call the distance_between_points function, append the result to a string and output the string using the Python print function.
Here is the test:
import DistanceCalcs as dc print('The distance is: ',dc.distance_between_points(-33.856784, 151.215297, -37.817979, 144.969058)) The distance is: 715.3571924529327
The function is telling us that it is 715 km from Sydney to Melbourne but if you have driven between those two cities you’ll know that it is a lot further than that! The difference is that this function is calculating the distance in a straight line whereas the road, of course, meanders around a bit.
Extending MV Basic
We now know that the function works, so let’s use this Python function to extend MV Basic. The Python API for MV Basic includes several functions for interacting with Python and one of those is the PYCALLFUNCTION function, which gives the ability to call a Python function from MV Basic. That is just what we want to do so let’s look at the syntax of the function:
pyresult = PyCallFunction(moduleName, functionName[,arg1,arg2,...])
pyresult is a standard U2 BASIC variable or a PYOBJECT variable.
moduleName is the name of the module where the function is defined.
functionName is the name of the function to be called.
arg1,arg2,… are the arguments to the function object that can be evaluated to a string, a number, or a PYOBJECT.
For more information about the Python API for MVBasic, please refer to the Rocket U2 Python User Guide which is available under the Rocket U2 section of the Rocket Software documentation site.
I’ll use the PyCallFunction function in a small MV Basic program that will ask for the IDs of two cities and then display the distance between those cities as calculated by the Python function.
The GPS co-ordinates for the cities are held in a UniVerse file called CITY, so here is a listing of that file:
CITY…… City…… Latitude.. Longitude.
CAN Canberra -35.30654 149.1266
HOB Hobart -42.87936 147.32941
PER Perth -31.95264 115.8574
BNE Brisbane -27.47126 153.0238
SYD Sydney -33.8568 151.2153
ADL Adelaide -34.9621 138.5999
MEL Melbourne -37.8180 144.9691
DAR Darwin -12.411153 130.877550
Here is the MV Basic program listing of the program CALCDISTANCE. Note the line where we call the Python function using the PYCALLFUNCTION function.
MODULE = 'DistanceCalcs' FUNC = 'distance_between_points' C1 = '' C2 = '' PROMPT '' PRINT 'What is city 1? ': INPUT C1 PRINT 'What is city 2? ': INPUT C2 OPEN 'CITY' TO F.CITY ELSE RESULT = '' ; RETURN READ C1REC FROM F.CITY, C1 THEN C1NAME = C1REC C1LAT = C1REC C1LONG = C1REC END ELSE ERRMSG = 'CITY ID IS NOT AVAILABLE' GOTO ERRORGETDISTANCE END READ C2REC FROM F.CITY, C2 THEN C2NAME = C2REC C2LAT = C2REC C2LONG = C2REC END ELSE ERRMSG = 'CITY ID IS NOT AVAILABLE' GOTO ERRORGETDISTANCE END PYDIST = PYCALLFUNCTION(MODULE, FUNC, C1LAT, C1LONG, C2LAT, C2LONG) CRT 'Distance from ':C1NAME:' to ':C2NAME:': ':OCONV(PYDIST, 'MD1P') EXITGETDISTANCE: CLOSE F.CITY STOP ERRORGETDISTANCE: CRT ERRMSG GOTO EXITGETDISTANCE END
Now we need to run the program and see what it does, so here again is the calculation of the distance between Sydney and Melbourne but this time using MV Basic and having that call the Python function:
RUN PBP CALCDISTANCE What is city 1? SYD What is city 2? MEL Distance from Sydney to Melbourne: 715.4
As before, it calculates the distance to be 715 kilometres.
This has been a short introduction to using Python to extend the ability of MV Basic. There is a lot more to do – particularly when we start using some of the many Python packages that are available. The logical next step from here is to show how to use Python packages and specifically how to use them to extend the functionality of the MultiValue platforms from Rocket Software but that’s for another day.
The sample shown here only includes that one Python function (distance_between_points), one MV Basic program (CALCDISTANCE) and the CITY file. I have shown them all here, so please feel free to try this out for yourself. Send me an email (firstname.lastname@example.org) and let me know how you get on – I’d love to hear from you!