Introduction
Do you want to find road
distance and time between 2 locations with current traffic conditions using BTP Services?
In this blog post we will walk through quick and easy
low-code steps to use the capabilities provided by SAP Kyma Serverless Functions.We will tackle the common but frequently occurring problem of finding the
road distance and
times to go from one place to another depending on
road and traffic conditions. For this SAP HANA Spatial Services provide extensive capabilities
For details on use of Kyma functions refer to
SAP Help Documentation for Kyma Functions. I will use python as the programming language for all code snippets provided here. Alternatively Node.js could also be used in Kyma Functions.
The steps below describe the creation of an API to provide driving distance and time between 2 address locations. For this purpose we will use the SAP HANA Spatial Services. For detailed reference on these services please refer to
SAP Help Documentation for SAP HANA Spatial Services and other helpful details are provided here
SAP Discovery Mission.
The approach described here takes away the hassle of managing high availability
infrastructure for providing services and that tooo with just a few lines of code. It utilizes the cloud-based offering running on powerful infra-structure managed by SAP for spatial services with a scalable runtime.
Pre-requistites
To go through the the steps you would need to ensure you already have the following:
- Setup BTP Account with HANA Spatial Services as described here SAP HANA Spatial Service on BTP. Alternatively you can create the Service Instance and Service Binding in Kyma cockpit. This is advisable as then you can use the service credentials from the binding instead of coding it in the function. In this case the service is created in the same subaccount as the Kyma cluster and you need to ensure the subaccount has entitlement to create the service.
- Setup BTP Account with SAP Kyma runtime as described here Create Kyma Environment on BTP
Setup HANA Spatial Services in Kyma Cockpit (Optional)
If you do not already have a HANA Spatial Service instance you can create one in Kyma Cockpit.
Go to your chosen namespace in Kyma where you plan to develop the functionality, navigate to Service Management Section.
1. Add BTP Service Instance
Go to BTP Service Instances and press Create and you will get the following form
BTP Service Instance Creation on Kyma
You can choose the Plan Name to be
standard or
lite depending on the entitlement of the spatial service to the BTP subaccount where the Kyma clusters is provisioned. The Offering Name has to be
spatialservices. The Name is of your own choice.
Once the service is created you will see the following
HANA Spatial Services Instance on Kyma
2. Add BTP Service Binding
Next step is to add a service binding to the above spatial service instance. This step creates the required credentials needed to get OAuth token for using the HANA Spatial Services.
Under Service Management now go to BTP Service Bindings and press Create. In the dropdown of the Service Instance Name you will see the existing service instances. Choose the one created in step 1 above, in the example above we called hss-instance
Creating HANA Spatial Service Binding on Kyma
This results in the following binding to be provisioned
HANA Spatial Service Binding Provisioned on Kyma
If you click on the binding name above, you will see that this creates an associated Secret with the same name, in our example hss-binding.
HANA Spatial Service Binding
You can go look at the hss-binding secret and decode it. We will use the uaa which has the url, clientid,clientsecret fields of this secret in the step to get OAuth token below. We will use the uri field of the secret to get HANA Spatial Service url for geocoding and route.
Kyma Secret for HANA Spatial Service
In steps below we will access the above secret properties in the Kyma function.
Create Python Functions to calculate distance between 2 locations
SAP HANA Spatial Services provide many advanced capabilities for geocoding, routing and mapping. For the example we will focus on answering the question "How to find the distance and time to go from one address to another using a car?" Also the service should gracefully return incase it is not able to map either of the origin or destination addresses.
Since the user-input is provided as addresses, first step is to have a function to get the latitude and longitude of the addresses
For this we will use the
POST /geocoding/v1/geocode API followed by the
POST /routing/v1/route API. Now using these APIs require the generation of
OAuth token. To generate this token you would need 3 things from the HANA Spatial Service instance created in step 1 of the pre-requisites above.
1. Get OAuth Token
Option 1
If you want to use an existing HANA Spatial Services without binding to your function. However, it is advisable to follow the steps to use the binding option so you do not have hard-coded values for secret parameters as described in Option 2.
From the BTP Cockpit access your service key and note the
url, clientid and clientsecret
Service Key on BTP Cockpit
Access Details from Service Key
Here is the code in python to use the above credentials to get the
OAuth token
import requests
import json
def getToken():
url = <url from service key>
params = { "grant_type": "client_credentials" }
auth = (<clientid from service key>,<clientsecret from service key>)
response = requests.post(url, data=params,auth=auth)
d = json.loads(response.text)
return d['access_token']
Option 2
In this case we will use the secret created in step
Setup HANA Spatial Services in Kyma Cockpit above. When you add a function in Kyma you also have the option to add associated Environment Variables. Since we already created a service binding and associated secret, you can choose to create an Environment variable to reference these in your code
Environment Variable for UAA
We will create 2 environment variables using the secret hss-binding (the name of our binding created before, if you chose a different name you will see it in the drop down above for Creating Secret Value. The variables are for uaa and uri fields from secret, here I called them hssuri and hssuaa.
Environment Variables for Accessing Secret
Here is code to get the OAuth token which uses the environment variables instead of coding it in the function
import requests
import json
import os
def getToken():
hssuaa = os.environ.get('hssuaa') #replace with environment variable for uaa if different name
if hssuaa:
hssuaa = json.loads(hssuaa)
url = hssuaa['url'] + '/oauth/token'
params = { "grant_type": "client_credentials"}
auth = (hssuaa['clientid'],hssuaa['clientsecret'])
response = requests.post(url, data=params,auth=auth)
d = json.loads(response.text)
return d['access_token']
else:
raise Exception("HANA Spatial Credentials missing")
2. Get Geo-code from address
Here is python code to use the HANA Spatial Services for geocoding to get latitude and longitude. The url in the code below needs to be replaced by the
uri from the service key and the token is obtained by the function above
def getCoord(address,token):
url = <uri from serice key>/geocoding/v1/geocode #If using Option 1 Spatial Service
url = os.environ.get('hssuri')+'/geocoding/v1/geocode' #If using Option 2 with Service Binding
headersAPI = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': 'Bearer '+ token,
}
body = {
"credentials": {
"provider": "Here",
"api_key": "SAP-KEY"
},
"addresses": [address]
}
response = requests.post(url, json=body,headers=headersAPI)
return json.loads(response.text)['features'][0]['geometry']['coordinates']
The geocode service also returns useful information like
matchScore and
addressType which can be useful for ambiguous cases where the address match is not perfect.
3. Get the distance and other metrics from HANA Spatial Service
The routing service from HANA Spatial Services requires latitude and longitude that we get from the function above. Here is python code to use the routing service to get distance and time metrics between 2 locations.
def getHSSDistance(from_address,to_address):
token = getToken()
from_coord = getCoord(from_address,token)
if not from_coord:
raise Exception("Unidentiified from address " + from_address)
to_coord = getCoord(to_address,token)
if not to_coord:
raise Exception("Unidentified to address " + to_address)
url = <uri from service key>/routing/v1/route #If using Option 1 for Spatial Service
url = os.environ.get('hssuri')+'/routing/v1/route' #If using Option 2 with Service Binding
body = {
"credentials": {
"provider": "Here",
"api_key": "SAP-KEY"
},
"waypoints": {
"type": "MultiPoint",
"coordinates": [
from_coord,
to_coord
]
},
"vehicleType": "car",
"returnGeometry": False
}
headersAPI = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': 'Bearer '+ token,
}
response = requests.post(url, json=body,headers=headersAPI)
data = json.loads(response.text)['properties']
duration = data['duration']/60 ## Converting to minutes
distance = data['distance']/1000 ## Converting to Meters to KM
return distance,duration
As you see we simply chose vehicleType to be car because we are interested in driving distance. There are other parameters which can be useful and by default the
traffic information is set to true and the
departureTime to now. The detailed list of parameters is
here. Also the parameters for the routes can be more complicated as a list of waypoints instead of only an origin and destination as in my example above.
Create Kyma Function to provide API to return distance and time between 2 addresses
Now that we have all the building blocks ready, we are ready to add this function to Kyma and provide an API endpoint which takes 2 addresses and returns the driving distance and time between. This is a detailed step-by-step description by following
Kyma documentation for creating Serverless Function
Go to your Kyma dashboard on BTP and choose the namespace you would want to add the service to, for example below I have 3 namespaces and I choose dev
Kyma Dashboard Namespace Selection
In the namespace now go to Functions and create an inline function. I chose Python as the code snippets above are for Python.
Kyma Inline Python Function
When you press create you get a "Hello World" sample code from Kyma. We will change the code to add the 3 functions above and a main function which uses it and returns the answer to the API
Kyma Main Function
All the functionality for geocoding and routing is provided by the HANA Spatial Services so we do not need to add any other packages. Incase there are additional packages needed they can be added in the Dependencies section. The format to add packages here is same as in requirements.txt, for example python package name with desired version.
Now add an API endpoint so we can call the above function
Kyma API Configuration
Creating the API rule is straightforward, you can change the Name as you like and provide a Subdomain which adds a prefix to the host and helps to distinguish this service from others you may create on the Kyma cluster
The advanced options above provide capabilities to scale the endpoint and the underlying compute required. For this usecase we can use the XS as there is not a lot of compute needed and its off-loaded to the HANA Spatial Service. We can add more replicas depending the expected usage of the endpoint and Kyma cluster size.
Kyma Resource Configuration
Once the API is created your function would look like this
Kyma Function with API
The host endpoint above can then be used to test the endpoint
Testing and Debugging the endpoint
You can test the endpoint by providing it a from and to address. To see the print messages in the code you can open the logs from the function logs in Kyma
Kyma Function Logs
When you call the API from curl or Postman as in example here, the logs can be seen in the Kyma dashboard
Sample Logs
For cases when there are errors in the code either at build time or deploy time, the logs can be checked in the Grafana logs. To see the grafana logs follow the instructions provided
here at Setup Grafana for Kyma Metrics and Logs. Some other useful examples for debugging are provided in this blog post
Kymas Serverless Python Functions A Short Excursion