cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Stay updated on what is happening on the PTC Community by subscribing to PTC Community Announcements. X

Trying to extend current GMaps extension to use Google Maps Heatmap Function.

emoreira
14-Alexandrite

Trying to extend current GMaps extension to use Google Maps Heatmap Function.

Hello Guys,

I am building an App which will have a Heatmap plotted in a map. There are some options that can be used, but to start it I plan to use Google Maps API to do so. I have 2 options:

  1. Use Fusion Tables: it is simple to use but the data has to be stored into a Google Fusion Table in order to be used. The good thing is that the heatmap is Server Side Computed which can be an important point to performance.
  2. Use HeatMap Function: has more flexibility as the data can be passed dinamically but it is client side computed.

I decided to go for the 2nd option but I cannot get it to work because it requires the Visualization library to be included and I do not know where I should put it. Does anyone have an idea of how to declare it? Here is what I have to include:

<script type="text/javascript"
 
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=visualization&sensor=true_or_false">
</script>

For the code, I included an option into the extension's googlemap.ide file to add an option in the UI to Enable the HeatMap;

'ShowHeatMap': {

                'description': 'Show Fusion Tables HeatMap',

                'isBindingTarget': true,

                'defaultValue': false,

                'baseType': 'BOOLEAN'

                }

Also included in the googlemap.runtime file the API Call (almost copied and pasted from google):

if(this.getProperty('ShowHeatMap')){

    /* Data points defined as an array of LatLng objects */

  var heatmapData = [

   new google.maps.LatLng(37.782, -122.447),

   new google.maps.LatLng(37.782, -122.445),

   new google.maps.LatLng(37.782, -122.443),

   new google.maps.LatLng(37.782, -122.441),

   new google.maps.LatLng(37.782, -122.439),

   new google.maps.LatLng(37.782, -122.437),

   new google.maps.LatLng(37.782, -122.435),

   new google.maps.LatLng(37.785, -122.447),

   new google.maps.LatLng(37.785, -122.445),

   new google.maps.LatLng(37.785, -122.443),

   new google.maps.LatLng(37.785, -122.441),

   new google.maps.LatLng(37.785, -122.439),

   new google.maps.LatLng(37.785, -122.437),

   new google.maps.LatLng(37.785, -122.435)

  ];

  var heatmap = new google.maps.visualization.HeatmapLayer({

   data: heatmapData

  });

  heatmap.setMap(this.map);

   }

Thanks a lot

Ewerton

11 REPLIES 11
emoreira
14-Alexandrite
(To:emoreira)

Responding to my own questions:

I eventually found how to include the google visualization library in the Google Maps extension.

I had to replace the tag:

<FileResource description="" isRuntime="true" isDevelopment="false" type="JS" url="https://maps.google.com/maps/api/js?sensor=false"/>

by

<FileResource type="JS" url="https://maps.googleapis.com/maps/api/js?key=MY_APP_KEY&libraries=visualization" description="" isDevelopment="false" isRuntime="true"  />

Also had to create a Browser App key in the Google developer community and include my instance as an Allowed HTTP Referer.

Still struggling with 2 things still:

  1. How to add my localhost to the list of Allowed Referer
  2. How to make the AppKey an input variable that has to be passed, so I can distribute the extension without distributing my AppKey.

If somebody has an idea on how to do it, I appreciate any help

Cheers!

Ewerton

cdsouza
12-Amethyst
(To:emoreira)

Hi Ewerton,

Did you get an answer to the second question? i.e. make the AppKey input variable?

Also, did you have any problems with the '&' in the FileResource's url above?  In my case, I'm looking to extend the google maps widget to include the drawing library, and was having problems (where TWX would error out importing the extension) around specifying the &libraries parameter.

Thanks!

Cletus

emoreira
14-Alexandrite
(To:cdsouza)

Hi  Cletus,

I did not get any feedback for the App key question, but I did figure out the syntax to use "&". Whenever you need to add it to an URL you have to actually add "&amp". So in your case it will be &amp;libraries=visualization"

Cheers

Ewerton

cdsouza
12-Amethyst
(To:emoreira)

Yes, thanks!  I figured it out as you need a double escape &amp;amp;  Just a single &amp; doesn't quite work alone.

emoreira
14-Alexandrite
(To:cdsouza)

Weird.. Mine is working  with only one escape.. Now my problem are the layers.. I add one and whenever a change the data the layer is not replaced, but a new one is added so I get layers on top of the other. Here is how it is coming out:

http://vozdascoisas.blogspot.com.br/2015/12/alterando-uma-extensao-do-thingworx_30.html

It is in portuguese but you can see some screenshots.

Cheers

Thank you for posting that here. I made my own version based on your code. This is what I changed:

metadata.xml widget definition:

<Widget name="googlemap">

  <UIResources>

  <!-- Studio ONLY -->

  <FileResource type="CSS" file="googlemap.ide.css" description="" isDevelopment="true" isRuntime="false" />

  <FileResource type="JS" file="googlemap.ide.js" description="" isDevelopment="true" isRuntime="false" />

  <!-- Runtime/Squeal ONLY -->

  <FileResource type="CSS" file="googlemap.runtime.css" description="" isDevelopment="false" isRuntime="true" />

  <FileResource type="JS" file="googlemap.runtime.js" description="" isDevelopment="false" isRuntime="true" />

  <FileResource type="JS" url="https://maps.googleapis.com/maps/api/js?key=<YOUR APPLICATION KEY>&amp;libraries=visualization" description="This one should include the library for a heat map." isDevelopment="false" isRuntime="true" />

  </UIResources>

  </Widget>

Add property definitions to googlemap.ide.js

'CustomLayerData': {

                    'description' : 'CustomLayer Data source',

                    'isBindingTarget': true,

                    'isVisible': true,

                    'baseType': 'INFOTABLE',

                    'warnIfNotBoundAsTarget': false

                },

'CustomLayerDataField': {
'description' : 'Field which will provide data for Custom Layer',
'isBindingTarget': true,
'isVisible': true,
'isEditable': true,
'defaultValue': 'Reading',
'sourcePropertyName': 'CustomLayerData',
'baseTypeRestriction': 'NUMBER',
'baseType': 'FIELDNAME'
},
'CustomLayerLocationField': {
'description' : 'Field which will provide data for Custom Layer',
'isBindingTarget': true,
'isVisible': true,
'isEditable': true,
'defaultValue': 'location',
'sourcePropertyName': 'CustomLayerData',
'baseTypeRestriction': 'LOCATION',
'baseType': 'FIELDNAME'
},
'heatMapDissipating': {
'description' : 'Specifies whether heatmaps dissipate on zoom. When dissipating is false the radius of influence increases with zoom level to ensure that the color intensity is preserved at any given geographic location. Defaults to false.',
'isBindingTarget': true,
'isVisible': true,
'isEditable': true,
'defaultValue': false,
'baseType': 'BOOLEAN'
},
'heatMapOpacity': {
'description' : 'The opacity of the heatmap, expressed as a number between 0 and 1.',
'isBindingTarget': true,
'isVisible': true,
'isEditable': true,
'defaultValue': 0.5,
'baseType': 'NUMBER'
},
'heatMapRadius': {
'description' : 'The radius of influence for each data point, in pixels.',
'isBindingTarget': true,
'isVisible': true,
'isEditable': true,
'defaultValue': 60,
'baseType': 'NUMBER'
},

googlemap.runtime.js define global variable heatmap in the begining:

var heatmap = undefined;


googlemap.runtime.js updatePropertyInfo case. ​Note how I delete the previous layer with heatmap.setMap(undefined).

if (updatePropertyInfo.TargetProperty === 'CustomLayerData') {

  var customLayerDataRows = updatePropertyInfo.ActualDataRows;

  var nRows = customLayerDataRows.length;

  var heatmapData=[];

  for (var rowNumber = 0; rowNumber < nRows; rowNumber++) {

  var row = customLayerDataRows[rowNumber];

  heatmapData[rowNumber] =

  {location: new google.maps.LatLng(parseFloat(row[this.getProperty('CustomLayerLocationField')].latitude) , parseFloat(row[this.getProperty('CustomLayerLocationField')].longitude)), weight: parseFloat(row[this.getProperty('CustomLayerDataField')])};

  }

  var heatMapDissipating = this.getProperty('heatMapDissipating');

  var heatMapGradient = this.getProperty('heatMapGradient');

  var heatMapOpacity= this.getProperty('heatMapOpacity')

  var heatMapRadius=this.getProperty('heatMapRadius')

  if (heatmap != undefined) {

  heatmap.setMap(undefined) // Delete old layer

  }

  heatmap = new google.maps.visualization.HeatmapLayer({

  data: heatmapData,

  dissipating: heatMapDissipating,

  opacity: heatMapOpacity,

  radius: heatMapRadius

  });

  heatmap.setMap(this.map);

  return;

  }

Thank you for your posting Heikki Pulkkinen

I have followed your steps to update the google maps existing extension. But I am unable to use it in the composer. I can load the extension but when I look for google maps the new properties related to heat map (such as CustomLayerDataField or HeatMapRadius) are not shown. Did you have to rename the extension? or load it in a different way?

emoreira
14-Alexandrite
(To:fcherkaoui)

I've seen this happening and what I did was to uninstall the Maps extension, stop tomcat, clear tomcat cache, start tomcat and then Install the extension again.

Thanks Ewerton Moreira​. Re-loading the extension and restarting tomcat worked.

One more question, how are you loading the custom data for the heatmap? Are you creating a thing with the latitude/longitude data? 
I would like to reuse the location data from the google heatmap example as a start.

emoreira
14-Alexandrite
(To:fcherkaoui)

The extension should be loading data from an Infotable that could come either from a Datatable or a Custom service. In my tests I created a Data Table, populated with some values and used the GetDataTableEntries service to populate the widget.

Cheers

Ewerton

I have multiple Things with location variables. I get their data with queryImplementingThingWithData and give it to both data and customLayerData fields in the extension.

If you don't want to create a Thing for every data point you should use an Infotable as Ewerton described.

Announcements


Top Tags