Category Archives: Maximo

Screen shot resized

Android – Running an ArcGIS Map Offline

Many people know how to run ArcGIS on a desktop computer, and ESRI provides many tools to do so.  But what about workers in the field that need to see a map with specific objects?  Running offline maps on an Android device is an extremely useful tool and can help workers do a wide variety of things that before could only be done while sitting in an office.  Creating an offline map app on an Android device is something that is possible with the ESRI mobile API, but it can be tricky. So I will go over how to do two fundamental functions on an Android device: 1) Loading the offline map, and 2) querying at a specific location.

Required:

  • An Android Device (or emulator, I recommend GenyMotion if you go this way)
  • Android Studios (the latest version)
  • A file geodatabase, created using ArcGIS for desktop (with the desired layers that you want in the map loaded into it).
  1. Store the .geodatabase file on the physical device, remembering the folder hierarchy where it is stored.  The .geodatabase file is stored on the physical device in order to make it easier to sync/update the information. If the GIS data was stored inside of the actual application, it would be a much more lengthy process to update and change when your mapping data was changed. (Example path: /sdcard/Geodatabase/basemap.geodatabase)
  2. Create a new Android project in Android Studios, import the ArcGIS libraries and edit the build.gradle file accordingly (See: https://developers.arcgis.com/android/guide/install-and-set-up.htm for more information on how to get your Android app ready for ArcGIS maps).
  3. Place a MapView in your main layout. Match_parent for both width and height (We’ll call it mMapView)
  4. Because loading all of the layers from the .geodatabase file can take a hefty load on the device, it is good practice to run that on a separate background thread. To do this create a private class that extends AsyncTask and create a skeleton doInBackground method within it. (See bottom for full code)
  5. Instantiate an object of that private class in the onCreate method and call .execute() on it.
    
     MapView mMapView;
     Geodatabase geodatabase;
     private GeodatabaseFeatureTable geodatabaseFeatureTable;
     private FeatureLayer featureLayer;
    
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
    
          mMapView = (MapView) findViewById(R.id.map); //Instantiates the MapView object
          LoadMap mLoadMap = new LoadMap(); //Runs the LoadMap class on another thread
          mLoadMap.execute(); //Calls the background thread
    
     }
    
    

     

  6. Remember where you put the .geodatabasefile on the device? We’re going to use that now.  To add the layers to the MapView, we need to do it one by one. First, we will find the .geodatabasefile and loop through each of the layers that are contained therein, adding them one by one to the MapView. NOTE: the extent of the map will be determined by the first layer that is added on, so keep that in mind when creating your .geodatabase file and adding layers on, due to how our file was constructed, I add them on starting from the back, to get the full extent of the map.
    private class LoadMap extends AsyncTask<Void, Void, Void> {
    
            @Override
            protected Void doInBackground(Void... params) {
    
                try { //Opens up the basemap.geodatabase file from it's location on the physical device
                    geodatabase = new Geodatabase("/sdcard/Geodatabase/basemap.geodatabase");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } finally { //Takes each layer one by one from the Geodatabase and adds it to the MapView
                    for (int i = (geodatabase.getGeodatabaseTables().size()) - 1; i >= 0; i--) {
                        geodatabaseFeatureTable = geodatabase.getGeodatabaseFeatureTableByLayerId(i);
                        featureLayer = new FeatureLayer(geodatabaseFeatureTable);
                        mMapView.addLayer(featureLayer);
                    }
                }
                return null;
            }
        }
    
  7. Once that is set up, run the app on the device that has the .geodatabase and it will loop through the layers, adding them to the view one by one. Your output should be to this:

Screenshot_2015-02-17-11-03-22

It is a very simple, but necessary, first step to getting a complicated, interactive custom ArcGIS application running on your Android device, free of any WiFi connection!  In my next blog post I will detail how to interact with the map, showing how to query objects at a point tapped on the map. Stay tuned!


 

 

ActiveG Jayu for Maximo—spatial visualization for all

Have you ever wanted to share maps of your Maximo work orders and assets with non-Maximo users?  Or ever wanted to integrate your GIS and Maximo, but were afraid of the cost and the effort to make it work? Then you’re not alone.  And that’s why we’ve created Jayu (pronounced Jah-Yoo).

ActiveG JayuJayu is a robust standalone web application specifically tailored to integrate GIS and Maximo data, making it accessible to whomever you choose. Jayu is the perfect blend of simplicity and power, giving your entire organization the access to a visual feature set based upon Maximo data.

Jayu builds upon the rich functionality of ActiveG MapEngine, and extends those tools to anyone in your organization.

Features:

  • Search Maximo Assets, Locations, Work Orders, and Service Requests
  • Create user-defined Work Order and Service Request visualizations
  • Filter work orders by Lead, Group, Dates, and more
  • Cluster work orders for better analysis
  • GIS Layer visibility controls
  • Generate work and asset lists based on location criteria

Quick Specs

  • Requires IIS 8+, SQL Server 2008 or later, Esri ArcGIS Server 10.x, and Maximo (of course).

So give us a call today to see how quickly you can be up and running with a GIS-based map for your Maximo data!

MBO Transactions: Beware the setWhere

Caution: setWhereOftentimes, creating, updating, or deleting an MBO is accompanied by other related MBO actions that should either succeed together or fail together within a single database transaction. 

For example, we recently added a feature to our mapping software that allows a user to select an asset on the map and enter meter readings for all of the active meters associated with that asset. We wanted to treat that collection of meter readings as a single bundle so that if, by chance, one of the input readings were to cause the database to fail to save, we didn’t want any of the other meter readings to save.

In the database world, this property is just one part of being ACID-compliant and is usually how people prefer their database to operate so that the database isn’t left in some partially incorrect or incomplete state. However, for performance reasons or for seeing steady progress in a long operation, it may be desirable at times to avoid transactions. Yet, this case should be rare. Always strive for transactions first and then choose to back away if needed.

Both IBM and Bruno provide some useful discussion on how child MBOs obtained via relationships from parent MBOs are contained in the same transaction as their parents.

So, for example:

// meters is a JSON array of JSON objects that holds meter data
for (int meterIndex = 0; meterIndex < meters.size(); ++meterIndex) {
    final JSONObject meterMap = (JSONObject) metersJson.get(i);
    final String meterName = (String) meterMap.get("meterName");
    final String meterReading = (String) meterMap.get("meterReading")

    // get asset meter
    final MboSetRemote assetMeters = mxSession.getMboSet("ASSETMETER");
    assetMeters.setWhere("assetnum = '" + assetNum + "' AND siteid = '" + siteId + "' AND metername = '" + meterName + "'");

    // update asset meter
    final MboRemote assetMeter = assetMeters.moveFirst();
    assetMeter.setValue("lastreading", meterReading);

    // update related measurements
    final MboSetRemote measurementSet = assetMeter.getMboSet("NEWMEASUREMENT");
    final MboRemote newMeasurement = measurementSet.add();
    newMeasurement.setValue("measuredate", today);

    assetMeters.save()
}

It should be noted that the above snippit is only a partial solution of how to correctly record meter data and has been gutted and rearranged for illustratation purposes. Notice that the call to assetMeters.save() is sufficent to persist both the assetMeter update as well as new additions to measurementSet. It is not necessary to call measurementSet.save() because the parent MBO tracks its children in the same transaction.

However, there is a major flaw in the code. Each meter reading is saved independently of the others; it is not transactional. It is tempting to push assetMeters.save() to the very end of the code outside the loop:

final MboSetRemote assetMeters = mxSession.getMboSet("ASSETMETER");

// meters is a JSON array of JSON objects that holds meter data
for (int meterIndex = 0; meterIndex < meters.size(); ++meterIndex) {
    ...

    // get asset meter
    assetMeters.setWhere("assetnum = '" + assetNum + "' AND siteid = '" + siteId + "' AND metername = '" + meterName + "'");

    ...
}
assetMeters.save()

Unfortunately, this fails to save meter data except for the very last meter. This leads to a crucial observation: even though assetMeters is declared above the loop, the setWhere() call resets the transaction. We must call setWhere() only once on the MBOSetRemote on which we will call save().

The corrected code looks like this:

final MboSetRemote assetMeters = mxSession.getMboSet("ASSETMETER");
final String meterList = "'meter1', 'meter2', 'meter3'";
assetMeters.setWhere("assetnum = '" + assetNum + "' AND siteid = '" + siteId + "' AND metername in (" + meterList +")";

for (MboRemote assetMeter = assetMeters.moveFirst(); assetMeter != null; assetMeter = assetMeters.moveNext()) {
    ...
}
assetMeters.save()

meterList is a String that is built up by iterating through the meters array. We also iterate through the actual MBOs instead of the meters (and reference a new map not shown to get the JSON data passed in).

The main point is that setWhere() is only called once to prevent new transactions from being saved. However, building up meterList is not ideal.

Another approach to do this, as noted by Chon, is to create assetMeters using a child relationship of some other MBO (e.g., an Asset) from another MBO set (e.g., an Asset set).  This would obviate the need to build up the meterList.  Instead of calling assetMeters.save(), we could call save() on the parent MBO set (AssetSet).

This approach would like:

final MboSetRemote assetSet = mxSession.getMboSet("ASSET");         
assetSet.setWhere("assetnum = '" + assetNum + "' AND siteid = '" + siteId + "'");
final MboRemote asset = assetSet.moveFirst();            
            
if (asset != null) {
    final MboSetRemote assetMeters = asset.getMboSet("ACTIVEASSETMETER");            
    for (MboRemote assetMeter = assetMeters.moveFirst(); assetMeter != null; assetMeter = assetMeters.moveNext()) {
        ...
    }
}
assetSet.save()

Replace Zoom In and Out Buttons for ArcGIS Javascript

Last year we finished a new product that integrates ESRI’s ArcGIS maps to Maximo. But unlike our flagship product, MapEngine, this new product can be run from outside of Maximo using any browser. We developed this so non-Maximo users such as managers would have access to the GIS and GIS-related Maximo data (like asset and work order physical locations).

One of the main requirements for this new product was a simpler, more modern interface. In implementing this we had to hide many of ArcGIS’s canned visual components and replace them with our own. We chose Twitter’s Bootstrap CSS library for its cross-browser compatibility and current styles. One ArcGIS component that needed to be replaced were the Zoom In and Zoom Out buttons. Here I am going to step you through the process of hiding the default buttons and replacing them with Bootstrap buttons.

First, we need to hide the default ArcGIS for Javascript buttons. To do this you can either set the slider to false when the map object is created:

var esriMap = new esri.Map("map", {
slider: false
}

Or hide the slider after the map object after creation:

esriMap.hideZoomSlider();

Now that the default Zoom Slider is hidden from the map, we can add our own. We’re using Bootstrap’s Toolbar and Button components but you can use any CSS library or your own for that matter. Our HTML and CSS look something like this:

HTML
Zoom In and Out HTML

CSS
Capture3

As the CSS shows, we chose to place our Zoom buttons in the bottom right corner. Our buttons look like this:

Zoom In and Out Buttons

Zoom In and Out Buttons

Once the buttons are added we need to add the event handlers that the buttons will use to Zoom In and Out. This took me a bit of time to figure out how to tell the map to Zoom In and Out, because ESRI chose to call the different Zooms “levels.” So we use the API’s setLevel function to zoom the map in and out one level at a time. Be sure this code is added after the map is created.

Zoom In and Out Event Handler

Once the setLevel functions are set using jQuery’s click event and an anonymous function, clicking the plus and minus buttons will zoom the map in and out one level at a time.

Stay tuned for further front-end tips of using ESRI’s ArcGIS for Javascript API.