Question
· Mar 1, 2023

ObjectScript ListOfObjects to 2D Python array

My general question is how to convert to a %Library.ListOfObjects to a Python "array-like" structure for use in Matplotlib.

Specifically, I have a Line Object which is comprised of a list of Points (see classes below). I want to pass the line to Python to create a Matplotlib Path.

Bonus points for converting the Point to a Python tuple!

Class geo.model.Point Extends %SerialObject
{
  Property latitude As %Float(MAXVAL = 90.0, MINVAL = -90.0, SCALE = 6);
  Property longitude As %Float(MAXVAL = 180.0, MINVAL = -180.0, SCALE = 6);
}
Class geo.model.Line Extends %SerialObject
{
  Property points As list Of geo.model.Point;
}
Product version: IRIS 2022.3
Discussion (13)3
Log in or sign up to continue

Project the list of geo.model.Point as a separate table:

Class geo.model.Line Extends %Persistent
{
Property points As list Of geo.model.Point(SQLPROJECTION = "table/column");
}

And you can use SQL query (via iris.sql) to get all points in line:

SELECT
    points_latitude, 
    points_longitude
FROM geo_model.Line_points
WHERE Line = ?
ORDER BY element_key

If you have thousands of points that would likely be the fastest way to transfer (barring callin/callout shenanigans).

I made a utility for converting between Cache Lists and Arrays with Python Lists and Dictionaries.

Bi-directional. Ensures the keys are strings when going from Array keys to Dictionary keys.

https://github.com/alexatwoodhead/PyNow/blob/main/Py.Helper.xml

Also have an approach for storing (pickle) and retrieving Numpy arrays in IRIS Property Streams if that is of use.

Using the the handy utility from @Alex Woodhead I was able to get something working. As of now you can get the Point, Line and Polygon code from https://github.com/isc-rsingh/spatialiris but I can't guarantee those classes won't change in the future. I include demo code here to show:

  1. $LIST can be converted to a Python list using Py.Helper
  2. 2-dimensional $LISTs (a $LIST of $LISTs) didn't work without running them through the invoke ClassMethod you see below. 
  3. you can integrate with matplotlib to take advantage of the broad and deep range of functionality offered by probably the most popular graphics library around
Class geo.ToolsExample Extends %RegisteredObject
{

ClassMethod createPolygon() As geo.model.Polygon
{
    set l = ##class(geo.model.Line).%New()
    do l.addPoint(##class(geo.model.Point).%New(0.01,0.01))
    do l.addPoint(##class(geo.model.Point).%New(2.01,0.01))
    do l.addPoint(##class(geo.model.Point).%New(2.01,2.01))
    do l.addPoint(##class(geo.model.Point).%New(0.01,2.01))
    do l.addPoint(##class(geo.model.Point).%New(0.01,0.01))
    set polygon = ##class(geo.model.Polygon).%New()
    set polygon.line = l
    Return polygon
}

ClassMethod test()
{
    set polygon = ..createPolygon()
    set isin = ..PointInPolygon(polygon, ##class(geo.model.Point).%New(1.01,1.01))
    w isin,!
    set isout = ..PointInPolygon(polygon, ##class(geo.model.Point).%New(3.01,3.01))
    w isout,!
}

ClassMethod PointInPolygon(poly As geo.model.Polygon, pt As geo.model.Point) As %Boolean
{
    set mp = ##class(%SYS.Python).Import("matplotlib")
    set mpltPath = mp.path
    set polylist = poly.getAsList()
    set nothing = ##class(Py.Helper).toPyListOrString(polylist,.poly2)
    set point = ##class(Py.Helper).toPyListOrString(pt.getAsList())
    set path = ..invoke(mpltPath,poly2)
    Return path."contains_point"(point)
}

ClassMethod invoke(ByRef mpltPath As %SYS.Python, ByRef poly2) As %SYS.Python [ Language = python ]
{
    return mpltPath.Path(poly2)
}

}