Mostrando entradas con la etiqueta requests. Mostrar todas las entradas
Mostrando entradas con la etiqueta requests. Mostrar todas las entradas

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.




lunes, 1 de abril de 2024

¿Donde está la Estación Espacial Internacional?

 Basado en el proyecto de la rapsberry pi org ¿Where is the Space Station?

Introducción.


En este proyecto vamos a usar un servicio web para encontrar la localización actual de la Estación Espacial Internacional o ISS por sus siglas en inglés. Con esos datos mostraremos su posición en un mapa.


Instrucciones.


El icono de la Estación Espacial Internacional aparecerá en el mapa mostrando donde está la ISS actualmente. El texto en amarillo nos indicará las personas que actualmente están en el espacio.


¿Quién está en el espacio?


Para saber el número de astronautas que están en la estación espacial internacional, vamos a usar un servicio web que nos va a proporcionar esa información y más.

Un servicio web tiene una dirección web (URL) al igual que muchas de las páginas web que visitas habitualmente. Lo único que en vez de devolvernos una página HTML  lo que nos devuelve son datos.

Prueba a entrar en la siguiente dirección web http://open-notify.org/Open-Notify-API/. Aquí encontraremos toda la información que necesitaremos. Esta página es de uso libre y nos la facilita y mantiene Nathan Bergey.

Comencemos buscando información sobre los astronautas que están en la estación. 

Abre está dirección en el navegador. http://api.open-notify.org/astros.json

Si todo ha ido bien deberías ver algo como esto:

{
  "message": "success",
  "people": [
    {
      "name": "Jasmin Moghbeli",
      "craft": "ISS"
    },
    {
      "name": "Andreas Mogensen",
      "craft": "ISS"
    },
    {
      "name": "Satoshi Furukawa",
      "craft": "ISS"
    },
    {
      "name": "Konstantin Borisov",
      "craft": "ISS"
    },
    {
      "name": "Oleg Kononenko",
      "craft": "ISS"
    },
    {
      "name": "Nikolai Chub",
      "craft": "ISS"
    },
    {
      "name": "Loral O'Hara",
      "craft": "ISS"
    }
  ],
  "number": 7
}

Los datos cambian continuamente, así que seguramente si estás consultando este post verás algo diferente. El formato en el que recibimos los datos se denomina "JSON". Se parecen bastante a los diccionario en Python, aunque ojo que no son iguales.

Vamos a construir un script de Python para poder usar esos datos.

Empieza creando un archivo de python y llámalo como quieras.

Lo primero que haremos es importar las librerías que necesitaremos para nuestro proyecto que son urllib.request, json y turtle

Ahora copia el siguiente código:

""" ¿Donde está la Estación Espacial Internacional? """

import urllib.request
import json
import turtle

# http://open-notify.org/Open-Notify-API/
url = 'http://api.open-notify.org/astros.json'
respuesta = urllib.request.urlopen(url)
astronautas = json.loads(respuesta.read())
print(astronautas)

Salida:

{'message': 'success', 'people': [{'name': 'Jasmin Moghbeli', 'craft': 'ISS'}, {'name': 'Andreas Mogensen', 'craft': 'ISS'}, {'name': 'Satoshi Furukawa', 'craft': 'ISS'}, {'name': 'Konstantin Borisov', 'craft': 'ISS'}, {'name': 'Oleg Kononenko', 'craft': 'ISS'}, {'name': 'Nikolai Chub', 'craft': 'ISS'}, {'name': "Loral O'Hara", 'craft': 'ISS'}], 'number': 7}


Este código utiliza la API (servicio web) de Open Notify para obtener información sobre la Estación Espacial Internacional (ISS, por sus siglas en inglés) y los astronautas que se encuentran en ella en ese momento. Vamos a ver como funciona.

1. `import urllib.request`: Importa el módulo `urllib.request`, que se utiliza para abrir y leer URL.

2. `import json`: Importa el módulo `json`, que se utiliza para trabajar con datos en formato JSON (JavaScript Object Notation).

3. `import turtle`: Importa el módulo `turtle`, que se utiliza para crear gráficos mediante programación.

4. `url = 'http://api.open-notify.org/astros.json'`: Define la URL de la API de Open Notify que proporciona información sobre los astronautas que se encuentran en la Estación Espacial Internacional en ese momento.

5. `respuesta = urllib.request.urlopen(url)`: Abre la URL definida anteriormente y almacena la respuesta en la variable `respuesta`. Es un objeto de Python.

6. `astronautas = json.loads(respuesta.read())`: Lee el contenido de la respuesta HTTP y lo carga como un objeto Python utilizando la función `loads` del módulo `json`. Esto convierte los datos JSON en un diccionario de Python, que se almacena en la variable `astronautas`.

7. `print(astronautas)`: Imprime en la consola el diccionario de Python que contiene la información sobre los astronautas en la ISS.

El diccionario astronautas, como puedes ver, tiene tres claves: message, people y number.

La clave message tiene el valor success lo que nos está diciendo que accedimos correctamente a la información del servicio web. Ten en cuenta que tu verás diferentes valores para la clave people y number dependiendo de quien esté actualmente en el espacio.

Para que la información sea más clara, ignoraremos los nombres de los astronautas de momento y nos centraremos solo en su número. Cambia la función print por:

print(f"Número actual de astronautas: {astronautas['number']}")

Para que los nombres de los astronautas sean más legibles utilizaremos un bucle for para recorrer el diccionario.

""" ¿Donde está la Estación Espacial Internacional? """

import urllib.request
import json
import turtle

# http://open-notify.org/Open-Notify-API/
url = 'http://api.open-notify.org/astros.json'
respuesta = urllib.request.urlopen(url)
astronautas = json.loads(respuesta.read())

print(f"Número actual de astronautas: {astronautas['number']}")
for persona in astronautas['people']:
    print(persona['name'])
Si lo ejecutas verás una salida parecida a esta:

Número actual de astronautas: 7
Jasmin Moghbeli
Andreas Mogensen
Satoshi Furukawa
Konstantin Borisov
Oleg Kononenko
Nikolai Chub
Loral O'Hara

Además de los nombres de los astronautas, la API o servicio web también nos proporciona información sobre la nave en la que están. ¿Sabrías modificar el código para que se muestre esto?

Por ejemplo:

Número actual de astronautas: 7
Jasmin Moghbeli está en ISS
Andreas Mogensen está en ISS
Satoshi Furukawa está en ISS
Konstantin Borisov está en ISS
Oleg Kononenko está en ISS
Nikolai Chub está en ISS
Loral O'Hara está en ISS

¿Dónde está la ISS?


La Estación Espacial Internacional orbita alrededor de la tierra. Completa una orbita a la misma aproximadamente cada hora y media, ya que viaja a una velocidad aproximada de 7,66 km por segundo. Bastante rápido ¿Verdad?

Vamos a volver a usar la API para saber las cordenadas de la ISS. Lo primero es abir el siguiente link en el navegador http://open-notify.org/Open-Notify-API/ISS-Location-Now/. Ahí encontraras la información que necesitas. 

Si ahora introduces esta dirección en el navegador http://api.open-notify.org/iss-now.json verás algo parecido a esto:

{
  "iss_position": {
    "longitude": "55.8408",
    "latitude": "4.2601"
  },
  "message": "success",
  "timestamp": 1711653120
}
El resultado contiene las coordenadas de la tierra sobre la que actualmente está la ISS. Recordemos:

- La latitud indica la posición sobre el eje norte-sur y puede tener valores entre 90 y -90 grados, estando el cero en el ecuador.
- La longitud indica la posición sobre el eje este-oeste y puede tomar valores entre 180 y -180 grados. El cero marca el primer meridiano que es el que atraviesa Greenwich en londres (UK)

Las coordenadas normalmente se dan como (latitud, longitud). En esta web https://www.latlong.net/ puedes encontrar las coordenadas de tu lugar de residencia o de cualquier otro. 

Necesitamos llamar al mismo servicio web desde Python para que nos facilite las coordenadas que necesitamos. Añade el siguiente código al programa, es muy parecido a lo que hicimos antes:

#...
url = 'http://api.open-notify.org/iss-now.json'
respuesta = urllib.request.urlopen(url)
iss_posicion = json.loads(respuesta.read())
print(iss_posicion)
Verás unos datos similares a estos:

{'iss_position': {'longitude': '67.5407', 'latitude': '-36.2401'}, 
'message': 'success', 
'timestamp': 1711659534}
Crearemos unas variables para guardar la latitud y la longitud de la nave. Reorganicemos el código, el programa será algo como esto:

""" ¿Donde está la Estación Espacial Internacional? """

import urllib.request
import json
import turtle

# http://open-notify.org/Open-Notify-API/
url = 'http://api.open-notify.org/astros.json'
respuesta = urllib.request.urlopen(url)
astronautas = json.loads(respuesta.read())

print(f"Número actual de astronautas: {astronautas['number']}")
for persona in astronautas['people']:
    print(f"{persona['name']} está en {persona['craft']}")
    
url = 'http://api.open-notify.org/iss-now.json'
respuesta = urllib.request.urlopen(url)
iss_posicion = json.loads(respuesta.read())
coordenadas = iss_posicion['iss_position']
lat = float(coordenadas['latitude'])
long = float(coordenadas['longitude'])
print(f"Latitud: {lat}, Longitud: {long}")

Posicionando la ISS en un mapa.


Sería útil mostrar su posición en un mapa. Podemos hacer esto fácilmente usando el módulo turtle. Lo primero que haremos será cargar una imagen de fondo facilitada por la NASA.

mapa del mundo


El mapa ya esta centrado en las coordenadas (0, 0) que es justo lo que necesitamos.

También tenemos que hacer coincidir el tamaño de la ventana con el tamaño de nuestra imagen que es de 720 x 360 pixels.

En definitivas cuentas el código que utilizaremos es el siguiente:

# image source:
# map.jpg: https://visibleearth.nasa.gov/view.php?id=57752 Credit: NASA
screen = turtle.Screen()
screen.setup(720, 360)
Por otra parte también necesitamos que sea posible enviar a la tortuga (el puntero del módulo turtle) a una determinada posición en el mapa. Para hacerlo lo más sencillo posible, haremos que coincidan las coordenadas con el tamaño del mapa de la siguiente forma, utilizando el método setworldcoordinates:

# map.jpg: https://visibleearth.nasa.gov/view.php?id=57752 Credit: NASA
screen = turtle.Screen()
screen.setup(720, 360)
screen.setworldcoordinates(-180, -90, 180, 90)
screen.bgpic('map.gif')
 Con esto la coordenada inferior (-90, -180) coincidirá con la esquina inferior izquierda del mapa y (90, 180) con la esquina superior derecha del mismo.

Para que el puntero no sea una tortuga, parece más lógico que usemos otro icono que se parezca más a la ISS. Para ello la imagen tiene que ser pequeña, para que no ocupe una gran parte de la pantalla.  Un tamaño de 50 * 50 pixeles ya es bastante grande. 

Si por ejemplo tenemos el icono en un archivo llamado "station.gif" lo primero que tenemos que hacer es registrar la imagen con screen

screen = turtle.Screen()
screen.register_shape('station.gif')

Luego lo establecemos con shape.

turtle.shape('station.gif')

Normalmente el puntero de la tortuga mira hacia la derecha, pero podemos hacer que empiece mirando hacia arriba con:

turtle.setheading(90) 

La tortuga empieza siempre en el centro de la pantalla, con lo cual tenemos que moverlo a las coordenadas correctas, las que nos proporciona la longitud y latitud dada. Tendremos que levantar el lápiz o el puntero de la tortuga para que no se marque el trazo y luego llevarla hasta el punto final:

iss.penup()
iss.goto(long, lat)


Con todo esto nuestro código quedaría de la siguiente forma:

""" ¿Donde está la Estación Espacial Internacional? """

import urllib.request
import json
import turtle

# http://open-notify.org/Open-Notify-API/
url = 'http://api.open-notify.org/astros.json'
respuesta = urllib.request.urlopen(url)
astronautas = json.loads(respuesta.read())

print(f"Número actual de astronautas: {astronautas['number']}")
for persona in astronautas['people']:
    print(f"{persona['name']} está en {persona['craft']}")
    
url = 'http://api.open-notify.org/iss-now.json'
respuesta = urllib.request.urlopen(url)
iss_posicion = json.loads(respuesta.read())
coordenadas = iss_posicion['iss_position']
lat = float(coordenadas['latitude'])
long = float(coordenadas['longitude'])
print(f"Latitud: {lat}, Longitud: {long}")
    
# map.jpg: https://visibleearth.nasa.gov/view.php?id=57752 Credit: NASA
screen = turtle.Screen()
screen.setup(720, 360)
screen.setworldcoordinates(-180, -90, 180, 90)
screen.bgpic('map.gif')

screen.register_shape('station.gif')
iss = turtle.Turtle()
iss.shape('station.gif')
# iss.setheading(90) La tortuga mira a la derecha por defecto.

iss.penup()
iss.goto(long, lat)
NOTA: La latitud normalmente se pone en primer lugar, pero nosotros necesitamos hacerlo al revés ya que las coordenadas se expresan en los ejes (x, y)

Prueba el programa. La ISS debería aparecer en su actual situación sobre la tierra.


posición actual de la ISS


Espera unos pocos segundos y vuelve a ejecutarlo para ver como la ISS se desplaza.

Añadiendo datos al mapa.


Ahora que hemos obtenido los datos que necesitábamos y hemos dibujado la posición de la ISS, podemos añadir algo de información al mapa. 

Empezaremos creando una nueva tortuga para escribir algo de texto.

num_personas = turtle.Turtle()


La nueva tortuga no debería dibujar líneas mientras está en la pantalla por lo que debemos ocultarla.

num_personas = turtle.Turtle()
num_personas.penup()
num_personas.hideturtle()

Escoge un color para el texto que quieras escribir y también la posición en el mapa donde quieras que se escriba.

# color del texto
num_personas.color('yellow')
# Ir al punto en el mapa
num_personas.goto(-175,-25)

Una vez escogido el color y la posición vamos a escribir el texto que queramos en el mapa. En este caso el texto informará al usuario del número de astronautas en el espacio.

num_personas.write(f"Nº Astronautas: {astronautas['number']}")

Puedes escribir el texto que tu quieras.

Al final del proyecto quedaría una imagen como esta:

imagen final del proyecto



Escribiendo texto con turtle.


Como has visto podemos usar turtle para escribir texto.

turtle.write('¡Hola!')

También podemos elegir el color del mismo.

turtle.color('deep pink')
turtle.write('¡Hola!')

Y también podemos cambiar la fuente y la alineación del mismo.

estilo = ('Courier', 30, 'italic')
turtle.write('Hello!', font=estilo, align='center')

La fuente es una tupla que contiene:
  • El nombre de la fuente: "Arial", "Courier" o "Times New Roman".
  • El tamaño de la fuente en pixeles.
  • El tipo de fuente: "normal", "bold" o"italic".
La alineación controla como se posiciona el texto basándose en la posición de la tortuga. La alineación puede tener tres opciones: "left", "center" o "right".

La ISS en movimiento.


Vamos a hacer una pequeña variación del código anterior para que la ISS actualice su posición en el mapa cada 5 segundos y además vaya dejando una traza en color amarillo de los lugares por donde ha pasado. Algo como esto:

trazado de la ruta de la ISS



Haremos solamente unos pequeños cambios. En vez de la librería urllib.request que viene de forma predeterminada con Python usaremos la librería requests.

Para ello comenzaremos instalando la librería.

python install requests

Capturando datos de páginas web con requests.

Algunas veces necesitaremos capturar datos desde una página web. Estos pueden ser datos proporcionados por una web API o quizás código HTML de la página. En cualquier caso, el módulo de Python requests hace que esta tarea sea muy fácil.

Veamos un poco como funciona. Como siempre, lo primero es importar el módulo.

import requests

Crearemos una variable que guarde la URL de la cual queremos capturar los datos. Para este ejemplo vamos a capturar la página de Inicio de Rapsberry pi org.

URL = "https://www.raspberrypi.org"

A continuación, le diremos al programa que capture la página web y la guarde en una variable.

r = requests.get(URL)

Esta variable "r" contiene un objeto con una gran cantidad de información. Puedes examinarla usando el shell. Por ejemplo:

>>> r
<RESPONSE [200]>
Este es un código de respuesta HTTP.  En este caso el código 200 es que todo ha ido bien. puedes obtener más información sobre los diferentes códigos de respuesta aquí.

Para obtener el contenido actual de la página, puedes usar r.text. Si quieres guardar el contenido puedes hacer algo como esto:

import requests
url = "https://www.raspberrypi.org"
r = requests.get(url)
data = r.text

Con frecuencia, los datos que obtenemos de una página web suelen estar en formato json(). De forma muy sencilla podemos convertirlos en diccionarios de Python usando el método json().

import requests
url = "https://www.raspberrypi.org"
r = requests.get(url)
data = r.json()


Ahora que hemos visto como funciona el módulo requests, vamos a modificar el código del programa para lograr que refleje el movimiento de la ISS:

"""¿Donde está la Estación Espacial Internacional?."""

import requests
import turtle
import time

# http://open-notify.org/Open-Notify-API/
URL = 'http://api.open-notify.org/astros.json'
with requests.get(URL) as respuesta:
    print(respuesta)
    print(respuesta.text) # en formato json
    astronautas = respuesta.json() # en formato diccionario
    print(astronautas)

print(f"Número actual de astronautas: {astronautas['number']}")
for persona in astronautas['people']:
    print(f"{persona['name']} está en {persona['craft']}")
    
# map.jpg: https://visibleearth.nasa.gov/view.php?id=57752 Credit: NASA
screen = turtle.Screen()
screen.setup(720, 360)
screen.setworldcoordinates(-180, -90, 180, 90)
screen.bgpic('map.gif')

screen.register_shape('station.gif')
iss = turtle.Turtle()
iss.shape('station.gif')
# iss.setheading(90) La tortuga mira a la derecha por defecto
iss.penup()
screen.colormode(255)
iss.pencolor(255,255,0)

while True:
    URL = 'http://api.open-notify.org/iss-now.json'
    with requests.get(URL) as respuesta:
        iss_posicion = respuesta.json()
    coordenadas = iss_posicion['iss_position']
    lat = float(coordenadas['latitude'])
    long = float(coordenadas['longitude'])
    print(f"Latitud: {lat}, Longitud: {long}")    
    iss.goto(long, lat)
    iss.pendown()
    time.sleep(5)


Capturando los datos de vuelo de la ISS.


He modificado un poco el programa para que guarde los datos de latitud y longitud de la ISS mientras da una vuelta completa a la tierra. Para analizarlos he guardado los datos en un archivo CSV.

¿Qué es un archivo CSV?

Un archivo CSV contiene líneas de datos separados por comas. Puede también haber una cabecerá que describa de que datos se tratan. Este tipo de datos son ampliamente utilizados para guardar tablas de datos. Para leerlos y analizarlos puedes usar Python o también cualquier hoja de datos.

El código que puedes usar para guardar los datos es el siguiente:

"""¿Donde está la Estación Espacial Internacional?."""

import requests
import turtle
import time
import csv

# http://open-notify.org/Open-Notify-API/
URL = 'http://api.open-notify.org/astros.json'
with requests.get(URL) as respuesta:
    print(respuesta)
    print(respuesta.text) # en formato json
    astronautas = respuesta.json() # en formato diccionario
    print(astronautas)

print(f"Número actual de astronautas: {astronautas['number']}")
for persona in astronautas['people']:
    print(f"{persona['name']} está en {persona['craft']}")
    
# map.jpg: https://visibleearth.nasa.gov/view.php?id=57752 Credit: NASA
screen = turtle.Screen()
screen.setup(720, 360)
screen.setworldcoordinates(-180, -90, 180, 90)
screen.bgpic('map.gif')

screen.register_shape('station.gif')
iss = turtle.Turtle()
iss.shape('station.gif')
# iss.setheading(90) La tortuga mira a la derecha por defecto
iss.penup()
screen.colormode(255)
iss.pencolor(255,255,0)

# contador para guardar la long y latitud
contador = 0

while True:
    contador += 1
    URL = 'http://api.open-notify.org/iss-now.json'
    with requests.get(URL) as respuesta:
        iss_posicion = respuesta.json()
    coordenadas = iss_posicion['iss_position']
    lat = float(coordenadas['latitude'])
    long = float(coordenadas['longitude'])
    print(f"Latitud: {lat}, Longitud: {long}")    
    iss.goto(long, lat)
    iss.pendown()
    # Escribimos los datos en un archivo csv
    with open('ejemplo.csv', 'a') as file:
        datos_a_escribir = csv.writer(file)
        datos_a_escribir.writerow([lat, long])
    time.sleep(5)
    # La ISS tarda aproximadamente 91 minutos en dar
    # una vuelta a la tiera. 91*12 = 1092
    # Si los datos se toman cada 5 segundos hay que guardar
    # 60 : 5 = 12 veces cada minuto.
    if contador == 1092:
        break
El comienzo del archivo ejemplo.csv mostrará algo como esto:

-51.1014,142.0155
-51.0416,142.5997
-50.9841,143.1338
-50.9295,143.6179
-50.8669,144.1493
-50.8017,144.679
-50.7339,145.2071
-50.6635,145.7336
-50.5973,146.2108
...
Tenía la curiosidad de comprobar con estos datos si podemos encontrar una aproximación a la velocidad que lleva la ISS que según la WIKIPEDIA es de unos 7,60 Km/s.

Buscando en internet aparece que para calcular la velocidad de un satélite basándonos en los datos de la latitud y longitud junto con el tiempo transcurrido entre cada medición (5 segundos) tenemos que utilizar la fórmula de la velocidad promedio. Esta se define como el cambio de posición dividido por el tiempo transcurrido. 

Dado que tenemos los datos de la latitud y longitud cada 5 segundos, podemos calcular la distancia entre dos puntos consecutivos utilizando la fórmula de la distancia entre dos puntos de la superficie de una esfera (ya que más o menos podemos considerar la tierra como una esfera). Esta formula se conoce como la fórmula de Haversine. 

Luego, podemos calcular la velocidad promedio entre dos puntos consecutivos dividiendo la distancia entre ellos por el tiempo transcurrido. Sumando todas las velocidades promedio y dividiendo por el número total de intervalos de tiempo, obtendremos una velocidad promedio general. 

Aquí tienes el código de como podríamos implementar esto en Python:

import csv
from math import radians, sin, cos, sqrt, atan2

# Función para calcular la distancia entre dos puntos en la superficie de la Tierra
def calcular_distancia(lat1, lon1, lat2, lon2):
    # Radio de la Tierra en kilómetros
    radio_tierra = 6371.0

    # Convertir coordenadas de grados a radianes
    lat1 = radians(lat1)
    lon1 = radians(lon1)
    lat2 = radians(lat2)
    lon2 = radians(lon2)

    # Diferencia de latitud y longitud
    dlat = lat2 - lat1
    dlon = lon2 - lon1

    # Fórmula de Haversine
    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * atan2(sqrt(a), sqrt(1 - a))

    # Distancia entre los dos puntos
    distancia = radio_tierra * c

    return distancia

# Coordenadas y tiempo
with open('ejemplo.csv', 'r') as file:
    datos = csv.reader(file)
    coordenadas = []
    for dato in datos:
        dato_x, dato_y = dato
        # convertimos las cadenas en numeros
        dato_x = float(dato_x)
        dato_y = float(dato_y)
        coordenadas.append((dato_x,dato_y))
        
# coordenadas = [
#     (-40.2785, 56.2131),
#     (-40.4843, 56.5698),
#     # Insertamos nuestras coordenadas reales de esta forma.
# ]

tiempo_por_punto = 5  # segundos

# Calcular la velocidad promedio
distancias = []
for i in range(1, len(coordenadas)):
    lat1, lon1 = coordenadas[i - 1]
    lat2, lon2 = coordenadas[i]
    distancia = calcular_distancia(lat1, lon1, lat2, lon2)
    distancias.append(distancia)

# Calcular la velocidad promedio
velocidades = [distancia / tiempo_por_punto for distancia in distancias]
velocidad_promedio = sum(velocidades) / len(velocidades)

print("La velocidad promedio del satélite es:", velocidad_promedio, "km/s")

SALIDA:

La velocidad promedio del satélite es: 7.467342537150308 km/s

¡Bastante parecido a la velocidad real!

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