Technical Articles
Geospatial Extensions with Cloud Application Programing (CAP)
- Data is stored only once, no copy in a different GIS system.
- A single API to expose the data to a web interface as well as other applications
- Data exposed in read and write
- Data modificiations must go through multiple checks and retain transactional consistancy
Those requirements made it difficult to use a traditional GIS middleware so we proved this could be done with CAP and following architecture:
Architecture with CAP
The geospatial data resides in HANA tables which can be local, a cached replica or remote, depending on the business needs. Stored Procedures and views are useful artifacts that are typically used in conjunction with spatial data types.
Functions/API represented on the right orange box perform various business logic:
- records containing any form of address field will benefit from geocoding to add coordinates and normalized fields such as country, state, county, city…
- A polygon representing a harvesting block is checked for an intersection with a powerlines.
- A polygon representing a harvesting block along with a harvest date range is checked for overlapping with wildlife sanctuary zones and seasons.
The purpose of using functions outside the CAP main module is to simplify developments, releases, even improve scalability if using serverless functions and micro services APIs.
Lastly, the CAP service layer is the main component. It holds 3 important artifacts:
- The 4 table definitions in CDS format
- The odata service definition
- The CAP service extension to inject specific logic on select, insert or update operations.
In the service extension file, we handle the geospatial format conversions: the value in and out of the HANA table is in binary format and we prefer the service layer to use a human readable format such as geojson or Well Known Text (WKT).
Example of web service requests are in the samples folder. For instance, adding a warehouse is done by posting to <host>/forest/WareHouse this body:
{"ID": 1, "label": "Warehouse 1", "address": "okipii, Finland" }
Geocoding is performed before the record is inserted, to check the result, you can simply fetch the record with a get request to <host>/forest/Warehouse?$filter=label%20eq%20’Warehouse 1′
The column geo_score with a value of 0.99 indicates that the geocoding is accurate.
HTTP/1.1 200 OK
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Date: Tue, 07 Sep 2021 13:41:27 GMT
Connection: close
Content-Length: 297
{
"@odata.context": "$metadata#Warehouse/$entity",
"ID": 1,
"label": "Warehouse 1",
"surface": null,
"address": "okipii, Finland",
"geo_score": 0.99,
"geo_point": {
"type": "Point",
"coordinates": [
22.59858989715576,
62.495699882507324
]
},
"geo_city": "Kurikka",
"geo_county": "Etelä-Pohjanmaa",
"geo_country_iso3": "FIN"
}
A more fun example is to have a look at the wild life sanctuaries defined and post a harvesting block inside a sanctuary during the protected season:
### Harvesting block in a protected area during a specific season.
POST http://localhost:4004/forest/HarvestingBlock
Content-Type: application/json
{"ID":2,"dt":"2022-10-10","geom_as_wkt":"POLYGON ((23.77344846725464 61.42467889322534, 23.77394199371338 61.425407593025284, 23.77162456512451 61.426126012789695, 23.77134561538696 61.42581812063022, 23.771259784698486 61.425161273869925, 23.77145290374756 61.42508943041672, 23.77344846725464 61.42467889322534))"}
The response returns the id of the first rule that was infringed:
HTTP/1.1 500 Internal Server Error
OData-Version: 4.0
content-type: application/json;odata.metadata=minimal
Date: Tue, 07 Sep 2021 14:11:37 GMT
Connection: close
Content-Length: 138
{
"error": {
"code": "500",
"message": "Harvesting in this location and date infringes the sanctuary \"Little Trolls of the woods in Autumn\""
}
}
The sample project with full code and example data is available on github. I’d be happy to take this further and build a UI on top unfortunately I have zero UI5 skills so I’d be happy to get some help!
Thanks for sharing!