Technology Blog Posts by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
lenapadeken
Product and Topic Expert
Product and Topic Expert
534

Why is the coffee shop Coffee for You in street X more popular than Coffee Beans in street Y? It could be that the coffee is just better in street X or that they have special offers that bind their customers. But it could also be as simple as the location. If you want to set up a store, it’s helpful to know if customers would come by or if there are competitors that could threaten your business. To find the best location, you can get help using spatial data.

Spatial data gives you information about the location of an object or event on the surface of the Earth. Other attributes, such as timestamps, can also be linked to the object or event. Doing so gives more opportunities to analyze the data and even predict behaviors. Representing spatial data is often done using 2D geometries, but can also take other forms. The typical geometrical forms are points, lines, and polygons.

Working with Spatial Data in ABAP

With ABAP Release 9.15, SAP BTP ABAP Environment 2505 functions were added to ABAP SQL to access, manipulate, and analyze spatial data in ABAP. These implemented functions are mapped to methods, functions, and constructors in SAP HANA. In the following demo, you will learn how to use ABAP SQL spatial functions.

Use Case: EMODnet Maps

The European Marine Observation and Data Network (EMODnet) offers maps like the vessel density map to visualize, for example, movement patterns and maritime traffic distribution. In a press release, EOMDnet explains the map's importance by, for example, “monitoring shipping emissions” and “planning offshore wind farms” (https://emodnet.ec.europa.eu/en/new-insights-european-maritime-traffic-emodnet-vessel-density-maps).

For the demo, we will extract the vessel density map's geometry data in JSON format using an Open Geospatial Consortium web service. In particular, we will examine the port locations in Europe using the following URL:

https://ows.emodnet-humanactivities.eu/wfs?service=WFS&version=2.0.0&request=GetFeature&typeNames=em....

This request returns the geometry data for each port. One entry is, for example:

{
   "type":"Feature",
   "id":"portlocations.16",
   "geometry":
   {
    "type":"Point",
    "coordinates":
    [
     4.40815,
     51.24165
    ]
   },
   "geometry_name":"the_geom",
   "properties":
   {
    "port_id":"BE003",
    "data_src_c":"0",
    "port_coor_":null,
    "country":"BE",
    "portname":null,
    "port":"Antwerp-Bruges",
    "source":"Port of Antwerp-Bruges",
    "pointx":4.40815,
    "pointy":51.24165,
    "traffic":"1"
   }
}

Having retrieved this information, we want to display all the ports in an SVG graphic.

Visualizing Spatial Data as SVG

To visualize all the European ports as SVG, the geodata must be available in a database table. You can do so by uploading the JSON file or calling the web service API in ABAP. The second solution can look like this:

method return_wfs_getfeature.
  DATA(wfs_features_url) = `https://ows.emodnet-humanactivities.eu/wfs?service=WFS&version=2.0.0` &&
                           `&request=GetFeature&typeNames=emodnet:portlocations&outputFormat=application/json`.

  cl_http_client=>create_by_url( exporting url  = wfs_features_url
                                 importing client = DATA(client) ).

  client->request->set_method( if_rest_message=>gc_method_get ).
  client->send( ).
  client->receive( exceptions http_communication_failure = 1
                              http_invalid_state         = 2
                              http_processing_failed     = 3
                              others                     = 4 ).

  response = client->response->get_cdata( ).

  out->write_json( response ).

  client->close( ).
endmethod.

Afterwards, the required data must be extracted from JSON. A simple transformation can be used for this. 

<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">

<tt:root name="PORTS"/>

<tt:template>
  <object>
    <str name="type">FeatureCollection</str>
    <array name="features">
      <tt:loop ref=".PORTS">
        <object>
          <str name="type">Feature</str>
          <tt:group>
            <tt:cond>
              <str name="id"><tt:value ref="ID"/></str>
            </tt:cond>
            <tt:cond>
              <object name="geometry">
                <tt:value ref="POINT" option="format(geo=4326)"/>
              </object>
            </tt:cond>
            <tt:cond>
              <object name="properties">
                <tt:group>
                  <tt:cond>
                    <str name="port_id"><tt:skip/></str>
                  </tt:cond>
                  <tt:cond>
                    <str name="data_src_c"><tt:skip/></str>
                  </tt:cond>
                  <tt:cond frq="?">
                    <str name="port_coor_"><tt:skip/></str>
                  </tt:cond>
                  <tt:cond frq="?">
                    <str name="country"><tt:value ref="COUNTRY"/></str>
                  </tt:cond>
                  <tt:cond frq="?">
                    <str name="portname"><tt:skip/></str>
                  </tt:cond>
                  <tt:cond frq="?">
                    <str name="port"><tt:value ref="PORT"/></str>
                  </tt:cond>
                  <tt:cond>
                    <tt:skip/>
                  </tt:cond>
                </tt:group>
              </object>
            </tt:cond>
            <tt:cond>
              <str name="geometry_name"><tt:skip/></str>
            </tt:cond>
          </tt:group>
        </object>
      </tt:loop>
    </array>
    <tt:skip/>
  </object>
</tt:template>

</tt:transform>

The result can then be appended to an existing database table.

method modify_db_tab.
  data ports type standard table of my_ports_db with key id.

  try.
      call transformation my_ports_st
        source json feature_selection
        result ports = ports.
    catch cx_transformation_error into final(transformation_error).
      out->write( transformation_error->get_text( ) ).
  endtry.

  delete ports where country is initial or port is initial.
  modify my_ports_db from table ports.
endmethod.

The geodata is now available in the database table my_ports_db. A point characterizes each port. These points can be aggregated using the ABAP SQL function ST_ASSVGAGG.

select from my_ports_db
  fields st_assvgaggr( point )
  into table (svg_graphic).

To display all the ports as SVG in a browser, you can use ICF.

method if_http_extension~handle_request.

  select from my_ports_db
    fields st_assvgaggr( point )
    into table @DATA(svg_graphic).

  data svg_data type string.
  svg_data = svg_graphic[ 1 ].

  server->response->set_header_field(
    name  = 'Content-Type' "#EC NOTEXT
    value = 'image/svg+xml' ).

  server->response->set_header_field(
    name  = 'Expires' "#EC NOTEXT
    value = '0' ).

  server->response->set_cdata( svg_data ).

endmethod.

The result looks as follows:

Points of  Ports in EuropePoints of Ports in Europe

 

To give these points more context, we can add a European map. Several land maps are available on the web. For this demo, we use the Natural Earth map. Given the extracted files, geoJSON can be retrieved using GeoPandas. Using the following Python script, a JSON file is created.

import geopandas as gpd

# Load shapefile
gdf = gpd.read_file("ne_10m_admin_0_countries.shp")

# Ensure it's in 4326
gdf = gdf.to_crs(epsg=4326)

# Convert geometries to EWKT
gdf['ewkt'] = gdf['geometry'].apply(lambda geom: f"SRID=4326;{geom.wkt}")

# Save as TXT with EWKT
gdf[['CONTINENT', 'NAME', 'ewkt']].to_json("continent_name_ewkt.json", index=False)

You can then upload the JSON file to the system and prepare the data for inserting into a new database.

method load_europe_map.
  data file_data type table of string.
  data filename type string.

  filename = '/my_file_path/ne_10m_admin_0_countries/continent_name_ewkt.json'.

  cl_gui_frontend_services=>gui_upload(
     exporting
       filename                 = filename
       filetype                 = 'ASC'
     changing
        data_tab                = file_data
      exceptions
        file_open_error         = 1
        file_read_error         = 2
        no_batch                = 3
        gui_refuse_filetransfer = 4
        invalid_type            = 5
        no_authority            = 6
        unknown_error           = 7
        others                  = 8
  ).

  data string_json type string.
    
  concatenate lines of file_data into string_json.

  data map_struc type my_map_db.
  data map_tab type standard table of my_map_db.
    
  data(json) = new cl_abap_json( string_json ).
  map_struc-id = 0.
  do.
    map_struc-name = json->get_value( |NAME.{ map_struc-id }| ).
    if map_struc-name is not initial.
      map_struc-continent = json->get_value( |CONTINENT.{ map_struc-id }| ).
      map_struc-geo_ewkt = json->get_value( |GEO_EWKT.{ map_struc-id }| ).
      append map_struc to map_tab.
      map_struc-id += 1.
    else.
      exit.
    endif.
  enddo.
    
  delete map_tab where continent <> 'Europe'.
  modify my_map_db from table @MAP_tab.
   
  commit work.

  update my_map_db set geometry4326 = st_geomfromewkt( geo_ewkt ) where length( geo_ewkt ) > 50.
  update my_map_db set geometry = st_geomfromwkt( st_aswkt( geometry4326 ), 0 ) where length( geo_ewkt ) > 50.
endmethod.

The database is now filled with the geodata needed to display the European map. With this underlying map, the ports are set into context.

method if_http_extension~handle_request.
  select from my_map_db
    fields st_assvgaggr( geometry4326 )
    where name <> 'Russia'
    union all
      select from my_ports_db
        fields st_assvgaggr( point )
  into table (svg_graphic).

  data svg_data type string.
  svg_data = svg_graphic[ 1 ].

  replace all occurrences of `grey` in svg_data with `#89D1FF`.
  replace all occurrences of `black` in svg_data with `#1B90FF`.

  server->response->set_header_field(
    name  = 'Content-Type' "#EC NOTEXT
    value = 'image/svg+xml' ).

  server->response->set_header_field(
    name  = 'Expires' "#EC NOTEXT
    value = '0' ).

  server->response->set_cdata( svg_data ).
endmethod.

The result now looks like this:

Points of Ports in a European MapPoints of Ports in a European Map

 

That's it. You have now seen one possible use case for displaying geodata using spatial data in ABAP SQL.

If you want to learn more about spatial data in ABAP SQL, explore the documentation. Additionally, a Devtober session about spatial functions in ABAP SQL is offered on 13 October 2025.