Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Creare una stazione metereologica

Come connettersi a un web service per recuperare le informazioni metereologiche di una determinata città e inviarle ad alcuni oggetti "osservatori" per una visualizzazione grafica dei dati
Come connettersi a un web service per recuperare le informazioni metereologiche di una determinata città e inviarle ad alcuni oggetti "osservatori" per una visualizzazione grafica dei dati
Link copiato negli appunti

Su Internet sono presenti una gran quantità di web service dedicati alle informazioni metereologiche; in questo articolo si prenderà in considerazione il service GlobalWeather, servizio in grado di fornire le principali informazioni metereologiche sulle più importanti città del mondo.

Le informazioni ottenute verranno visualizzate tramite diverse rappresentazioni grafiche: una semplice scritta nella finestra di output, una visualizzazione testuale riassuntiva e una immagine rappresentativa del tempo.

Per collegare i dati in arrivo con le diverse modalità di visualizzazione verrà utilizzato l'Observer Pattern, un modello di programmazione particolarmente adatto a tale situazione che permette di creare un progetto modulare.

In questo esempio verranno utilizzati i dati relativi alla città di Milano, sentitevi comunque liberi di aggiungere alcuni componenti per recuperare le informazioni in maniera più interattiva.

Il Web Service GlobalWeather

Per prima cosa è necessario conoscere come funziona il web service GlobalWeather: il modo più semplice per farlo è utilizzare lo strumento Servizi Web messo a disposizione da Flash 8 e raggiungibile tramite il menu Finestra>Altri pannelli>Servizi Web. Premiamo il pulsante Definisci servizi web, rappresentato da un mappamondo, poi il pulsante "+" e inseriamo la stringa dell'indirizzo WSDL del web service: nel nostro caso http://www.webservicex.com/globalweather.asmx?WSDL. A questo punto possiamo osservare come utilizzando il metodo getWeather() assieme a una stringa contenente il nome di una città, sia possibile ottenere come risposta una stringa contenente i dati richiesti sotto forma di documento XML.

Figura 1. Funzionamento del Web Service
Funzionamento del Web Service

La classe Web Service

È necessario importare WebServiceClasses nella libreria; lo troviamo nel pannello Classi raggiungibile tramite Finestra>Librerie Comuni>Classi. Apriamo il pannello Azioni ed effettuiamo l'import delle prime librerie necessarie all'operazione inserendo:

import mx.services.WebService;
import mx.services.PendingCall;

La prima riga importa la classe WebService necessaria per la connessione a GlobalWeather, mentre PendingCall rappresenta una istanza di mx.services e gestisce i risultati della connessione.

Listato 1. Crea una variabile con l'indirizzo della WSDL Location

var wsUrl:String = "http://www.webservicex.com/globalweather.asmx?WSDL";
var webService:WebService = new WebService(wsUrl);
var categorie_pc:PendingCall;

Con il codice sopra viene creata una variabile contenente l'indirizzo relativo alla WSDL Location e un'istanza della classe WebService associata all'indirizzo WSDL. Viene inoltre definita la variabile categorie_pc come oggetto della classe PendingCall.

Listato 2. Richiede e gestisce l'arrivo delle informazioni

categorie_pc = webService.GetWeather("milano");
  categorie_pc.onResult = function(risultato:String):Void {
    xml = new XML();
    // devo eliminare le linee vuote che mi ritornano dal web service
    xml.ignoreWhite = true;
    xml.parseXML(risultato);
    trace(xml)
}

Utilizzando il metodo GetWeather() sull'oggetto webService si creerà un'istanza di PendingCall che gestirà l'arrivo dei risultati tramite i callback onResult() oppure onFault() in caso di problemi con la connessione. Se i dati arrivano correttamente, verrà invocato onResult() e le informazioni in arrivo verranno catturate nella variabile risultato. Viene poi creato un oggetto XML in grado di eliminare le linee di testo vuote contenute nel testo XML, il quale verrà successivamente esaminato tramite parseXML(). Se osserviamo la finestra di output, vediamo le informazioni arrivate dal web service.

Listato 3. Risultati restituiti dal Web Service

<?xml version="1.0" encoding="utf-16"?>
<CurrentWeather>
    <Location>Milano / Linate, Italy (LIML) 45-26N 009-17E 103M</Location>
    <Time>Jun 16, 2007 - 05:50 AM EDT / 2007.06.16 0950 UTC</Time>
    <Wind> Variable at 3 MPH (3 KT):0</Wind>
    <Visibility> greater than 7 mile(s):0</Visibility>
    <SkyConditions> partly cloudy</SkyConditions>
    <Temperature> 77 F (25 C)</Temperature>
    <DewPoint> 62 F (17 C)</DewPoint>
    <RelativeHumidity> 61%</RelativeHumidity>
    <Pressure> 29.80 in. Hg (1009 hPa)</Pressure>
    <Status>Success</Status>
</CurrentWeather>

Nel nostro caso verranno presi in considerazione i nodi "Time", "Location", "SkyConditions", "Temperature" e "RelativeHumidity". Utilizzeremo ora la classe XPathAPI, presente da Flash MX 2004 in poi e capace di trovare rapidamente nodi e attributi in un documento XML.

La sintassi per eseguire il metodo selectSingleNode() presente nella classe XPathAPI è la seguente:

XPathAPI.selectSingleNode(node, path)

Node è il parent node dal quale inizierà la ricerca e path è la stringa che identifica il nodo da ricercare.

La classe richiede che importiamo DataBindingClasses dal pannello Classi alla libreria. Inseriamo all'inizio del listato l'import della classe XPathAPI e definiamo l'oggetto datiTempo dove verranno memorizzate le informazioni relative al tempo, oltre a un array chiamato nodi dove vanno inseriti i nodi da recuperare dal documento XML.

Listato 4. Imposta gli array e le variabili

import mx.xpath.XPathAPI;
var datiTempo:Object = new Object();
var nodi:Array = ["Time","Location","SkyConditions", "Temperature", "RelativeHumidity"];

Ora bisogna inserire il codice relativo alla ricerca dei nodi subito dopo l'esame del documento XML, al posto del comando trace().

Listato 5. Ricerca dei nodi

nodoRoot = xml.firstChild;
for (var i = 0; i<nodi.length; i++) {
var nodo:Object = XPathAPI.selectSingleNode(nodoRoot, "CurrentWeather/"+nodi[i]);  
var nodeValue = nodo.firstChild.nodeValue;
  datiTempo[nodi[i]] = nodeValue.substring(1, nodeValue.length);
  if (nodi[i] == "SkyConditions") {
    datiTempo[nodi[i]] = nodeValue.substring(1, nodeValue.length);
  } else if (nodi[i] == "Temperature") {
    var indice:Number = datiTempo["Temperature"].indexOf("(");
    datiTempo["Temperature"] = datiTempo["Temperature"].substr(indice+1, 3);
  }
  var indice:Number = datiTempo ["Temperature"].indexOf("(");
  datiTempo ["Temperature"] = datiTempo ["Temperature"].substr(indice+1, 3);
}

Come prima cosa viene definito il nodo dal quale inizierà la ricerca, in questo caso in corrispondenza della voce "CurrentWeather". Il metodo statico selectSingleNode della classe XPathAPI ritorna il nodo corrispondente alla ricerca effettuata, il quale verrà in questo caso memorizzato nell'oggetto temporaneo nodo.

Poiché la temperatura viene inviata sia in gradi Celsius che Fahrenheit (ed esempio "77 F (25 C)"), sarà necessario estrarre tramite il metodo substr() solamente i caratteri relativi ai gradi (25).

A questo punto l'oggetto datiTempo conterrà quattro variabili: "Time", "Location","SkyConditions", "Temperature" e "RelativeHumidity", ognuna con il suo relativo valore recuperato dal web service.

L'Observer Pattern

L'Observer Pattern è un pattern di programmazione molto utilizzato nel caso sia necessario connettere alcuni Observer (osservatori) a un determinato Subject (soggetto) per ricevere dei dati. Nel nostro caso, il subject racchiude in sé i dati relativi al tempo, ricevuti dal web service, mentre gli observer che devono ricevere tali dati sono rappresentati da oggetti creati a partire dalla classe Output, testo e grafico, ognuna delle quali si incaricherà delle diverse rappresentazioni grafiche delle informazioni relative la tempo.

In pratica, il subject deve conoscere quali observer devono ricevere le informazioni e quest'ultimi devono possedere un metodo comune che permette di ricevere le informazioni dal subject quando queste cambiano (o vengono notificate). Bisogna assegnare quindi al subject dei metodi che permettono agli observer di registrarsi ad esso e agli observer un metodo update() che il subject possa chiamare quando deve inviare le informazioni.

Figura 2. Schema di funzionamento
Schema di funzionamento

La superclass Observer

Una buona tecnica consiste nel creare una superclass chiamata Observable contenente tutti i metodi necessari per la registrazione degli observer, classe dalla quale erediterà il nostro subject. Creiamo quindi un file ActionScript, nella cartella contenente il progetto, e chiamiamolo Observable.

Listato 6. I metodi necessari per la registrazione degli observer (Guarda il codice completo)

class Observable {
  private var observers:Array;
  public function Observable() {
    observers = new Array();
  }
  public function aggiungiObserver(observer):Boolean {
    if (observer == null) {
      return false;
    }
    for (var i:Number = 0; i<observers.lenght; i++) {
      if (observers[i] == observer) {
        return false;
      }
    }
    observers.push(observer);
  }
  public function rimuoviObserver(observer):Boolean {
    var len:Number = observers.length;
    for (var i:Number = 0; i<len; i++) {
      if (observers[i] == observer) {
        observers.splice(i, 1);
        return true;
      }
    }
    return false;
  }
  public function notificaObservers(infoObj:Object):Void {
    for (var i:Number = observers.length-1; i>=0; i--) {
      observers [i].update(this, infoObj);
    }
  }
}

Con il codice sopra viene creato un array observers atto a contenere la lista di tutti gli observer che si registreranno al subject (ricordo che eredita da questa superclass) utilizzando i metodi: aggiungiObserver(nome observer) ed eventualmente rimuoviObserver(nome observer). Il metodo notificaObservers(infoObj) viene utilizzato per invocare il metodo update() presente negli observer inviando così i dati relativi al tempo.

La classe Subject

Listato 7. Aggiorna e distribuisce i dati in arrivo

class Subject extends Observable {
  // referenza privata alla istanza
  private static var subject:Subject = null;
  function Subject() {
  }
  public function setTempo(tempo:Object) {
    var messaggioRicevuto:Messaggio = new Messaggio(tempo);
    // invoca update( ) su tutti gli observer registrati
    notificaObservers(messaggioRicevuto);
  }
}

Una volta arrivati i dati dal web service, bisognerà invocare il metodo setTempo() nel subject, il quale creerà un oggetto della classe Messaggio contenente i dati relativi al tempo e lo invierà a tutti gli osservatori tramite il metodo notificaObservers(messaggio).

La classe Messaggio

Listato 8. Crea un messaggio che contiene i dati

// memorizza il messaggio all'interno della istanza
// @param msg  contenuto del messaggio
class Messaggio {
  private var msg:Object;
  public function Messaggio(messaggio:Object) {
    msg = messaggio;
  }
  public function getMessaggio(prop:String):String {
    return msg[prop];
  }
}

Un oggetto creato tramite la classe Messaggio presenta quindi una variabile dal nome msg contenente i dati del tempo inviati dal subject e il metodo getMessaggio() che permetterà di recuperare la proprietà desiderata relativa al tempo dall'oggetto msg.

Gli observer

È venuto il momento di creare le classi relative agli observer. Dal momento che questa tipologia di oggetti deve obbligatoriamente possedere un metodo chiamato update(), al quale verrà passato il nome dell'observable e l'oggetto contente i dati del tempo, è buona pratica creare un'interfaccia in grado di assicurare coerenza con tali dettami. Salviamo quindi un file ActionScript chiamato iObserver, nella cartella del progetto.

Listato 9. Interfaccia iObserver

// @param observerObj una istanza di Observable
// @param infoObj un oggetto contenente i dati mandato dall'istanza di Observable
interface iObserver {
  function update(observerObj:Observable, infoObj:Object):Void;
}

Tulle le classi relative agli observer dovranno quindi implementare l'interfaccia iObserver

L'observer Output

Creiamo il primo observer con l'incarico di mostrare i dati nella finestra di output.

Listato 10. Mostra i dati nella finestra di output

class Output implements iObserver {
  // riferimento all'istanza di Subject
  private var subject:Subject;
  public function Output(s:Subject) {
    subject = s;
  }
  public function update(observerObj:Observable, infoObj:Object):Void {
    // casting nella classe Messaggio
    var msg:Messaggio = Messaggio(infoObj);
    trace(msg.getMessaggio("Time"));
    trace(msg.getMessaggio("Location"));
    trace("Il tempo è "+msg.getMessaggio("SkyConditions"));
    trace("La temperatura è di "+msg.getMessaggio("Temperature")+"gradi");
    trace("L'umidità è del"+msg.getMessaggio("RelativeHumidity"));
  }
}

Alla creazione dell'istanza di Output viene passato il nome del subject; il metodo update(), una volta invocato dal subject, riceve l'oggetto contenente i dati del tempo (infoObj). Da notare come sia necessario effettuare il casting di infoObj per poter utilizzare il metodo getMessaggio() in quanto, nonostante sia stato originato dalla classe Messaggio, viene passato all'observer come appartenente alla classe Object.

L'observer Testo

La classe Testo è molto simile a quella precedente e serve per visualizzare sullo stage una casella di testo contenente i dati relativi al tempo.

Listato 11. Output in formato testo

class Testo implements iObserver {
  private var subject:Subject;
  private var tf:TextField;
  function Testo(s:Subject, target:MovieClip, x:Number, y:Number, w:Number, h:Number) {
    subject = s;
    creaTextField(target, x, y, w, h);
  }
  function update(observerObj:Observable, infoObj:Object):Void {
    var msg = Messaggio(infoObj);
    var tempo = msg.getMessaggio("Time")+"n"+msg.getMessaggio("Location")+"n"+"Il tempo oggi si preannuncia "
      +msg.getMessaggio("SkyConditions")+"n"+" con una temperatura di "+msg.getMessaggio("Temperature")+
      " gradi "+"n"+" e una umidità del "+msg.getMessaggio("RelativeHumidity");
    tf.text = tempo;
  }
  public function creaTextField(target, x, y, w, h) {
    target.createTextField("testo", target.getNextHighestDepth(), x, y, w, h);
    tf = target.testo;
    tf.border = true;
    tf.autoSize = true;
  }
}

L'observer Grafico

La classe Grafico si prende incarico di visualizzare sullo stage una immagine di riferimento per la condizione climatica recuperata dal web service.

Listato 12. Output in formato grafico

class Grafico implements iObserver {
  private var subject:Subject;
  var tempo_mc:MovieClip;
  function Grafico(s:Subject) {
    tempo_mc = _root.attachMovie("tempo", "tempo", 1, {_x:Stage.width/2, _y:Stage.height/3});
    tempo_mc._visible = false;
    subject = s;
  }
  public function update(observerObj:Observable, infoObj:Object):Void {
    var msg:Messaggio = Messaggio(infoObj);
    var tempo = msg.getMessaggio("SkyConditions");
    tempo_mc._visible = true;
    tempo_mc.gotoAndStop(tempo);
  }
}

La classe presume la presenza nella libreria di una Clip Filmato con l'identificatore di concatenamento dal nome "tempo" e creata in modo che ogni fotogramma contenga il disegno delle diverse condizioni climatiche e abbia come nome il termine associato alle diverse situazioni climatiche.

Figura 3. Stage con i vari fotogrammi grafici
Stage con i vari fotogrammi grafici

L'inizializzazione delle classi

Infine diamo vita all'interazione delle classi che abbiamo appena visto. Inseriamo le linee seguenti all'inizio del codice presente sulla timeline principale; queste si incaricheranno di creare gli oggetti relativi agli observer e al subject, e di registrare i primi presso il secondo grazie al metodo aggiungiObserver().

Listato 13. Creano gli oggetti relativi agli observer e al subject

// istanza di Subject
var subject:Subject = new Subject();
// istanze degli observer
var observerOutput:Output = new Output(subject);
var observerGrafico:Grafico = new Grafico(subject);
var observerTesto:Testo = new Testo(subject, this, 150, 300, 0, 0);
// registrazione observer presso il subject
subject.aggiungiObserver(observerOutput);
subject.aggiungiObserver(observerGrafico);
subject.aggiungiObserver(observerTesto);

Giunti a questo punto, per inviare i dati del tempo al subject una volta ricevuti dal web service, sarà sufficiente utilizzare il metodo setTempo() messo a disposizione del subject, passando come parametro la variabile datiTempo.

Listato 14. Invia dati al subject

..//
datiTempo["Temperature"] = datiTempo["Temperature"].substr(indice+1, 4);
subject.setTempo(datiTempo);

Figura 4. Schema di funzinamento
Schema di funzionamento

Ti consigliamo anche