0

I have a work order in Maximo 7.6.1.1:

  • The WO has LatitudeY and LongitudeX coordinates in the Service Address tab.
  • The WO has a custom zone field.

And there is a feature class (polygons) in a separate GIS database.


I want to do spatial query to return an attribute from the polygon record that the WO intersects and use it to populate zone in the WO.

How can I do this?

Related keyword: Maximo Spatial

User1974
  • 276
  • 1
  • 17
  • 63
  • 1
    Are you able to get Maximo to run some custom SQL statement ? If yes you can just make it run a SELECT statement that will return the id of the zone that contains your point. – Albert Godfrind Jun 14 '19 at 18:22
  • @AlbertGodfrind Good idea. I don't know enough about Maximo customization yet to answer that. I asked a separate question about [referencing an Oracle function](https://stackoverflow.com/questions/56460154/take-value-from-fielda-send-to-db-function-return-value-to-fieldb), but was told that it would be a bad practice. – User1974 Jun 14 '19 at 19:53
  • 1
    Ah bad practices. Or good practices. I have read so many books and conference papers where authors push their own idea of what is a “good” or “bad” practice. You may want to ask why exactly invoking a SELECT is bad practice ... – Albert Godfrind Jun 15 '19 at 20:02
  • 1
    On the other hand there is a possibility that requires no change in Maximo: just add a trigger on the work orders table, that will automatically fill the zone column whenever a work order is inserted (or the location of an existing work order is changed). That trigger just needs to fetch the id of the zone containing the location of the work order using a simple SELECT. – Albert Godfrind Jun 15 '19 at 21:39
  • 1
    I can't give the detail to qualify as an answer, but I have used Maximo's automation scripting to call an ArcGIS Reverse Geocoding service via its REST API over HTTPS. I sent it a point (lat/long), and it gave me back what was there in JSON. [Scripting 76 Features](https://www.ibm.com/developerworks/community/wikis/form/anonymous/api/wiki/02db2a84-fc66-4667-b760-54e495526ec1/page/03ad118c-6040-43dd-bc6d-d7a03510d135/attachment/5b9f2379-83a3-4a9f-8584-7920757fc141/media/Scritping76Features.pdf) helped a lot. – Preacher Jun 18 '19 at 04:36

3 Answers3

2

To do this live in Maximo using an automation script is possible or by writing custom code into Spatial (more challenging). You want to use the /MapServer/identify tool and post the geometry xy, coordinate system, and the layer you want to query. identify window

You will have to format the geometry object correctly and test your post from the window. I usually grab the post from the network section of developer tools once I get it to work and change the output format to json and use it in my code.

Milton
  • 121
  • 4
1

You may actually not need to touch your Maximo environment at all. How about just using a trigger on your work orders table ? That trigger can then automatically fill the zone ID from a simple select statement that matches x and y with the zones in the zones table. Here is how that could look like.

This assumes that your work orders are in a table like this:

create table work_orders (
  wo_id number primary key,
  x number,
  y number,
  zone_id number
);

and the zones in a table like this

create table zones (
  zone_id number primary key,
  shape st_geometry
)

Then the trigger would be like this

create or replace trigger work_orders_fill_zone
  before insert or update of x,y on work_orders
  for each row
begin
  select zone_id
  into :new.zone_id
  from zones
  where sde.st_contains (zone_shape, sde.st_point (:new.x, :new.y, 4326) ) = 1;
end;
/

Some assumptions:

  1. The x and y columns contain coordinates in WGS84 longitude/latitude (not in some projection or some other long/lat coordinate system)

  2. Zones don't overlap: a work order point is always therefore in one and only one zone. If not, then the query may return multiple results, which you then need to handle.

  3. Zones fully cover the territory your work orders can take place in. If a work order location can be outside all your zones, then you also need to handle that (the query would return no result).

  4. The x and y columns are always filled. If they are optional, then you also need to handle that case (set zone_id to NULL if either x or y is NULL)

After that, each time a new work order is inserted in the work_orders table, the zone_id column will be automatically updated.

You can initialize zone_id in your existing work orders with a simple update:

update work_orders set x=x, y=y;

This will make the trigger run for each row in the table ... It may take some time to complete if the table is large.

Albert Godfrind
  • 1,978
  • 1
  • 12
  • 20
  • Maximo has its business logic in Java classes, outside of the database. These classes validate data, do extra "knock-on" work when data is validated, record audit trails, and send updates to integrated, external systems, among other things. So, while this answer is good quality and shows a lot of effort, using database triggers is strongly discouraged for Maximo developers. Automation Scripts are intended to replace the need for database triggers. – Preacher Sep 21 '20 at 23:00
1

Adapt the code in the Library Scripts section of Maximo 76 Scripting Features (pdf):

#What the script does:
#     1. Takes the X&Y coordinates of a work order in Maximo
#     2. Generates a URL from the coordinates
#     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. Returns JSON text to Maximo with the attributes of the zone that the work 
#        order intersected
#     6. Parses the zone number from the JSON text
#     7. Inserts the zone number into the work order record

from psdi.mbo import MboConstants
from java.util import HashMap
from com.ibm.json.java import JSONObject

field_to_update = "ZONE"
gis_field_name = "ROADS_ZONE"

def get_coords():
    """
    Get the y and x coordinates(UTM projection) from the WOSERVICEADDRESS table
    via the SERVICEADDRESS system relationship.
    The datatype of the LatitdeY and LongitudeX fields is decimal.
    """
    laty  = mbo.getDouble("SERVICEADDRESS.LatitudeY")
    longx = mbo.getDouble("SERVICEADDRESS.LongitudeX")

    #Test values
    #laty  = 4444444.7001941890
    #longx = 666666.0312127020

    return laty, longx


def is_latlong_valid(laty, longx):
    #Verify if the numbers are legitimate UTM coordinates
    return (4000000 <= laty <= 5000000 and
            600000 <= longx <= 700000)


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

    url = (
        "http://hostname.port"
        "/arcgis/rest/services/Example"
        "/Zones/MapServer/15/query?"
        "geometry={0}%2C{1}&"
        "geometryType=esriGeometryPoint&"
        "spatialRel=esriSpatialRelIntersects&"
        "outFields={2}&"
        "returnGeometry=false&"
        "f=pjson"
    ).format(longx, laty, gis_field_name)

    return url


def fetch_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("LIBHTTPCLIENT", ctx)
    json_text = str(ctx.get("response"))

    # Parse the zone value from the JSON text
    obj = JSONObject.parse(json_text)
    parsed_val = obj.get("features")[0].get("attributes").get(gis_field_name)

    return parsed_val


try:
    laty, longx = get_coords()
    if not is_latlong_valid(laty, longx):
        service.log('Invalid coordinates')
    else:
        url = make_url(laty, longx, gis_field_name)
        zone = fetch_zone(url)

        #Insert the zone value into the zone field in the work order
        mbo.setValue(field_to_update, zone, MboConstants.NOACCESSCHECK)
        service.log(zone)
except:
    #If the script fails, then set the field value to null.
    mbo.setValue(field_to_update, None, MboConstants.NOACCESSCHECK)
    service.log("An exception occurred")

LIBHTTPCLIENT: (a reusable Jython library script)

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")
User1974
  • 276
  • 1
  • 17
  • 63