From 446896d335a1372ae280c6bbf319d0d5b03077e3 Mon Sep 17 00:00:00 2001 From: djmgit Date: Fri, 28 Jul 2017 04:12:20 +0530 Subject: [PATCH] fixes #275 - Adding heatmap overlay for CountryTweetMap app Fixes issue #275, added heatmap layer to CountryTweetMap app. User can add or remove heatmap layer according to their wish. --- .coafile | 5 +-- CountryTweetMap/index.html | 1 + CountryTweetMap/scripts/app.js | 48 +++++++++++++++++++++---- CountryTweetMap/scripts/leaflet-heat.js | 11 ++++++ 4 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 CountryTweetMap/scripts/leaflet-heat.js diff --git a/.coafile b/.coafile index 4973b842..866fae9f 100644 --- a/.coafile +++ b/.coafile @@ -1,7 +1,7 @@ [default] bears = LineLengthBear, SpaceConsistencyBear files = **.js, **.css, **.html, **.yml, **.json -ignore = **.min.js, **.min.css, tweetheatmap/public/lib/**, webtweets/js/bootstrap/**, webtweets/css/bootstrap.css, js/typeahead.bundle.js, webtweets/js/bootstrap.js, **/node_modules/**, **/index_bundle.js, WorldMoodTracker/index.html, emojiHeatmapper/emoji.json, emojiHeatmapper/lib/** +ignore = **.min.js, **.min.css, tweetheatmap/public/lib/**, webtweets/js/bootstrap/**, webtweets/css/bootstrap.css, js/typeahead.bundle.js, webtweets/js/bootstrap.js, **/node_modules/**, **/index_bundle.js, WorldMoodTracker/index.html, emojiHeatmapper/emoji.json, emojiHeatmapper/lib/**, CountryTweetMap/scripts/leaflet-heat.js max_line_length = 129 use_spaces = True @@ -20,7 +20,8 @@ use_spaces = True [js] bears = PHPCodeSnifferBear files = **.js -ignore = **.min.js, tweetheatmap/public/lib/**, /app/webtweets/js/bootstrap/**, **/node_modules/**, **/index_bundle.js +ignore = **.min.js, tweetheatmap/public/lib/**, /app/webtweets/js/bootstrap/**, **/node_modules/**, **/index_bundle.js, +CountryTweetMap/scripts/leaflet-heat.js javascript_strictness = False use_spaces = True check_class_declaration = False diff --git a/CountryTweetMap/index.html b/CountryTweetMap/index.html index 0862f081..a8f89012 100644 --- a/CountryTweetMap/index.html +++ b/CountryTweetMap/index.html @@ -23,6 +23,7 @@ crossorigin=""> +
diff --git a/CountryTweetMap/scripts/app.js b/CountryTweetMap/scripts/app.js index 8ffdecdc..542db97f 100644 --- a/CountryTweetMap/scripts/app.js +++ b/CountryTweetMap/scripts/app.js @@ -14,6 +14,8 @@ app.controller("app", function ($scope, $http) { $scope.plot = null; $scope.isLoading = false; $scope.error = ""; + $scope.heatmapData = []; + $scope.totalTweets = 0; $(".date").datepicker(); $(".date").datepicker( "option", "dateFormat", "yy-mm-dd"); var LeafIcon = L.Icon.extend({ @@ -91,6 +93,23 @@ app.controller("app", function ($scope, $http) { }); } + // Prepare heatmap data + $scope.setupHeatMapData = function () { + $scope.totalTweets = 0; + $scope.heatmapData = []; + + for (var key in $scope.tweetFreq) { + for (var i = 0; i < $scope.tweetFreq[key].length; i++) { + $scope.heatmapData.push([ + $scope.tweetFreq[key][0].place_country_center[1], + $scope.tweetFreq[key][0].place_country_center[0], + $scope.tweetFreq[key].length + ]); + } + $scope.totalTweets += $scope.tweetFreq[key].length; + } + } + // Function to prepare frequency of tweets for individual countries $scope.prepareFreq = function(data) { data.forEach(function(status) { @@ -105,8 +124,8 @@ app.controller("app", function ($scope, $http) { var sumLon = 0.0; for (var key in $scope.tweetFreq) { - sumLat += $scope.tweetFreq[key][0].place_country_center[0]; - sumLon += $scope.tweetFreq[key][0].place_country_center[1]; + sumLat += $scope.tweetFreq[key][0].place_country_center[1]; + sumLon += $scope.tweetFreq[key][0].place_country_center[0]; $scope.modalList.push({ country_name: $scope.tweetFreq[key][0].place_country, country_code: $scope.tweetFreq[key][0].place_country_code, @@ -116,6 +135,7 @@ app.controller("app", function ($scope, $http) { } mapCenterLat = sumLat / $scope.countryCount; mapCenterLon = sumLon / $scope.countryCount; + $scope.setupHeatMapData(); } $scope.setUpInitialMap = function() { @@ -175,15 +195,15 @@ app.controller("app", function ($scope, $http) { var markerIcon = null; var marker = null; if (size === $scope.tweetMax) { - markerIcon = L.marker(country.place_country_center, {icon: redIcon}); + markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: redIcon}); marker = $scope.getMarker(markerIcon, country, key); countriesHigh.push(marker); } else if (size > rangeMid) { - markerIcon = L.marker(country.place_country_center, {icon: blueIcon}); + markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: blueIcon}); marker = $scope.getMarker(markerIcon, country, key); countriesMedium.push(marker) } else { - markerIcon = L.marker(country.place_country_center, {icon: greenIcon}); + markerIcon = L.marker([country.place_country_center[1], country.place_country_center[0]], {icon: greenIcon}); marker = $scope.getMarker(markerIcon, country, key); countriesLow.push(marker); } @@ -192,6 +212,20 @@ app.controller("app", function ($scope, $http) { var countryMediumGroup = L.layerGroup(countriesMedium); var countryLowGroup = L.layerGroup(countriesLow); + var gradientStart = (Math.round(($scope.totalTweets / 3) * 10) / 10).toFixed(1); + var gradientMiddle = (Math.round(($scope.totalTweets / 2) * 10) / 10).toFixed(1); + var gradientEnd = $scope.totalTweets; + + var cfg = { + radius: 25, + max: $scope.totalTweets, + minOpacity: 0.5, + blur: 8, + maxZoom: 4 + } + + var heat = L.heatLayer($scope.heatmapData, cfg); + var backgroundLight = L.tileLayer( 'http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', { @@ -212,12 +246,14 @@ app.controller("app", function ($scope, $http) { var overlayMaps = { "Maximum tweets": countryHighGroup, "Medium tweets": countryMediumGroup, - "Low tweets": countryLowGroup + "Low tweets": countryLowGroup, + "Heatmap": heat }; var baseMaps = { "basemap": backgroundLight }; L.control.layers(baseMaps, overlayMaps).addTo($scope.map); + $scope.isLoading = false; } diff --git a/CountryTweetMap/scripts/leaflet-heat.js b/CountryTweetMap/scripts/leaflet-heat.js new file mode 100644 index 00000000..aa8031ab --- /dev/null +++ b/CountryTweetMap/scripts/leaflet-heat.js @@ -0,0 +1,11 @@ +/* + (c) 2014, Vladimir Agafonkin + simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas + https://github.com/mourner/simpleheat +*/ +!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* + (c) 2014, Vladimir Agafonkin + Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. + https://github.com/Leaflet/Leaflet.heat +*/ +L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)}; \ No newline at end of file