6
\$\begingroup\$

Part 1: General Cleanup

(Edit: Part 2 is here.)


I have a script in a Work Order Management System (Maximo) that performs a spatial query.

Details:

  1. Takes the X&Y coordinates of a work order in Maximo
  2. Generates a URL from the coordinates (including other spatial query information)
  3. Executes the URL via a separate script/library (LIB_HTTPCLIENT)
  4. Performs a spatial query in an ESRI REST feature service (a separate GIS system)
  5. JSON text is returned to Maximo with the attributes of the zone that the work order intersected
  6. The zone number is parsed from the JSON text via a separate script/library (LIB_PARSE_JSON)
  7. Inserts the zone number into the work order record

Jython main automation script

from psdi.mbo import MboConstants
from java.util import HashMap

#Get the y and x coordinates(UTM projection) from the WOSERVICEADDRESS table via the SERVICEADDRESS system relationship
#The datatype of the LatitydeY and LongitudeX is decimal: 1234567.8335815760, 123456.4075621164
#Convert the decimals to integers, and then strings, for the purpose of generating the URL (I don't think the URL can have decimal places)
laty = str(mbo.getInt("SERVICEADDRESS.LatitudeY"))
longx = str(mbo.getInt("SERVICEADDRESS.LONGITUDEX"))

#Verify if the numbers are legitimate UTM coordinates
if len(laty) == 7 and len(longx) == 6:
    #Assemble the URL (including the longx and the laty). Note: The coordinates are flipped in the url
    url="http://something.com/arcgis/rest/services/something/Zones/MapServer/15/query?geometry=" + longx + "%2C" + laty + "&geometryType=esriGeometryPoint&spatialRel=esriSpatialRelIntersects&outFields=*&returnGeometry=false&f=pjson"
    #Get the JSON text from the feature service (the JSON text contains the zone value)
    ctx = HashMap()
    ctx.put("url",url)
    service.invokeScript("LIB_HTTPCLIENT",ctx)
    json_text = str(ctx.get("response"))

    #Parse the zone value from the JSON text
    ctx = HashMap()
    ctx.put("json_text",json_text)
    service.invokeScript("LIB_PARSE_JSON",ctx)
    parsed_val = str(ctx.get("parsed_val"))

    #Enter the zone value into the zone field in the work order
    mbo.setValue("DESCRIPTION","Waste Zone: "+parsed_val,MboConstants.NOACCESSCHECK)

Jython library (LIB_HTTPCLIENT)

from psdi.iface.router import HTTPHandler
from java.util import HashMap
from java.lang import String
handler = HTTPHandler()
map = HashMap()
map.put("URL",url)
map.put("HTTPMETHOD","GET")
responseBytes = handler.invoke(map,None)
response = String(responseBytes,"utf-8")

JavaScript library (LIB_PARSE_JSON)

#The field name (ZONE) is hardcoded. I'm not sure if this is best practice or not.
var obj = JSON.parse(json_text);
parsed_val = obj.features[0].attributes.ZONE

How can this code be improved?

\$\endgroup\$
1
  • \$\begingroup\$ Jython... I know nothing about Maximo/PSDI but it already makes me sad. Are you able to use "real Python libraries" like Requests? \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 1:04

1 Answer 1

5
\$\begingroup\$

Functions are your friend

Even in Jython, functions are a valid construct. They allow for scope, so that you can better reason about temporary variables; give better stack traces when things go wrong; increase testability; etc. So you should move your code into some functions.

Invert your logic

This:

if len(laty) == 7 and len(longx) == 6:

should probably be inverted, and something done about it, i.e.

if len(laty) != 7 || len(longx) != 6:
    # throw, or at least print...
# continue on with the rest of the function

If you had requests...

then this:

url="http://something.com/arcgis/rest/services/something/Zones/MapServer/15/query?geometry=" + longx + "%2C" + laty + "&geometryType=esriGeometryPoint&spatialRel=esriSpatialRelIntersects&outFields=*&returnGeometry=false&f=pjson"

could be fairly reduced in insanity. The query params can be formed as a dictionary passed to the get method.

PEP8

Use any kind of linter or modern IDE, and it will give you suggestions on how to reformat this thing to follow Python formatting standards (PEP8). The most obvious thing it will notice is lack of spaces after commas.

Otherwise - perhaps you should provide some context about why this thing exists in Jython.

Suggested

I don't know whether this will work, because I don't have your setup; so you'll probably have to tweak it.

from psdi.mbo import MboConstants
from java.util import HashMap
from urllib import urlencode
from urlparse import urlunparse, ParseResult


def get_coords():
    """
    Get the y and x coordinates(UTM projection) from the WOSERVICEADDRESS table
    via the SERVICEADDRESS system relationship.
    The datatype of the LatitydeY and LongitudeX is decimal, i.e.
    1234567.8335815760, 123456.4075621164.
    """
    laty = mbo.getDouble("SERVICEADDRESS.LatitudeY")
    longx = mbo.getDouble("SERVICEADDRESS.LONGITUDEX")
    return laty, longx


def is_valid(laty, longx):
    """
    Verify if the numbers are legitimate UTM coordinates
    """
    return (0 <= laty <= 10e6 and
            167e3 <= longx <= 833e3)


def make_url(laty, longx):
    """
    Assemble the URL (including the longx and the laty). Note: The coordinates
    are flipped in the url
    """

    query = {
        'geometry': '%d,%d' % (laty, longx),
        'geometryType': 'esriGeometryPoint',
        'spatialRel': 'esriSpatialRelIntersects',
        'outFields': '*',  # You should narrow this if you only care about work zone.
        'returnGeometry': 'false',
        'f': 'pjson'
    }

    parts = ParseResult(scheme='http',
                        netloc='something.com',
                        path='/arcgis/rest/services/something/Zones/MapServer'
                             '/15/query',
                        query=urlencode(query),
                        fragment='')

    url = urlunparse(parts)
    return url


def fetch_waste_zone(url):
    # Get the JSON text from the feature service (the JSON text contains the 
    # zone value)
    ctx = HashMap()
    ctx.put("url", url)
    service.invokeScript("LIB_HTTPCLIENT", ctx)
    json_text = str(ctx.get("response"))

    # Parse the zone value from the JSON text
    ctx = HashMap()
    ctx.put("json_text", json_text)
    service.invokeScript("LIB_PARSE_JSON", ctx)
    parsed_val = str(ctx.get("parsed_val"))

    return parsed_val


def main():
    laty, longx = get_coords()
    if not is_valid(laty, longx):
        print('Invalid coordinates')
        return

    url = make_url(laty, longx)
    waste_zone = fetch_waste_zone(url)

    # Enter the zone value into the zone field in the work order
    mbo.setValue("DESCRIPTION", "Waste Zone: " + waste_zone,
                 MboConstants.NOACCESSCHECK)


main()

Note:

  • There are functions
  • The functions contain """docstrings""" at the top
  • You should preserve the UTM coordinates as doubles until it's necessary to format them as integers in the URL string
  • UTM coordinates have real limits that you should use - not just string length
  • Even if you don't have requests, you can still do somewhat saner query string formation via dictionary
  • If the coordinates are invalid, do something - maybe print, definitely quit
\$\endgroup\$
8
  • \$\begingroup\$ Good question! Rather than you, the developer, having to write out the long-form URL manually - including ampersands, escape characters if needed, etc., you compose a logic-friendly dictionary. This is more maintainable and less prone to error. \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 1:43
  • \$\begingroup\$ Fine; but that doesn't mean that you can't write sub-routines (which you still should). Subroutines can still use global variables when needed. \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 1:48
  • 4
    \$\begingroup\$ It's not frowned upon, per se, but it's also not doing you any favours. I seem to remember Jython being stuck in a very old version of Python with fairly poor compatibility to external Python libraries, coupled with awkward access to Java constructs. As a small scripting language embedded in Java it's fine, but you're better to either go full Java or full Python, if you can. \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 1:56
  • \$\begingroup\$ I'll post an example. \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 12:05
  • \$\begingroup\$ @User1973 edited \$\endgroup\$
    – Reinderien
    Commented Aug 20, 2019 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.