{"id":11773,"date":"2026-04-15T18:14:54","date_gmt":"2026-04-15T16:14:54","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11773"},"modified":"2026-04-15T18:14:57","modified_gmt":"2026-04-15T16:14:57","slug":"aplicacion-android-para-fotogrametria-con-ia-fase-8-condiciones-de-vuelo-meteorologia-aemet-indice-kp-y-gps-en-tiempo-real","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11773","title":{"rendered":"Aplicaci\u00f3n Android para fotogrametr\u00eda con IA. Fase 8: Condiciones de vuelo \u2014 meteorolog\u00eda AEMET, \u00edndice Kp y GPS en tiempo real"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 11 de 11 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div>\n<p>Hay una pregunta que cualquier piloto de drones se hace antes de salir al campo: \u00bfse puede volar hoy? Para responderla, hasta ahora hab\u00eda que abrir la app de AEMET, mirar el viento, consultar otra fuente para el \u00edndice geomagn\u00e9tico, ver en la pantalla del tel\u00e9fono cu\u00e1ntos sat\u00e9lites GPS tienen <em>fix <\/em>y cruzar mentalmente todos esos datos para tomar la decisi\u00f3n. La Fase 8 integra esa informaci\u00f3n directamente en la app, en un panel accesible desde el mapa sin interrumpir ninguno de los flujos existentes. El resultado es concreto: abrir el panel, ver los sem\u00e1foros, saber si se puede volar y cu\u00e1ndo es el mejor momento en las pr\u00f3ximas 48 horas.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"461\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-1024x461.png\" alt=\"Captura de la pantalla de meteorolog\u00eda y estado de GPS\" class=\"wp-image-11772\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-1024x461.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-300x135.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-768x346.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-1536x691.png 1536w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260413-190523-2048x922.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Captura de la pantalla de meteorolog\u00eda y estado de GPS<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Tres fuentes de datos, un panel<\/h2>\n\n\n\n<p>El panel integra tres fuentes independientes. La meteorolog\u00eda viene de <strong>AEMET OpenData<\/strong> con la API <em>key <\/em>del usuario: predicci\u00f3n horaria por municipio con viento, racha, probabilidad de lluvia, nubosidad, temperatura, sensaci\u00f3n t\u00e9rmica, humedad, visibilidad y UV, con cobertura de 48 horas. La actividad geomagn\u00e9tica viene de <strong>NOAA SWPC<\/strong>, la agencia espacial estadounidense: el \u00edndice Kp en tiempo real y su previsi\u00f3n para las pr\u00f3ximas 72 horas en ventanas de tres horas, sin ning\u00fan tipo de autenticaci\u00f3n. Y el estado del GPS viene del propio tel\u00e9fono: el <code>GnssStatusCallback<\/code> de Android expone en tiempo real los sat\u00e9lites visibles, los que est\u00e1n en <em>fix <\/em>y las constelaciones detectadas (GPS, GLONASS, Galileo, BeiDou).<\/p>\n\n\n\n<p>El \u00edndice Kp merece una explicaci\u00f3n para quien no lo conozca. Es una medida de la actividad geomagn\u00e9tica global: valores entre 0 y 3 indican condiciones normales, Kp 4 es una perturbaci\u00f3n menor que puede degradar ligeramente la precisi\u00f3n GPS, y Kp 5 o superior indica tormenta geomagn\u00e9tica con impacto real en la se\u00f1al de los sat\u00e9lites. Para una misi\u00f3n de fotogrametr\u00eda donde la precisi\u00f3n del GPS determina directamente la calidad del georreferenciado de las im\u00e1genes, este indicador es relevante.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El problema de AEMET: doble salto y Latin-1<\/h2>\n\n\n\n<p>La API de AEMET OpenData tiene una particularidad que no est\u00e1 especialmente documentada pero que se descubre r\u00e1pidamente al trabajar con ella: usa un patr\u00f3n de <strong>doble salto<\/strong>. La primera petici\u00f3n al <em>endpoint <\/em>de predicci\u00f3n no devuelve los datos directamente, sino una URL a otro JSON donde est\u00e1n los datos reales. El <code>WeatherRepository<\/code> hace los dos saltos en serie:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GET \/api\/prediccion\/especifica\/municipio\/horaria\/{cod_ine}?api_key=XXX\n\u2192 { \"datos\": \"https:\/\/opendata.aemet.es\/opendata\/sh\/...json\" }\n\nGET https:\/\/opendata.aemet.es\/opendata\/sh\/...json\n\u2192 &#91;array con la predicci\u00f3n por horas]<\/code><\/pre>\n\n\n\n<p>El segundo salto tiene otra particularidad: la respuesta no viene en UTF-8 sino en <strong>ISO-8859-1<\/strong> (Latin-1). Esto no es un problema para los datos num\u00e9ricos, pero s\u00ed para los campos de texto \u2014el nombre del municipio, la descripci\u00f3n del estado del cielo\u2014 que contienen caracteres como tildes o la \u00f1. Si se <em>parsea <\/em>el JSON como UTF-8, esos caracteres aparecen corruptos. La soluci\u00f3n fue leer los bytes brutos de la respuesta y decodificarlos expl\u00edcitamente:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>val bytes = response.body.bytes()\nval json = String(bytes, Charsets.ISO_8859_1)<\/code><\/pre>\n\n\n\n<p>Este hallazgo se confirm\u00f3 con <code>file<\/code> sobre un fichero de respuesta guardado en disco: <em>ISO-8859 text<\/em>. No hay forma de saberlo mirando la documentaci\u00f3n de AEMET, solo probando.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resolver la ubicaci\u00f3n: de coordenadas a c\u00f3digo INE<\/h2>\n\n\n\n<p>AEMET no trabaja con coordenadas geogr\u00e1ficas sino con <strong>c\u00f3digos INE de municipio<\/strong>. Para obtener la predicci\u00f3n de una ubicaci\u00f3n hay que saber a qu\u00e9 municipio pertenece, y eso requiere resolver lat\/lng a un c\u00f3digo INE.<\/p>\n\n\n\n<p>El plan original contemplaba incluir una tabla CSV embebida en los <em>assets <\/em>de la <em>app <\/em>con los ~8.000 municipios de Espa\u00f1a. La soluci\u00f3n implementada en la pr\u00e1citca fue diferente: descargar la tabla de municipios de la propia API de AEMET y cachearla en disco. La ventaja es que la tabla siempre est\u00e1 actualizada y el APK no se engrosa con 200 KB de datos est\u00e1ticos que pueden cambiar. La desventaja es que requiere red en el primer uso, que es un escenario perfectamente asumible.<\/p>\n\n\n\n<p>La resoluci\u00f3n de municipio m\u00e1s cercano a unas coordenadas se hace por distancia Haversine sobre la tabla cacheada: para cada municipio se calcula la distancia al punto y se devuelve el m\u00e1s pr\u00f3ximo. O(n) sobre 8.000 elementos es perfectamente r\u00e1pido en un dispositivo moderno.<\/p>\n\n\n\n<p>Las coordenadas de los municipios en la tabla de AEMET vienen en dos formatos: grados\/minutos\/segundos con s\u00edmbolos (que sufren el problema de la codificaci\u00f3n que indic\u00e1bamos antes) y decimal como <em>string <\/em>en los campos <code>latitud_dec<\/code>\/<code>longitud_dec<\/code>. Se usan los decimales para evitar el <em>parsing <\/em>DMS, que requerir\u00eda lidiar con los s\u00edmbolos de grado corruptos en Latin-1.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El panel: <em>slider <\/em>de 48 horas y sem\u00e1foros configurables<\/h2>\n\n\n\n<p>El panel de condiciones es un <code>ModalBottomSheet<\/code> que se abre desde el selector de capas del mapa, sin ocupar espacio permanente en la interfaz. El elemento central es un <em>slider <\/em>de 0 a 48 horas: al deslizarlo, las tarjetas de meteorolog\u00eda se actualizan con los datos de esa hora concreta de la predicci\u00f3n AEMET.<\/p>\n\n\n\n<p>Encima del <em>slider <\/em>hay una barra de resumen de 48 bloques, uno por hora, coloreados seg\u00fan el sem\u00e1foro de esa hora. El bloque correspondiente a la hora seleccionada se dibuja m\u00e1s alto que el resto para actuar como indicador visual de posici\u00f3n. Tocar cualquier bloque salta el <em>slider <\/em>a esa hora. El color de cada bloque es el peor indicador entre todos los par\u00e1metros evaluados: si la meteorolog\u00eda est\u00e1 en verde pero el Kp est\u00e1 en \u00e1mbar, el bloque es \u00e1mbar.<\/p>\n\n\n\n<p>Los sem\u00e1foros eval\u00faan cada par\u00e1metro contra umbrales configurables en los ajustes de la app:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Viento m\u00e1ximo (m\/s)<\/li>\n\n\n\n<li>Racha m\u00e1xima (m\/s)<\/li>\n\n\n\n<li>Lluvia m\u00e1xima (mm\/h)<\/li>\n\n\n\n<li>Visibilidad m\u00ednima (km)<\/li>\n\n\n\n<li>Kp m\u00e1ximo para verde<\/li>\n\n\n\n<li>Sat\u00e9lites m\u00ednimos en fix para verde<\/li>\n<\/ul>\n\n\n\n<p>Un bot\u00f3n \u00abMejor ventana de vuelo\u00bb recorre las 48 horas buscando el bloque contiguo m\u00e1s largo en verde y salta el slider al inicio de ese intervalo, mostrando un mensaje con el rango horario encontrado.<\/p>\n\n\n\n<p>La tarjeta GPS merece una aclaraci\u00f3n de dise\u00f1o: los sat\u00e9lites visibles no tienen predicci\u00f3n futura, porque calcularla requerir\u00eda TLE orbitales y la posici\u00f3n del observador \u2014una complejidad injustificada cuando el \u00edndice Kp ya es el indicador real de calidad GPS en una ventana temporal. El panel siempre muestra los sat\u00e9lites actuales del tel\u00e9fono, con una etiqueta que indica que es el valor \u00abahora\u00bb aunque el <em>slider <\/em>est\u00e9 en el futuro. Las tarjetas de meteorolog\u00eda y GPS son independientes: la barra de resumen y la tarjeta Meteo eval\u00faan solo par\u00e1metros meteorol\u00f3gicos, y la tarjeta GPS tiene su propio sem\u00e1foro.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Las pruebas: primero las APIs, luego el dispositivo<\/h2>\n\n\n\n<p>La verificaci\u00f3n de la Fase 8 sigui\u00f3 el mismo protocolo de fases anteriores: primero validar las APIs externas con <code>curl<\/code> antes de escribir ning\u00fan c\u00f3digo de integraci\u00f3n, luego tests de compilaci\u00f3n, y finalmente pruebas en el dispositivo real.<\/p>\n\n\n\n<p>La validaci\u00f3n previa con curl confirm\u00f3 el comportamiento del doble salto de AEMET, la codificaci\u00f3n ISO-8859-1 y la estructura del JSON de predicci\u00f3n horaria. Tambi\u00e9n confirm\u00f3 que la API de NOAA SWPC devuelve un <em>array <\/em>bidimensional con cabecera, donde las filas tienen la forma <code>[time_tag, kp, observed, noaa_scale]<\/code>, lo que determin\u00f3 el modelo de datos antes de escribir el <em>parser<\/em>.<\/p>\n\n\n\n<p>Las pruebas en el Motorola Moto G73 5G el 12 de abril revelaron cuatro bugs que se corrigieron en el momento:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>URL base incorrecta<\/strong>: <code>WeatherRepository<\/code> usaba <code>https:\/\/opendata.aemet.es<\/code> como base, pero todas las rutas de la API requieren el prefijo <code>\/opendata<\/code>. Resultado: 404 en todas las peticiones. La URL correcta es <code>https:\/\/opendata.aemet.es\/opendata<\/code>.<\/li>\n\n\n\n<li><strong>Endpoint de validaci\u00f3n inexistente<\/strong>: el bot\u00f3n \u00abProbar AEMET\u00bb llamaba a <code>\/api\/maestro\/provincias<\/code>, que devuelve 404 incluso con una clave v\u00e1lida. Cambiado a <code>\/api\/maestro\/municipios<\/code>, que devuelve 200 con key v\u00e1lida y 401 sin ella.<\/li>\n\n\n\n<li><strong>Fallback de ubicaci\u00f3n demasiado agresivo<\/strong>: sin pol\u00edgono dibujado, el panel usaba siempre Madrid como ubicaci\u00f3n por defecto. Se a\u00f1adi\u00f3 una consulta a <code>LocationManager<\/code> antes de caer al hardcode. Verificado en campo: con el dispositivo en Pontevedra, el panel resolvi\u00f3 correctamente \u00abForcarei (36018)\u00bb.<\/li>\n\n\n\n<li><strong>Sem\u00e1foro de la tarjeta Meteo inconsistente con la barra de resumen<\/strong>: la barra usa solo par\u00e1metros meteorol\u00f3gicos para colorear cada bloque, pero la tarjeta Meteo usaba la evaluaci\u00f3n completa (meteorolog\u00eda + GPS + Kp). Con 0\/0 sat\u00e9lites en fix \u2014situaci\u00f3n normal en interior\u2014 la tarjeta Meteo mostraba \u00abDesaconsejado\u00bb aunque la meteorolog\u00eda estuviera perfectamente en verde. Se separ\u00f3 la evaluaci\u00f3n: la tarjeta Meteo usa solo par\u00e1metros meteorol\u00f3gicos, coherente con la barra; la tarjeta GPS tiene su propio sem\u00e1foro independiente.<\/li>\n<\/ol>\n\n\n\n<p>La prueba con datos reales de AEMET \u201412 de abril, Galicia, lluvia previsible\u2014 devolvi\u00f3 exactamente lo esperado: \u00abViento 3,6 m\/s N \u00b7 Racha 9,2 m\/s\u00bb, \u00abLluvia Prob 80% \u00b7 0.1 mm\/h\u00bb, \u00abTemp 13\u00b0C \u00b7 ST 13\u00b0C\u00bb, sem\u00e1foro general en rojo por probabilidad de lluvia superior al umbral. La tarjeta GPS mostraba 0\/0 sat\u00e9lites en fix, esperado en interior.<\/p>\n\n\n\n<p>Queda pendiente de verificar en exterior la tarjeta GNSS con <em>fix <\/em>real. En interior el comportamiento es el correcto \u2014cero sat\u00e9lites, sem\u00e1foro rojo\u2014 pero la actualizaci\u00f3n en tiempo real del <em>StateFlow <\/em>al adquirir <em>fix <\/em>necesita confirmarse en campo abierto con el GPS activo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Degradaci\u00f3n elegante<\/h2>\n\n\n\n<p>Como en todas las fases de integraci\u00f3n con servicios externos, el sistema est\u00e1 dise\u00f1ado para degradarse sin bloquear la app. Sin API key de AEMET, el panel muestra un mensaje de configuraci\u00f3n. Sin red pero con cach\u00e9 v\u00e1lida (menos de una hora), muestra los datos cacheados con el <em>timestamp <\/em>de \u00faltima actualizaci\u00f3n. Sin red y sin cach\u00e9, el panel queda vac\u00edo sin errores ni fallos. Si NOAA SWPC no responde, la tarjeta GPS sigue mostrando los sat\u00e9lites en tiempo real, simplemente sin el dato de Kp. Si el GPS del tel\u00e9fono est\u00e1 desactivado, la tarjeta lo indica.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El estado del proyecto: ocho fases, un ciclo completo<\/h2>\n\n\n\n<p>Con la Fase 8 completada, la aplicaci\u00f3n cubre ahora todo el proceso de una misi\u00f3n de fotogrametr\u00eda con drones: consultar si las condiciones son adecuadas antes de salir, planificar el \u00e1rea de vuelo, ejecutar la misi\u00f3n con el dron de forma aut\u00f3noma, procesar las im\u00e1genes en el servidor, ver los resultados sobre el mapa y consultar los hallazgos detectados por la IA durante el vuelo. Ocho fases que empezaron con un mapa vac\u00edo y un <em>slider <\/em>de altura.<\/p>\n\n\n\n<p>Lo que queda en el horizonte \u2014soporte para otros modelos de drones DJI, descarga activa de tiles de mapa, drag directo de v\u00e9rtices del pol\u00edgono\u2014 est\u00e1 documentado como Fase 9. Cuando haya algo concreto que contar, habr\u00e1 art\u00edculo. <img src=\"https:\/\/bitacora.eniac2000.com\/wp-includes\/images\/smilies\/mrgreen.png\" alt=\":mrgreen:\" class=\"wp-smiley\" style=\"height: 1em; max-height: 1em;\" \/><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 11 de 11 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div><p>Hay una pregunta que cualquier piloto de drones se hace<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1845,13],"tags":[1861,106,1959,745,824,1971],"series":[1957],"class_list":["post-11773","post","type-post","status-publish","format-standard","hentry","category-generado-con-ia","category-informatica","tag-aemet","tag-android","tag-claude-code","tag-gps","tag-ia","tag-noaa","series-fotogrametria-asistida-por-ia"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11773","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11773"}],"version-history":[{"count":3,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11773\/revisions"}],"predecessor-version":[{"id":11776,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11773\/revisions\/11776"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11773"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11773"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11773"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11773"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}