SAP Lumira Extension: Google Maps
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.
- Hello World extension for SAP Lumira
- How to add a D3 extension for SAP Lumira
- Lumira Geoextension with datamaps.js and topojson.js
- SAP Lumira 1.20 documentation
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:
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
Locating the Chicago Cloud Gate using Google Maps API
Note the following:
- 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 - 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!
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.
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:
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.
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”.
Here is the final result:
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:
Mapping crime data in HANA cloud with google maps thanks to @jdelvat‘s Lumira extension http://t.co/4s1ZOjqjnM pic.twitter.com/PQnp1mEQPf
— Robert Russell (@rjruss) November 18, 2014
Thanks for the blog on a very popular topic!
Hi Julien,
Nice post - i am looking forward to try your Lumira googlemaps visualization edition!
Capital! Just tried it and it worked no problem. Love the creativity Julien!
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.
Cool. I know David Dalley has some feedback on how you might want to update to support multiple maps on one board. Let's see if he responds đ
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.
Great! I'm happy I used GitHub,
everybody will benefit from this new and improved version ... đ
Thanks, David!
Thank you Julien for sharing this step-by-step guide.
Can this be done with on-premise GIS server, like ArcGIS? Thoughts, anyone?
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?
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" ?
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)
Yes, CVOM extensions, like this one, are supported with Lumira Server. Not on Lumira Cloud. Yet...
I'm dreaming about a world, in which the viz extensions would be automatically published with the stories, no IT involved at all đ
Agreed that we need a simpler deployment mechanism. Don't have an answer right now, but the need is noted.
Great post....
Thanks! I always wondered if this was possible! đ
Now you know đ
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
Even better đ Thanks for the feedback!
This is way too cool. Thanks for sharing Julien.
Appreciate your feedback ;o)
Very good post. Thanks for sharing.
Thanks
This is awesome Julien. Thanks for sharing.
Pleasure
Great job Julien! very nicely explained. I am going to try for sure.
Let us know what you were able to achieve.
Hello Julien
Like it very much., i Just made one for the country of Honduras. But how can I make so i can graph information on top of the google map ? for example a pie chart in the latitude/longitude ?
Regards
Hector
Thanks for your feedback.
In order to show more information on your map, you need to adapt the javascript.
Some examples can be found online, like this one:
http://stackoverflow.com/questions/17502438/pie-chart-on-google-map
Good luck
Julien,
Awesome work !!!!
Hi Julien/Anyone,
I have tried this step by step , but im getting only the barchart in the vizpacker... im new to sdk . could you please help me out...
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
I'll upgrade, retest and let you know.
Thanks for raised the issue.
Julien
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
I can confirm - worked Pre 1.22 and White screen of White đ from 1.22 onwards
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
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
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)
Thanks for your reply
Karim
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.
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.
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.
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
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
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.
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.
So then I decided to follow step by step guide. When I am doing that I am getting below error.
I have been trying to solve this issue from last three days. I am very new to this. Can you please help ?
Thank,
Happy
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.
Hello,
I’ve
downloaded zip file from https://github.com/CostingGeek/LumiraGoogleMaps.
and Lumira 1.27 from menu, click Extension, Click Manual installation, select LumiraGoogleMaps-master.zip.
I got the error says “The extension is not
valid and could not be installed”
What's wrong with me ? Any advice would be very appreciated. Many Thanks. JH
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.
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
Extensions aren't supported yet on Lumira Server for BI Platform. Coming soon.
It's available since 1.29 release on platform
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.