Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Anoop_L
Explorer
1,329

Hello All,  

In this Blog I would like Exhibit a Scenario Where the RESTful webservice is used to Display SAP Data using MAPS API. In this Blog, Displaying the Location of SAP Entity like Plant, Distribution Center, Warehouse, Vendor Location on Google Maps. 

We need to Fetch Latitude and Longitude (Geo Coordinates) information for the given entities and then further we need to pass Geo Coordinates to the Google Map API in an HTML Block, Here I would pass Route as parameter in the URL then will retrieve customer location and display them on Google Map. 

API

While working with Google Maps API, I’ll be passing locations as Addresses or Co-Ordinates. I need to specify the start of address and end of address. If you have Stopover in between you can also define them as Waypoints. I will pass all the details to the API, and it will process all the rest. 

 

To Use Google Product API, I need to get an API Key. This API key is free up to certain usage Limit. If we go Beyond, we need to pay for licensing. We can Object an API Key ourselves Obtaining Google Maps API Key 

To Learn More about Google Maps API and Geo Coordinates, we can look up Google Maps JS API 

RESTful WS  

I Would use RESTful Web Service to Expose our data. I Will Build up the response message in HTML containing the JavaScript. The JavaScript code will call Google Maps API with Selected Geo Co-Ordinates. 

Within the Handler Method for the RESTful WS, 

  • Extract the parameter from URL  
  • Get the Data or Prepare the Data 
  • Build Up JavaScript and HTML Code 
  • Send the Response 

 

For this Scenario I will create a Service ZAL_DEMO in Transaction code SICF and assigned a Handler Class ZCL_AL_DEMO_HANDLER within it and a Standard Interface IF_HTTP_EXTENSION to Handle the HTTP. 

 

First Step I will Navigate to Transaction SICF 

Screenshot 2024-06-21 121658.png

 

After Navigating to Transaction SICF we can see the screen Define Service as below, then we need to click on Execute Button 

 

Anoop_L_0-1719295237495.png

 

Anoop_L_1-1719295359829.png

 

We can Create a New Node whenever we are creating RESTful WS as we can create different services under that Node. We shouldn’t Use default_host/sap/bc/srt as it required to have SOAMANAGER Configuration, which I will be not using for our Service. 

So, I will place the cursor on desired node and Click on Drop Down 

Default_host--->sap--->bc--->ZAL_DEMO 

 

Anoop_L_2-1719295770152.png

 

Anoop_L_3-1719295837933.png

 

I will Right click on “bc” and create a Sub-Element, Further I will Enter the name of the Service in Next Popup. In the subsequent screen I’ll provide the Description. 

 

Anoop_L_4-1719295891204.png

 

Anoop_L_5-1719295918441.png

 

Next, I’ll Provide the Necessary Credentials in Logon Data Tab like Description, Client which I’ll be working on, User ID and Password. 

Anoop_L_6-1719295954052.png

 

In the Handler Tab I’ll assign a custom class which would be used to handle the request coming from the web. This class should Implement the standard Interface IF_HTTP_EXTENSION and to gain access of the request and also required method.

 

Anoop_L_7-1719295994400.png

 

Now I’ll add the Interface IF_HTTP_EXTENSION in my Handler class and Implement the Method. 

 

Anoop_L_8-1719296032698.png

 

Anoop_L_9-1719296068205.png

 

HTML Build 

For HTML Body we can load our template in Transaction SMW0  

 

Anoop_L_10-1719296146496.png

 

But I Have chose to Manually Build Utility HTML class within ABAP. This utility wraps the logic for building HTML Tags and string manipulation within itself for simple client access. 

---> Template with Static Data = like Main CSS, Headers, Table Headers 

---> Place Holders for Dynamic Data = Table Details 

 

  • CREATE ELEMNT – Create any HTML element, Here I need to pass HTML Tag as Parameter. This should be the very first to get called after object Instantiation. 
  • ADD_ATT – Add Attribute to a HTML Tag like CSS class, style, ID  
  • ADD_INNER_HTML – Add Inner HTML to the Tag, Here I’ll specify the HTML Content which is visible on the Document like text of link on Tag 
  • APPEND_CHILD – Append HTML object to parent HTML object like creating a <P> Tag within <DIV> Tag 
  • GET_HTML – This Method will return the HTML Text. Method Traverse through all child Nodes of any Given Node. Method is based on Composite Design Pattern, So I need to call this method for my Parent or Root Node If I need to get any specific HTML, I can call it for any Node. 

I Will create 3 Private Attributes which can be accessed through above methods. 

 

  • TAG – This can contain type of the tag like <DIV> which is set by CREATE_ELEMNT. 
  • INNER_HTML – This contains the Visible text of the HTML Tag set by ADD_INNER_HTML 
  • ATTR – Table containing pair of with name of attribute and a value 
  • CHILDREN – Table Containing all Child Nodes for a given node. 

Now I’ll Create a Global Class with the Above Methods and Attributes. 

 

METHODS 

Anoop_L_11-1719296309156.png

 

ATTRIBUTES 

Anoop_L_12-1719296351535.png

Anoop_L_13-1719296381273.png

Anoop_L_0-1719379026212.png

 

 

METHOD IMPLEMENTATION

 

Anoop_L_15-1719296457098.png

 

Anoop_L_16-1719296482903.png

 

Anoop_L_17-1719296508895.png

 

Anoop_L_18-1719296535182.png

 

 

Anoop_L_20-1719296580803.png

 

 

Anoop_L_21-1719296605557.png

 

Preference in Local Class

 

 

 

 

 

CLASS lcl_html DEFINITION.
  PUBLIC SECTION.
   METHODS : create_elemnt IMPORTING tag TYPE string,
             add_att IMPORTING attr TYPE string
                                     val TYPE string,
             add_inner_html IMPORTING val TYPE string,
             append_child IMPORTING io_tag TYPE REF TO lcl_html,
             get_html RETURNING VALUE(rv_string) TYPE string.

  PRIVATE SECTION.
   DATA : tag TYPE string.
   DATA : inner_html TYPE string.
   TYPES : BEGIN OF ty_attr,
           attr TYPE string,
           val  TYPE string,
           END OF ty_attr.
   DATA : attrs TYPE STANDARD TABLE OF ty_attr.
   DATA : children TYPE STANDARD TABLE OF REF TO lcl_html.

ENDCLASS.

CLASS lcl_html IMPLEMENTATION.

METHOD create_elemnt.
  me->tag = tag.
ENDMETHOD.

METHOD append_child.
  APPEND io_tag TO me->children.
ENDMETHOD.

METHOD add_att.
  DATA : ls_attr LIKE LINE OF me->attrs.

  ls_attr-attr = attr.
  ls_attr-val = val.

  APPEND ls_attr TO me->attrs.
ENDMETHOD.

METHOD add_inner_html.
  me->inner_html = val.
ENDMETHOD.

METHOD get_html.
  DATA : lv_string TYPE string.
  DATA : lv_attr_val TYPE string.
  DATA : ls_attr LIKE LINE OF me->attrs.
  DATA : lo_child TYPE REF TO lcl_html.
  DATA : lv_child_html TYPE string.

*opening bracket

    lv_string = |<{ me->tag }|.

  LOOP AT me->attrs INTO ls_attr.

    lv_attr_val = |{ ls_attr-attr }='{ ls_attr-val }'|.

    lv_string = |{ lv_string } { lv_attr_val }|.

  ENDLOOP.

    lv_string = |{ lv_string }>|.

*Inner HTML

    lv_string = |{ lv_string }{ me->inner_html }|.

*Child

  LOOP AT me->children INTO lo_child.
    lv_child_html = lo_child->get_html( ).
    lv_string = |{ lv_string }{ lv_child_html }|.
    CLEAR lv_child_html.
  ENDLOOP.

*Closing
     lv_string = |{ lv_string }</{ me->tag }>|.

*Back the HTML

     rv_string = lv_string.

ENDMETHOD.

ENDCLASS.

 

 

 

 

 

IMPLEMENTATION 

In this Implementation part I'll Use the HTML Builder Class in IF_HTTP_EXTENSION~HANDLE_REQUEST Method of my Handler Class ZCL_AL_DEMO_HANDLER. 

 

 

 

 

DATA:
  lv_path TYPE STRING,
  lv_cdata TYPE STRING,
  lv_param TYPE STRING.

  DATA: lt_request TYPE STANDARD TABLE OF STRING.
  DATA: lv_route TYPE char10.

* get the request attributes
  lv_path = server->request->get_header_field( NAME = '~path_info' ).
  SHIFT lv_path LEFT BY 1 PLACES.
  SPLIT lv_path AT '/' INTO TABLE lt_request.
  READ TABLE lt_request INTO lv_param INDEX 1.

* Get Customer associated to ROute
* Get Geo Codes for those customers
  TYPES: BEGIN OF lty_geo,
           kunnr TYPE kna1-kunnr,
           lon   TYPE STRING,
           lat   TYPE STRING,
         END   OF lty_geo.
  DATA: li_geo TYPE STANDARD TABLE OF lty_geo.
  DATA: lwa_geo LIKE LINE OF li_geo.

*  DEFINE append_data.
*    lwa_geo-kunnr = &1.
*    lwa_geo-lon = &2.
*    lwa_geo-lat = &3.
*    APPEND lwa_geo TO li_geo.
*  END-OF-DEFINITION.
*
*  append_data: '1001' '33.042342' '-96.768866',
*               '1002' '33.057954' '-96.772127',
*               '1003' '33.060364' '-96.796782',
*               '1004' '33.030794' '-96.795516',
*               '1005' '33.027699' '-96.790152',
*               '1006' '33.027771' '-96.768866'.

  li_geo = value #( ( kunnr = '1001' lon = '33.042342' lat = '-96.768866' )
                    ( kunnr = '1002' lon = '33.057954' lat = '-96.772127' )
                    ( kunnr = '1003' lon = '33.060364' lat = '-96.796782' )
                    ( kunnr = '1004' lon = '33.030794' lat = '-96.795516' )
                    ( kunnr = '1005' lon = '33.027699' lat = '-96.790152' )
                    ( kunnr = '1006' lon = '33.027771' lat = '-96.768866' ) ).

* Build up JavaScript
  DATA: lv_tot  TYPE i.
  DATA: lv_from TYPE i.
  DATA: lv_to   TYPE i.
  DATA: lv_start TYPE STRING.
  DATA: lv_end   TYPE STRING.
  DATA: lv_wp     TYPE STRING,
        lv_pair   TYPE STRING,
        lv_iw     TYPE STRING.

  lv_tot = LINES( li_geo ).
  IF lv_tot GT 2.
    lv_from = 2.
    lv_to   = lv_tot - 1.
  ENDIF.

* start / end point
  READ TABLE li_geo INTO lwa_geo INDEX 1.
  CONCATENATE 'var startpoint = new google.maps.LatLng('
    lwa_geo-lon ',' lwa_geo-lat ');' INTO lv_start.
* Info window
  lv_iw = 'var infoWindow;'.
  CONCATENATE
    '    infoWindow = new google.maps.InfoWindow();'
    '    infoWindow.setOptions({'
    '        content: "<div>First Customer to Visit :' lwa_geo-kunnr '</div>",'
    '        position: startpoint,'
    '    });'
    '    infoWindow.open(map); '
    INTO lv_iw.

  READ TABLE li_geo INTO lwa_geo INDEX lv_tot.
  CONCATENATE 'var endpoint = new google.maps.LatLng('
    lwa_geo-lon ',' lwa_geo-lat ');' INTO lv_end.
  CONCATENATE
    lv_iw
    '    infoWindow = new google.maps.InfoWindow();'
    '    infoWindow.setOptions({'
    '        content: "<div>Last Customer : ' lwa_geo-kunnr '</div>",'
    '        position: endpoint,'
    '    });'
    '    infoWindow.open(map); '
    INTO lv_iw.

* Way points
  LOOP AT li_geo INTO lwa_geo FROM lv_from TO lv_to.
    CONCATENATE `location:"` lwa_geo-lon `,` lwa_geo-lat `",`
      INTO lv_pair.
    CONCATENATE
      lv_wp
      ' waypts.push({'
*      '   location:"33.077842,-96.820428&#8243;,'
      lv_pair
      '   stopover:true'
      ' });'
      INTO lv_wp SEPARATED BY cl_abap_char_utilities=>cr_lf.
  ENDLOOP.

  DATA: lv_js TYPE STRING.
  CONCATENATE
    '<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />'
    '<style type="text/css">'
    '  html { height: 100% }'
    '  body { height: 100%; margin: 0; padding: 0 }'
    '  #map_canvas { height: 100% }'
    '</style>'
    '<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>'
    '<script'
    ' src="http://maps.googleapis.com/maps/api/js?key=API_KEY&sensor=false">'
    '</script>'
    '<script type="text/javascript">'
    'var directionsDisplay;'
    'var directionsService = new google.maps.DirectionsService();'
    'var map;'
    lv_start
    lv_end
    'function initialize() {'
    ' directionsDisplay = new google.maps.DirectionsRenderer();'
    ' var myOptions = {'
    '   zoom: 16,'
    '   mapTypeId: google.maps.MapTypeId.ROADMAP,'
    '   center: startpoint'
    ' }'
    ' map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);'
    ' directionsDisplay.setMap(map);    '
    ' var waypts = [];'
*    ' waypts.push({'
*    '   location:"33.077842,-96.820428",'
*    '   stopover:true'
*    ' });'
    lv_wp
    ' console.log(waypts);'
    ' var request = {'
    '   origin:startpoint,'
    '     destination:endpoint,'
    '   waypoints: waypts,'
    '   optimizeWaypoints: true,'
    '   travelMode: google.maps.TravelMode.DRIVING'
    ' };    '
    ' directionsService.route(request, function(result, status) {'
    '   if (status == google.maps.DirectionsStatus.OK) {'
    '     directionsDisplay.setDirections(result);'
    '   }'
    ' }); '
    lv_iw
*    '    var infoWindow = new google.maps.InfoWindow();'
*    '    infoWindow.setOptions({'
*    '        content: "<div></div>",'
*    '        position: startpoint,'
*    '    });'
*    '    infoWindow.open(map); '
    '}'
    '</script>'
    INTO lv_js SEPARATED BY cl_abap_char_utilities=>cr_lf.

* Body HTML
  DATA: lv_body TYPE STRING.
  DATA: lv_html TYPE STRING.

  DATA: lo_body  TYPE REF TO zcl_html.
  DATA: lo_table TYPE REF TO zcl_html.
  DATA: lo_thead TYPE REF TO zcl_html.
  DATA: lo_tbody TYPE REF TO zcl_html.
  DATA: lo_tr    TYPE REF TO zcl_html.
  DATA: lo_td    TYPE REF TO zcl_html.
  DATA: lo_tag   TYPE REF TO zcl_html.
  data: lo_button TYPE REF TO zcl_html.
  DATA: lv_val TYPE STRING.

  CONCATENATE '<html>'
              '<head><title>Route Map</title>'
              '<style>body{ font: normal normal 14px Arial, Tahoma, Helvetica, FreeSans, sans-serif; }</style>'
              INTO lv_html.

 CREATE OBJECT lo_body.
  lo_body->create_elemnt( 'body' ).
  lo_body->add_att( attr = 'onload' val = 'initialize()' ).

  CREATE OBJECT lo_tag.
  lo_tag->create_elemnt( 'h3' ).
  lo_tag->add_inner_html( 'Generated Route Map from SAP' ).
  lo_body->append_child( lo_tag ).

  CONCATENATE 'Route:' lv_route INTO lv_val SEPARATED BY SPACE.
  CREATE OBJECT lo_tag.
  lo_tag->create_elemnt( 'p' ).
  lo_tag->add_inner_html( lv_val ).
  lo_body->append_child( lo_tag ).

  CREATE OBJECT lo_table.
  lo_table->create_elemnt( 'table' ).
  lo_table->add_att( attr = 'style' val = 'float:left' ).
  lo_body->append_child( lo_table ).

  CREATE OBJECT lo_thead.
  lo_thead->create_elemnt( 'thead' ).
  lo_table->append_child( lo_thead ).

  CREATE OBJECT lo_tr.
  lo_tr->create_elemnt( 'tr' ).
  lo_thead->append_child( lo_tr ).

  CREATE OBJECT lo_td.
  lo_td->create_elemnt( 'td' ).
  lo_td->add_inner_html( 'Customer' ).
  lo_tr->append_child( lo_td ).

  CREATE OBJECT lo_td.
  lo_td->create_elemnt( 'td' ).
  lo_td->add_inner_html( 'Long' ).
  lo_tr->append_child( lo_td ).

  CREATE OBJECT lo_td.
  lo_td->create_elemnt( 'td' ).
  lo_td->add_inner_html( 'Lat' ).
  lo_tr->append_child( lo_td ).

  CREATE OBJECT lo_tbody.
  lo_tbody->create_elemnt( 'tbody' ).
  lo_table->append_child( lo_tbody ).

  LOOP AT li_geo INTO lwa_geo.
    CREATE OBJECT lo_tr.
    lo_tr->create_elemnt( 'tr' ).
    lo_tbody->append_child( lo_tr ).

    lv_val = lwa_geo-kunnr.
    CREATE OBJECT lo_td.
    lo_td->create_elemnt( 'td' ).
    lo_td->add_inner_html( lv_val ).
    lo_tr->append_child( lo_td ).

    lv_val = lwa_geo-lon.
    CREATE OBJECT lo_td.
    lo_td->create_elemnt( 'td' ).
    lo_td->add_inner_html( lv_val ).
    lo_tr->append_child( lo_td ).

    lv_val = lwa_geo-lat.
    CREATE OBJECT lo_td.
    lo_td->create_elemnt( 'td' ).
    lo_td->add_inner_html( lv_val ).
    lo_tr->append_child( lo_td ).
  ENDLOOP.

  CREATE OBJECT lo_tag.
  lo_tag->create_elemnt( 'div' ).
  lo_tag->add_att( attr = 'id' val = 'map_canvas' ).
  lo_tag->add_att( attr = 'style' val = 'width: 100%, height: 100%' ).
  lo_body->append_child( lo_tag ).

  lv_body = lo_body->get_html( ).

  CONCATENATE lv_html lv_js '</head>' lv_body INTO lv_html.

  lv_cdata = lv_html.

* Send the response back
  server->response->set_cdata( DATA = lv_cdata ).

 

 

 

 

 

Now I’ll Use this ZCL_AL_DEMO_HANDLER Class in my Service in the Tab Handler List which was created in Transaction SICF. 

Anoop_L_22-1719297256792.png

 

OUTPUT

For the Output Firstly, we should activate the service and then we can Execute URL or Test the Service which generates Nice Representation on Google Map.

 

Anoop_L_23-1719297398401.png

 

 

Anoop_L_25-1719297465649.png

 

 

 

 

2 Comments
Labels in this area