This blog post is the first part of my entry for the 2014 Data Geek Challenge.

Edit Nov 18th, 2014: addedRobert Russell ‘s application with crime data at the end

Edit Nov 24th, 2014: integrated coding improvements suggested by Matt Lloyd and David Dalley

During my research for the Data Geek Challenge, I found out that the map options available within SAP Lumira are focused on country / region / city. However, for the analysis of Delays for Buses in Chicago, I wanted to go down to the street level. Therefore, I took this challenge as an opportunity to extend SAP Lumira with the Google Maps API.

Before I go any further, I need to thank Matt Lloyd and Manfred Schwarz for their very detailed blog posts on SAP Lumira Extensions, as well as the documentation, especially the debug part. I will assume you have read these articles prior to reading this exercise. Also, David Dalley made valuable contributions to the code through GitHub. Lastly, I only tested this script with SAP Lumira 1.20.

Fast Track


If you are interested in testing the Google Maps API Extension without the development phase, you can simply download the coding from GitHub. Click on “Download ZIP”. Extract the files and merge them in “C:\Program Files\SAP\Lumira\Desktop\extensions”. Don’t forget to restart your Lumira.

CostingGeek/LumiraGoogleMaps · GitHub


Here’s the expected result:

SAP_Lumira_Extension_Google_Maps_Result.png

Prerequisites:

You probably need to know a little about Google Maps API. There are actually 2 types: static or dynamic. Since we would like the end user to be able to move around in the map and zoom in / out, we’ll need to focus on the dynamic one. Here’s a very small example (created as a .html file):

<!DOCTYPE html>

<html>

<head>

  <title>Google Map Test</title>

  <style>html, body, #map-canvas { height: 100%; margin: 0px; padding: 0px; }</style>

  <script src=”https://maps.googleapis.com/maps/api/js?v=3.exp&language=en“></script>

  <script>

  function initialize() {

    var chicago = new google.maps.LatLng(41.850033,-87.6500523);

    var mapOptions = { zoom: 8, center: chicago };

    var map = new google.maps.Map(document.getElementById(‘map-canvas’), mapOptions);

  }

  </script>

</head>

<body onload = “javascript:initialize();”>

  <div id=”map-canvas”></div>

</body>

</html>

This example can be found here: http://www.costinggeek.com/data_geek_3/map_test.html

Picture_01.png

Locating the Chicago Cloud Gate using Google Maps API


Note the following:

  1. Line 6: <script src=”https://maps.googleapis.com/maps/api/js?v=3.exp&language=en“></script>
    This loads the script and should happen in the <head> section. This is not possible in Lumira because of the RequireJS infrastructure
  2. Line 18: <div id=”map-canvas”></div>
    By default, Lumira uses SVG elements instead of DIV, so keep this in mind.

More documentation on Google Maps APIs here:

Solving the Asynchronous Call

In the above Lumira extension examples, one can add a script in the render.js. However, RequireJS and Google Maps API are not compatible because of the order in which the scripts are called. The solution is to perform an asynchronous call to the Google Maps API. Thanks to Miller Medeiros, a Brazilian designer and developer, such a script is supported under the MIT license and can be downloaded here: https://github.com/millermedeiros/requirejs-plugins

Simply download the “src/async.js” script from GitHub and indicate in your coding where the file is located. This is done in the requires.config call. A typical coding example looks like this:

requirejs.config({

    baseUrl : ‘<your_base_url>’,

    paths: {

        ‘async’: ‘<folder_to_async_file>’

    }

});

require([‘async!https://maps.googleapis.com/maps/api/js?v=3.exp&language=en&sensor=false‘], function ( ) {

    // Some coding here

});

For the moment, place it in: C:\Program Files\SAP\Lumira\Desktop\utilities\VizPacker\libs\js

Preparing VizPacker

We now have most of the pieces of the puzzle. Start your VizPacker like in the articles above. This time, don’t forget to check the “Use DIV Container” flag. As detailed in the javascript warning, all code changes will be lost, so do this first!

Picture_02.png

Notice a DIV named “chart” inserted on the HTML tab:

<!– <<container –>

    <div id=”chart” style=”position: absolute; left:0px; right: 0px; top:0px; bottom:0px; background-color: #ffffff”></div>

<!– container>> –>

Be careful when setting the properties of your chart. For every “.” you use in the Chart Id, a sub-hierarchy will be created, that needs to be reproduced. In my example, the chart id is “com.costinggeek.googlemaps”, which is a 3-level hierarchy.

Picture_03.png

Sample Data

VizPacker has its own way of testing data. Prepare a CSV file with the following data:

“Latitude”,”Longitude”,”Description”,”Quantity”

“49.293523”,”8.641915″,”Building WDF 01″,1000

“49.294583”,”8.642838″,”Building WDF 02″,500

“49.292876”,”8.644190″,”Building WDF 03″,750

“49.294149”,”8.644115″,”Building WDF 04″,350

“49.292246”,”8.639973″,”Building WDF 05″,50

Then, on the Data Model tab, upload your data from that CSV file and start the mapping:

  • Assign dimension “Latitude” to Set 1,
  • Assign dimension “Longitude” to Set 1,
  • Assign dimension “Description” to Set 1,
  • Assign measure “Quantity” to Set 1,
  • Rename dimension Set 1 to “Latitude / Longitude / Desc”,
  • Rename measure Set 1 to “Quantity”,
  • Click on Apply and confirm the warning.

Configuring RequireJS

In the render.js, remove all sample coding between

// START: sample render code for a column chart

and

// END: sample render code

Remove this section since we won’t need any SVG component:

var vis = container.append(‘svg’).attr(‘width’, width).attr(‘height’, height)

.append(‘g’).attr(‘class’, ‘vis’).attr(‘width’, width).attr(‘height’, height);

Then, insert the following coding:

require.config({

    paths: {

        ‘com_costinggeek_googlemaps-async’: ‘sap/bi/bundles/com/costinggeek/googlemaps/com_costinggeek_googlemaps-src/js/async’

    }

});

// add DIV but make sure it’s done only once

var mapsContainer = container.select(‘div’);

if (!mapsContainer.node()) {

    mapsContainer = container.append(‘div’).attr(‘width’, ‘100%’).attr(‘height’, ‘100%’).attr(‘class’, ‘com_costinggeek_googlemaps-cg_map’);

}

// create asynchronous call to google maps api

require([‘com_costinggeek_googlemaps-async!https://maps.googleapis.com/maps/api/js?v=3.exp&language=en&sensor=false‘], function ( ) {

    // call google maps API after everything is loaded

    load_gmap();

});

// MDL: Get the name of the dimension columns from dimension group: Latitude / Longitude / Desc

var dimArr_latLongDesc = data.meta.dimensions(‘Latitude / Longitude / Desc’);

var dim_lattitude   = dimArr_latLongDesc[0];

var dim_longitude   = dimArr_latLongDesc[1];

var dim_description = dimArr_latLongDesc[2];

// MDL: end

// MDL: Get the name of the measure column from the measure group: Quantity

var msrArr_Qty = data.meta.measures(‘Quantity’);

var msr_Quantity = msrArr_Qty[0];

// MDL: end

// set global variable accessible by all sub-functions

var my_map;

var my_LatLng;

var my_LatLngBounds;

// function to show popup when markers are clicked

function attach_details( my_marker, my_description, my_quantity ) {

    var infowindow = new google.maps.InfoWindow(

    {

content: ‘<div class=”com_costinggeek_googlemaps-infoWindow”><strong>’ + my_description + ‘: </strong> ‘ + my_quantity + ‘</div>’

    });

    google.maps.event.addListener( my_marker, ‘click’, function() {

infowindow.open( my_map, my_marker );

    });

}

// function to place a marker on the map

function add_marker_lat_long( my_description, my_lat, my_long, my_quantity ) {

    my_LatLng = new google.maps.LatLng( my_lat, my_long );

    my_LatLngBounds.extend( my_LatLng );

    var my_marker = new google.maps.Marker({

map:  my_map,

position:     my_LatLng,

//icon:       icon_shop,

title:my_description,

status:     ‘active’

    });

    attach_details( my_marker, my_description, my_quantity );

}

// initialize the google map

function load_gmap( ) {

    my_LatLngBounds = new google.maps.LatLngBounds();

    var my_center = new google.maps.LatLng( 49.2933, 8.6419 );

    var mapOptions = {

        mapTypeId: google.maps.MapTypeId.ROADMAP,

        center:    my_center,

        zoom:10

    };

    my_map = new google.maps.Map(mapsContainer.node(),

        mapOptions);

    // convert all data to markers

    var j = 0;

    for ( var i = 0; i < data.length; i++ )

    {

        // MDL: Updated to use column names from data set.

        if( data[i][dim_lattitude] != undefined && data[i][dim_longitude] != undefined )

        // MDL: end

        {

            // MDL: Updated to use column names from data set.

            add_marker_lat_long( data[i][dim_description], data[i][dim_lattitude],  data[i][dim_longitude], data[i][msr_Quantity] );

            // MDL: end

            j++;

        }

    }

    // Auto center the map based on given markers

    if( j > 0 )

    {

        my_map.fitBounds(    my_LatLngBounds );

        my_map.panToBounds( my_LatLngBounds );

    }

}

Styling

On the style.css tab, replace the whole content with your preferred options. For instance:

.com_costinggeek_googlemaps-cg_map{

    border: solid 1px black;

    width:  100%;

    height: 100%;

}

.com_costinggeek_googlemaps-infoWindow {

    width:   250px;

    padding:  10px;

}

Testing in VizPacker

Once all the coding is in place, you just need to open the preview window and click the “Run Code” button to let the magic happen:

Picture_04.png

Testing in Lumira

Now that your script is ready in VizPacker, it is time for its final test in Lumira. Click on the “PACK” button and extract your files to your Lumira extension folder, typically “C:\Program Files\SAP\Lumira\Desktop\extensions”. Your folder hierarchy should look close to this picture.

Picture_05.png

Then, copy your “async.js” file into the “js” folder, so it is alongside the “render.js” file. Based on Manfred Schwarz’s research, we also know that some changes have to be made in render.js. Located the require.config section and update the path as follows (beware typos):

require.config({

    paths: {

    ‘async’: ‘sap/bi/bundles/com/costinggeek/googlemaps/com_costinggeek_googlemaps-src/js/async’

    }

});

Start Lumira (restart if it was open) and create a new dataset with the sample CSV file. In the Prepare section, remove all measures, except “Quantity”. In Dimensions, click on the gear icon next to Latitude and choose “Display Formatting”. Make sure the number of decimals is set to 6 (by default, Lumira truncates to 2 decimals and you would end up with only one marker). In Visualize, select your new extension. As Dimensions, select “Latitude”, “Longitude”, and “Description”. As Measure, select “Quantity”.

Picture_06.pngPicture_07.png

Here is the final result:

SAP_Lumira_Extension_Google_Maps_Result.png


Conclusion

I hope you were able to follow this process to extend your SAP Lumira with Google Maps. Do not hesitate to ask your questions in the comments section below. If this was useful, let us know what you did with it!

Update 11/18/14: Thanks to Robert Russell for sharing his application with crime data:

To report this post you need to login first.

49 Comments

You must be Logged on to comment or reply to a post.

      1. Julien Delvat Post author

        Hi Ty,

        Thanks for giving it a try. I’m thrilled it worked!

        Thanks to Matt Lloyd, I have updated the coding (marked MDL) so we can use dimensions and measures with different names. Also, he gave me a trick for the decimals so the markers are placed more accurately. Compare the screenshot in VizPacker (original) with the one in Lumira (updated).

        The coding in GitHub has also been updated with these changes.

        (0) 
          1. David Dalley

            Looks great, Julien! I made a pull request in github with a couple of suggested changes, one of which allows multiple maps to coexist in a story.

            (0) 
    1. Julien Delvat Post author

      Hi Leonardi,

      I’m not an expert in ArcGIS, but from what I saw on their website, I believe you don’t need Lumira to reproduce what I just did.

      What visualization are you trying to reproduce?

      (0) 
  1. Alper Derici

    Awesome!!! Thanks for sharing. Just a disclaimer, google maps usage in a corporate environment might require licenses.

    I’d love to see a way to create “layers” of markers depending on a dimension, some way to hide/show etc. Do you think that’s possible?

    Another question is, can we say it would also work in “Lumira Server” ?

    (0) 
    1. Julien Delvat Post author

      You are absolutely right about the licenses. Here are the details:

      https://developers.google.com/maps/licensing

      Looks like the license fees are very reasonable (about 50¢ per 1,000 map loads):

      Google Geo Developers Blog: Lower pricing and simplified limits with the Google Maps API

      About the Lumira Server, I’m not 100% sure since I don’t have access to one, but my guess would be yes. However, I believe extensions don’t work on the Lumira Cloud. Can Matt Lloyd or Harleen Kaur or Ty Miller confirm?

      My plan is to use layers for my data geek entry in 2 weeks. Stay tuned ;o)

      (0) 
  2. Robert Russell

    Hi Julien,

    I like it, thanks for sharing. It actually allows lat/lon mapping from HANA as well, which I found impossible with standard maps in the free version of Lumira.

    Thanks

    Robert

    (0) 
  3. Daniel Davis

    Is this feature still working in 1.22 for everybody? I’ve recently upgraded, applied changes back to the ini file and the Google map is just black white in any new or existing visualisations πŸ™

    Any ideas ?

    Thanks and regards Daniel

    (0) 
      1. Imoette Akpan

        Hi Julien,

        Love the extension, but I’m also getting blank spaces when I attempt to use it on Lumira 1.22. Where could I find the upgraded extension?

        Thanks and Best Regards,

        Imoette

        (0) 
  4. Karim Salhaoui-Martial

    Hi everyone

    Thanks a lot for this really interesting topic. We are just facing a problem : Doing your procedure works with a SAP Lumira 1.22 and windows 7 version. Point is it doesn’t work with a SAP Lumira 1.23 and windows 7, nor a SAP Lumira 1.22 and windows 8 OS. So it is quite hard to tell whether it could be a js problem or a lumira compatibility.

    Did anyone ever face that problem too ?

    Regards

    Karim

    (0) 
    1. Henry Banks

      Hi,

      i got a notification from GitHub on 5th March for all the published extensions. Here’s 1 example:

      Update profile to be compatible with Lumira 1.23 by geogezw · Pull Request #7 · SAP/lumira-extension-viz · GitHub

      the info reads:

      From Lumira 1.23, VizPacker added a key “bindings” when calling createViz(), but this key does not exist in previous Lumira versions. It will cause the profile failing to load in Lumira 1.23. The fix is added a line of “var bindings = {}” in HTML section of profile

      Does that apply to this example?

      regards,

      H

      (0) 
      1. Karim Salhaoui-Martial

        Hi Henry,

        We tried that on the 1.23 version but unfortunately it didn’t work. Nevertheless, we also got a problem on the SAP Lumira 1.22 version with a windows 8 system. Just a grey screen is displaying. The extension can be launch though (see below)/wp-content/uploads/2015/03/2015_03_18_17_40_46_667221.png

        Thanks for your reply

        Karim

        (0) 
        1. Paul Ekeland

          I had this issue as well, but it turned out lat/long dimensions need to be of string type and not numbers. That solved it for me.

          (0) 
  5. Robert Russell

    Hi,

    I think from my experience with 1.23 it is a clash of extensions with require.js or caching issue.

    When I load one of my own extensions first and then use Julien’s google maps it does not open.

    notworking.PNG

    When I put back my options in the SAPLumira.ini file which 1.23 install had kindly removed.

    -Dhilo.cef.frame.debug=true

    -Dhilo.cef.cache.enabled=false

    Restarted and loaded the google maps extension FIRST thing it works.

    working.PNG

    Although I can no longer open my own extensions πŸ™ .

    So I think the require.js only works with the first loaded extension or the cache is not clearing correctly.

    Anyway I would be interested if the above steps work for anyone else.

    Cheers

    Robert

    (0) 
  6. Christoph Pahlke-Lerch

    Hi Julien,

    I’m trying out the Google Maps extension. Installation went smooth and the map shows up :-). However, I have a query view on HANA that provides latitude and longitude with reasonable values. Currently no pins with the locations are shown. Any idea why the locations are not showing up? I’m interested in the Google Maps extension since the ESRI map currently does not support locations with latitude and longitude when running with HANA.

    Best Regards, Christoph

    (0) 
  7. CR RR

    Hello Jullien,

    Thanks for sharing your hard work and innovative approach with the communitiy.

    I was wondering if this can be utilized with Australian Map as even after providing correct longitudes /Latitude I couldn’t get through to correct map.

    Appreciate your feedback ,thanks.

    (0) 
  8. Happy Singh

    Hi Julian,

    Thanks you so much for sharing this information with us. It is very helpful.

    I am on version 1.25. When I am using the zip file which is there on github. I get blank screen.

    Lumira error 1.JPG

    So then I decided to follow step by step guide. When I am doing that I am getting below error.

    Lumia Error 2.JPG

    I have been trying to solve this issue from last three days. I am very new to this. Can you please help ?

    Thank,

    Happy

    (0) 
  9. Pitiwat Bumrungkit

    Hi Julien Delvat

    I have some problem after I composed google map extension in Lumira then I want to export to PDF but google map not show in file but another thing like cross tab table or Bar Chart are find.

    Please help,

    Thank in advance.

    (0) 
    1. Vipulkumar Rupareliya

      Hi Jee,

      I have tried doing the same way and it wont work. The way it worked for me was. I had to extract the folder in extension folder.

      see attached snapshot for better understanding. Capture.JPG

      (0) 
  10. Meng Wang

    Hi Julien,

    The component works fine with me on Lumira 1.27.

    One question, when i save the lumira document on BI Platform, how to make the customized component also be available on BI Platform?

    Now what i get is”Cannot display this chart, extention with id ….. is missing.”

    Best Regards.

    Meng

    (0) 
  11. Ryan Goodman

    One important note for using this extension (any google maps extension for that matter) is you will need some additional licensing for Google Maps for Work once you get past the development / testing phase.

    There is an officially supported Google Maps extension for SAP Lumira now with CMaps Analytics on the SAP extension gallery: https://analytics-extensions.enter.sap/sap/aed/ui/?keywords=CMaps%20Analytics


    In the extension library there are also non- Google Maps extensions too.

    (0) 

Leave a Reply