Work order spatial query (Part 1)

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)

#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?

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

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.

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.
"""
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
• 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. Aug 20, 2019 at 1:43
• 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. Aug 20, 2019 at 1:48
• 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. Aug 20, 2019 at 1:56
• I'll post an example. Aug 20, 2019 at 12:05
• @User1973 edited Aug 20, 2019 at 12:35