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

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
- Intercept map rendering via
ADJUST_NEW_OBJECTS
. - Call a routing API to get a polyline for each stage (origin/destination).
- Decode that polyline into a dense list of (latitude, longitude) points.
- Populate
ct_link_objects-ROUTE_POINTS
with those points (xpos = lon
,ypos = lat
). - 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_VB21
→ ADJUST_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.

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
- Open your Map View profile in the Planning Cockpit.
- Load a scenario with FU/FO stages (at least two stops).
- When the map renders, the BAdI will:
- Call HERE,
- Decode the path,
- Insert the route points.
- 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
vscar
affects the path (truck restrictions, tunnels, etc.). Match your TM planning context. - Coordinate order: HERE uses
lat,lon
. TM expectsxpos = 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.