WillyWeb
22/10/17, 10:41:35
- - ADVERTENCIA - -
En este tutorial se explica cómo procesar los XML de la predicción meteorológica de AEMET usando código JavaScript ... pero mucho código. :rolleyes:
Aunque no sepas nada de JS deberías leer todo el tutorial igualmente. Si luego ves que el tema de usar un JavaScriptlet como el que propongo te supera, revisa este otro tutorial ....
[Tutorial] Procesar los XML de AEMET (versión Código>JavaScript) (http://www.htcmania.com/showthread.php?t=1364592)
Es una solución basada en esta misma, pero mucho más sencilla de implementar. ;-)
- - - -
Hace unos días el compañero Mitrhandir me lanzó el "reto", como a él le gusta decir, de hacer una tarea para procesar el XML de la "Predicción por horas" de AEMET, pero sin usar nada más que Tasker. :silbando:
No es la primera vez que se toca el tema de procesar los datos de AEMET en el foro...
Tarea que te dice el tiempo y temperatura (http://www.htcmania.com/showthread.php?t=567903&highlight=aemet)
Cómo hacer una tarea para saber el tiempo que hace o va a hacer (http://www.htcmania.com/showthread.php?t=1289407&highlight=aemet)
...pero después de revisar esas conversaciones me decidí por un nuevo enfoque.
Tras unas vueltas al asunto, y descartar un par de ideas por inviables o exceso de complejidad (convertir el XML a JSON en local o mediante un webservice), terminé por escribir un JavaScriptlet que se encargase de procesar ese XML directamente. Lo podía haber dejado aquí pero resulta que AEMET también publica una "Predicción 7 días" y el código JS que tenía sólo procesaba la "Predicción por horas" (de 3 días). Me puse a ello y no me quedó más remedio que escribir otro JavaScriptlet para procesar ese otro XML. El siguiente paso estaba cantado, unirlo todo en un único JavaScriptlet capaz de procesar los dos XML (por horas y 7 días) de la "Predicción por Municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios)" que publica AEMET.
Estos son los pasos que he ido dando hasta llegar a la solución final... :idea:
Los XML de AEMET
Tenemos estas dos fuente de datos:
Predicción por horas
http://www.aemet.es/xml/municipios_h/localidad_h_INE.xml
Predicción 7 días (Interpretación)
http://www.aemet.es/xml/municipios/localidad_INE.xml
Ejemplos:
"Predicción por horas" de Alegría-Dulantzi en Álava (http://www.aemet.es/xml/municipios_h/localidad_h_01001.xml)
"Predicción 7 días" de Alegría-Dulantzi en Álava (http://www.aemet.es/xml/municipios/localidad_01001.xml)
El contenido de esos XML está explicado en la página ... Interpretación: Predicción por municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios/ayuda).
El valor INE es un código de cinco dígitos que identifica cada municipio de España. Están disponibles en la web del Instituto Nacional de Estadística (http://www.ine.es/jaxi/menu.do?type=pcaxis&path=/t20/e245/codmun&file=inebase) y aunque lo parezca no es el Código Postal (https://www.codigospostales.com/) que usa Correos (https://www.correos.es/). La forma más fácil de conseguir el código INE de un lugar concreto es buscarlo en la web de la "Predicción por Municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios)" y fijarte en el número que aparece al final de la URL (...-idxxxxx).
A primera vista ambos XML parecen iguales, pero un análisis detallado pone de manifiesto unas pocas "particularidades":
El atributo "fecha" está al revés de lo que consideramos habitual ... aaaa-mm-dd (mes y día siempre tienen dos cifras).
El atributo "periodo" tiene varios "formatos"; hh una hora de 00 a 23, hh una hora cada 6 de 06 a 24, hhhh un rango de 6 horas cada 6 horas, hh-hh en rangos de 6/12/24 horas desde las 00 AM.
La información relevante unas veces aparece como valor de un elemento/nodo y otras en un atributo.
Un mismo dato no siempre se obtiene con la misma secuencia de elementos/nodos y/o atributos.
No siempre existen todos los elementos/nodos y atributos.
La forma de declarar un elemento/nodo sin valor (por ejemplo, <cota_nieve_prov/>) no siempre es interpretada correctamente por el "parser" XML de JavaScript.
Obtener un XML y pasarlo al JavaScriptlet para "parsearlo"
Cualquiera de los XML se pueden obtener fácilmente poniendo su URL en una acción "HTTP Get" de Tasker. El resultado de esa acción queda almacenado en la variable global %HTTPD y lo tenemos que pasar al JavaScriplet para poder procesarlo. Eso es fácil con este código JS...
httpd = global("HTTPD");
dom = new DOMParser();
xml = dom.parseFromString(httpd,"text/xml");
Con eso se crea el objeto "xml" de JS con el contenido del XML obtenido desde Tasker. Esto se tiene que hacer antes de usar las funciones que extraen los datos del XML, que describiré a continuación, ya que requieren que el objeto "xml" exista.
Solución al problema de la "fecha"
Además de generar el formato adecuado (aaaa-mm-dd) está el tema de saber qué fecha es dentro de 2 días o de 3 horas, algo imprescindible en el caso que nos ocupa. Para resolver eso he creado esta función JS...
function fecha(_nd=0,_nh=0){
_x=new Date();
_a=_x.getFullYear();
_m=_x.getMonth();
_d=_x.getDate();
_h=_x.getHours();
_x=new Date(_a,_m,_d+_nd,_h+_nh);
_a=_x.getFullYear();
_m=_x.getMonth()+1;
_d=_x.getDate();
_x=_a+"-"+("0"+_m).slice(-2)+"-"+("0"+_d).slice(-2);
return _x;
}
Sintaxis:
cadena = fecha([número días[, número horas]])
Ejemplos:
//fecha de hoy
dia = fecha();
//fecha de pasado mañana
dia = fecha(2);
//fecha dentro de tres horas
dia = fecha(0,3);
//fecha dentro de día y medio (o 36 horas)
dia = fecha(1,12);
dia = fecha(0,36);
Solución al problema del "periodo"
La dificultad en este caso está en conseguir un determinado formato de "periodo" para ahora o para dentro de X horas. He creado esta función JS para eso...
function periodo(_nh=0,_fmt="h0"){
_x=new Date();
_a=_x.getFullYear();
_m=_x.getMonth();
_d=_x.getDate();
_h=_x.getHours();
_x=new Date(_a,_m,_d,_h+_nh);
_h=_x.getHours();
if(_fmt=="h0"){
_x=("0"+_h).slice(-2);
}
if(_fmt=="h6"){
_t=Math.floor(_h/6);_t=_t*6+6;
_x=("0"+_t).slice(-2);
}
if(_fmt=="r6"){
_to=_x.getTimezoneOffset()/60;
_it=Math.floor(_h/6);_it=_it*6-_to;
_xx=new Date(_a,_m,_d,_it+6);
_ft=_xx.getHours();
_x=("0"+_it).slice(-2)+("0"+_ft).slice(-2);
}
if(_fmt=="r6-"){
_it=Math.floor(_h/6);_it=_it*6;
_ft=_it+6;
_x=("0"+_it).slice(-2)+"-"+("0"+_ft).slice(-2);
}
if(_fmt=="r12-"){
_x=(_h<12)?"00-12":"12-24";
}
if(_fmt=="r24-"){
_x="00-24";
}
return _x;
}
Sintaxis:
cadena = periodo([número horas[, "formato"]])
Donde "formato" puede ser:
h0 devuelve "00" a "23" [formato por defecto]
h6 devuelve "06", "12", "18", "24"
r6 devuelve "0006", "0612", "1218", "1824" más la diferencia horaria con UTC
r6- devuelve "00-06", "06-12", "12-18", "18-24"
r12- devuelve "00-12" o "12-24"
r24- devuelve "00-24"
Ejemplos:
//hora actual con dos dígitos
per = periodo();
//dentro de tres horas con dos dígitos
per = periodo(3);
//rango de doce horas de la hora actual en formato "hh-hh"
per = periodo(0,"r12-");
//rango de seis horas para dentro de dos horas en formato "hh-hh"
per = periodo(2,"r6-");
Extraer los datos genéricos del XML
Al principio de ambos XML aparecen unos pocos datos genéricos que se pueden sacar casi directamente. Para este caso concreto he creado esta sencilla función JS...
function valornodo(_elem){
_res=xml.getElementsByTagName(_elem)[0].childNodes[0].nodeValue;
return _res;
}
Sintaxis:
cadena = valornodo("nombre nodo")
Ejemplos:
//enlace a la página de la predicción
enlace = valornodo("enlace");
//fecha y hora de elaboración
elaborado = valornodo("elaborado");
//municipio
municipio = valornodo("nombre");
municipio = municipio.split("/");
municipio = municipio[municipio.length-1];
En el valor de los nodos <nombre> y <provincia> nos podemos encontrar con varios nombres separados por una barra (/). Una forma simple de extraer el últmo de esos nombres (el que está escrito en castellano) es convertir el valor en un array, usando la barra como separador, y quedándonos con el último elemento. Esa técnica siempre extrae el valor correctamente, tanto si existe la barra como si no.
Extraer los datos de la predicción del XML
Esta es la parte que más trabajo me ha dado, principalmente por usar instrucciones básicas de JavaScript en vez de otras técnicas más directas, como las expresiones "XPath (http://www.mclibre.org/consultar/xml/lecciones/xml-xpath.html)", pero quería hacer algo que se "entendiera" y que pudiera funcionar en cualquier dispositivo. Esta es la función JS que lo hace posible...
function prediccion(_fecha,_periodo,_elem1,_elem2,_attr){
_xmlres=xml.getElementsByTagName("prediccion")[0];
_xmlres=_xmlres.getElementsByTagName("dia");
for(_i=0;_i<_xmlres.length;_i++){
_attfecha=_xmlres[_i].getAttribute("fecha");
if(_attfecha==_fecha){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
if(_attr=="fecha" || _attr=="orto" || _attr=="ocaso"){
_res=_xmlres.getAttribute(_attr);
return _res;
}
_xmlres=_xmlres.getElementsByTagName(_elem1);
if(_xmlres.length==0){return null;}
if(_xmlres.length==1){ //sin periodos
_xmlres=_xmlres[0];
}else{
for(_i=0;_i<_xmlres.length;_i++){
_attperiodo=_xmlres[_i].getAttribute("periodo");
if(_attperiodo==_periodo){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
}
if(_xmlres.childNodes.length>1){ //con subnodos
_xmlres=_xmlres.getElementsByTagName(_elem2);
if(_xmlres.length==0){return null;}
if(_xmlres.length==1){
_xmlres=_xmlres[0];
}else{
//en 7d: dato
for(_i=0;_i<_xmlres.length;_i++){
_atthora=_xmlres[_i].getAttribute("hora");
if(_atthora==_periodo){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
}
}
if(_attr){
_res=_xmlres.getAttribute(_attr);
}else{
if(_xmlres.childNodes.length==0){
_res=null;
}else{
_res=_xmlres.childNodes[0].nodeValue;
}
}
return _res;
}
Sintaxis:
cadena = prediccion("fecha", "periodo", "nodo1"[, "nodo2"[, "atributo"]])
Los nombres de los parámetros son bastante aclaratorios, pero la mejor forma de entender su funcionamiento es con ejemplos.
Ejemplos:
//datos de la próxima hora (XML por horas)
dia = fecha();
hh = periodo(1);
orto = prediccion(dia,"","","","orto");
ocaso = prediccion(dia,"","","","ocaso");
cielo = prediccion(dia,hh,"estado_cielo","","descripcion");
temp = prediccion(dia,hh,"temperatura");
dirviento = prediccion(dia,hh,"viento","direccion");
velviento = prediccion(dia,hh,"viento","velocidad");
//datos de mañana (XML de 7 días)
dia = fecha(1);
h24 = "00-24";
cielo = prediccion(dia,h24,"estado_cielo","","descripcion");
dirviento = prediccion(dia,h24,"viento","direccion");
velviento = prediccion(dia,h24,"viento","velocidad");
uvmax = prediccion(dia,"","uv_max");
Si se intenta extraer un valor que no existe en el XML la función devuelve "null". También devuelve "null" en el caso de que el elemento/nodo esté declarado sin valor (<etiqueta/>).
La subtarea TiempoAEMET
Llegados a este punto ya podemos abordar la creación de una tarea para procesar cualquiera de los XML de AEMET. Me ha parecido que lo más interesante es hacerla como una subtarea que reciba un código INE en %par1 y devuelva el texto de la predicción para mostrarlo o decirlo, a gusto del consumidor.
La subtarea tiene esta pinta...
TiempoAEMET (999)
A1: Establecer variable [ Nombre:%par1 A:01001 ] Si (if) [ %caller1 ~ ui ]
A2: HTTP Get [ Servidor:http://www.aemet.es Ruta:xml/municipios_h/localidad_h_%par1.xml ]
A3: JavaScriptlet [ Código://** parser **
httpd = global("HTTPD");
dom = new DOMParser();
xml = dom.parseFromString(httpd,"text/xml");
//** funciones **
function valornodo(){...}
function prediccion(){...}
function fecha(){...}
function periodo(){...}
//** datos xml **
dia = fecha();
per = periodo();
var ...
var ...
var ...
...
Librerías: Salida Automática:Encendido Cuenta atrás (segundos):45 ]
A4: Establecer variable [ Nombre:%texto A:El cielo en %nombre está %descielo, la temperatura es de %temp grados Añadir:Apagado ]
A5: Establecer variable [ Nombre:%texto A: con sensación de %senter Añadir:Encendido ] Si (if) [ %temp nig %senter ]
A6: Establecer variable [ Nombre:%texto A:, el viento es de %velviento kilómetros por hora Añadir:Encendido ]
A7: Establecer variable [ Nombre:%texto A: con rachas máximas de %rachamax Añadir:Encendido ] Si (if) [ %rachamax > 0 ]
A8: Establecer variable [ Nombre:%texto A:. La probabilidad de lluvia es del %problluvia por ciento. Añadir:Encendido ] Si (if) [ %problluvia > 10 ]
A9: Decir [ Texto:%texto ] Si (if) [ %caller1 ~ ui ]
A10: Devolver [ Valor:%texto Detener:Encendido ]
A1 y A9 están puestas para que sea posible hacer pruebas directamente desde el editor de Tasker.
A2 obtiene el XML de AEMET.
A4-A8 se encargan de componer el %texto final con la predicción.
Y A10 devuelve el %texto final a la tarea desde la que se llama a TiempoAEMET.
El contenido de A3 (el JavaScriptlet) requiere unas aclaraciones adicionales. :rolleyes:
En el apartado de ** funciones ** se tiene que copiar el código de las cuatro funciones que he explicado anteriormente. No lo he puesto para dejar la tarea un poco más limpia a la vista.
El apartado ** datos xml ** es para definir las variables que almacenarán los valores que nos interesan del XML. Esas definiciones tienen que comenzar con la instrucción "var ..." de JS para que pasen a Tasker como variables locales manejables desde la tarea. Es decir, la definición de variable JS "var nombre ..." genera la variable local %nombre en la tarea de Tasker.
Y la forma de usar esta subtarea podría ser tal que así...
Decir el tiempo (888)
A1: Realizar tarea [ Nombre:TiempoAEMET Prioridad:%priority Parámetro 1 (%par1):10001 Parámetro 2 (%par2): Devolver Valor de Variable:%resultado ]
A2: Decir [ Texto:%resultado ]
Casos prácticos
Al explicar cada función he puesto dos o tres ejemplos de cómo se usa cada una de ellas, pero puede que no sea tan evidente cómo se usan en conjunto. A continuación os dejo unos casos prácticos que sacan TODOS los valores de cada tipo de predicción.
Todos los valores para la hora actual del XML de la "Predicción por horas"
//** datos xml **
dia = fecha();
h0 = periodo();
r6 = periodo(0,"r6");
var enlace = valornodo("enlace");
var elaborado = valornodo("elaborado");
var nombre = valornodo("nombre");
nombre=nombre.split("/");
nombre=nombre[nombre.length-1];
var provincia = valornodo("provincia");
provincia=provincia.split("/");
provincia=provincia[provincia.length-1];
var orto = prediccion(dia,"","","","orto");
var ocaso = prediccion(dia,"","","","ocaso");
var descielo = prediccion(dia,h0,"estado_cielo","","descripcion");
var estcielo = prediccion(dia,h0,"estado_cielo");
var lluvia = prediccion(dia,h0,"precipitacion");
var problluvia = prediccion(dia,r6,"prob_precipitacion");
var probtorm = prediccion(dia,r6,"prob_tormenta");
var nieve = prediccion(dia,h0,"nieve");
var probnieve = prediccion(dia,r6,"prob_nieve");
var temp = prediccion(dia,h0,"temperatura");
var senter = prediccion(dia,h0,"sens_termica");
var humrel = prediccion(dia,h0,"humedad_relativa");
var dirviento = prediccion(dia,h0,"viento","direccion");
var velviento = prediccion(dia,h0,"viento","velocidad");
var rachamax = prediccion(dia,h0,"racha_max");
Todos los valores para la hora actual del XML de la "Predicción 7 días"
//** datos xml **
dia = fecha();
r6 = periodo(0,"r6-");
h6 = periodo(0,"h6");
var enlace = valornodo("enlace");
var elaborado = valornodo("elaborado");
var nombre = valornodo("nombre");
nombre=nombre.split("/");
nombre=nombre[nombre.length-1];
var provincia = valornodo("provincia");
provincia=provincia.split("/");
provincia=provincia[provincia.length-1];
var problluvia = prediccion(dia,r6,"prob_precipitacion");
var cotanieve = prediccion(dia,r6,"cota_nieve_prov");
var descielo = prediccion(dia,r6,"estado_cielo","","descripcion");
var estcielo = prediccion(dia,r6,"estado_cielo");
var dirviento = prediccion(dia,r6,"viento","direccion");
var velviento = prediccion(dia,r6,"viento","velocidad");
var rachamax = prediccion(dia,r6,"racha_max");
var temp = prediccion(dia,"","temperatura","maxima");
var tempmin = prediccion(dia,"","temperatura","minima");
var dtem = prediccion(dia,h6,"temperatura","dato");
var senter = prediccion(dia,"","sens_termica","maxima");
var sentermin = prediccion(dia,"","sens_termica","minima");
var dster = prediccion(dia,h6,"sens_termica","dato");
var humrel = prediccion(dia,"","humedad_relativa","maxima");
var humrelmin = prediccion(dia,"","humedad_relativa","minima");
var dhrel = prediccion(dia,h6,"humedad_relativa","dato");
var uvmax = prediccion(dia,"","uv_max");
Todos los valores para TODOS los días de la "Predicción 7 días" (ver mensaje #10)
//** datos xml **
var diapred=[];
var problluvia=[];var cotanieve=[];
var descielo=[];var estcielo=[];
var dirviento=[];var velviento=[];var rachamax=[];
var tempmax=[];var tempmin=[];
var sentermax=[];var sentermin=[];
var hummax=[];var hummin=[];
var uvmax=[];
for(xd=-1;xd!=7;xd++){
dia=fecha(xd);
xfecha=prediccion(dia,"","","","fecha");
if(xfecha){
diapred.push(prediccion(dia,"","","","fecha"));
problluvia.push(prediccion(dia,"00-24","prob_precipitacion"));
cotanieve.push(prediccion(dia,"00-24","cota_nieve_prov"));
descielo.push(prediccion(dia,"00-24","estado_cielo","","descripcion"));
estcielo.push(prediccion(dia,"00-24","estado_cielo"));
dirviento.push(prediccion(dia,"00-24","viento","direccion"));
velviento.push(prediccion(dia,"00-24","viento","velocidad"));
rachamax.push(prediccion(dia,"00-24","racha_max"));
tempmax.push(prediccion(dia,"","temperatura","maxima"));
tempmin.push(prediccion(dia,"","temperatura","minima"));
sentermax.push(prediccion(dia,"","sens_termica","maxima"));
sentermin.push(prediccion(dia,"","sens_termica","minima"));
hummax.push(prediccion(dia,"","humedad_relativa","maxima"));
hummin.push(prediccion(dia,"","humedad_relativa","minima"));
uvmax.push(prediccion(dia,"","uv_max"));
}
}
Añade máximos y mínimos al bloque de datos anterior (ver mensaje #10)
//** máximos y mínimos **
var problluvia0,mpll=0;
var cotanieve0,mcn=0;
var velviento0,mvv=0;
var rachamax0,mrx=0;
var tempmax0,mtx=0;
var tempmin0,mtm=999;
var senterpmax0,mstx=0;
var sentermin0,mstm=999;
var hummax0,mhx=0;
var hummin0,mhm=999;
var uvmax0,mux=0;
for(i=0;i<diapred.length;i++){
if(problluvia[i]>mpll){mpll=problluvia[i]/1;problluvia0=i+1;}
if(cotanieve[i]>mcn){mcn=cotanieve[i]/1;cotanieve0=i+1;}
if(velviento[i]>mvv){mvv=velviento[i]/1;velviento0=i+1;}
if(rachamax[i]>mrx){mrx=rachamax[i]/1;rachamax0=i+1;}
if(tempmax[i]>mtx){mtx=tempmax[i]/1;tempmax0=i+1;}
if(tempmin[i]<mtm){mtm=tempmin[i]/1;tempmin0=i+1;}
if(sentermax[i]>mstx){mstx=sentermax[i]/1;sentermax0=i+1;}
if(sentermin[i]<mstm){mstm=sentermin[i]/1;sentermin0=i+1;}
if(hummax[i]>mhx){mhx=hummax[i]/1;hummax0=i+1;}
if(hummin[i]<mhm){mhm=hummin[i]/1;hummin0=i+1;}
if(uvmax[i]>mux){mux=uvmax[i]/1;uvmax0=i+1;}
}
Iconos de cielo/viento/uv
En la web de AEMET tienen unos pequeños iconos para el estado del cielo, la dirección del viento y el índice ultravioleta que os podrían venir bien para vuestra tarea. Añadiendo el siguiente código JS justo después del bloque ** datos xml ** también dispondréis en la tarea de Tasker de unas variables locales con las URLs de las imágenes correspondientes a esos valores.
//** iconos **
var icocielo = (estcielo) ? "http://www.aemet.es/imagenes_gcd/_iconos_municipios/"+estcielo+".png" : null;
var icoviento = (dirviento) ? "http://www.aemet.es/imagenes_gcd/_iconos_municipios/"+dirviento+".png" : null;
var icouvmax = (uvmax) ? "http://www.aemet.es/imagenes_gcd/_iconos_localidades/uvi_c"+uvmax+"_pred.gif" : null;
Precauciones:
Este código JS requiere que las variables estcielo/dirviento/uvmax tengan un valor definido.
Si se usa la variante que saca los ** datos xml ** de TODOS los días se tendrá que modificar ligeramente este código.
La dirección del viento
Otra cosa que os puede interesar, sobre todo si queréis "Decir" el tiempo, es la versión "pronunciable" de las letras de la dirección del viento. Añadiendo este código JS después del bloque ** datos xml ** tendréis en la tarea una variable local más con esa conversión.
//** viento **
vd = {N:"norte",NE:"nordeste",E:"este",SE:"sudeste",S:"sur",SO:"suroeste",O:"oeste",NO:"noroeste",C:"calma"};
var vientodel = (dirviento) ? vd[dirviento] : null;
Precauciones:
Este código JS requiere que la variable dirviento tenga un valor definido.
Si se usa la variante que saca los ** datos xml ** de TODOS los días se tendrá que modificar ligeramente este código.
Y eso es todo. Ya tenéis entretenimiento. :cucu:
En este tutorial se explica cómo procesar los XML de la predicción meteorológica de AEMET usando código JavaScript ... pero mucho código. :rolleyes:
Aunque no sepas nada de JS deberías leer todo el tutorial igualmente. Si luego ves que el tema de usar un JavaScriptlet como el que propongo te supera, revisa este otro tutorial ....
[Tutorial] Procesar los XML de AEMET (versión Código>JavaScript) (http://www.htcmania.com/showthread.php?t=1364592)
Es una solución basada en esta misma, pero mucho más sencilla de implementar. ;-)
- - - -
Hace unos días el compañero Mitrhandir me lanzó el "reto", como a él le gusta decir, de hacer una tarea para procesar el XML de la "Predicción por horas" de AEMET, pero sin usar nada más que Tasker. :silbando:
No es la primera vez que se toca el tema de procesar los datos de AEMET en el foro...
Tarea que te dice el tiempo y temperatura (http://www.htcmania.com/showthread.php?t=567903&highlight=aemet)
Cómo hacer una tarea para saber el tiempo que hace o va a hacer (http://www.htcmania.com/showthread.php?t=1289407&highlight=aemet)
...pero después de revisar esas conversaciones me decidí por un nuevo enfoque.
Tras unas vueltas al asunto, y descartar un par de ideas por inviables o exceso de complejidad (convertir el XML a JSON en local o mediante un webservice), terminé por escribir un JavaScriptlet que se encargase de procesar ese XML directamente. Lo podía haber dejado aquí pero resulta que AEMET también publica una "Predicción 7 días" y el código JS que tenía sólo procesaba la "Predicción por horas" (de 3 días). Me puse a ello y no me quedó más remedio que escribir otro JavaScriptlet para procesar ese otro XML. El siguiente paso estaba cantado, unirlo todo en un único JavaScriptlet capaz de procesar los dos XML (por horas y 7 días) de la "Predicción por Municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios)" que publica AEMET.
Estos son los pasos que he ido dando hasta llegar a la solución final... :idea:
Los XML de AEMET
Tenemos estas dos fuente de datos:
Predicción por horas
http://www.aemet.es/xml/municipios_h/localidad_h_INE.xml
Predicción 7 días (Interpretación)
http://www.aemet.es/xml/municipios/localidad_INE.xml
Ejemplos:
"Predicción por horas" de Alegría-Dulantzi en Álava (http://www.aemet.es/xml/municipios_h/localidad_h_01001.xml)
"Predicción 7 días" de Alegría-Dulantzi en Álava (http://www.aemet.es/xml/municipios/localidad_01001.xml)
El contenido de esos XML está explicado en la página ... Interpretación: Predicción por municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios/ayuda).
El valor INE es un código de cinco dígitos que identifica cada municipio de España. Están disponibles en la web del Instituto Nacional de Estadística (http://www.ine.es/jaxi/menu.do?type=pcaxis&path=/t20/e245/codmun&file=inebase) y aunque lo parezca no es el Código Postal (https://www.codigospostales.com/) que usa Correos (https://www.correos.es/). La forma más fácil de conseguir el código INE de un lugar concreto es buscarlo en la web de la "Predicción por Municipios (http://www.aemet.es/es/eltiempo/prediccion/municipios)" y fijarte en el número que aparece al final de la URL (...-idxxxxx).
A primera vista ambos XML parecen iguales, pero un análisis detallado pone de manifiesto unas pocas "particularidades":
El atributo "fecha" está al revés de lo que consideramos habitual ... aaaa-mm-dd (mes y día siempre tienen dos cifras).
El atributo "periodo" tiene varios "formatos"; hh una hora de 00 a 23, hh una hora cada 6 de 06 a 24, hhhh un rango de 6 horas cada 6 horas, hh-hh en rangos de 6/12/24 horas desde las 00 AM.
La información relevante unas veces aparece como valor de un elemento/nodo y otras en un atributo.
Un mismo dato no siempre se obtiene con la misma secuencia de elementos/nodos y/o atributos.
No siempre existen todos los elementos/nodos y atributos.
La forma de declarar un elemento/nodo sin valor (por ejemplo, <cota_nieve_prov/>) no siempre es interpretada correctamente por el "parser" XML de JavaScript.
Obtener un XML y pasarlo al JavaScriptlet para "parsearlo"
Cualquiera de los XML se pueden obtener fácilmente poniendo su URL en una acción "HTTP Get" de Tasker. El resultado de esa acción queda almacenado en la variable global %HTTPD y lo tenemos que pasar al JavaScriplet para poder procesarlo. Eso es fácil con este código JS...
httpd = global("HTTPD");
dom = new DOMParser();
xml = dom.parseFromString(httpd,"text/xml");
Con eso se crea el objeto "xml" de JS con el contenido del XML obtenido desde Tasker. Esto se tiene que hacer antes de usar las funciones que extraen los datos del XML, que describiré a continuación, ya que requieren que el objeto "xml" exista.
Solución al problema de la "fecha"
Además de generar el formato adecuado (aaaa-mm-dd) está el tema de saber qué fecha es dentro de 2 días o de 3 horas, algo imprescindible en el caso que nos ocupa. Para resolver eso he creado esta función JS...
function fecha(_nd=0,_nh=0){
_x=new Date();
_a=_x.getFullYear();
_m=_x.getMonth();
_d=_x.getDate();
_h=_x.getHours();
_x=new Date(_a,_m,_d+_nd,_h+_nh);
_a=_x.getFullYear();
_m=_x.getMonth()+1;
_d=_x.getDate();
_x=_a+"-"+("0"+_m).slice(-2)+"-"+("0"+_d).slice(-2);
return _x;
}
Sintaxis:
cadena = fecha([número días[, número horas]])
Ejemplos:
//fecha de hoy
dia = fecha();
//fecha de pasado mañana
dia = fecha(2);
//fecha dentro de tres horas
dia = fecha(0,3);
//fecha dentro de día y medio (o 36 horas)
dia = fecha(1,12);
dia = fecha(0,36);
Solución al problema del "periodo"
La dificultad en este caso está en conseguir un determinado formato de "periodo" para ahora o para dentro de X horas. He creado esta función JS para eso...
function periodo(_nh=0,_fmt="h0"){
_x=new Date();
_a=_x.getFullYear();
_m=_x.getMonth();
_d=_x.getDate();
_h=_x.getHours();
_x=new Date(_a,_m,_d,_h+_nh);
_h=_x.getHours();
if(_fmt=="h0"){
_x=("0"+_h).slice(-2);
}
if(_fmt=="h6"){
_t=Math.floor(_h/6);_t=_t*6+6;
_x=("0"+_t).slice(-2);
}
if(_fmt=="r6"){
_to=_x.getTimezoneOffset()/60;
_it=Math.floor(_h/6);_it=_it*6-_to;
_xx=new Date(_a,_m,_d,_it+6);
_ft=_xx.getHours();
_x=("0"+_it).slice(-2)+("0"+_ft).slice(-2);
}
if(_fmt=="r6-"){
_it=Math.floor(_h/6);_it=_it*6;
_ft=_it+6;
_x=("0"+_it).slice(-2)+"-"+("0"+_ft).slice(-2);
}
if(_fmt=="r12-"){
_x=(_h<12)?"00-12":"12-24";
}
if(_fmt=="r24-"){
_x="00-24";
}
return _x;
}
Sintaxis:
cadena = periodo([número horas[, "formato"]])
Donde "formato" puede ser:
h0 devuelve "00" a "23" [formato por defecto]
h6 devuelve "06", "12", "18", "24"
r6 devuelve "0006", "0612", "1218", "1824" más la diferencia horaria con UTC
r6- devuelve "00-06", "06-12", "12-18", "18-24"
r12- devuelve "00-12" o "12-24"
r24- devuelve "00-24"
Ejemplos:
//hora actual con dos dígitos
per = periodo();
//dentro de tres horas con dos dígitos
per = periodo(3);
//rango de doce horas de la hora actual en formato "hh-hh"
per = periodo(0,"r12-");
//rango de seis horas para dentro de dos horas en formato "hh-hh"
per = periodo(2,"r6-");
Extraer los datos genéricos del XML
Al principio de ambos XML aparecen unos pocos datos genéricos que se pueden sacar casi directamente. Para este caso concreto he creado esta sencilla función JS...
function valornodo(_elem){
_res=xml.getElementsByTagName(_elem)[0].childNodes[0].nodeValue;
return _res;
}
Sintaxis:
cadena = valornodo("nombre nodo")
Ejemplos:
//enlace a la página de la predicción
enlace = valornodo("enlace");
//fecha y hora de elaboración
elaborado = valornodo("elaborado");
//municipio
municipio = valornodo("nombre");
municipio = municipio.split("/");
municipio = municipio[municipio.length-1];
En el valor de los nodos <nombre> y <provincia> nos podemos encontrar con varios nombres separados por una barra (/). Una forma simple de extraer el últmo de esos nombres (el que está escrito en castellano) es convertir el valor en un array, usando la barra como separador, y quedándonos con el último elemento. Esa técnica siempre extrae el valor correctamente, tanto si existe la barra como si no.
Extraer los datos de la predicción del XML
Esta es la parte que más trabajo me ha dado, principalmente por usar instrucciones básicas de JavaScript en vez de otras técnicas más directas, como las expresiones "XPath (http://www.mclibre.org/consultar/xml/lecciones/xml-xpath.html)", pero quería hacer algo que se "entendiera" y que pudiera funcionar en cualquier dispositivo. Esta es la función JS que lo hace posible...
function prediccion(_fecha,_periodo,_elem1,_elem2,_attr){
_xmlres=xml.getElementsByTagName("prediccion")[0];
_xmlres=_xmlres.getElementsByTagName("dia");
for(_i=0;_i<_xmlres.length;_i++){
_attfecha=_xmlres[_i].getAttribute("fecha");
if(_attfecha==_fecha){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
if(_attr=="fecha" || _attr=="orto" || _attr=="ocaso"){
_res=_xmlres.getAttribute(_attr);
return _res;
}
_xmlres=_xmlres.getElementsByTagName(_elem1);
if(_xmlres.length==0){return null;}
if(_xmlres.length==1){ //sin periodos
_xmlres=_xmlres[0];
}else{
for(_i=0;_i<_xmlres.length;_i++){
_attperiodo=_xmlres[_i].getAttribute("periodo");
if(_attperiodo==_periodo){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
}
if(_xmlres.childNodes.length>1){ //con subnodos
_xmlres=_xmlres.getElementsByTagName(_elem2);
if(_xmlres.length==0){return null;}
if(_xmlres.length==1){
_xmlres=_xmlres[0];
}else{
//en 7d: dato
for(_i=0;_i<_xmlres.length;_i++){
_atthora=_xmlres[_i].getAttribute("hora");
if(_atthora==_periodo){break;}
}
if(_i==_xmlres.length){return null;}
_xmlres=_xmlres[_i];
}
}
if(_attr){
_res=_xmlres.getAttribute(_attr);
}else{
if(_xmlres.childNodes.length==0){
_res=null;
}else{
_res=_xmlres.childNodes[0].nodeValue;
}
}
return _res;
}
Sintaxis:
cadena = prediccion("fecha", "periodo", "nodo1"[, "nodo2"[, "atributo"]])
Los nombres de los parámetros son bastante aclaratorios, pero la mejor forma de entender su funcionamiento es con ejemplos.
Ejemplos:
//datos de la próxima hora (XML por horas)
dia = fecha();
hh = periodo(1);
orto = prediccion(dia,"","","","orto");
ocaso = prediccion(dia,"","","","ocaso");
cielo = prediccion(dia,hh,"estado_cielo","","descripcion");
temp = prediccion(dia,hh,"temperatura");
dirviento = prediccion(dia,hh,"viento","direccion");
velviento = prediccion(dia,hh,"viento","velocidad");
//datos de mañana (XML de 7 días)
dia = fecha(1);
h24 = "00-24";
cielo = prediccion(dia,h24,"estado_cielo","","descripcion");
dirviento = prediccion(dia,h24,"viento","direccion");
velviento = prediccion(dia,h24,"viento","velocidad");
uvmax = prediccion(dia,"","uv_max");
Si se intenta extraer un valor que no existe en el XML la función devuelve "null". También devuelve "null" en el caso de que el elemento/nodo esté declarado sin valor (<etiqueta/>).
La subtarea TiempoAEMET
Llegados a este punto ya podemos abordar la creación de una tarea para procesar cualquiera de los XML de AEMET. Me ha parecido que lo más interesante es hacerla como una subtarea que reciba un código INE en %par1 y devuelva el texto de la predicción para mostrarlo o decirlo, a gusto del consumidor.
La subtarea tiene esta pinta...
TiempoAEMET (999)
A1: Establecer variable [ Nombre:%par1 A:01001 ] Si (if) [ %caller1 ~ ui ]
A2: HTTP Get [ Servidor:http://www.aemet.es Ruta:xml/municipios_h/localidad_h_%par1.xml ]
A3: JavaScriptlet [ Código://** parser **
httpd = global("HTTPD");
dom = new DOMParser();
xml = dom.parseFromString(httpd,"text/xml");
//** funciones **
function valornodo(){...}
function prediccion(){...}
function fecha(){...}
function periodo(){...}
//** datos xml **
dia = fecha();
per = periodo();
var ...
var ...
var ...
...
Librerías: Salida Automática:Encendido Cuenta atrás (segundos):45 ]
A4: Establecer variable [ Nombre:%texto A:El cielo en %nombre está %descielo, la temperatura es de %temp grados Añadir:Apagado ]
A5: Establecer variable [ Nombre:%texto A: con sensación de %senter Añadir:Encendido ] Si (if) [ %temp nig %senter ]
A6: Establecer variable [ Nombre:%texto A:, el viento es de %velviento kilómetros por hora Añadir:Encendido ]
A7: Establecer variable [ Nombre:%texto A: con rachas máximas de %rachamax Añadir:Encendido ] Si (if) [ %rachamax > 0 ]
A8: Establecer variable [ Nombre:%texto A:. La probabilidad de lluvia es del %problluvia por ciento. Añadir:Encendido ] Si (if) [ %problluvia > 10 ]
A9: Decir [ Texto:%texto ] Si (if) [ %caller1 ~ ui ]
A10: Devolver [ Valor:%texto Detener:Encendido ]
A1 y A9 están puestas para que sea posible hacer pruebas directamente desde el editor de Tasker.
A2 obtiene el XML de AEMET.
A4-A8 se encargan de componer el %texto final con la predicción.
Y A10 devuelve el %texto final a la tarea desde la que se llama a TiempoAEMET.
El contenido de A3 (el JavaScriptlet) requiere unas aclaraciones adicionales. :rolleyes:
En el apartado de ** funciones ** se tiene que copiar el código de las cuatro funciones que he explicado anteriormente. No lo he puesto para dejar la tarea un poco más limpia a la vista.
El apartado ** datos xml ** es para definir las variables que almacenarán los valores que nos interesan del XML. Esas definiciones tienen que comenzar con la instrucción "var ..." de JS para que pasen a Tasker como variables locales manejables desde la tarea. Es decir, la definición de variable JS "var nombre ..." genera la variable local %nombre en la tarea de Tasker.
Y la forma de usar esta subtarea podría ser tal que así...
Decir el tiempo (888)
A1: Realizar tarea [ Nombre:TiempoAEMET Prioridad:%priority Parámetro 1 (%par1):10001 Parámetro 2 (%par2): Devolver Valor de Variable:%resultado ]
A2: Decir [ Texto:%resultado ]
Casos prácticos
Al explicar cada función he puesto dos o tres ejemplos de cómo se usa cada una de ellas, pero puede que no sea tan evidente cómo se usan en conjunto. A continuación os dejo unos casos prácticos que sacan TODOS los valores de cada tipo de predicción.
Todos los valores para la hora actual del XML de la "Predicción por horas"
//** datos xml **
dia = fecha();
h0 = periodo();
r6 = periodo(0,"r6");
var enlace = valornodo("enlace");
var elaborado = valornodo("elaborado");
var nombre = valornodo("nombre");
nombre=nombre.split("/");
nombre=nombre[nombre.length-1];
var provincia = valornodo("provincia");
provincia=provincia.split("/");
provincia=provincia[provincia.length-1];
var orto = prediccion(dia,"","","","orto");
var ocaso = prediccion(dia,"","","","ocaso");
var descielo = prediccion(dia,h0,"estado_cielo","","descripcion");
var estcielo = prediccion(dia,h0,"estado_cielo");
var lluvia = prediccion(dia,h0,"precipitacion");
var problluvia = prediccion(dia,r6,"prob_precipitacion");
var probtorm = prediccion(dia,r6,"prob_tormenta");
var nieve = prediccion(dia,h0,"nieve");
var probnieve = prediccion(dia,r6,"prob_nieve");
var temp = prediccion(dia,h0,"temperatura");
var senter = prediccion(dia,h0,"sens_termica");
var humrel = prediccion(dia,h0,"humedad_relativa");
var dirviento = prediccion(dia,h0,"viento","direccion");
var velviento = prediccion(dia,h0,"viento","velocidad");
var rachamax = prediccion(dia,h0,"racha_max");
Todos los valores para la hora actual del XML de la "Predicción 7 días"
//** datos xml **
dia = fecha();
r6 = periodo(0,"r6-");
h6 = periodo(0,"h6");
var enlace = valornodo("enlace");
var elaborado = valornodo("elaborado");
var nombre = valornodo("nombre");
nombre=nombre.split("/");
nombre=nombre[nombre.length-1];
var provincia = valornodo("provincia");
provincia=provincia.split("/");
provincia=provincia[provincia.length-1];
var problluvia = prediccion(dia,r6,"prob_precipitacion");
var cotanieve = prediccion(dia,r6,"cota_nieve_prov");
var descielo = prediccion(dia,r6,"estado_cielo","","descripcion");
var estcielo = prediccion(dia,r6,"estado_cielo");
var dirviento = prediccion(dia,r6,"viento","direccion");
var velviento = prediccion(dia,r6,"viento","velocidad");
var rachamax = prediccion(dia,r6,"racha_max");
var temp = prediccion(dia,"","temperatura","maxima");
var tempmin = prediccion(dia,"","temperatura","minima");
var dtem = prediccion(dia,h6,"temperatura","dato");
var senter = prediccion(dia,"","sens_termica","maxima");
var sentermin = prediccion(dia,"","sens_termica","minima");
var dster = prediccion(dia,h6,"sens_termica","dato");
var humrel = prediccion(dia,"","humedad_relativa","maxima");
var humrelmin = prediccion(dia,"","humedad_relativa","minima");
var dhrel = prediccion(dia,h6,"humedad_relativa","dato");
var uvmax = prediccion(dia,"","uv_max");
Todos los valores para TODOS los días de la "Predicción 7 días" (ver mensaje #10)
//** datos xml **
var diapred=[];
var problluvia=[];var cotanieve=[];
var descielo=[];var estcielo=[];
var dirviento=[];var velviento=[];var rachamax=[];
var tempmax=[];var tempmin=[];
var sentermax=[];var sentermin=[];
var hummax=[];var hummin=[];
var uvmax=[];
for(xd=-1;xd!=7;xd++){
dia=fecha(xd);
xfecha=prediccion(dia,"","","","fecha");
if(xfecha){
diapred.push(prediccion(dia,"","","","fecha"));
problluvia.push(prediccion(dia,"00-24","prob_precipitacion"));
cotanieve.push(prediccion(dia,"00-24","cota_nieve_prov"));
descielo.push(prediccion(dia,"00-24","estado_cielo","","descripcion"));
estcielo.push(prediccion(dia,"00-24","estado_cielo"));
dirviento.push(prediccion(dia,"00-24","viento","direccion"));
velviento.push(prediccion(dia,"00-24","viento","velocidad"));
rachamax.push(prediccion(dia,"00-24","racha_max"));
tempmax.push(prediccion(dia,"","temperatura","maxima"));
tempmin.push(prediccion(dia,"","temperatura","minima"));
sentermax.push(prediccion(dia,"","sens_termica","maxima"));
sentermin.push(prediccion(dia,"","sens_termica","minima"));
hummax.push(prediccion(dia,"","humedad_relativa","maxima"));
hummin.push(prediccion(dia,"","humedad_relativa","minima"));
uvmax.push(prediccion(dia,"","uv_max"));
}
}
Añade máximos y mínimos al bloque de datos anterior (ver mensaje #10)
//** máximos y mínimos **
var problluvia0,mpll=0;
var cotanieve0,mcn=0;
var velviento0,mvv=0;
var rachamax0,mrx=0;
var tempmax0,mtx=0;
var tempmin0,mtm=999;
var senterpmax0,mstx=0;
var sentermin0,mstm=999;
var hummax0,mhx=0;
var hummin0,mhm=999;
var uvmax0,mux=0;
for(i=0;i<diapred.length;i++){
if(problluvia[i]>mpll){mpll=problluvia[i]/1;problluvia0=i+1;}
if(cotanieve[i]>mcn){mcn=cotanieve[i]/1;cotanieve0=i+1;}
if(velviento[i]>mvv){mvv=velviento[i]/1;velviento0=i+1;}
if(rachamax[i]>mrx){mrx=rachamax[i]/1;rachamax0=i+1;}
if(tempmax[i]>mtx){mtx=tempmax[i]/1;tempmax0=i+1;}
if(tempmin[i]<mtm){mtm=tempmin[i]/1;tempmin0=i+1;}
if(sentermax[i]>mstx){mstx=sentermax[i]/1;sentermax0=i+1;}
if(sentermin[i]<mstm){mstm=sentermin[i]/1;sentermin0=i+1;}
if(hummax[i]>mhx){mhx=hummax[i]/1;hummax0=i+1;}
if(hummin[i]<mhm){mhm=hummin[i]/1;hummin0=i+1;}
if(uvmax[i]>mux){mux=uvmax[i]/1;uvmax0=i+1;}
}
Iconos de cielo/viento/uv
En la web de AEMET tienen unos pequeños iconos para el estado del cielo, la dirección del viento y el índice ultravioleta que os podrían venir bien para vuestra tarea. Añadiendo el siguiente código JS justo después del bloque ** datos xml ** también dispondréis en la tarea de Tasker de unas variables locales con las URLs de las imágenes correspondientes a esos valores.
//** iconos **
var icocielo = (estcielo) ? "http://www.aemet.es/imagenes_gcd/_iconos_municipios/"+estcielo+".png" : null;
var icoviento = (dirviento) ? "http://www.aemet.es/imagenes_gcd/_iconos_municipios/"+dirviento+".png" : null;
var icouvmax = (uvmax) ? "http://www.aemet.es/imagenes_gcd/_iconos_localidades/uvi_c"+uvmax+"_pred.gif" : null;
Precauciones:
Este código JS requiere que las variables estcielo/dirviento/uvmax tengan un valor definido.
Si se usa la variante que saca los ** datos xml ** de TODOS los días se tendrá que modificar ligeramente este código.
La dirección del viento
Otra cosa que os puede interesar, sobre todo si queréis "Decir" el tiempo, es la versión "pronunciable" de las letras de la dirección del viento. Añadiendo este código JS después del bloque ** datos xml ** tendréis en la tarea una variable local más con esa conversión.
//** viento **
vd = {N:"norte",NE:"nordeste",E:"este",SE:"sudeste",S:"sur",SO:"suroeste",O:"oeste",NO:"noroeste",C:"calma"};
var vientodel = (dirviento) ? vd[dirviento] : null;
Precauciones:
Este código JS requiere que la variable dirviento tenga un valor definido.
Si se usa la variante que saca los ** datos xml ** de TODOS los días se tendrá que modificar ligeramente este código.
Y eso es todo. Ya tenéis entretenimiento. :cucu: