Datos en Tiempo Real
Capítulo 9: Revolucionando el análisis geoespacial con datos vivos
Un dicho común entre analistas geoespaciales es: "Un mapa queda obsoleto tan pronto como se crea". Esto refleja el hecho de que la Tierra y todo lo que hay en ella está en constante cambio. La edad de los datos geoespaciales en un mapa se conoce como data currency (vigencia de datos).
El mapeo web, los módems celulares inalámbricos y las antenas GPS de bajo costo han cambiado este enfoque. Ahora es logísticamente factible y económico monitorear un objeto o sistema que cambia rápidamente y transmitir esos cambios a millones de personas en línea. Este concepto de conectar dispositivos en una red de datos e información se llama The Internet of Things (IoT).
Limitaciones de Datos en Tiempo Real
El término datos en tiempo real típicamente significa casi en tiempo real. Algunos dispositivos de rastreo capturan datos en tiempo real y pueden actualizarse varias veces por segundo. Sin embargo, las limitaciones de la infraestructura que transmite esos datos pueden restringir la salida a cada 10 segundos o más.
El radar meteorológico es un ejemplo perfecto. Un Radar Meteorológico Doppler (DWR) barre continuamente, pero los datos típicamente están disponibles en línea cada cinco minutos.
Limitaciones Principales
- Ancho de banda de red - Restricciones en el tamaño de datos transmitidos
- Latencia de red - Limita la frecuencia de actualización de datos
- Disponibilidad de fuente - Restricciones como vida de batería del dispositivo
- Falta de control de calidad - Datos disponibles instantáneamente sin verificación
- Vulnerabilidades de seguridad - Ingesta rápida de datos no verificados
Rastreo de Vehículos
Para nuestra primera fuente de datos en tiempo real, usaremos la excelente NextBus API. NextBus es un servicio comercial que rastrea transporte público para municipalidades incluyendo autobuses, tranvías y trenes.
NextBus publica datos de rastreo a través de una API REST. Usando llamadas URL API, los desarrolladores pueden solicitar información sobre un vehículo y recibir un documento XML sobre su ubicación.
Obtener Lista de Agencias
Esta llamada devuelve un documento XML conteniendo información de agencias:
<agency tag="lametro" title="Los Angeles Metro" regionTitle="California-Southern"/>
Obtener Lista de Rutas
Obtener Ubicaciones de Vehículos
El parámetro t=0 retorna datos de los últimos 15 minutos. El documento XML devuelto contiene:
<vehicle id="7582" routeTag="2" lat="34.097992" lon="-118.350365" secsSinceReport="44" speedKmHr="0"/>
Script de Rastreo
import urllib.request import urllib.parse import urllib.error from xml.dom import minidom # Nextbus API command mode command = "vehicleLocations" # Nextbus customer to query agency = "lametro" # Bus we want to query route = "2" # Time: 0 returns last 15 minutes epoch = "0" # Build query URL url = "http://webservices.nextbus.com" url += "/service/publicXMLFeed?" url += "command=" + command url += "&a=" + agency url += "&r=" + route url += "&t=" + epoch # Access the REST URL feed = urllib.request.urlopen(url) if feed: # Parse the xml feed xml = minidom.parse(feed) # Get the vehicle tags vehicles = xml.getElementsByTagName("vehicle") if vehicles: bus = vehicles.pop() att = bus.attributes print(att["lon"].value, ",", att["lat"].value) else: print("No vehicles found.")
Mapeo de Ubicaciones NextBus
La mejor fuente de datos de mapeo de calles disponible gratuitamente es el proyecto OpenStreetMap (OSM). Usaremos la API de MapQuest para crear imágenes de mapas estáticos con marcadores de ubicación.
Función nextbus()
def nextbus(a, r, c="vehicleLocations", e=0): """Returns the most recent latitude and longitude of the selected bus line using the NextBus API (nbapi) Arguments: a=agency, r=route, c=command, e=epoch timestamp""" nbapi = "http://webservices.nextbus.com" nbapi += "/service/publicXMLFeed?" nbapi += "command={}&a={}&r={}&t={}".format(c, a, r, e) xml = minidom.parse(urllib.request.urlopen(nbapi)) # If more than one vehicle, just get the first bus = xml.getElementsByTagName("vehicle")[0] if bus: at = bus.attributes return (at["lat"].value, at["lon"].value) else: return (False, False)
Función nextmap()
def nextmap(a, r, mapimg): """Plots a nextbus location on a map image and saves it to disk using MapQuest API""" # Fetch the latest bus location lat, lon = nextbus(a, r) if not lat: return False # Build MapQuest Static Map API URL osmapi = "https://www.mapquestapi.com/staticmap/v4/getmap?" osmapi += "type=map&" # Red pushpin marker at bus location osmapi += "mcenter={},{}|&".format(lat, lon) # Zoom level (1-18, higher = lower scale) osmapi += "zoom=18&" # Center map around bus location osmapi += "center={},{}&".format(lat, lon) # Set map image size osmapi += "&size=1500,1000" # Add API Key osmapi += "&key=YOUR_API_KEY_HERE" # Create PNG image osmapi += "imagetype=png&" img = urllib.request.urlopen(osmapi) # Save the map image with open("{}png".format(mapimg), "wb") as f: f.write(img.read()) return True
Loop de Rastreo
import time # Nextbus API variables agency = "lametro" route = "2" nextimg = "nextmap" # Number of updates requests = 3 # Update frequency (seconds) freq = 5 # Map the bus location every few seconds for i in range(requests): success = nextmap(agency, route, nextimg) if not success: print("No data available.") continue print("Saved map {} at {}".format(i, time.asctime())) time.sleep(freq)
Saved map 1 at Sun Nov 1 22:35:24 2015
Saved map 2 at Sun Nov 1 22:35:32 2015
https://developer.mapquest.com/
Storm Chasing: Datos Meteorológicos
Ahora queremos ir más allá de lo que hace el mashup de NextBus. Agregaremos otra fuente de datos en tiempo real importante: el clima. El programa Mesonet de Iowa State University proporciona datos meteorológicos gratuitos y pulidos.
Podemos usar el estándar Web Map Service (WMS) del OGC para solicitar una imagen sobre nuestra área de interés. Un WMS es un estándar para servir imágenes de mapas georreferenciados a través de la web.
URL de Ejemplo WMS Mesonet
Función de Conversión Lat/Lon a Metros
import math def ll2m(lon, lat): """Lat/lon to meters (Web Mercator)""" x = lon * 20037508.34 / 180.0 y = math.log(math.tan((90.0 + lat) * math.pi / 360.0)) / (math.pi / 180.0) y = y * 20037508.34 / 180 return (x, y)
Función WMS Genérica
def wms(minx, miny, maxx, maxy, service, lyr, epsg, style, img, w, h): """Retrieve a WMS map image from the specified service and save as JPEG.""" wms = service wms += "?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&" wms += "LAYERS={}".format(lyr) wms += "&STYLES={}&".format(style) wms += "SRS=EPSG:{}&".format(epsg) wms += "BBOX={},{},{},{}&".format(minx, miny, maxx, maxy) wms += "WIDTH={}&".format(w) wms += "HEIGHT={}&".format(h) wms += "FORMAT=image/jpeg" wmsmap = urllib.request.urlopen(wms) with open(img + ".jpg", "wb") as f: f.write(wmsmap.read())
Combinación de Mapas con PIL
from PIL import Image # Transparency level (0=invisible, 1=opaque) opacity = 0.5 icon_size = 30 # Open the basemap image in PIL im1 = Image.open("basemap.png").convert('RGBA') # Open the weather image in PIL im2 = Image.open("weather.png").convert('RGBA') # Convert weather image mode to match basemap im2 = im2.convert(im1.mode) # Create blended image im3 = Image.blend(im1, im2, opacity) # Open bus icon image im4 = Image.open("busicon.png") im4.thumbnail((icon_size, icon_size)) # Calculate center position w, h = im3.size w2, h2 = im4.size center_width = int((w/2) - (w2/2)) center_height = int((h/2) - (h2/2)) # Paste icon in center of image im3.paste(im4, (center_width, center_height), im4) # Save the finished map im3.save("bus-weather-map.png")
Reportes desde el Campo
Los smartphones, tablets y laptops modernos nos permiten actualizar un GIS y ver esas actualizaciones desde cualquier lugar. Usaremos HTML, GeoJSON, la librería JavaScript Leaflet, y una librería Python pura llamada Folium para crear una aplicación cliente-servidor.
Primero, necesitamos un formulario web que muestre tu ubicación actual y actualice el servidor cuando envíes el formulario con comentarios sobre tu ubicación.
Creación de Mapa con Folium
Folium proporciona una API Python simple para crear mapas web Leaflet. Este script son solo unas pocas líneas y producirá una página web llamada map.html:
import folium # Create base map m = folium.Map() # Add GeoJSON layer from URL m.geo_json(geo_path="https://api.myjson.com/bins/467pm") # Save map to HTML file m.create_map(path="map.html")
Pasamos la URL GeoJSON al objeto mapa, que trazará las ubicaciones en el mapa. El mapa interactivo resultante mostrará puntos como marcadores. Cuando hagas clic en un marcador, se mostrará la información del formulario.
Instalación de Folium
pip install folium
https://github.com/python-visualization/folium
Resumen del Capítulo
01 // Data Currency
La edad de los datos geoespaciales es crítica. Los datos en tiempo real revolucionan cómo hacemos análisis geoespacial.
02 // REST APIs
APIs como NextBus permiten acceder a feeds crudos de ubicación en tiempo real mediante llamadas URL simples.
03 // WMS Services
El estándar OGC Web Map Service permite adquirir subconjuntos de datos raster en tiempo real como imágenes meteorológicas.
04 // Data Blending
PIL permite combinar diferentes tipos de datos en tiempo real en productos de análisis de mapas personalizados.
05 // Client-Server GIS
Folium y Leaflet permiten construir aplicaciones cliente-servidor para actualizar un GIS en tiempo real.
06 // Internet of Things
El concepto IoT conecta dispositivos en una red de datos, desde GPS hasta sensores meteorológicos.