Analisi dei sentimenti Twitter in tempo reale con HBase in HDInsight

Informazioni su come eseguire l'analisi del sentiment di Big Data in tempo reale da Twitter usando un cluster HBase in HDInsight.

I siti Web di social networking rappresentano una delle principali forze trainanti per l'adozione di Big Data. Le API pubbliche offerte da siti quali Twitter costituiscono un'utile origine di dati per l'analisi e la comprensione delle tendenze più popolari. In questa esercitazione viene spiegato come sviluppare un'applicazione di servizio per lo streaming su console e un'applicazione Web ASP.NET al fine di:

Analisi del sentiment di Twitter con HBase in HDInsight

  • Applicazione di streaming

    • ottenere tweet geotaggati in tempo reale usando l'API di streaming di Twitter
    • valutare i sentimenti di tali tweet
    • memorizzare i dati sui sentimenti in HBase usando la SDK di Microsoft HBase
  • Applicazione Siti Web di Azure

    • tracciare i dati statistici in tempo reale sulle mappe Bing usando un'applicazione Web ASP.NET. Una visualizzazione dei tweet è simile alla schermata seguente:

      hdinsight.hbase.twitter.sentiment.bing.map

      È possibile effettuare query sui tweet usando specifiche parole chiave per determinare se l'opinione espressa nei tweet è positiva, negativa o neutrale.

Un esempio di soluzione completa di Visual Studio è disponibile su GitHub, ovvero l' app di analisi dei sentimenti dei social media in tempo reale.

Prerequisiti

Prima di iniziare questa esercitazione, è necessario disporre di quanto segue:

Creazione dell'ID e dei segreti di un'applicazione Twitter

Le API di streaming di Twitter usano OAuth per l'autorizzazione delle richieste. Il primo passaggio da seguire per usare OAuth consiste nel creare una nuova applicazione sul sito Twitter Developers.

Per creare l'ID e i segreti di un'applicazione Twitter

  1. Accedere a Twitter Apps. Se non si dispone di un account Twitter, fare clic sul collegamento Sign up now .
  2. Fare clic su Create New App.
  3. Compilare i campi Name (Nome), Description (Descrizione) e Website (Sito Web). Il nome dell'applicazione Twitter deve essere univoco. Il campo Website non viene realmente usato. Non è necessario inserire un URL valido.
  4. Fare clic su Yes, I agree e su Create your Twitter application.
  5. Fare clic sulla scheda Permissions (Autorizzazioni) e quindi su Read only (Sola lettura). L'autorizzazione di sola lettura è sufficiente ai fini di questa esercitazione.
  6. Fare clic sulla scheda Keys and Access Tokens .
  7. Fare clic su Create my access token (Crea il mio token di accesso) nella parte inferiore della pagina.
  8. Copiare i valori dei campi Consumer Key (API Key) (Chiave utente - Chiave API), Consumer Secret (API Secret) (Segreto utente - Segreto API), Access token (Token di accesso) e Access token secret (Segreto token di accesso). Sarà necessario usare questi valori più avanti nell'esercitazione.

    ![NOTA] Il pulsante Test OAuth non funziona più.

Creare un servizio di streaming di Twitter

È necessario creare un'applicazione per ricevere i tweet, calcolare i punteggi dei sentimenti dei tweet e inviare i termini dei tweet elaborati a HBase.

Per creare l'applicazione di streaming

  1. Aprire Visual Studio e creare un'applicazione console Visual C# denominata TweetSentimentStreaming.
  2. Nella finestra Console di Gestione pacchettieseguire i comandi seguenti:

     Install-Package Microsoft.HBase.Client -version 0.4.2.0
     Install-Package TweetinviAPI -version 1.0.0.0
    

    I comandi seguenti permettono di installare il pacchetto HBase .NET SDK, ovvero la libreria client che consente di accedere al cluster HBase, e il pacchetto Tweetinvi API, usato per accedere all'API Twitter.

    Nota

    L'esempio usato in questo articolo è stato testato con la versione specificata in precedenza. È possibile rimuovere la versione per installare la più recente.

  3. In Esplora soluzioni aggiungere System.Configuration al riferimento.
  4. Aggiungere un nuovo file di classe al progetto denominato HBaseWriter.cse quindi sostituire il codice con quanto segue:

     using System;
     using System.Collections.Generic;
     using System.IO;
     using System.Linq;
     using System.Text;
     using System.Threading;
     using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
     using org.apache.hadoop.hbase.rest.protobuf.generated;
     using Microsoft.HBase.Client;
     using Tweetinvi.Models;
    
     namespace TweetSentimentStreaming
     {
         class HBaseWriter
         {
             // HDinsight HBase cluster and HBase table information
             const string CLUSTERNAME = "https://<Enter Your Cluster Name>.azurehdinsight.net/";
             const string HADOOPUSERNAME = "admin"; //the default name is "admin"
             const string HADOOPUSERPASSWORD = "<Enter the Hadoop User Password>";
    
             const string HBASETABLENAME = "tweets_by_words";
             const string COUNT_ROW_KEY = "~ROWCOUNT";
             const string COUNT_COLUMN_NAME = "d:COUNT";
    
             long rowCount = 0;
    
             // Sentiment dictionary file and the punctuation characters
             const string DICTIONARYFILENAME = @"..\..\dictionary.tsv";
             private static char[] _punctuationChars = new[] {
         ' ', '!', '\"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',   //ascii 23--47
         ':', ';', '<', '=', '>', '?', '@', '[', ']', '^', '_', '`', '{', '|', '}', '~' };   //ascii 58--64 + misc.
    
             // For writting to HBase
             HBaseClient client;
    
             // a sentiment dictionary for estimate sentiment. It is loaded from a physical file.
             Dictionary<string, DictionaryItem> dictionary;
    
             // use multithread write
             Thread writerThread;
             Queue<ITweet> queue = new Queue<ITweet>();
             bool threadRunning = true;
    
             // This function connects to HBase, loads the sentiment dictionary, and starts the thread for writting.
             public HBaseWriter()
             {
                 ClusterCredentials credentials = new ClusterCredentials(new Uri(CLUSTERNAME), HADOOPUSERNAME, HADOOPUSERPASSWORD);
                 client = new HBaseClient(credentials);
    
                 // create the HBase table if it doesn't exist
                 if (!client.ListTablesAsync().Result.name.Contains(HBASETABLENAME))
                 {
                     TableSchema tableSchema = new TableSchema();
                     tableSchema.name = HBASETABLENAME;
                     tableSchema.columns.Add(new ColumnSchema { name = "d" });
                     client.CreateTableAsync(tableSchema).Wait();
                     Console.WriteLine("Table \"{0}\" is created.", HBASETABLENAME);
                 }
    
                 // Read current row count cell
                 rowCount = GetRowCount();
    
                 // Load sentiment dictionary from a file
                 LoadDictionary();
    
                 // Start a thread for writting to HBase
                 writerThread = new Thread(new ThreadStart(WriterThreadFunction));
                 writerThread.Start();
             }
    
             ~HBaseWriter()
             {
                 threadRunning = false;
             }
    
             private long GetRowCount()
             {
                 try
                 {
                     RequestOptions options = RequestOptions.GetDefaultOptions();
                     options.RetryPolicy = RetryPolicy.NoRetry;
                     var cellSet = client.GetCellsAsync(HBASETABLENAME, COUNT_ROW_KEY, null, null, options).Result;
                     if (cellSet.rows.Count != 0)
                     {
                         var countCol = cellSet.rows[0].values.Find(cell => Encoding.UTF8.GetString(cell.column) == COUNT_COLUMN_NAME);
                         if (countCol != null)
                         {
                             return Convert.ToInt64(Encoding.UTF8.GetString(countCol.data));
                         }
                     }
                 }
                 catch(Exception ex)
                 {
                     if (ex.InnerException.Message.Equals("The remote server returned an error: (404) Not Found.", StringComparison.OrdinalIgnoreCase))
                     {
                         return 0;
                     }
                     else
                     {
                         throw ex;
                     }
    
                 }
    
                 return 0;
             }
    
             // Enqueue the Tweets received
             public void WriteTweet(ITweet tweet)
             {
                 lock (queue)
                 {
                     queue.Enqueue(tweet);
                 }
             }
    
             // Load sentiment dictionary from a file
             private void LoadDictionary()
             {
                 List<string> lines = File.ReadAllLines(DICTIONARYFILENAME).ToList();
                 var items = lines.Select(line =>
                 {
                     var fields = line.Split('\t');
                     var pos = 0;
                     return new DictionaryItem
                     {
                         Type = fields[pos++],
                         Length = Convert.ToInt32(fields[pos++]),
                         Word = fields[pos++],
                         Pos = fields[pos++],
                         Stemmed = fields[pos++],
                         Polarity = fields[pos++]
                     };
                 });
    
                 dictionary = new Dictionary<string, DictionaryItem>();
                 foreach (var item in items)
                 {
                     if (!dictionary.Keys.Contains(item.Word))
                     {
                         dictionary.Add(item.Word, item);
                     }
                 }
             }
    
             // Calculate sentiment score
             private int CalcSentimentScore(string[] words)
             {
                 Int32 total = 0;
                 foreach (string word in words)
                 {
                     if (dictionary.Keys.Contains(word))
                     {
                         switch (dictionary[word].Polarity)
                         {
                             case "negative": total -= 1; break;
                             case "positive": total += 1; break;
                         }
                     }
                 }
                 if (total > 0)
                 {
                     return 1;
                 }
                 else if (total < 0)
                 {
                     return -1;
                 }
                 else
                 {
                     return 0;
                 }
             }
    
             // Popular a CellSet object to be written into HBase
             private void CreateTweetByWordsCells(CellSet set, ITweet tweet)
             {
                 // Split the Tweet into words
                 string[] words = tweet.Text.ToLower().Split(_punctuationChars);
    
                 // Calculate sentiment score base on the words
                 int sentimentScore = CalcSentimentScore(words);
                 var word_pairs = words.Take(words.Length - 1)
                                     .Select((word, idx) => string.Format("{0} {1}", word, words[idx + 1]));
                 var all_words = words.Concat(word_pairs).ToList();
    
                 // For each word in the Tweet add a row to the HBase table
                 foreach (string word in all_words)
                 {
                     string time_index = (ulong.MaxValue - (ulong)tweet.CreatedAt.ToBinary()).ToString().PadLeft(20) + tweet.IdStr;
                     string key = word + "_" + time_index;
    
                     // Create a row
                     var row = new CellSet.Row { key = Encoding.UTF8.GetBytes(key) };
    
                     // Add columns to the row, including Tweet identifier, language, coordinator(if available), and sentiment
                     var value = new Cell { column = Encoding.UTF8.GetBytes("d:id_str"), data = Encoding.UTF8.GetBytes(tweet.IdStr) };
                     row.values.Add(value);
    
                     value = new Cell { column = Encoding.UTF8.GetBytes("d:lang"), data = Encoding.UTF8.GetBytes(tweet.Language.ToString()) };
                     row.values.Add(value);
    
                     if (tweet.Coordinates != null)
                     {
                         var str = tweet.Coordinates.Longitude.ToString() + "," + tweet.Coordinates.Latitude.ToString();
                         value = new Cell { column = Encoding.UTF8.GetBytes("d:coor"), data = Encoding.UTF8.GetBytes(str) };
                         row.values.Add(value);
                     }
    
                     value = new Cell { column = Encoding.UTF8.GetBytes("d:sentiment"), data = Encoding.UTF8.GetBytes(sentimentScore.ToString()) };
                     row.values.Add(value);
    
                     set.rows.Add(row);
                 }
             }
    
             // Write a Tweet (CellSet) to HBase
             public void WriterThreadFunction()
             {
                 try
                 {
                     while (threadRunning)
                     {
                         if (queue.Count > 0)
                         {
                             CellSet set = new CellSet();
                             lock (queue)
                             {
                                 do
                                 {
                                     ITweet tweet = queue.Dequeue();
                                     CreateTweetByWordsCells(set, tweet);
                                 } while (queue.Count > 0);
                             }
    
                             // Write the Tweet by words cell set to the HBase table
                             client.StoreCellsAsync(HBASETABLENAME, set).Wait();
                             Console.WriteLine("\tRows written: {0}", set.rows.Count);
                         }
                         Thread.Sleep(100);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine("Exception: " + ex.Message);
                 }
             }
         }
         public class DictionaryItem
         {
             public string Type { get; set; }
             public int Length { get; set; }
             public string Word { get; set; }
             public string Pos { get; set; }
             public string Stemmed { get; set; }
             public string Polarity { get; set; }
         }
     }
    
  5. Impostare le costanti nel codice precedente, incluse CLUSTERNAME, HADOOPUSERNAME, HADOOPUSERPASSWORD e DICTIONARYFILENAME. DICTIONARYFILENAME è il nome e il percorso del file direction.tsv. Il file può essere scaricato da https://hditutorialdata.blob.core.windows.net/twittersentiment/dictionary.tsv. Per cambiare il nome della tabella HBase, è necessario modificare il nome della tabella nell'applicazione Web di conseguenza.
  6. Aprire Program.cse sostituire il codice con quello seguente:

     using System;
     using System.Diagnostics;
     using Tweetinvi;
     using Tweetinvi.Models;
    
     namespace TweetSentimentStreaming
     {
         class Program
         {
             const string TWITTERAPPACCESSTOKEN = "<Enter Twitter App Access Token>";
             const string TWITTERAPPACCESSTOKENSECRET = "<Enter Twitter Access Token Secret>";
             const string TWITTERAPPAPIKEY = "<Enter Twitter App API Key>";
             const string TWITTERAPPAPISECRET = "<Enter Twitter App API Secret>";
    
             static void Main(string[] args)
             {
                 Auth.SetUserCredentials(TWITTERAPPAPIKEY, TWITTERAPPAPISECRET, TWITTERAPPACCESSTOKEN, TWITTERAPPACCESSTOKENSECRET);
    
                 Stream_FilteredStreamExample();
             }
    
             private static void Stream_FilteredStreamExample()
             {
                 for (;;)
                 {
                     try
                     {
                         HBaseWriter hbase = new HBaseWriter();
                         var stream = Stream.CreateFilteredStream();
                         stream.AddLocation(new Coordinates(90, -180), new Coordinates(-90,180));
    
                         var tweetCount = 0;
                         var timer = Stopwatch.StartNew();
    
                         stream.MatchingTweetReceived += (sender, args) =>
                         {
                             tweetCount++;
                             var tweet = args.Tweet;
    
                             // Write Tweets to HBase
                             hbase.WriteTweet(tweet);
    
                             if (timer.ElapsedMilliseconds > 1000)
                             {
                                 if (tweet.Coordinates != null)
                                 {
                                     Console.ForegroundColor = ConsoleColor.Green;
                                     Console.WriteLine("\n{0}: {1} {2}", tweet.Id, tweet.Language.ToString(), tweet.Text);
                                     Console.ForegroundColor = ConsoleColor.White;
                                     Console.WriteLine("\tLocation: {0}, {1}", tweet.Coordinates.Longitude, tweet.Coordinates.Latitude);
                                 }
    
                                 timer.Restart();
                                 Console.WriteLine("\tTweets/sec: {0}", tweetCount);
                                 tweetCount = 0;
                             }
                         };
    
                         stream.StartStreamMatchingAllConditions();
                     }
                     catch (Exception ex)
                     {
                         Console.WriteLine("Exception: {0}", ex.Message);
                     }
                 }
             }
    
         }
     }
    
  7. Impostare le costanti, incluse TWITTERAPPACCESSTOKEN, TWITTERAPPACCESSTOKENSECRET, TWITTERAPPAPIKEY e TWITTERAPPAPISECRET.

Per eseguire il servizio di streaming, premere F5. Di seguito è riportata una schermata dell'applicazione console:

hdinsight.hbase.twitter.sentiment.streaming.service

Lasciare l'applicazione console di streaming in esecuzione durante lo sviluppo dell'applicazione Web, al fine di disporre di una maggiore quantità di dati. Per esaminare i dati inseriti nella tabella, è possibile usare la shell HBase. Vedere Introduzione a HBase in HDInsight.

Visualizzare i sentimenti in tempo reale

In questa sezione si creerà un'applicazione Web ASP.NET MVC per leggere i dati dei sentimenti in tempo reale su HBase e tracciare i dati sulle mappe Bing.

Per creare un'applicazione Web ASP.NET MVC

  1. Aprire Visual Studio.
  2. Fare clic su File, Nuovo e quindi su Progetto.
  3. Immettere le seguenti informazioni:

    • Categoria modello: Visual C#/Web
    • Modello: Applicazione Web ASP.NET
    • Nome: TweetSentimentWeb
    • Percorso: C:\Tutorials
  4. Fare clic su OK.
  5. In Seleziona un modello fare clic su MVC.
  6. In Microsoft Azure fare clic su Gestisci sottoscrizioni.
  7. In Gestisci sottoscrizioni di Microsoft Azure fare clic su Accedi.
  8. Immettere le credenziali di Azure. Le informazioni relative alla sottoscrizione di Azure vengono visualizzate nella scheda Account.
  9. Fare clic su Chiudi per chiudere la finestra Gestisci sottoscrizioni di Microsoft Azure.
  10. In Nuovo progetto ASP.NET - TweetSentimentWeb fare clic su OK.
  11. In Configura impostazioni sito Web di Microsoft Azure selezionare l'area geografica più vicina nel campo Area geografica. Non è necessario specificare un server database.
  12. Fare clic su OK.

Per installare i pacchetti NuGet

  1. Dal menu Strumenti fare clic su Gestione pacchetti NuGet, quindi su Console di Gestione pacchetti. Il pannello della console viene visualizzato nella parte inferiore della pagina.
  2. Usare il comando seguente per installare il pacchetto HBase .NET SDK , ovvero la libreria client che consente di accedere al cluster HBase:

     Install-Package Microsoft.HBase.Client
    

Per aggiungere una classe HBaseReader

  1. In Esplora soluzioni espandere TweetSentiment.
  2. Fare clic con il pulsante destro del mouse su Modelli, fare clic su Aggiungi, quindi su Classe.
  3. Nel campo Nome digitare HBaseReader.cs e fare clic su Aggiungi.
  4. Sostituire il codice con il codice seguente:

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Web;
    
     using System.Configuration;
     using System.Threading.Tasks;
     using System.Text;
     using Microsoft.HBase.Client;
     using org.apache.hadoop.hbase.rest.protobuf.generated;
    
     namespace TweetSentimentWeb.Models
     {
         public class HBaseReader
         {
             // For reading Tweet sentiment data from HDInsight HBase
             HBaseClient client;
    
             // HDinsight HBase cluster and HBase table information
             const string CLUSTERNAME = "<HBaseClusterName>";
             const string HADOOPUSERNAME = "<HBaseClusterHadoopUserName>"
             const string HADOOPUSERPASSWORD = "<HBaseCluserUserPassword>";
             const string HBASETABLENAME = "tweets_by_words";
    
             // The constructor
             public HBaseReader()
             {
                 ClusterCredentials creds = new ClusterCredentials(
                                 new Uri(CLUSTERNAME),
                                 HADOOPUSERNAME,
                                 HADOOPUSERPASSWORD);
                 client = new HBaseClient(creds);
             }
    
             // Query Tweets sentiment data from the HBase table asynchronously
             public async Task<IEnumerable<Tweet>> QueryTweetsByKeywordAsync(string keyword)
             {
                 List<Tweet> list = new List<Tweet>();
    
                 // Demonstrate Filtering the data from the past 6 hours the row key
                 string timeIndex = (ulong.MaxValue -
                     (ulong)DateTime.UtcNow.Subtract(new TimeSpan(6, 0, 0)).ToBinary()).ToString().PadLeft(20);
                 string startRow = keyword + "_" + timeIndex;
                 string endRow = keyword + "|";
                 Scanner scanSettings = new Scanner
                 {
                     batch = 100000,
                     startRow = Encoding.UTF8.GetBytes(startRow),
                     endRow = Encoding.UTF8.GetBytes(endRow)
                 };
    
                 // Make async scan call
                 ScannerInformation scannerInfo =
                     await client.CreateScannerAsync(HBASETABLENAME, scanSettings);
    
                 CellSet next;
    
                 while ((next = await client.ScannerGetNextAsync(scannerInfo)) != null)
                 {
                     foreach (CellSet.Row row in next.rows)
                     {
                         // find the cell with string pattern "d:coor"
                         var coordinates =
                             row.values.Find(c => Encoding.UTF8.GetString(c.column) == "d:coor");
    
                         if (coordinates != null)
                         {
                             string[] lonlat = Encoding.UTF8.GetString(coordinates.data).Split(',');
    
                             var sentimentField =
                                 row.values.Find(c => Encoding.UTF8.GetString(c.column) == "d:sentiment");
                             Int32 sentiment = 0;
                             if (sentimentField != null)
                             {
                                 sentiment = Convert.ToInt32(Encoding.UTF8.GetString(sentimentField.data));
                             }
    
                             list.Add(new Tweet
                             {
                                 Longtitude = Convert.ToDouble(lonlat[0]),
                                 Latitude = Convert.ToDouble(lonlat[1]),
                                 Sentiment = sentiment
                             });
                         }
    
                         if (coordinates != null)
                         {
                             string[] lonlat = Encoding.UTF8.GetString(coordinates.data).Split(',');
                         }
                     }
                 }
    
                 return list;
             }
         }
    
         public class Tweet
         {
             public string IdStr { get; set; }
             public string Text { get; set; }
             public string Lang { get; set; }
             public double Longtitude { get; set; }
             public double Latitude { get; set; }
             public int Sentiment { get; set; }
         }
     }
    
  5. Nella classe HBaseReader modificare i valori costanti, come indicato di seguito:

    • CLUSTERNAME: nome del cluster HBase, ad esempio https://.azurehdinsight.net/.
    • HADOOPUSERNAME: nome utente del cluster HBase (Hadoop). Il nome predefinito è admin.
    • HADOOPUSERPASSWORD: password utente del cluster HBase (Hadoop).
    • HBASETABLENAME = "tweets_by_words";

      Il nome della tabella HBase è "tweets_by_words". Per consentire all'applicazione Web di leggere i dati dalla stessa tabella HBase, i valori devono corrispondere a quelli inviati nel servizio streaming.

Per aggiungere il controller TweetsController

  1. In Esplora soluzioni espandere TweetSentimentWeb.
  2. Fare clic con il pulsante destro del mouse su Controller, quindi scegliere Aggiungi e infine fare clic su Controller.
  3. Fare clic su Web API 2 Controller - Empty e scegliere Aggiungi.
  4. Nel campo Nome controller digitare TweetsController, quindi fare clic su Aggiungi.
  5. In Esplora soluzionifare doppio clic su TweetsController.cs per aprire il file.
  6. Modificare il file come segue:

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Net;
     using System.Net.Http;
     using System.Web.Http;
    
     using System.Threading.Tasks;
     using TweetSentimentWeb.Models;
    
     namespace TweetSentimentWeb.Controllers
     {
         public class TweetsController : ApiController
         {
             HBaseReader hbase = new HBaseReader();
    
             public async Task<IEnumerable<Tweet>> GetTweetsByQuery(string query)
             {
                 return await hbase.QueryTweetsByKeywordAsync(query);
             }
         }
     }
    

Per aggiungere heatmap.js

  1. In Esplora soluzioni espandere TweetSentimentWeb.
  2. Fare clic con il pulsante destro del mouse su Script, quindi scegliere Aggiungi e infine fare clic su File JavaScript.
  3. Nel campo Nome elemento digitare heatmap.js.
  4. Incollare il seguente codice nel file. Il codice è stato scritto da Alastair Alastair Aitchison. Per altre informazioni, vedere la libreria HeatMap di Bing Maps AJAX v7.

     /*******************************************************************************
     * Author: Alastair Aitchison
     * Website: http://alastaira.wordpress.com
     * Date: 15th April 2011
     *
     * Description:
     * This JavaScript file provides an algorithm that can be used to add a heatmap
     * overlay on a Bing Maps v7 control. The intensity and temperature palette
     * of the heatmap are designed to be easily customisable.
     *
     * Requirements:
     * The heatmap layer itself is created dynamically on the client-side using
     * the HTML5 &lt;canvas> element, and therefore requires a browser that supports
     * this element. It has been tested on IE9, Firefox 3.6/4 and
     * Chrome 10 browsers. If you can confirm whether it works on other browsers or
     * not, I'd love to hear from you!
     *
     * Usage:
     * The HeatMapLayer constructor requires:
     * - A reference to a map object
     * - An array or Microsoft.Maps.Location items
     * - Optional parameters to customise the appearance of the layer
     *  (Radius,, Unit, Intensity, and ColourGradient), and a callback function
     */
    
     var HeatMapLayer = function (map, locations, options) {
    
         /* Private Properties */
         var _map = map,
             _canvas,
             _temperaturemap,
             _locations = [],
             _viewchangestarthandler,
             _viewchangeendhandler;
    
         // Set default options
         var _options = {
             // Opacity at the centre of each heat point
             intensity: 0.5,
    
             // Affected radius of each heat point
             radius: 1000,
    
             // Whether the radius is an absolute pixel value or meters
             unit: 'meters',
    
             // Colour temperature gradient of the map
             colourgradient: {
                 "0.00": 'rgba(255,0,255,20)',  // Magenta
                 "0.25": 'rgba(0,0,255,40)',    // Blue
                 "0.50": 'rgba(0,255,0,80)',    // Green
                 "0.75": 'rgba(255,255,0,120)', // Yellow
                 "1.00": 'rgba(255,0,0,150)'    // Red
             },
    
             // Callback function to be fired after heatmap layer has been redrawn
             callback: null
         };
    
         /* Private Methods */
         function _init() {
             var _mapDiv = _map.getRootElement();
    
             if (_mapDiv.childNodes.length >= 3 && _mapDiv.childNodes[2].childNodes.length >= 2) {
                 // Create the canvas element
                 _canvas = document.createElement('canvas');
                 _canvas.style.position = 'relative';
    
                 var container = document.createElement('div');
                 container.style.position = 'absolute';
                 container.style.left = '0px';
                 container.style.top = '0px';
                 container.appendChild(_canvas);
    
                 _mapDiv.childNodes[2].childNodes[1].appendChild(container);
    
                 // Override defaults with any options passed in the constructor
                 _setOptions(options);
    
                 // Load array of location data
                 _setPoints(locations);
    
                 // Create a colour gradient from the suppied colourstops
                 _temperaturemap = _createColourGradient(_options.colourgradient);
    
                 // Wire up the event handler to redraw heatmap canvas
                 _viewchangestarthandler = Microsoft.Maps.Events.addHandler(_map, 'viewchangestart', _clearHeatMap);
                 _viewchangeendhandler = Microsoft.Maps.Events.addHandler(_map, 'viewchangeend', _createHeatMap);
    
                 _createHeatMap();
    
                 delete _init;
             } else {
                 setTimeout(_init, 100);
             }
         }
    
         // Resets the heat map
         function _clearHeatMap() {
             var ctx = _canvas.getContext("2d");
             ctx.clearRect(0, 0, _canvas.width, _canvas.height);
         }
    
         // Creates a colour gradient from supplied colour stops on initialisation
         function _createColourGradient(colourstops) {
             var ctx = document.createElement('canvas').getContext('2d');
             var grd = ctx.createLinearGradient(0, 0, 256, 0);
             for (var c in colourstops) {
                 grd.addColorStop(c, colourstops[c]);
             }
             ctx.fillStyle = grd;
             ctx.fillRect(0, 0, 256, 1);
             return ctx.getImageData(0, 0, 256, 1).data;
         }
    
         // Applies a colour gradient to the intensity map
         function _colouriseHeatMap() {
             var ctx = _canvas.getContext("2d");
             var dat = ctx.getImageData(0, 0, _canvas.width, _canvas.height);
             var pix = dat.data; // pix is a CanvasPixelArray containing height x width x 4 bytes of data (RGBA)
             for (var p = 0, len = pix.length; p < len;) {
                 var a = pix[p + 3] * 4; // get the alpha of this pixel
                 if (a != 0) { // If there is any data to plot
                     pix[p] = _temperaturemap[a]; // set the red value of the gradient that corresponds to this alpha
                     pix[p + 1] = _temperaturemap[a + 1]; //set the green value based on alpha
                     pix[p + 2] = _temperaturemap[a + 2]; //set the blue value based on alpha
                 }
                 p += 4; // Move on to the next pixel
             }
             ctx.putImageData(dat, 0, 0);
         }
    
         // Sets any options passed in
         function _setOptions(options) {
             for (attrname in options) {
                 _options[attrname] = options[attrname];
             }
         }
    
         // Sets the heatmap points from an array of Microsoft.Maps.Locations  
         function _setPoints(locations) {
             _locations = locations;
         }
    
         // Main method to draw the heatmap
         function _createHeatMap() {
             // Ensure the canvas matches the current dimensions of the map
             // This also has the effect of resetting the canvas
             _canvas.height = _map.getHeight();
             _canvas.width = _map.getWidth();
    
             _canvas.style.top = -_canvas.height / 2 + 'px';
             _canvas.style.left = -_canvas.width / 2 + 'px';
    
             // Calculate the pixel radius of each heatpoint at the current map zoom
             if (_options.unit == "pixels") {
                 radiusInPixel = _options.radius;
             } else {
                 radiusInPixel = _options.radius / _map.getMetersPerPixel();
             }
    
             var ctx = _canvas.getContext("2d");
    
             // Convert lat/long to pixel location
             var pixlocs = _map.tryLocationToPixel(_locations, Microsoft.Maps.PixelReference.control);
             var shadow = 'rgba(0, 0, 0, ' + _options.intensity + ')';
             var mapWidth = 256 * Math.pow(2, _map.getZoom());
    
             // Create the Intensity Map by looping through each location
             for (var i = 0, len = pixlocs.length; i < len; i++) {
                 var x = pixlocs[i].x;
                 var y = pixlocs[i].y;
    
                 if (x < 0) {
                     x += mapWidth * Math.ceil(Math.abs(x / mapWidth));
                 }
    
                 // Create radial gradient centred on this point
                 var grd = ctx.createRadialGradient(x, y, 0, x, y, radiusInPixel);
                 grd.addColorStop(0.0, shadow);
                 grd.addColorStop(1.0, 'transparent');
    
                 // Draw the heatpoint onto the canvas
                 ctx.fillStyle = grd;
                 ctx.fillRect(x - radiusInPixel, y - radiusInPixel, 2 * radiusInPixel, 2 * radiusInPixel);
             }
    
             // Apply the specified colour gradient to the intensity map
             _colouriseHeatMap();
    
             // Call the callback function, if specified
             if (_options.callback) {
                 _options.callback();
             }
         }
    
         /* Public Methods */
    
         this.Show = function () {
             if (_canvas) {
                 _canvas.style.display = '';
             }
         };
    
         this.Hide = function () {
             if (_canvas) {
                 _canvas.style.display = 'none';
             }
         };
    
         // Sets options for intensity, radius, colourgradient etc.
         this.SetOptions = function (options) {
             _setOptions(options);
         }
    
         // Sets an array of Microsoft.Maps.Locations from which the heatmap is created
         this.SetPoints = function (locations) {
             // Reset the existing heatmap layer
             _clearHeatMap();
             // Pass in the new set of locations
             _setPoints(locations);
             // Recreate the layer
             _createHeatMap();
         }
    
         // Removes the heatmap layer from the DOM
         this.Remove = function () {
             _canvas.parentNode.parentNode.removeChild(_canvas.parentNode);
    
             if (_viewchangestarthandler) { Microsoft.Maps.Events.removeHandler(_viewchangestarthandler); }
             if (_viewchangeendhandler) { Microsoft.Maps.Events.removeHandler(_viewchangeendhandler); }
    
             _locations = null;
             _temperaturemap = null;
             _canvas = null;
             _options = null;
             _viewchangestarthandler = null;
             _viewchangeendhandler = null;
         }
    
         // Call the initialisation routine
         _init();
     };
    
     // Call the Module Loaded method
     Microsoft.Maps.moduleLoaded('HeatMapModule');
    

Per aggiungere twitterStream.js

  1. In Esplora soluzioni espandere TweetSentimentWeb.
  2. Fare clic con il pulsante destro del mouse su Script, quindi scegliere Aggiungi e infine fare clic su File JavaScript.
  3. Nel campo Nome elemento digitare twitterStream.js.
  4. Copiare e incollare il seguente codice nel file:

     var liveTweetsPos = [];
     var liveTweets = [];
     var liveTweetsNeg = [];
     var map;
     var heatmap;
     var heatmapNeg;
     var heatmapPos;
    
     function initialize() {
         // Initialize the map
         var options = {
             credentials: "AvFJTZPZv8l3gF8VC3Y7BPBd0r7LKo8dqKG02EAlqg9WAi0M7la6zSIT-HwkMQbx",
             center: new Microsoft.Maps.Location(23.0, 8.0),
             mapTypeId: Microsoft.Maps.MapTypeId.ordnanceSurvey,
             labelOverlay: Microsoft.Maps.LabelOverlay.hidden,
             zoom: 2.5
         };
         var map = new Microsoft.Maps.Map(document.getElementById('map_canvas'), options);
    
         // Heatmap options for positive, neutral and negative layers
    
         var heatmapOptions = {
             // Opacity at the centre of each heat point
             intensity: 0.5,
    
             // Affected radius of each heat point
             radius: 15,
    
             // Whether the radius is an absolute pixel value or meters
             unit: 'pixels'
         };
    
         var heatmapPosOptions = {
             // Opacity at the centre of each heat point
             intensity: 0.5,
    
             // Affected radius of each heat point
             radius: 15,
    
             // Whether the radius is an absolute pixel value or meters
             unit: 'pixels',
    
             colourgradient: {
                 0.0: 'rgba(0, 255, 255, 0)',
                 0.1: 'rgba(0, 255, 255, 1)',
                 0.2: 'rgba(0, 255, 191, 1)',
                 0.3: 'rgba(0, 255, 127, 1)',
                 0.4: 'rgba(0, 255, 63, 1)',
                 0.5: 'rgba(0, 127, 0, 1)',
                 0.7: 'rgba(0, 159, 0, 1)',
                 0.8: 'rgba(0, 191, 0, 1)',
                 0.9: 'rgba(0, 223, 0, 1)',
                 1.0: 'rgba(0, 255, 0, 1)'
             }
         };
    
         var heatmapNegOptions = {
             // Opacity at the centre of each heat point
             intensity: 0.5,
    
             // Affected radius of each heat point
             radius: 15,
    
             // Whether the radius is an absolute pixel value or meters
             unit: 'pixels',
    
             colourgradient: {
                 0.0: 'rgba(0, 255, 255, 0)',
                 0.1: 'rgba(0, 255, 255, 1)',
                 0.2: 'rgba(0, 191, 255, 1)',
                 0.3: 'rgba(0, 127, 255, 1)',
                 0.4: 'rgba(0, 63, 255, 1)',
                 0.5: 'rgba(0, 0, 127, 1)',
                 0.7: 'rgba(0, 0, 159, 1)',
                 0.8: 'rgba(0, 0, 191, 1)',
                 0.9: 'rgba(0, 0, 223, 1)',
                 1.0: 'rgba(0, 0, 255, 1)'
             }
         };
    
         // Register and load the Client Side HeatMap Module
         Microsoft.Maps.registerModule("HeatMapModule", "scripts/heatmap.js");
         Microsoft.Maps.loadModule("HeatMapModule", {
             callback: function () {
                 // Create heatmap layers for positive, neutral and negative tweets
                 heatmapPos = new HeatMapLayer(map, liveTweetsPos, heatmapPosOptions);
                 heatmap = new HeatMapLayer(map, liveTweets, heatmapOptions);
                 heatmapNeg = new HeatMapLayer(map, liveTweetsNeg, heatmapNegOptions);
             }
         });
    
         $("#searchbox").val("xbox");
         $("#searchBtn").click(onsearch);
         $("#positiveBtn").click(onPositiveBtn);
         $("#negativeBtn").click(onNegativeBtn);
         $("#neutralBtn").click(onNeutralBtn);
         $("#neutralBtn").button("toggle");
     }
    
     function onsearch() {
         var uri = 'api/tweets?query=';
         var query = $('#searchbox').val();
         $.getJSON(uri + query)
             .done(function (data) {
                 liveTweetsPos = [];
                 liveTweets = [];
                 liveTweetsNeg = [];
    
                 // On success, 'data' contains a list of tweets.
                 $.each(data, function (key, item) {
                     addTweet(item);
                 });
    
                 if (!$("#neutralBtn").hasClass('active')) {
                     $("#neutralBtn").button("toggle");
                 }
                 onNeutralBtn();
             })
             .fail(function (jqXHR, textStatus, err) {
                 $('#statustext').text('Error: ' + err);
             });
     }
    
     function addTweet(item) {
         //Add tweet to the heat map arrays.
         var tweetLocation = new Microsoft.Maps.Location(item.Latitude, item.Longtitude);
         if (item.Sentiment > 0) {
             liveTweetsPos.push(tweetLocation);
         } else if (item.Sentiment < 0) {
             liveTweetsNeg.push(tweetLocation);
         } else {
             liveTweets.push(tweetLocation);
         }
     }
    
     function onPositiveBtn() {
         if ($("#neutralBtn").hasClass('active')) {
             $("#neutralBtn").button("toggle");
         }
         if ($("#negativeBtn").hasClass('active')) {
             $("#negativeBtn").button("toggle");
         }
    
         heatmapPos.SetPoints(liveTweetsPos);
         heatmapPos.Show();
         heatmapNeg.Hide();
         heatmap.Hide();
    
         $('#statustext').text('Tweets: ' + liveTweetsPos.length + "   " + getPosNegRatio());
     }
    
     function onNeutralBtn() {
         if ($("#positiveBtn").hasClass('active')) {
             $("#positiveBtn").button("toggle");
         }
         if ($("#negativeBtn").hasClass('active')) {
             $("#negativeBtn").button("toggle");
         }
    
         heatmap.SetPoints(liveTweets);
         heatmap.Show();
         heatmapNeg.Hide();
         heatmapPos.Hide();
    
         $('#statustext').text('Tweets: ' + liveTweets.length + "   " + getPosNegRatio());
     }
    
     function onNegativeBtn() {
         if ($("#positiveBtn").hasClass('active')) {
             $("#positiveBtn").button("toggle");
         }
         if ($("#neutralBtn").hasClass('active')) {
             $("#neutralBtn").button("toggle");
         }
    
         heatmapNeg.SetPoints(liveTweetsNeg);
         heatmapNeg.Show();
         heatmap.Hide();;
         heatmapPos.Hide();;
    
         $('#statustext').text('Tweets: ' + liveTweetsNeg.length + "\t" + getPosNegRatio());
     }
    
     function getPosNegRatio() {
         if (liveTweetsNeg.length == 0) {
             return "";
         }
         else {
             var ratio = liveTweetsPos.length / liveTweetsNeg.length;
             var str = parseFloat(Math.round(ratio * 10) / 10).toFixed(1);
             return "Positive/Negative Ratio: " + str;
         }
     }
    

Per modificare layout.cshtml

  1. In Esplora soluzioni, espandere TweetSentimentWeb, espandere Viste, espandere Condivise e fare doppio clic su _Layout.cshtml.
  2. Sostituire il contenuto con i contenuti seguenti:

     <!DOCTYPE html>
     <html>
     <head>
         <meta charset="utf-8" />
         <meta name="viewport" content="width=device-width, initial-scale=1.0">
         <title>@ViewBag.Title</title>
         @Styles.Render("~/Content/css")
         @Scripts.Render("~/bundles/modernizr")
         <!-- Bing Maps -->
         <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-gb"></script>
         <!-- Spatial Dashboard JavaScript -->
         <script src="~/Scripts/twitterStream.js" type="text/javascript"></script>
     </head>
     <body onload="initialize()">
         <div class="navbar navbar-inverse navbar-fixed-top">
             <div class="container">
                 <div class="navbar-header">
                     <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                         <span class="icon-bar"></span>
                         <span class="icon-bar"></span>
                         <span class="icon-bar"></span>
                     </button>
                 </div>
                 <div class="navbar-collapse collapse">
                     <div class="row">
                         <ul class="nav navbar-nav col-lg-5">
                             <li class="col-lg-12">
                                 <div class="navbar-form">
                                     <input id="searchbox" type="search" class="form-control">
                                     <button type="button" id="searchBtn" class="btn btn-primary">Go</button>
                                 </div>
                             </li>
                         </ul>
                         <ul class="nav navbar-nav col-lg-7">
                             <li>
                                 <div class="navbar-form">
                                     <div class="btn-group" data-toggle="buttons-radio">
                                         <button type="button" id="positiveBtn" class="btn btn-primary">Positive</button>
                                         <button type="button" id="neutralBtn" class="btn btn-primary">Neutral</button>
                                         <button type="button" id="negativeBtn" class="btn btn-primary">Negative</button>
                                     </div>
                                 </div>
                             </li>
                             <li><span id="statustext" class="navbar-text"></span></li>
                         </ul>
                     </div>
                 </div>
             </div>
         </div>
         <div class="map_container">
             @RenderBody()
         </div>
         @Scripts.Render("~/bundles/jquery")
         @Scripts.Render("~/bundles/bootstrap")
         @RenderSection("scripts", required: false)
     </body>
     </html>
    

Per modificare Index.cshtml

  1. In Esplora soluzioni, espandere TweetSentimentWeb, espandere Viste, espandere Home e fare doppio clic su _Index.cshtml.
  2. Sostituire il contenuto con i contenuti seguenti:

     @{
         ViewBag.Title = "Tweet Sentiment";
     }
    
     <div class="map_container">
         <div id="map_canvas"/>
     </div>
    

Per modificare il file site.css

  1. In Esplora soluzioni, espandere TweetSentimentWeb, espandere Contenuto e fare doppio clic su _Site.css.
  2. Aggiungere il seguente codice al file:

     /* make container, and thus map, 100% width */
     .map_container {
         width: 100%;
         height: 100%;
     }
    
     #map_canvas{
       height:100%;
     }
    
     #tweets{
       position: absolute;
       top: 60px;
       left: 75px;
       z-index:1000;
       font-size: 30px;
     }
    

Per modificare il file global.asax

  1. In Esplora soluzioni, espandere TweetSentimentWeb e fare doppio clic su _Global.asax.
  2. Aggiungere l'istruzione using seguente:

     using System.Web.Http;
    
  3. Aggiungere le seguenti righe all'interno della funzione Application_Start():

     // Register API routes
     GlobalConfiguration.Configure(WebApiConfig.Register);
    

    Modificare la registrazione delle route API per consentire il funzionamento del controller API Web all'interno dell'applicazione MVC.

Per eseguire l'applicazione Web

  1. Accertarsi che l'applicazione console di streaming sia ancora in esecuzione, in modo tale da visualizzare le modifiche in tempo reale.
  2. Premere F5 per eseguire l'applicazione Web:

    hdinsight.hbase.twitter.sentiment.bing.map

  3. Immettere una parola chiave nella casella di testo, quindi fare clic su Go. A seconda dei dati prelevati dalla tabella HBase, alcune parole chiave potrebbero non essere rilevate. Provare con parole chiavi comuni come "amore", xbox" e "playstation".
  4. Selezionare Positive (Positivo), Neutral (Neutro) e Negative (Negativo) per confrontare i sentimenti sull'argomento.
  5. Lasciare il servizio streaming in esecuzione per un'altra ora, quindi cercare le stesse parole chiave e confrontare i risultati.

In alternativa, è possibile distribuire l'applicazione in Siti Web di Azure. Per le istruzioni vedere l'introduzione ai siti Web di Azure e ASP.NET.

Passaggi successivi

In questa esercitazione si è appreso come ricevere tweet, analizzare i sentimenti dei tweet, salvare i dati sui sentimenti su HBase e presentare i dati sui sentimenti di Twitter in tempo reale sulle mappe Bing. Per altre informazioni, vedere: