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:
- Takes the X&Y coordinates of a work order in Maximo
- Generates a URL from the coordinates (including other spatial query information)
- Executes the URL via a separate script/library (LIB_HTTPCLIENT)
- Performs a spatial query in an ESRI REST feature service (a separate GIS system)
- JSON text is returned to Maximo with the attributes of the zone that the work order intersected
- The zone number is parsed from the JSON text via a separate script/library (LIB_PARSE_JSON)
- 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?
-
\$\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– Reinderien2019年08月20日 01:04:30 +00:00Commented Aug 20, 2019 at 1:04
1 Answer 1
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
-
\$\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– Reinderien2019年08月20日 01:43:37 +00:00Commented 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– Reinderien2019年08月20日 01:48:14 +00:00Commented 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– Reinderien2019年08月20日 01:56:28 +00:00Commented Aug 20, 2019 at 1:56
-
\$\begingroup\$ I'll post an example. \$\endgroup\$Reinderien– Reinderien2019年08月20日 12:05:54 +00:00Commented Aug 20, 2019 at 12:05
-
\$\begingroup\$ @User1973 edited \$\endgroup\$Reinderien– Reinderien2019年08月20日 12:35:26 +00:00Commented Aug 20, 2019 at 12:35
Explore related questions
See similar questions with these tags.