domingo, 7 de abril de 2024

Captura el clima.

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.


- El módulo rich de Python.


Capturando datos sobre el tiempo.


Hay un montón de estaciones meteorológicas que registran datos para la Agencia Española de Meteorología. (AEMET). En ellas se recogen un gran cantidad de datos de forma constante y se envían a la base de datos de la AEMET, en donde se guardan y a los cuales, junto con otra mucha información, podemos acceder.

En este post veremos como acceder a la información de la estación meteorológica en la que estés interesado, y como obtener los últimos datos climáticos de la misma.


Encontrando una estación meteorológica.


Lo primero que necesitaremos para este post es conseguir una API Key o clave de acceso para poder obtener datos de la AEMET Opendata. Esta operación es muy sencilla y tan solo tenemos que acceder a la siguiente página https://opendata.aemet.es/centrodedescargas/inicio y hacemos clic en el botón "Solicitar" debajo de la obtención de API Key.


obtención de la API KEY


A continuación nos pedirán una dirección de correo electrónico para generar y enviarnos dicha clave.

correo para enviar el API

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.

Vamos a ver como podemos obtener un listado con todas las estaciones de las que dispone la AEMET. Creamos un fichero de Python, como por ejemplo estaciones.py. Lo primero que vamos a necesitar es importar los módulos que vamos a utilizar. Requests no es un módulo que pertenezca a la biblioteca estandar así que tenemos que instalarlo previamente con:

pip install requests

Y lo mismo ocurre con el módulo rich, así que lo instalaremos con:

pip install rich

Escribimos el siguiente código:

estaciones.py

import requests
# El print del módulo rich sobreescribe el print normal de Python.
from rich import print

El módulo requests nos permite capturar datos de las páginas web, bien sea código html o datos que nos proporcione la página en cuestión a través de un API. Por su parte el módulo rich nos sirve para mostrar los datos de una forma más clara y bonita. Si en vez de instalar rich quieres hacer algo parecido con un módulo de la biblioteca estandar puedes usar pprint.

La primera cosa que tendremos que hacer es guardar la URL que nos proporcionará las estaciones disponibles. Esta y mucha más páginas las puedes obtener entrando en la página  https://opendata.aemet.es/centrodedescargas/inicio, y accediendo a donde pone "Acceso Desarrolladores".

Acceso desarrolladores

Nos interesa acceder a la opción "Documentación AEMET OpenData. HATEOAS"

aemet opendata

y concretamente nos interesa esta:

api inventario estaciones

Podemos pulsar sobre cada tema y se mostrará la síntesis a usar para cada petición de datos. 

Vamos a comenzar consiguiendo un objeto que contenga la información que necesitamos. Escribe el siguiente código:

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)


Nota: para no saturar de peticiones al servidor, solo se permite un número de usos de la API Key por día.

Si todo ha salido bien, el cuerpo de la respuesta es el siguiente:

<Response [200]>

Y el diccionario que obtenemos al transformar los datos en formato JSON que estaban en la respuesta será algo parecido a esto:

{
  "descripcion": "exito",
  "estado": 200,
  "datos": "https://opendata.aemet.es/opendata/sh/793dafaa",
  "metadatos": "https://opendata.aemet.es/opendata/sh/0556af7a"
}
En este código se utiliza la biblioteca 'requests' para realizar una solicitud HTTP a una API de la Agencia Estatal de Meteorología (AEMET) de España, específicamente para obtener información sobre estaciones climatológicas.

Aquí está la explicación línea por línea:

1. 'import requests': Importa la biblioteca `requests`, que se utiliza para enviar solicitudes HTTP en Python.

2. `from rich import print`: Importa la función `print` de la biblioteca `rich`, que se utiliza para imprimir texto con formato enriquecido en la consola.

3. Se define la URL de la API de AEMET que se utilizará para obtener la información de las estaciones climatológicas.

4. `KEY = "...NiJ9.eyJzdWIi.....IjoiIn0.twKIGtehh...............................TgOYFY"`: Aquí se define una clave de API proporcionada por AEMET para autenticación. Esta clave se utiliza más adelante en los parámetros de la solicitud.

5. `querystring = {"api_key": KEY}`: Se define un diccionario `querystring` que contiene los parámetros que se enviarán con la solicitud. En este caso, el único parámetro es `api_key`, que tiene el valor de la clave de API definida anteriormente.

6. `headers = {'cache-control': "no-cache"}`: Se define un diccionario `headers` que contiene los encabezados HTTP para la solicitud. En este caso, el único encabezado es `cache-control`, que se establece en `no-cache`, lo que indica que no se debe almacenar en caché la respuesta.

7. `respuesta = requests.request("GET", url, headers=headers, params=querystring)`: Se realiza una solicitud GET a la URL definida anteriormente, utilizando los encabezados y parámetros definidos. La respuesta se guarda en la variable `respuesta`.

8. `diccionario = respuesta.json()`: Se convierte la respuesta de la solicitud que está en formato JSON en un diccionario de Python y se guarda el mismo en la variable, que hemos llamado diccionario.

En resumen, este código realiza una solicitud a la API de AEMET para obtener información sobre las estaciones climatológicas, utilizando una clave de API para autenticación, y luego procesa la respuesta en formato JSON transformándola en un diccionario de Python.

Lo que nos interesa obtener esta bajo la clave del diccionario "datos" cuyo valor en el ejemplo es "https://opendata.aemet.es/opendata/sh/793dafaa". Volveremos a realizar una petición a esta dirección URL que nos han facilitado para obtener de nuevo un objeto de tipo lista con todas las estaciones disponibles. Pero como son muchas vamos primero a obtener su número. Añade al archivo estaciones.py el siguiente código resaltado en azul:

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')
La salida del programa será algo parecido a esto:

https://opendata.aemet.es/opendata/sh/793dafaa
Hay 947 estaciones meteorológicas registradas en la AEMET
Podríamos imprimir las 947 estaciones recorriendo la lista, pero para nuestros propósitos solo vamos a imprimir la primera para ver su estructura. Añade al final del archivo esta línea:

estaciones.py

# ...
print(estaciones[0])
Vuelve a ejecutar el programa y verás algo parecido a esto:

<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'
}
Lo que estás viendo se corresponde con los siguientes campos:

  • 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.
Destacar que las coordenadas que nos facilitan están en el formato que se conoce como "Coordenadas geográficas en grados, minuto y segundos (DMS por sus siglas en inglés). Vamos a explicarlo con el ejemplo de la primera estación que hemos obtenido.

La estación de "ESCORCA, LLUC" tiene una latitud de:
  • 39 grados
  • 49 minutos
  • 24 segundos 
  • La N al final indica que esta al NORTE del ecuador.
La Longitud es:
  • 02 grados
  • 53 minutos
  • 09 segundos
  • La E al final indica que esta al este del meridiano de Greenwich
La cuestión es que normalmente los sitios de internet, como Google Maps por ejemplo, para mostrar coordenadas utilizan otra notación, grados decimales, que es para la latitud de -90 a 90 grados y para la longitud de -180 a 180 grados. Los minutos y segundos son subdivisiones de los grados, donde 1 grado equivale a 60 minutos, y 1 minuto equivale a 60 segundos. Para obtenerlas en base a lo que tenemos, primero deberías convertir los minutos y segundos a fracciones decimales de grado y luego aplicar el signo correcto según la dirección (Norte/Sur para latitud, Este/Oeste para longitud).

Aunque en internet puedes encontrar conversores de grados DMS a grados decimales nosotros lo haremos más tarde directamente mediante código en el programa.

Lo que haremos a continuación es seleccionar una estación meteorológica y obtener los últimos datos del tiempo.

Obteniendo el clima más reciente.


Ahora que tienes una Estación Meteorológica para consultar, puedes aprender cómo obtener la última grabación meteorológica de esa estación. Esto se realiza nuevamente utilizando la API RESTful de la base de datos de la Estación Meteorológica. Esta vez, la URL que necesitamos es la siguiente:

URL para obtener los datos de una estación

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)
SALIDA:

<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.


Sería bueno si pudieras seleccionar una Estación Meteorológica que esté cerca de ti para obtener los mejores datos. Puedes hacer esto porque la base de datos almacena la longitud y latitud de todas las Estaciones Meteorológicas de España. Echemos un vistazo a lo que queremos decir con longitud y latitud.

Si quisieras señalar un lugar en un objeto 2D como una hoja de papel, podrías usar coordenadas x e y. La coordenada x ubicaría la posición horizontal del punto, y la coordenada y ubicaría la posición vertical. Puedes ver un ejemplo de esto a continuación.


representación de coordenadas

Las cosas no son tan simples cuando intentas señalar una ubicación en una esfera, como la Tierra. Las posiciones vertical y horizontal se envuelven alrededor de la esfera, para empezar. Además, viajar 5 unidades de distancia a lo largo del ecuador sería una distancia completamente diferente a caminar 5 unidades de distancia cerca de uno de los polos. Por esta razón, usamos longitud y latitud para ubicar elementos en la superficie de la Tierra.

Puedes dibujar dos círculos imaginarios alrededor de la Tierra. El primero se llama ecuador, con el que probablemente estés familiarizado. El segundo se llama meridiano de Greenwich, que pasa por ambos polos Norte y Sur y también por Greenwich en Londres.

longitud y latitud del mundo



El centro de estos dos círculos está en el centro de la Tierra. Imagina que estás de pie en el centro de la Tierra; podrías señalar cualquier ubicación en la superficie hablando sobre cuántos grados necesitas girar dentro de cada uno de estos círculos. La longitud te indica cuántos grados necesitas girar hacia el este o hacia el oeste desde el meridiano de Greenwich. La latitud te indica cuántos grados necesitas girar hacia el norte o hacia el sur desde el ecuador.

mapa del mundo en coordenadas

  • Para encontrar la latitud y longitud de un lugar abre Google Maps en el ordenador.
Busca el lugar del que quieras obtener la latitud y longitud. Haz clic con el botón derecho en el sitio o en el área del mapa. Se abrirá una ventana emergente. Puede ver la latitud y longitud en formato decimal en la parte superior.

latitud y longitud en google maps


El primer número es la latitud y el segundo la longitud. 

Toma nota de los valores porque los necesitaras posteriormente.


Calculando la distancia entre dos puntos de la tierra.


La siguiente parte es un poco técnica. Necesitas poder encontrar la distancia entre dos puntos en la Tierra, dadas sus longitudes y latitudes. Esto te permitirá encontrar la Estación Meteorológica más cercana a ti.

Si no estás particularmente interesado en cómo funciona esto, en lugar de escribir el código, puedes descargar el archivo que necesitas desde aquí. Solo asegúrate de guardarlo como haversine.py y almacenarlo en el mismo directorio que el resto de tu código.

Como se discutió anteriormente, usamos longitud y latitud para calcular la posición exacta de lugares en la Tierra. Encontrar distancias entre estos puntos es bastante complicado, ya que la distancia es sobre la superficie de una esfera y, por lo tanto, no en línea recta. Para hacer este cálculo, necesitas una parte inteligente de las matemáticas llamada fórmula del haversine.

Sin entrar en detalles técnicos, la fórmula del haversine puede proporcionar la distancia entre dos puntos en una esfera utilizando longitudes y latitudes.

  • 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)
código de haversine

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/"

La segunda URLs no está completa, así que necesitaremos añadir la identificación de la estación al final.

  • 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.


Para este trabajo, tendremos que pasar la latitud y la longitud de cada una de las estaciones a través de la función haversine. El truco estará en encontrar aquella más cercana a tu posición en terminos de latitud y longitud y guardarla en una variable.
  • 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

Ahora puedes usar un bucle for para iterar a través de todas las estaciones. Comencemos imprimiendo los datos de cada una de ellas:

for station in all_stations:
    print(station)

Quedaría algo así como esto:

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()
Si ejecutas el programa el resultado será una larga lista de diccionarios. Cada uno de ellos tendrá un aspecto parecido a este, como ya vimos:

{
    'latitud': '394924N',
    'provincia': 'ILLES BALEARS',
    'altitud': '490',
    'indicativo': 'B013X',
    'nombre': 'ESCORCA, LLUC',
    'indsinop': '08304',
    'longitud': '025309E'
}
Los datos que nos interesan son la latitud y la longitud de cada estación. Estos son los valores que queremos pasar a la función haversine.
  • Vuelve al programa; conseguiremos esos valores a través de la función encontrar_cercana. Elimina las siguientes líneas del programa:
          print(lista_estaciones)
          print(estacion)
          encontrar_cercana()

Y añade las siguientes dentro del bucle for, justamente donde estaba la instrucción print(estacion) :
estacion_lon = estacion['longitud']
estacion_lat = estacion['latitud']
Sin embargo aún tenemos un pequeño problemilla. Compara el formato de los datos que nos proporciona la AEMET con los que necesita la función haversine.

                                        AEMET                                función Haversine
___________________________________________________________________________
                                        394924N                                39.823
                                        025309E                                  2.885

¡Efectivamente! Si recuerdas al principio del post vimos que la AEMET proporciona los datos en coordenadas DMS mientras que para hacer los cálculos necesitamos los datos coordenadas decimales.

Aunque no vamos a entrar en detalles vamos a incluir una nueva función dms_to_decimal en el archivo haversine.py para hacer la conversión. Requiere como argumento una cordenada en fomato DMS.

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

Ahora que hemos solventado este problema y tenemos todos los datos vamos a pasárselos a la función haversine para que calcule las distancias existentes entre la estaciones meteorológicas y tu posición.

En el archivo tiempo.py importa también la función dms_to_decimal, el archivo tiempo.py quedaría algo así:

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()
Ejecuta de nuevo el programa para ver que todo funciona. Lo que te encontrarás será una larga lista de distancias al punto donde de te encuentras.

Lo siguiente, es encontrar la distancia más pequeña a tu localización, que es la que nos interesa y luego grabar su identificador para poder saber cual es la estación meteorológica mas próxima. Volvemos a utilizar la variable menor_distancia. Lo que haremos será comparar en cada vuelta del bucle la distancia que nos proporciona la función haversine y compararla con el valor de la variable. Si esta distancia es menor que el valor guardado en la variable menor_distancia la sustituimos por esta. Y así en cada vuelta del bucle.

La función entrar más cercana, ahora debería quedar así:

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())
Ten mucho cuidado con las sangrías, de lo contrario la función no funcionará correctamente.

Obtención de los datos del tiempo.


Ahora que tenemos la identificación de la estación más proxima vamos a obtener sus datos como ya vimos anteriormente. Como la función encontrar_mas_cercana ya nos devuelve el indicativo de la estación, vamos a utilizarla para construir la URL a la que pedir los datos. Recuerda que era esta:


Construcción de la URL de la estación mas proxima

  • 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)



Finalmente usaremos de nuevo el módulo requests para obtener los datos y los imprimiremos de una forma bonita usando print pero el que nos ha facilitado el modulo rich. El código final del programa es el siguiente. La lista que nos facilita la AEMET son los datos de las últimas 24 horas, pero solamente mostraremos la última lectura facilitada.

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 salida final será parecida a esta:

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
}

Puedes encontrar el código de este proyecto en el siguiente enlace de Github.




No hay comentarios:

Publicar un comentario