Java House
Gambino - Corsi
Italiano     English

Lettura delle condizioni meteo con il Java



Ottobre 2009, by Mario
Yahoo! Weather

Nel numero di Agosto di LinuxPro ho letto un articolo in cui si fa uso del Python per elaborare le informazioni metereologiche messe a disposizione dal servizio di Yahoo! Weather mediante l'interfaccia dei feed RSS, come definito nelle specifiche delle API.

Mi ha colpito la semplicità con cui si possano recuperare le informazioni usando il Python e la libreria scelta nell'articolo, anche se ho alcune riserve sull'appropriato uso dei feed per questo caso specifico. Ma andiamo con ordine.



Ho provato a fare la stessa cosa con il Java e, riguardo i feed, mi sono orientato sul progetto Rome perché è open source ed è supportato dalla Sun. Nel momento in cui scrivo, l'ultima versione di Rome è la 1.0 ma fate attenzione che necessita di un'altra libreria non inclusa, la jDom, che dovete scaricare dal relativo sito.

I passi preliminari sono:

  • Scaricate da https://rome.dev.java.net il file rome-1.0.jar
  • Scaricate da http://www.jdom.org il file jdom-1.1.1.zip
  • Estraete dall'archivio jdom-1.1.1.zip la libreria jdom.jar
  • Copiate rome-1.0.jar e jdom.jar nella cartella lib/ext del Java Runtime Enviroment


Ad ogni modo, il cuore del codice in Python che legge il feed è il seguente: import feedparser url = "http://weather.yahooapis.com/forecastrss?p=ITXX0090&u=c" data = feedparser.parser(url) summary = data.entries[0].summary

Leggendo le specifiche di Yahoo! Weather, ho capito che la libreria del Python recupera le informazioni dal nodo standard dei feed <description> (contenuto nel nodo standard <item>) il quale, secondo l'uso che ne fa Yahoo!, contiene una porzione di html già pronta e formattata per essere visualizzata. L'articolo esegue delle elaborazioni su tale testo per estrapolare le informazioni volute. Anticipo che secondo me non è il modo corretto di procedere ma per il momento vediamo di implementare la stessa funzionalità in Java.

Il cuore delle operazioni in Java è: reader = new InputStreamReader( new URL("http://weather.yahooapis.com/forecastrss?p=ITXX0090&u=c") .openConnection().getInputStream()); WireFeedInput inputFeed = new WireFeedInput(); feed = (Channel) inputFeed.build(reader); List<Item> item = feed.getItems(); String summary = item.get(0).getDescription());

Per analogia con il codice in Python, ho omesso il controllo sull'effettiva presenza di oggetti Item nel feed ma ciò ci garantito dalle specifiche di Yahoo!. Il codice sopra non è molto più complesso rispetto al Python, ma il Java non è uno script ed è più prolisso, le variabili vanno definite, le eccezioni catturate e le librerie spesso sono più complesse nella loro architettura per poter essere più flessibili.

Il codice scritto con il Python, ad esempio, non prende in considerazione il passaggio delle connessioni per un proxy, caso certamente verificato se si esegue il codice da una rete aziendale, questa informazione non può essere passata in una semplice stringa ed ecco perché il complicato Java usa una libreria che richiede un più tecnico stream di dati. Detto questo, il codice di base è più complesso perché prima dobbiamo creare lo stream con le istruzioni: URL("URL feed").openConnection().getInputStream()); la maggiore potenza sta nel poter creare una connessione che fa uso di un proxy, ovviamente anche l'oggetto proxy deve essere configurato con l'indirizzo e la porta corretti: Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("IP proxy", Porta proxy)); L'apertura dello stream allora diventa: URL("URL del feed").openConnection(proxy).getInputStream());

L'effettiva lettura del feed è semplice quanto in Python e anche simile nel codice: WireFeedInput inputFeed = new WireFeedInput(); feed = (Channel) inputFeed.build(reader); List<Item> item = feed.getItems(); String summary = item.get(0).getDescription()); Con la differenza che il metodo che legge il feed non è statico e quindi la classe deve prima essere istanziata.

Per questi motivi il codice minimo da scrivere in Java è il seguente: package testfeed; import com.sun.syndication.*; import java.io.*; import java.net.*; public class Main { public static void main(String[] args) { InputStreamReader reader = null; Channel feed = null; try { // Uso del proxy Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress( "IP proxy", Porta proxy)); reader = new InputStreamReader(new URL("URL feed"). openConnection(proxy).getInputStream()); /* Senza proxy reader = new InputStreamReader(new URL("URL feed"). openConnection().getInputStream()); */ WireFeedInput inputFeed = new WireFeedInput(); feed = (Channel) inputFeed.build(reader); List<Item> item = feed.getItems(); String summary = item.get(0).getDescription()); } catch (Exception ex) { System.out.println(ex.getMessage()); } finally { try { if (reader != null) reader.close(); } catch (IOException ex) { System.out.println(ex.getMessage()); } } } }

Si osserva come sia necessario chiudere le risorse e catturare le eccezioni. Per mantenere il codice più semplice ho usato una generica Exception ma è possibile avere più controllo catturando quelle più specifiche lanciate dalle istruzioni che ho usato: MalformedURLException, IOException, IllegalArgumentException e FeedException

Con il cast alla classe Channel gestiamo i feed di tipo RSS, se ci fossimo trovati a leggere un feed di tipo Atom avremmo dovuto usare la classe Feed.

L'articolo che ho mensionato inizialmente, prosegue elaborando il contenuto del nodo <description> dei feed ma secondo me questo non è il modo corretto di procedere. Le specifiche di Yahoo! Weather descrivono quel nodo nel seguente modo:

A simple summary of the current conditions and tomorrow's forecast, in HTML format, including a link to Yahoo! Weather for the full forecast. Cioè non specificano niente! Contiene una porzione di html pronta all'uso ma chi ci garantisce che il testo resti inalterato nella forma, che contenga le stesse parole e nello stesso ordine, pur mantenendo la stessa versione delle API del servizio?

Le reali informazioni sono contenute nel nodo specifico <yweather:condition />, nel senso che così è chiaramente scritto nelle specifiche della versione attuale delle API e tale resterà a meno di un cambio delle specifiche. Lo riporto con un esempio, tanto ciò che cambia sono solo i valori, non gli attributi a cui fare riferimento nel codice come invece accade nel testo del nodo <description>: <yweather:condition text="Fair" code="33" temp="8" date="Thu, 29 Oct 2009 6:56 am PDT" /> Le specifiche lo descrivono così:

The current weather conditions. Attributes:

- text: a textual description of conditions, for example, "Partly Cloudy" (string)

- code: the condition code for this forecast. You could use this code to choose a text description or image for the forecast. The possible values for this element are described in Condition Codes (integer)

- temp: the current temperature, in the units specified by the yweather:units element (integer)

- date: the current date and time for which this forecast applies. The date is in RFC822 Section 5 format, for example "Wed, 30 Nov 2005 1:56 pm PST" (string)
E' chiaro che nel feed ricevuto c'è sempre uno e un solo nodo <yweather:condition /> e ha sempre l'attributo "temp" che indica la temperatura e l'attributo "code" che individua un codice sulle condizioni meteo (che possiamo tradurre nella nostra lingua come fa già l'attributo "text" per l'inglese). Questo nodo non fa parte dello standard dei feed e immagino sia per questo che l'articolo sul Python usi il nodo <description> che invece è standard.

Detto questo, ricordiamo che lo scopo iniziale è leggere le informazioni meteo e quindi il modo più semplice è procedere a un livello più basso: leggere i dati come XML e cercare il nodo <yweather:condition />.

Non ho idea di come si faccia in Python, suppongo che sia più semplice che farlo in java. In Java il codice completo è il seguente, che tra l'altro non richiede nessuna libreria aggiuntiva rispetto all'uso dei feed perchè tutto il necessario si trova nel Java Runtime Enviroment standard: package provaxml; import java.io.*; import java.net.*; import javax.xml.parsers.*; import org.w3c.dom.*; public class Main { public static void main(String[] args) { try { InputStream inputXml = null; // Uso del proxy Proxy proxy = null; proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("IP proxy", Porta proxy))); inputXml = new URL("URL feed").openConnection(proxy) .getInputStream(); DocumentBuilderFactory factory = DocumentBuilderFactory. newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(inputXml); NodeList nodi = doc.getElementsByTagName("yweather:condition"); if (nodi.getLength() > 0) { /* Il feed di Yahoo! Weather ha sempre uno e un solo nodo "<yweather:condition>" con l'attributo "temp" che contiene la temperatura */ Element nodo = (Element)nodi.item(0); String strTemp = nodo.getAttribute("temp"); System.out.println("Temperatura: " + strTemp); } } catch (Exception ex) { System.out.println(ex.getMessage()); } finally { try { if (inputXml != null) inputXml.close(); } catch (IOException ex) { System.out.println(ex.getMessage()); } } } } Anche in questo caso sarebbe meglio catturare tutte le eccezioni specifiche: ParserConfigurationException, SAXException e IOException.

Spero di essere stato chiaro, in caso contrario inviatemi i vostri commenti mediante i contatti.



Mario