Basado en el proyecto de la rapsberrypi org ¿Fetching the weather?
Introducción.
En esta píldora te mostraremos como acceder a los datos del clima que nos proporciona la AEMET (Agencia Estatal de Meteorología Española) usando una RESTful API, como usar la fórmula de haversine para calcular que estación meteorológica esta mas próxima a ti y como capturar los últimos datos del clima de esta estación.
Que aprenderemos.
- Como acceder a un API RESTful en Python.
- Como convertir datos en formato JSON en un diccionario.
- Como imprimir datos en un formato elegante con el módulo rich
- Como calcular las distancias entre dos puntos de la superficie terrestre.
Que necesitaremos.
Capturando datos sobre el tiempo.
Encontrando una estación meteorológica.
Nos enviarán un correo de confirmación y a continuación nos enviarán un segundo correo con la clave de acceso. La tendremos bien a mano para usarla posteriormente.
estaciones.py
import requests
# El print del módulo rich sobreescribe el print normal de Python.
from rich import print
estaciones.py
import requests from rich import print url = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/" # API Key proporcionada por la AEMET KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY" querystring = {"api_key": KEY} headers = { 'cache-control': "no-cache" } respuesta = requests.request("GET", url, headers=headers, params=querystring) print(respuesta) diccionario = respuesta.json() print(diccionario)
<Response [200]>
{ "descripcion": "exito", "estado": 200, "datos": "https://opendata.aemet.es/opendata/sh/793dafaa", "metadatos": "https://opendata.aemet.es/opendata/sh/0556af7a" }
estaciones.py
import requests from rich import print url = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/" # API Key proporcionada por la AEMET KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY" querystring = {"api_key": KEY} headers = { 'cache-control': "no-cache" } respuesta = requests.request("GET", url, headers=headers, params=querystring) diccionario = respuesta.json() URL = diccionario['datos'] print(URL) respuesta = requests.get(URL) estaciones = respuesta.json() # Número de estaciones metereológicas print(f'Hay {len(estaciones)} estaciones meteorológicas registradas en la AEMET')
https://opendata.aemet.es/opendata/sh/793dafaa
Hay 947 estaciones meteorológicas registradas en la AEMET
estaciones.py
# ...
print(estaciones[0])
<Response [200]>
{
'descripcion': 'exito',
'estado': 200,
'datos': 'https://opendata.aemet.es/opendata/sh/793dafaa',
'metadatos': 'https://opendata.aemet.es/opendata/sh/0556af7a'
}
https://opendata.aemet.es/opendata/sh/793dafaa
Hay 947 estaciones meteorológicas registradas en la AEMET
{
'latitud': '394924N',
'provincia': 'ILLES BALEARS',
'altitud': '490',
'indicativo': 'B013X',
'nombre': 'ESCORCA, LLUC',
'indsinop': '08304',
'longitud': '025309E'
}
- latitud - la coordenada de la latitud de la estación
- provincia - la provincia en la que se encuentra
- altitud - la altitud respecto al nivel del mar de la misma
- indicativo - es el código que identifica la estación meteorológica. (indicativo climatológico)
- nombre - Como se denomina la estación para la AEMET
- indsinop - Si es una estación que emite partes SYNOP, su identificador sinóptico.
- longitud - la coordenada de la longitud de la estación.
- 39 grados
- 49 minutos
- 24 segundos
- La N al final indica que esta al NORTE del ecuador.
- 02 grados
- 53 minutos
- 09 segundos
- La E al final indica que esta al este del meridiano de Greenwich
Obteniendo el clima más reciente.
El idema o indicativo de la estación es un parámetro obligatorio. Puedes usar la estación que quieras pero para este ejemplo vamos a seguir con la estación que utilizamos antes, la primera del listado cuyo idema es "B013X". Añade el siguiente código al programa.
estaciones.py
# ...
URL = "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/B013X"
respuesta = requests.request("GET", URL, headers=headers, params=querystring)
print(respuesta)
datos = respuesta.json()
print(datos)
URL = datos['datos']
respuesta = requests.get(URL)
datos_clima = respuesta.json()
print(datos_clima)
<Response [200]>
{
'descripcion': 'exito',
'estado': 200,
'datos': 'https://opendata.aemet.es/opendata/sh/c42c6f47',
'metadatos': 'https://opendata.aemet.es/opendata/sh/55c2971b'
}
[
{
'idema': 'B013X',
'lon': 2.885828,
'fint': '2024-04-03T21:00:00',
'prec': 0.0,
'alt': 490.0,
'vmax': 2.6,
'vv': 0.5,
'dv': 94.0,
'lat': 39.823338,
'dmax': 126.0,
'ubi': 'LLUC',
'hr': 80.0,
'tamin': 10.4,
'ta': 10.4,
'tamax': 12.5,
'tpr': 7.1,
'rviento': 28.0
},
{
'idema': 'B013X',
'lon': 2.885828,
'fint': '2024-04-03T22:00:00',
'prec': 0.0,
'alt': 490.0,
'vmax': 1.6,
'vv': 0.3,
'dv': 111.0,
'lat': 39.823338,
'dmax': 112.0,
'ubi': 'LLUC',
'hr': 88.0,
'tamin': 8.8,
'ta': 8.8,
'tamax': 10.3,
'tpr': 7.0,
'rviento': 12.0
},
# ...
Se nos proporciona los datos de observación horarios de las últimas 24 horas de la estación meteorológica que se pasa como parámetro (idema). Frecuencia de actualización: continuamente.
Los principales datos que se nos proporcionan son los siguientes:
1.- Indicativo climatológico de la estación seleccionada.
2.- longitud de la estación meteorológica (grados)
3.- 'FINT' Fecha hora final del período de observación, se trata de datos del periodo de la hora anterior a la indicada por este campo (hora UTC) (AAAA-MM-DDTHH:MM:SS)
4.- precipitación acumulada durante los 60 minutos anteriores.
5.- altitud de la estación en metros.
6.- Velocidad máxima del viento, valor máximo del viento mantenido 3 segundos y registrado en los 60 minutos anteriores a la hora indicada por el período de observación 'fint' (m/s)"
7.- velocidad media del viento.
8.- dirección del viento (grados)
9.- latitud de la estación meteorológica. (grados)
10.- Las que empiezan por t dan información de la temperatura.
Longitud y Latitud.
- Para encontrar la latitud y longitud de un lugar abre Google Maps en el ordenador.
Calculando la distancia entre dos puntos de la tierra.
- Empezaremos creando un nuevo programa en el mismo directorio y lo llamaremos haversine.py
- A continuación importaremos unas cuantas funciones matemáticas que vamos a utilizar.
from math import radians, cos, sin, asin, sqrt
- Ahora ya podemos crear una nueva función a la que llamaremos haversine. Tomará cuatro argumentos que son la latitud y longitud pero de dos puntos. Uno será la de una estación meteorológica y el otro el punto del lugar que hayamos escogido.
def haversine(lon1, lat1, lon2, lat2):
- La fórmula que vamos a utilizar requiere que se utilicen radianes en lugar de grados cuando tratemos con ángulos así que lo primero que haremos será convertirlos, pasando cada longitud y latitud de ambos puntos a radianes.
def haversine(lon1, lat1, lon2, lat2):
#convertir ángulos de grados a radianes
lon1 = radians(lon1)
lat1 = radians(lat1)
lon2 = radians(lon2)
lat2 = radians(lat2)
Ahora queremos encontrar la diferencia entre las dos longitudes y las dos latitudes así que añadimos esto a la función.
dlon = lon2 - lon1
dlat = lat2 - lat1
A continuación aplicamos la fórmula de haversine. Si quieres aprender más sobre ella puedes leer este articulo de la wikipedia. De cualquier forma, el resumen es que con esta formula vamos a calcular la distancia entre los dos puntos, quédate con eso.
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
distancia = 2 * asin(sqrt(a)) * 6371
#6371 es el radio de la tierra.
return distancia
El número que aparece arriba, 6371, es el radio de la tierra.
Guarda el archivo y desde el Shell de Python ejecuta el siguiente código:
>>> from haversine import haversine
>>> haversine(74.0059, 40.7128, 0.1278, 51.5074)
Deberías haber obtenido como respuesta 5570. Esta es la distancia desde Londres a Nueva York. Puedes comprobar la respuesta en internet si quieres, aunque la distancia puede variar un poco ya que la tierra no es una esfera perfecta, pero para nuestro fines nos basta.
- Puedes probar con más latitudes y longitudes de Google Maps.
Comprueba el tiempo en tu localidad.
Al comienzo de este post, recuperamos los datos del tiempo de la primera estación metereológica que nos salia en la lista de la AEMET. Los datos de las estaciones venían en una lista en las que los datos de cada estación estaban guardados en un diccionario. Iterando a través de esta lista, podemos coger la longitud y latitud de cada estación. Luego podemos pasar esos datos a la función de haversine para encontrar la más cercana.
Creemos un nuevo archivo para unir todos los conceptos anteriores. Puedes llamarlo como quieras, yo lo llamaré tiempo.py y asegúrate que este en el mismo directorio que haversine.py.
- Comienza importando los modulos requests y rich, que ya habíamos usado antes y también el módulo haversine anterior.
import request
from rich import print
from haversine import haversine
- Anteriormente usamos dos URLs para obtener la estaciones y los últimos datos del tiempo. Podemos en este momento declararlas.
estaciones = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/"
clima =
"https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/"
- No nos podemos olvidar de añadir la API Key que obtuvimos de la AEMET.
KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY"
- Ahora añade en las variables la latitud y longitud de tu ubicación. Por ejemplo para la localidad de Aguilar de Campoo las coordenadas son latitud 42,75948 y la longitud -4,26946
mi_lat = 42.75948
mi_lon = -4.26946
- Para finalizar este apartado, vamos a obtener la lista de todas las estaciones, al igual que hicimos la principio del post. Añade el código resaltado en azul.
tiempo.py
import requests
from rich import print
from haversine import haversine
# URLs de las estaciones y de los datos del clima
estaciones = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/"
clima = "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/"
# API Key proporcionada por la AEMET
KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY"
# Mis coordenadas
mi_lat = 42.75948
mi_lon = -4.26946
# Listado de todas las estaciones metereológicas.
querystring = {"api_key": KEY}
headers = {
'cache-control': "no-cache"
}
respuesta = requests.request("GET", estaciones, headers=headers, params=querystring)
datos_estaciones = respuesta.json()
lista_estaciones = requests.get(datos_estaciones['datos'])
lista_estaciones = lista_estaciones.json()
print(lista_estaciones)
Encuentra la estación más cercana.
- Empezaremos definiendo una nueva función y creando una variable que recoja la distancia más pequeña. La mayor distancia que puede haber entre dos puntos de la superficie de la tierra es de 20.036km, así que este puede ser un buen valor para iniciar la variable.
def encontrar_cercana():
menor_distancia = 20036
for station in all_stations:
print(station)
tiempo.py
import requests from rich import print from haversine import haversine # URLs de las estaciones y de los datos del clima estaciones = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/" clima = "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/" # API Key proporcionada por la AEMET KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY" # Mis coordenadas mi_lat = 42.75948 mi_lon = -4.26946 # Listado de todas las estaciones metereológicas. querystring = {"api_key": KEY} headers = { 'cache-control': "no-cache" } respuesta = requests.request("GET", estaciones, headers=headers, params=querystring) datos_estaciones = respuesta.json() lista_estaciones = requests.get(datos_estaciones['datos']) lista_estaciones = lista_estaciones.json() print(lista_estaciones) def encontrar_cercana(): menor_distancia = 20_036 for estacion in estaciones: print(estacion) encontrar_cercana()
{
'latitud': '394924N',
'provincia': 'ILLES BALEARS',
'altitud': '490',
'indicativo': 'B013X',
'nombre': 'ESCORCA, LLUC',
'indsinop': '08304',
'longitud': '025309E'
}
- Vuelve al programa; conseguiremos esos valores a través de la función encontrar_cercana. Elimina las siguientes líneas del programa:
estacion_lon = estacion['longitud']
estacion_lat = estacion['latitud']
haversine.py
# ... def dms_to_decimal(coord): # Verificar si la longitud de la coordenada es válida if len(coord) != 7: return None # Extraer los componentes de la coordenada grados = int(coord[:2]) minutos = int(coord[2:4]) segundos = int(coord[4:6]) direccion = coord[-1] # Verificar la validez de la dirección if direccion not in ['N', 'S', 'E', 'W']: return None # Calcular la coordenada decimal decimal = grados + (minutos / 60) + (segundos / 3600) if direccion in ['N', 'E'] else -(grados + (minutos / 60) + (segundos / 3600)) return decimal
tiempo.py
import requests from rich import print from haversine import haversine, dms_to_decimal # URLs de las estaciones y de los datos del clima estaciones = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/" clima = "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/" # API Key proporcionada por la AEMET KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY" # Mis coordenadas (Aguilar de Campoo) mi_lat = 42.75948 mi_lon = -4.26946 # Listado de todas las estaciones metereológicas. # Listado de todas las estaciones metereológicas. querystring = {"api_key": KEY} headers = { 'cache-control': "no-cache" } respuesta = requests.request("GET", estaciones, headers=headers, params=querystring) datos_estaciones = respuesta.json() lista_estaciones = requests.get(datos_estaciones['datos']) estaciones = lista_estaciones.json() print(estaciones) def encontrar_cercana(): menor_distancia = 20_036 for estacion in estaciones: estacion_lon = estacion['longitud'] estacion_lat = estacion['latitud'] #convertimos grados dms a grados decimales estacion_lon = dms_to_decimal(estacion_lon) estacion_lat = dms_to_decimal(estacion_lat) distancia = haversine(mi_lon, mi_lat, estacion_lon, estacion_lat) print(distancia) encontrar_cercana()
tiempo.py
# ... def encontrar_cercana(): menor_distancia = 20_036 for estacion in estaciones: estacion_lon = estacion['longitud'] estacion_lat = estacion['latitud'] #convertimos grados dms a grados decimales estacion_lon = dms_to_decimal(estacion_lon) estacion_lat = dms_to_decimal(estacion_lat) distancia = haversine(mi_lon, mi_lat, estacion_lon, estacion_lat) # si la distancia es menor que la almacenada la guardamos if distancia < menor_distancia: menor_distancia = distancia estacion_mas_cercana = estacion['indicativo'] return estacion_mas_cercana print(encontrar_cercana())
Obtención de los datos del tiempo.
- Añadiremos este valor a la URL que tenemos guardada en la variable clima, que está al principio del programa.
idema = encontrar_cercana()
clima = clima + str(idema)
tiempo.py
import requests
from rich import print
from haversine import haversine, dms_to_decimal
# URLs de las estaciones y de los datos del clima
estaciones = "https://opendata.aemet.es/opendata/api/valores/climatologicos/inventarioestaciones/todasestaciones/"
clima = "https://opendata.aemet.es/opendata/api/observacion/convencional/datos/estacion/"
# API Key proporcionada por la AEMET
KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY"
# Mis coordenadas
mi_lat = 42.75948
mi_lon = -4.26946
# Listado de todas las estaciones metereológicas.
querystring = {"api_key": KEY}
headers = {
'cache-control': "no-cache"
}
respuesta = requests.request("GET", estaciones, headers=headers, params=querystring)
datos_estaciones = respuesta.json()
lista_estaciones = requests.get(datos_estaciones['datos'])
estaciones = lista_estaciones.json()
def encontrar_cercana():
menor_distancia = 20_036
for estacion in estaciones:
estacion_lon = estacion['longitud']
estacion_lat = estacion['latitud']
#convertimos grados dms a grados decimales
estacion_lon = dms_to_decimal(estacion_lon)
estacion_lat = dms_to_decimal(estacion_lat)
distancia = haversine(mi_lon, mi_lat, estacion_lon, estacion_lat)
# si la distancia es menor que la almacenada la guardamos
if distancia < menor_distancia:
menor_distancia = distancia
estacion_mas_cercana = estacion['indicativo']
return estacion_mas_cercana
idema = encontrar_cercana()
print(f"La estación mas cercana tiene el idema {idema}")
clima = clima + str(idema)
respuesta = requests.request("GET", clima, headers=headers, params=querystring)
datos_estacion = respuesta.json()
print(datos_estacion)
respuesta = requests.get(datos_estacion['datos'])
datos_clima = respuesta.json()
# La respuesta nos da los datos de las ultimas 24 horas
# Queremos imprimir solo la última observación.
print(datos_clima[-1])
La estación mas cercana tiene el idema 2243A
{
'descripcion': 'exito',
'estado': 200,
'datos': 'https://opendata.aemet.es/opendata/sh/d6828e71',
'metadatos': 'https://opendata.aemet.es/opendata/sh/55c2971b'
}
{
'idema': '2243A',
'lon': -4.277134,
'fint': '2024-04-06T17:00:00',
'prec': 0.0,
'alt': 910.0,
'vmax': 14.9,
'vv': 6.9,
'dv': 270.0,
'lat': 42.79806,
'dmax': 277.0,
'ubi': 'AGUILAR DE CAMPOO',
'hr': 60.0,
'tamin': 14.3,
'ta': 14.3,
'tamax': 14.9
}
No hay comentarios:
Publicar un comentario