Skip to Content
Author's profile photo Saurabh Sharma

How to build a production grade UI5 application with GIS integration using 3rd party API (ARCGIS)

In one of my customer engagements the business requirement was to build a UI5 application with geospatial capabilities. I used Interactive maps from ARCGIS enriched with business data from SAP to accomplish this. This blog outlines my journey and challenges I faced to impliment it.

We faced a major hurdle in deploying the application to the Frontend server due to Cross origin resource Sharing (CORS) errors but I will not discuss it here. (Maybe a separate blog later)

Here are the steps I followed.

  • Created a CDS view to read the assets from backend SAP system. Included UI annotations so that the smart control directly build the view and I don’t have to worry about enabling it at the UI5 app level (That’s why S/4Hana is fun).

 

  • Used Webide to build a List report application. I was tempted earlier to use the smart template but then decided not to use as I needed more control doing the GIS integration and facet replacement has its own set of restrictions. So I ended up using smart controls like Smart Filter and Smart Table to build a list report. The idea was to show a list of all assets( from mapping perspective assets have a physical location associated with them and can be identified by coordinates) and clicking on one will display it on the  ESRI map
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" xmlns:smartFilterBar="sap.ui.comp.smartfilterbar"
	xmlns:smartTable="sap.ui.comp.smarttable" xmlns:sem="sap.m.semantic" controllerName="zxxx.controller.list">
	<App id="app">
		<sem:FullscreenPage id="fp" title="List Search">
			<smartFilterBar:SmartFilterBar id="smartFilterBar" entitySet="ZXXX_ ASSET_XXX" enableBasicSearch="true" basicSearchFieldName="Name"
				showFilterConfiguration="true"/>
			<smartTable:SmartTable id="smartTable" smartFilterId="smartFilterBar" tableType="Table" editable="false" entitySet="ZXXX_ ASSET_XXX"
				useVariantManagement="true" useTablePersonalisation="true" header="Assets" showRowCount="true" useExportToExcel="false"
				enableAutoBinding="true">
				<Table>
					<items>
						<ColumnListItem type="Navigation" press="onListItemPress"></ColumnListItem>
					</items>
				</Table>
			</smartTable:SmartTable>			
		</sem:FullscreenPage>
	</App>
</mvc:View>

 

  • Now the fun part. There were 2 options to load the ARCGIS JavaScript APIS. One that is available on their website(See Example) You can use a jQuery.sap.registerModulePath to load this and it works perfectly in WebIDE environment. But the problem with this approach was that the API here reside remotely and not in our environment. So when you deploy it on Frontend server (on premise) chrome (browser) thinks it is a cross origin reference (like a malicious activity) and blocks it and you cannot load the maps.

 

  • So I went with option two. I downloaded the latest version of ESRI JS API (Link) and loaded it in frontend server as a library (See my last blog) You may have to create an account for that if you want to try it. Now this package is pretty big and webide will not able to handle such volume. So I used the report /UI5/UI5_REPOSITORY_LOAD to upload it directly from system (laptop) in Frontend server. I can now reference the library from any GIS enabled UI5 apps(consuming application) without making the apps bulky and unmanageable. Declare it as a dependency in Manifest along with standard UI5 library calls. The final structure of the ARCGIS should look like the second screenshot with the entire package loaded.

 

  • Now I needed a perfect UI5 control to display maps. My initial idea was to extend the SAP standard Viewport (sap.ui.vk.Viewport) control. Which worked out initially. But as I went on adding more widgets to the interactive maps I realized that the standard control was actually restricting some of the ESRI features(like search widget). So I built a very simple UI5 control (viewPort.js) with just html <div> and loaded the map in its onAfterRendering() method.Please Note The code here is just for understanding the concepts and cannot be copy pasted. The ARCGIS query reads asset key from SAP and navigates to it using goto method.
sap.ui.define([
		'sap/ui/core/Control',
		'zxxx/model/models'
	],
	function(ViewPort, Model) {
		var view;
		var query;
		var queryStatesTask;
		var resultsLyr;
		var symbol;
		var webmap;
		var that;
		
		return ViewPort.extend("zxxx.control.viewPort", {
			metadata: {
				properties: {
					width: {
						type: "sap.ui.core.CSSSize",
						defaultValue: "100%"
					},
					height: {
						type: "sap.ui.core.CSSSize",
						defaultValue: "100%"
					}
				}
			},

			renderer: function(oRm, oControl) {

				oRm.write("<div");
				// Write controles and classes defined by the developer
				oRm.writeControlData(oControl);
				oRm.writeClasses(oControl);

				oRm.addStyle("height", oControl.getHeight());
				oRm.addStyle("width", oControl.getWidth());
				oRm.writeStyles();
				
				oRm.write(">");
				oRm.write("<div id='legendDiv'></div>");				
				oRm.write("<div id='laylstDiv'></div>");				
				oRm.write("</div>");
				
			},

			onAfterRendering: function(args) {
				this.mapLoad();
			},
			
			mapLoad: function() {
				that = this;
				// Initialize ARCGIS  
				if (initialized == 0) {
					//Include ESRI style sheets
					jQuery.sap.includeStyleSheet("https://js.arcgis.com/4.5/esri/css/main.css");
					jQuery.sap.includeStyleSheet("https://js.arcgis.com/4.5/esri/css/view.css");
					jQuery.sap.includeStyleSheet("https://js.arcgis.com/4.5/dijit/themes/claro/claro.css");
					//This referes to the namespace of the faceless component for ESRI libraries
					jQuery.sap.require("XXX/ZARCGIS/arcgis/dojo/dojo");
					initialized = 1;
				}
				
				require([
						"esri/Map",
						"esri/WebMap",
						"esri/views/MapView",
						"esri/config",
						"esri/tasks/QueryTask",
						"esri/tasks/support/Query",
						"esri/layers/GraphicsLayer",
						"esri/widgets/LayerList",
						"esri/widgets/Legend",
						"esri/widgets/Search",
						"esri/layers/FeatureLayer",
						"esri/core/urlUtils",
						"esri/widgets/Print",
						"dojo/domReady"
					], function(Map, WebMap, MapView, esriConfig, QueryTask, Query, GraphicsLayer, LayerList, Legend,
						Search, FeatureLayer, urlUtils, Print, ready) {
						
						ready(function() {
							// Impliment Utility to read the ARCGIS URLs and mapid
							
							// Poral URL to point to ARCGIS Portal 
							esriConfig.portalUrl = portal;
						
							// Load webmap from the map id 
							webmap = new WebMap({
								portalItem: {
									id: mapid
								}
							});
							
							//Load map to view
							view = new MapView({
								map: webmap,
								container: that.getId()
							});

							view.center = [133, -27]; // Sets the center point of the view at a specified lon/lat
							view.zoom = 5;

							query = new Query({
								returnGeometry: true,
								outFields: ["*"],
							});

							query.geometry = view.extent;
						
							//QueryInt points to a feature server where the query will be executed
							queryStatesTask = new QueryTask({
								url: queryInt
							});
						
							resultsLyr = new GraphicsLayer();
							
							//Declate the layerlist and retrive it from the view 
							var layerList = new LayerList({
								view: view
							}, "laylstDiv");
							
							//Add it to view to be displayed as a widget on top left
							view.ui.add(layerList, "top-left");
							
							// Use Javascript Promises so that widget loading happens in proper sequence. 
							// Here featurelayer is dependent on webmap being loaded and legend is dependent on feature layer
							webmap.then(function() {
								var featureLayer = webmap.layers.getItemAt(0);
								featureLayer.then(function() {
									var legend = new Legend({
										view: view,
										id: "leg",
										style: "card"
									}, "legendDiv");
									view.ui.add(legend, "top-left");
								})

							});
							
							//This is where we pass the Asset id to map to show it in map
							if (view) {
								if (Model) {
									//Idea is as soon as you click on a asset in list report 
									//its key is captured in Model and is retrived here 
									var intKey = Model.getKey();
								}
								//If key is found execute the query
								if (intKey) {
									view.then(function() {
										console.log("Querying the service");
										query.outSpatialReference = view.spatialReference;
										query.spatialRelationship = "intersects";
										query.where = "Asset='" + intKey + "'";
										
										queryStatesTask.execute(query).then(getResults).then(addToLayer).then(goToLayer);
										
										//if the query retunsresult based on asset id
										function getResults(result) {
											console.log("found " + result.features.length + " features");

											// Loop through each of the results and assign a symbol and PopupTemplate
											// to each so they may be visualized on the map
											var assets = [];
											require(["dojo/_base/array"], function(arrayUtils) {
												assets = arrayUtils.map(result.features, function(
													feature) {
													return feature;
												});
											});
											return assets;
										}
										
										// Add the assets to Results Layer(graphic LAyer) declared earlier and add it to webmap
										function addToLayer(assets) {
											console.log("Add Layer");
											resultsLyr.addMany(assets);
											webmap.add(resultsLyr);
											return assets;
										}
										
										//finally navigate to the asset
										function goToLayer(assets) {
											console.log("Goto");
											view.goTo({
												target: assets,
												zoom: 18
											});
										}

									});
									
								}
							}

						})
					});

			}
		});
	});

 

  • Once the control is ready we just need to build a view to display it. My custom control was present in a folder “control” so I declared it at the top of view with the following lines where zxxxx is the namespace-
xmlns:mapCont="zxxxx.control"

The map control can be placed at any desirable place as below.

<l:VerticalLayout id="VL" width="100%">
      <mapCont:viewPort id='GIS' class='claro'/>
</l:VerticalLayout>

That’s it, the UI5 app is ready and it has an embedded interactive map which work by consuming SAP business data. We can also build UI5 controls like combobox or select and use the user input to interact with the map ( zoom, highlight, goto and so on )

Assigned Tags

      1 Comment
      You must be Logged on to comment or reply to a post.
      Author's profile photo gobinath palanisamy
      gobinath palanisamy

      can you please let us know how you handled the CORS Issues. Just a hint also fine.