Road Routes on the SAP TM Map (with HERE Routing + custom BAdI)

SAP TM Planning Cockpit showing multiple road routes

This post walks through how we rendered actual road routes (not just straight lines) on the SAP TM Planning Cockpit map by enhancing the standard map objects with a custom BAdI and two external services:

  • BAdI: /SCMTMS/IF_MAP_ENHANCE_VB21
  • Method: ADJUST_NEW_OBJECTS
  • Service 1: Route calculation + polyline generation (HERE Routing v8)
  • Service 2: Polyline decoding (custom service returning every point along the path)

Goal: inject the decoded route points into the ROUTE_POINTS of each map link so that TM draws the exact road geometry between origins and destinations for FU/FO legs (stages).


What we built — in plain terms

  1. Intercept map rendering via ADJUST_NEW_OBJECTS.
  2. Call a routing API to get a polyline for each stage (origin/destination).
  3. Decode that polyline into a dense list of (latitude, longitude) points.
  4. Populate ct_link_objects-ROUTE_POINTS with those points (xpos = lon, ypos = lat).
  5. Let TM draw the route with the real road shape in the Planning Cockpit.

Prerequisites

  • SAP TM (S/4HANA) with Planning Cockpit map active.
  • An API key for HERE Routing v8 (or any equivalent routing provider that returns polyline).
  • Access to a polyline decoding service (we used our own HTTP endpoint described below).
  • Basic knowledge of ABAP HTTP calls and JSON parsing.

1) BAdI hook: /SCMTMS/IF_MAP_ENHANCE_VB21ADJUST_NEW_OBJECTS

This enhancement point is called when the map objects are being created. In ADJUST_NEW_OBJECTS, you receive and can alter the collection ct_link_objects. Each link typically represents a connection between stops (stages) in FU/FO.

High level logic inside the method:

  • Iterate over new link objects.
  • Extract origin and destination coordinates TM already provides.
  • Call Service 1 (routing) to get the polyline.
  • Call Service 2 (decoder) to expand that polyline into coordinates.
  • Replace the link’s ROUTE_POINTS with the decoded list.

2) Service 1 — Route calculation & polyline (HERE Routing v8)

HTTP (GET)

https://router.hereapi.com/v8/routes
  ?transportMode={VEHICLE_TYPE}
  &origin={ORIG_LAT},{ORIG_LON}
  &destination={DEST_LAT},{DEST_LON}
  &return=polyline,summary
  &apikey={YOUR_APIKEY}
  • transportMode: e.g., car, truck
  • origin/destination: taken from TM’s stage coordinates
  • Response contains a polyline (HERE flexible polyline format)

We initially tried to decode in ABAP, but the math (precision + flexible polyline encoding) made it impractical without extra libraries. To guarantee fidelity at road scale, we opted for a dedicated decoding service.


3) Service 2 — Polyline decoding (custom API)

Endpoint (POST):

https://sdo.dev.api.portaldotransportador.com/api/swagger/#/Polyline/post_polyline_convert

Example request body:

{
  "polyline": "{FLEXIBLE_POLYLINE_STRING}",
  "format": "here-flex" 
}

Example response (simplified):

[
  { "index": 0, "lat": -23.51123, "lng": -46.62345 },
  { "index": 1, "lat": -23.51201, "lng": -46.62202 },
  ...
]

We then map each array element to a TM route point line.


4) ABAP: calling the services and injecting ROUTE_POINTS

Below is a compact pattern you can adapt. It uses /UI2/CL_JSON for JSON handling (available in modern S/4HANA). If your system differs, swap for your JSON utility of choice.

Tip: keep your API keys outside code (e.g., in SICF destinations, STRUST, or the Secure Store).

METHOD /scmtms/if_map_enhance_vb21~adjust_new_objects.
  DATA: lt_links         TYPE /scmtms/cl_ui_map_srv=>tt_map_link,  \"or relevant link type
        ls_link          LIKE LINE OF lt_links,
        lv_here_url      TYPE string,
        lv_polyline      TYPE string,
        lv_json          TYPE string,
        lt_points        TYPE STANDARD TABLE OF ty_point,          \"define ty_point: lat DEC16_8, lng DEC16_8, index INT4
        ls_point         TYPE ty_point.

  FIELD-SYMBOLS: <fs_link>         TYPE any,
                 <fs_route_points> TYPE any,   \"component table for ROUTE_POINTS
                 <fs_point>        TYPE any.

  \"Work on the links provided by TM:
  lt_links = ct_link_objects.

  LOOP AT lt_links ASSIGNING <fs_link>.
    \"1) Read origin/destination from the link (TM provides them during map build)
    \"   Replace these getters with the actual structure fields in your system:
    DATA(lv_orig_lat) = <fs_link>-from_lat.
    DATA(lv_orig_lon) = <fs_link>-from_lon.
    DATA(lv_dest_lat) = <fs_link>-to_lat.
    DATA(lv_dest_lon) = <fs_link>-to_lon.

    \"2) Build HERE Routing URL
    lv_here_url = |https://router.hereapi.com/v8/routes?transportMode=truck| &&
                  |&origin={ lv_orig_lat },{ lv_orig_lon }| &&
                  |&destination={ lv_dest_lat },{ lv_dest_lon }| &&
                  |&return=polyline,summary&apikey={ YOUR_APIKEY }|.

    \"3) Call HERE
    DATA(lv_here_response) = zcl_http_util=>get( lv_here_url ). \"Implement a simple GET helper (CL_HTTP_CLIENT)

    \"4) Parse HERE JSON to extract flexible polyline (adjust the path as per HERE response)
    DATA(ls_here) = /ui2/cl_json=>deserialize( json = lv_here_response ).
    \" Navigate to routes(1)->sections(1)->polyline, depending on provider’s schema:
    lv_polyline = zcl_json_nav=>get_string( ls_here
                     path = VALUE stringtab( ( 'routes[1]' ) ( 'sections[1]' ) ( 'polyline' ) ) ).

    \"5) Call Polyline Decoder service
    DATA(lv_body) = /ui2/cl_json=>serialize(
                       data = VALUE ty_decode_req( polyline = lv_polyline format = 'here-flex' ) ).
    DATA(lv_decode_resp) = zcl_http_util=>post_json(
                             url  = 'https://sdo.dev.api.portaldotransportador.com/api/polyline/convert'
                             body = lv_body ).

    \"6) Parse decoded points
    lt_points = /ui2/cl_json=>deserialize( json = lv_decode_resp ).

    \"7) Clear current route points and insert decoded ones
    \"   ROUTE_POINTS is a component of the link object; access it via field-symbols
    ASSIGN COMPONENT 'ROUTE_POINTS' OF STRUCTURE <fs_link> TO <fs_route_points>.
    IF <fs_route_points> IS ASSIGNED.
      DELETE <fs_route_points> WHERE xpos IS NOT INITIAL.

      LOOP AT lt_points INTO ls_point.
        APPEND INITIAL LINE TO <fs_route_points> ASSIGNING <fs_point>.
        ASSIGN COMPONENT 'XPOS' OF STRUCTURE <fs_point> TO FIELD-SYMBOL(<fs_x>).
        ASSIGN COMPONENT 'YPOS' OF STRUCTURE <fs_point> TO FIELD-SYMBOL(<fs_y>).

        \"IMPORTANT: X = longitude, Y = latitude
        <fs_x> = ls_point-lng.
        <fs_y> = ls_point-lat.
      ENDLOOP.
    ENDIF.
  ENDLOOP.

  \"Return the modified links to TM:
  ct_link_objects = lt_links.
ENDMETHOD.

And this is the tiny part that clears and fills ROUTE_POINTS—the heart of the rendering:

\" Delete current route points
DELETE <fs_link_objects>-route_points WHERE xpos IS NOT INITIAL.

LOOP AT ls_polyline_response-data-coordinates ASSIGNING FIELD-SYMBOL(<fs_coordinates>).

  \" Insert coordinates from polyline
  APPEND INITIAL LINE TO <fs_link_objects>-route_points ASSIGNING FIELD-SYMBOL(<fs_route_points>).
  <fs_route_points>-xpos = <fs_coordinates>-lng.
  <fs_route_points>-ypos = <fs_coordinates>-lat.

ENDLOOP.
ABAP snippet filling ROUTE_POINTS (xpos = lon, ypos = lat)

Why X=longitude and Y=latitude?
TM’s map model follows the common convention where x is horizontal (lon) and y is vertical (lat). Mixing them will flip or distort the route.


Testing in the Planning Cockpit

  1. Open your Map View profile in the Planning Cockpit.
  2. Load a scenario with FU/FO stages (at least two stops).
  3. When the map renders, the BAdI will:
  • Call HERE,
  • Decode the path,
  • Insert the route points.
  1. You should now see road-accurate paths between each pair of stops.

Operational tips & gotchas

  • Precision & density: if the decoded list is too sparse, the line may cut corners. Ensure the decoder returns all points (or resample as needed).
  • Performance: Cache recent polylines by (origin, destination, mode) to avoid repeated external calls while planning.
  • Error handling: If routing fails, fall back to the standard straight line so planners aren’t blocked.
  • Security: Never hard-code API keys. Use secure storage and HTTPS only. Consider IP allowlists and quotas.
  • Transport modes: transportMode=truck vs car affects the path (truck restrictions, tunnels, etc.). Match your TM planning context.
  • Coordinate order: HERE uses lat,lon. TM expects xpos = lon, ypos = lat. Be consistent at every step.

Minimal helpers (sketch)

CLASS zcl_http_util DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    CLASS-METHODS get
      IMPORTING url TYPE string
      RETURNING VALUE(response) TYPE string.
    CLASS-METHODS post_json
      IMPORTING url TYPE string body TYPE string
      RETURNING VALUE(response) TYPE string.
ENDCLASS.

CLASS zcl_http_util IMPLEMENTATION.
  METHOD get.
    DATA(client) = NEW if_http_client( ).
    cl_http_client=>create_by_url( EXPORTING url = url IMPORTING client = client ).
    client->send( ).
    client->receive( ).
    response = client->response->get_data( ).
    client->close( ).
  ENDMETHOD.

  METHOD post_json.
    DATA(client) = NEW if_http_client( ).
    cl_http_client=>create_by_url( EXPORTING url = url IMPORTING client = client ).
    client->request->set_method( 'POST' ).
    client->request->set_content_type( 'application/json' ).
    client->request->set_cdata( body ).
    client->send( ).
    client->receive( ).
    response = client->response->get_data( ).
    client->close( ).
  ENDMETHOD.
ENDCLASS.

In production, replace the sketch with proper destinations (SM59), timeouts, retries, and error logging.


Troubleshooting checklist

  • Route draws as a straight line
  • Decoder failed or returned empty list. Log both service responses.
  • ROUTE_POINTS not filled due to wrong component name or structure binding.
  • Route appears mirrored or shifted
  • Lat/Lon flipped. Confirm xpos = lon, ypos = lat.
  • Nothing shows up
  • The BAdI implementation didn’t trigger (wrong enhancement spot or filters).
  • Authorization/HTTP errors. Check ICM/SMICM logs and HTTP return codes.
  • Performance issues
  • Add caching on your side; batch requests when possible.

Takeaways

By enhancing the map objects via /SCMTMS/IF_MAP_ENHANCE_VB21→ADJUST_NEW_OBJECTS and combining a routing API with a polyline decoder, SAP TM can render true road geometry in the Planning Cockpit—hugely improving planner trust and usability.


Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *