This blog shows an example application combining SAP AppGyver with the spatial engine of SAP HANA Cloud by making use of GeoServer on BTP as a middleware. Everything has been done on the free tier and can be replicated without involving any license costs.
Whatever the target audience of low-code/no-code platforms is: I am probably part of it. I have started my career as a software developer and have a good technical understanding. However, to be honest, I have not written productive code for some years now. (…a proper software engineer could even challenge if I have ever done this, given that my education as a mathematician spared me from any solid engineering know-how)
So, I started to investigate if I could use SAP AppGyver to develop a simple application, that handles or visualizes spatial data. For those, who know me, that does not come as a surprise since my work focus is on SAP HANA’s multi-model engines and the spatial engine in particular.
Quickly I have run into two challenges:
- AppGyver does not seem to offer a map widget and also embedding of 3rd parties like Leaflet is not straight-forward, if possible at all. This is not a big deal since spatial data can be used for so much more than just visualizing maps.
- The preferred way of connecting AppGyver to an instance of SAP HANA Cloud is via ODATA services exposed by CAP. Unfortunately, CAP does not (yet) come with support for spatial data and multi-model data in general.
The simplest application I could think of is a mobile app that determines the current location of the user and presents a list with some nearby point of interests, which already circumvents my first challenge as a map visualization is not mandatory in this case.
Said and done. It was lunch time. So I decided to upload a dataset of 320,000 fast food restaurants from OpenStreetMap (filter amenity = ‘fast_food’) to a SAP HANA Cloud instance on the BTP Free Tier.
On the AppGyver side I also decided to go with the free offering and created an account. My very first hello-world example was an application which just opens a popup with the current location of the user. This is actually fairly easy and can be achieved by just adding two nodes to the application logic. I was thrilled to see the first result of almost two minutes of hard work.
After this first feeling of success and invincibility, I was confronted with my second challenge: How do I connect my SAP HANA Cloud backend to the AppGyver frontend in a way, that also supports spatial data and the spatial data type of SAP HANA in particular.
As AppGyver also accepts generic REST services in addition to ODATA, I had the idea to use GeoServer with its plug-in for SAP HANA as a middleware. This way GeoServer does understand SAP HANA’s spatial datatype and would publish a GeoJSON REST service, that can be consumed by AppGyver.
When trying to figure out where my GeoServer instance shall be hosted, I remembered a project by my fellow colleague Remi ASTIER, who prepared a ready-to-use Kyma runtime extension with GeoServer and the SAP HANA plug-in pre-installed. I accurately followed the steps described by Remi to deploy a GeoServer instance on my BTP Free Tier.
Although I have never worked with Kyma before, it only took me a few minutes to get GeoServer up and running and create a layer, that populates my fast food data as a GeoJSON server. A detailed description on how to populate an SAP HANA layer on GeoServer can be found in my previous blog. As a little twist I added two parameters for latitude and longitude to the service, which ultimately are passed to SAP HANA. On database side the 5 closest points of interest a determined by using the ST_Distance method of the spatial engine. The general scheme of the query is similar to
SELECT TOP 5 ... FROM FAST_FOOD ORDER BY ... ST_DISTANCE(...) DESC
(That is a simple use of the spatial engine. For some more advanced usage, you can check my blogs on Spatial Data Science)
Next step was to setup the data source in AppGyver and configure the query parameters.
Additionally I configured the GET COLLECTION service…
…and the GET RECORD service.
That data, that is returned by the REST service, adheres to the GeoJSON specification. To give you an impression, you can find a sample here:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "FAST_FOOD_5NEAREST.1630023494",
"geometry": {
"type": "Point",
"coordinates": [
8.6511,
49.2932
]
},
"geometry_name": "SHAPE",
"properties": {
"NAME": "Döner",
"TAGS": "{"opening_hours":"Mo-Fr 11:00-15:00","cuisine":"kebab"}",
"DISTANCE": 873.5042731926794
},
"bbox": [
8.6511,
49.2932,
8.6511,
49.2932
]
},
{
// .. more entries ..
}
],
"totalFeatures": 5,
"numberMatched": 5,
"numberReturned": 5,
"timeStamp": "2022-07-27T08:33:55.439Z",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:EPSG::4326"
}
},
"bbox": [
8.6372,
49.2932,
8.6521,
49.3035
]
}
Now, that the REST service has been configured, we need to load a data variable based on the current location of the user. The associated logic flow looks more complicated than it actually is. The functionality can be achieved by just adding the three nodes in the red rectangle. The remaining nodes are just sugar for error handling, visualization, etc.
With the three highlighted nodes, the user location is determined, handed over to the REST service and the result is written back to a data variable. Additionally we store the current location, latitude and longitude, in two app variables to make it usable by other components of the application (e.g. text fields to show the location). The Hide spinner node makes sure, that the loading animation disappears as soon as the data has been loaded.
The rest of the application is a classic master-detail page. I have added a master page showing the closest 5 locations. Make sure to use the Repeat with property of the list item to create one list item per received result.
When clicking a list item, we need to model a logic flow, that opens a detail page and hands over the currently selected data item. Instead of handing over the actual data, I decided to pass the ID as a page parameter and use the GET RECORD service, which we configured earlier to retrieve the actual record on the details page.
On the detail page I use a logic flow to fetch the respective record and set the page variables, that I will need to display information to the user.
I mentioned earlier, that geospatial data can be used for much more than just creating map. However, sometimes it’s nice to still see a map. So I decided to at least have the ability to open Google Maps for the respective location by using the Open URL node (needs to be installed from Marketplace). This way users can navigate straight to their closest fast food place by the push of a button.
Finally, I ended up with a very simple application, that would fetch the five closest fast food places based on a location. The spatial calculations are done in SAP HANA, GeoServer publishes the results, which are then consumed with AppGyver.
Admittedly, due to the new technologies, a steep learning curve and some research this prototype took me a couple of days. However, with the gained knowledge, the AppGyver platform in combination with SAP HANA Cloud enables the development and deployment of similar apps in less than an hour and I am looking forward building more demos on this stack in future.
If I had a wish for free, it would be a nice map widget to visualize points and polygons directly in my application. There is a widget available in Marketplace. However it was marked as being a beta and I was not able to get it to a working state.
To wrap this up let me summarize what I’ve done for this prototype and what are the most crucial resources:
- Create a BTP Free Tier account
- Spin up an SAP HANA Cloud instance
- Upload some point of interest data. You may browse the community content to learn the various way of uploading spatial data.
- Create a free AppGyver account
- Deploy and setup GeoServer via Kyma
- Enjoy app development with little to no code 🤗
P.S.: The one bad thing about low-code/no-code is that I cannot share any prototype code with you on GitHub.