Visualización de Redes Geoespaciales con

Dash Sylvereye

Dr. Alberto García Robledo

CentroGeo Querétaro

Semana de las Ciencias de Información Geoespacial

¿Qué es una red geoespacial?

¿Qué es Dash?

  • Dash es un framework web escrito en Python para el desarrollo de tableros de visualización.

  • Está construido sobre Flask, Plotly.js y React.js

  • Permite el desarrollo de aplicaciones web de visualización de datos sin la necesidad de escribir HTML, JavaScript o CSS.

¿Qué es Dash?

  • Dash es código abierto, publicado bajo la licencia MIT.

  • Está disponible para múltiples lenguajes:

    • Python

    • R

    • Julia

    • .NET

Componentes Dash de terceros

  • Dash provee herramientas para que puedas desarrollar tus propios componentes

  • Básicamente, un componente Dash no es más que un envoltorio de un componente React.js

  • Dash goza de una nutrida comunidad de desarrolladores que han creado sus propios componentes Dash

Dash Cytoscape

  • Sin soporte de dibujo de poli-líneas para los enlaces

  • Sin soporte de coordenadas geográficas

  • Sin soporte de mapas web

  • Sin aceleración por GPU

Estado del Arte

Dash Sylvereye

  • Biblioteca Dash desarrollada en CentroGeo.

  • Permite la visualización de redes de caminos grandes utilizando WebGL.

  • Las visualizaciones son altamente personalizables.

  • Eventos de click para nodos y enlaces de la red.

Incrustación en un tablero Dash

  • Dash Sylvereye permite incrustar la visualización de una red de caminos en tu tablero Dash

  • La topología de una red de caminos puede ser obtenida desde OSM a través de la biblioteca OSMnx

  • Dash Sylvereye permite la configuración de la capa de mapa web sobre la que se dibuja la red de caminos

Ejemplo 1: Visualización de una red de caminos 

Ejemplo 1: Visualización de una red de caminos 

import osmnx as ox
from dash import Dash
from dash_html_components import Div
from dash_sylvereye import SylvereyeRoadNetwork
from dash_sylvereye.utils import load_from_osmnx_graph

# configuración de la visualización
OSMNX_QUERY = 'Kamppi, Helsinki, Finland'
TILE_LAYER_URL = '//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'
TILE_LAYER_SUBDOMAINS = 'abcd'
TILE_LAYER_ATTRIBUTION = 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
MAP_CENTER = [60.1663, 24.9313]
MAP_ZOOM = 15
MAP_STYLE = {'width': '100%', 'height': '98vh'}

# obtener la topología de la red desde OSM
road_network = ox.graph_from_place(OSMNX_QUERY, network_type='drive') 
nodes_data, edges_data = load_from_osmnx_graph(road_network)

# construir visualización
app = Dash()
app.layout = Div([
    SylvereyeRoadNetwork(
                         id='sylvereye-roadnet',
                         tile_layer_url=TILE_LAYER_URL,
                         tile_layer_subdomains=TILE_LAYER_SUBDOMAINS,
                         tile_layer_attribution=TILE_LAYER_ATTRIBUTION,
                         map_center=MAP_CENTER,
                         map_zoom=MAP_ZOOM,
                         map_style=MAP_STYLE,
                         nodes_data=nodes_data,
                         edges_data=edges_data
                        )
])

# correr visualización
if __name__ == '__main__':
    app.run_server()

Interacción con nodos y enlaces

  • Dash Sylvereye permite interactuar con los nodos (cruces de caminos) y los enlaces (caminos)

  • Lo anterior a través de la detección de eventos de click  

  • Esto significa que puede asociar un callback al evento click de los nodos y/o enlaces

  • Dash Sylvereye pasará al callback los datos del elemento clickeado

Ejemplo 2: Interacción con la red de caminos 

Ejemplo 2: Interacción con la red de caminos

import osmnx as ox
from dash import Dash
from dash.dependencies import Input, Output
from dash_html_components import Div
from dash_html_components import H2, H3
from dash_sylvereye import SylvereyeRoadNetwork
from dash_sylvereye.utils import load_from_osmnx_graph

# configuración de la visualización
OSMNX_QUERY = 'Kamppi, Helsinki, Finland'
TILE_LAYER_URL = '//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'
TILE_LAYER_SUBDOMAINS = 'abcd'
TILE_LAYER_ATTRIBUTION = 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
MAP_CENTER = [60.1663, 24.9313]
MAP_ZOOM = 15
MAP_STYLE = {'width': '100%', 'height': '84vh'}

# obtener la topología de la red desde OSM
road_network = ox.graph_from_place(OSMNX_QUERY, network_type='drive') 
nodes_data, edges_data = load_from_osmnx_graph(road_network)

# construcción de la visualización
app = Dash()
app.layout = Div([
    SylvereyeRoadNetwork(
                         id='sylvereye-roadnet',
                         tile_layer_url=TILE_LAYER_URL,
                         tile_layer_subdomains=TILE_LAYER_SUBDOMAINS,
                         tile_layer_attribution=TILE_LAYER_ATTRIBUTION,
                         map_center=MAP_CENTER,
                         map_zoom=MAP_ZOOM,
                         map_style=MAP_STYLE,
                         nodes_data=nodes_data,
                         edges_data=edges_data
                        ),
    H5("Clicked elements:"),
    H6(id='h3-clicked-node-coords'),
    H6(id='h3-clicked-edge-coords')    
])

# callbacks
@app.callback(
    Output('h3-clicked-node-coords', 'children'),
    [Input('sylvereye-roadnet', 'clicked_node')])
def update_node_data(clicked_node):    
    if clicked_node:
        return f'Clicked node coords: {clicked_node["data"]["lat"]}, \
                                      {clicked_node["data"]["lon"]}'

@app.callback(
    Output('h3-clicked-edge-coords', 'children'),
    [Input('sylvereye-roadnet', 'clicked_edge')])
def update_edge_data(clicked_edge):    
    if clicked_edge:
        return f'Clicked edge coords: {clicked_edge["data"]["coords"]}'

# correr visualización      
if __name__ == '__main__':
    app.run_server()

Personalización de los estilos de la visualización

  • Dash Sylvereye permite la configuración visual de los nodos y enlaces individuales de la red

  • Para los nodos es posible configurar su diámetro, color y transparencia

  • Para los enlaces es posible configurar su grosor, color y transparencia

  • Dash Sylvereye puede calcular automáticamente el color de nodos y enlaces en función de un atributo de peso

Ejemplo 3: Personalización de la visualización de una red

Ejemplo 3: Personalización de la visualización de una red

import osmnx as ox
import numpy as np
from dash import Dash
from dash_html_components import Div
from dash_sylvereye import SylvereyeRoadNetwork
from dash_sylvereye.utils import load_from_osmnx_graph 
from dash_sylvereye.enums import NodeSizeMethod, EdgeColorMethod, EdgeWidthMethod
from dash_sylvereye.defaults import get_default_node_options, get_default_edge_options

OSMNX_QUERY = 'Kamppi, Helsinki, Finland'
TILE_LAYER_URL = '//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'
TILE_LAYER_SUBDOMAINS = 'abcd'
TILE_LAYER_ATTRIBUTION = 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
MAP_CENTER = [60.1663, 24.9313]
MAP_ZOOM = 15
MAP_STYLE = {'width': '100%', 'height': '84vh'}

# obtener la red desde OSM
road_network = ox.graph_from_place(OSMNX_QUERY, network_type='drive') 
nodes_data, edges_data = load_from_osmnx_graph(road_network)

# asignar pesos aleatorios a los nodos y enlaces de acuerdo a una dist. libre de escala
for node in nodes_data: node["data"]["weight"] = 1 - np.random.power(a=3, size=None)
for edge in edges_data: edge["data"]["weight"] = 1 - np.random.power(a=3, size=None)

# configurar las opciones visuales de los nodos
node_options = get_default_node_options()
node_options["alpha_default"] = 0.25
node_options["size_method"] = NodeSizeMethod.SCALE
node_options["size_scale_field"] = "weight"

# configurar las opciones visualies de los enlaces
edge_options = get_default_edge_options()
edge_options["width_method"] = EdgeWidthMethod.SCALE
edge_options["width_scale_field"] = "weight"
edge_options["color_method"] = EdgeColorMethod.SCALE
edge_options["color_scale_field"] = "weight"
edge_options["color_scale_left"] = 0xcbdbff
edge_options["color_scale_right"] = 0x06696

# construir la visualización
app = Dash()
app.layout = Div([
    SylvereyeRoadNetwork(
                         id='sylvereye-roadnet',
                         tile_layer_url=TILE_LAYER_URL,
                         tile_layer_subdomains=TILE_LAYER_SUBDOMAINS,
                         tile_layer_attribution=TILE_LAYER_ATTRIBUTION,
                         map_center=MAP_CENTER,
                         map_zoom=MAP_ZOOM,
                         map_style=MAP_STYLE,
                         nodes_data=nodes_data,
                         edges_data=edges_data,
                         node_options=node_options,
                         edge_options=edge_options
                        )    
])

# correr servidor
if __name__ == '__main__':
    app.run_server()

Visualización de marcadores

  • Dash Sylvereye permite visualizar marcadores encima tanto del mapa web como de la red

  • Los marcadores son interactivos: es posible asociar un callback al evento click de cada marcador

  • Dash Sylvereye pasará los datos del marcador clickeado a un parámetro del callback

Ejemplo 4: Visualización de marcadores sobre la red

Ejemplo 4: Visualización de marcadores sobre la red

import osmnx as ox
from dash import Dash
from dash.dependencies import Input, Output
from dash_html_components import Div
from dash_html_components import H2, H3
from dash_sylvereye import SylvereyeRoadNetwork
from dash_sylvereye.utils import load_from_osmnx_graph, generate_markers_from_coords
from dash_sylvereye.defaults import get_default_marker_options

OSMNX_QUERY = 'Kamppi, Helsinki, Finland'
TILE_LAYER_URL = '//stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png'
TILE_LAYER_SUBDOMAINS = 'abcd'
TILE_LAYER_ATTRIBUTION = 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>.'
MAP_CENTER = [60.1663, 24.9313]
MAP_ZOOM = 15
MAP_STYLE = {'width': '100%', 'height': '80vh'}

# obtener red de caminos desde OSM
road_network = ox.graph_from_place(OSMNX_QUERY, network_type='drive') 
nodes_data, edges_data = load_from_osmnx_graph(road_network)

# crear un marcador por cada nodo de la red
markers_coords = [ [node_data["lat"], node_data["lon"]] for node_data in nodes_data ]
markers_data = generate_markers_from_coords(markers_coords)

# escalar el tamaño de los marcadores al nivel de zoom
marker_options = get_default_marker_options()
marker_options["enable_zoom_scaling"] = True

# construir visualización
app = Dash()
app.layout = Div([
    SylvereyeRoadNetwork(
                         id='sylvereye-roadnet',
                         tile_layer_url=TILE_LAYER_URL,
                         tile_layer_subdomains=TILE_LAYER_SUBDOMAINS,
                         tile_layer_attribution=TILE_LAYER_ATTRIBUTION,
                         map_center=MAP_CENTER,
                         map_zoom=MAP_ZOOM,
                         map_style=MAP_STYLE,
                         nodes_data=nodes_data,
                         edges_data=edges_data,
                         markers_data=markers_data,
                         marker_options=marker_options
                        ),
    H2("Clicked elements:"),
    H3(id='h3-clicked-marker-coords')
])

@app.callback(
    Output('h3-clicked-marker-coords', 'children'),
    [Input('sylvereye-roadnet', 'clicked_marker')])
def update_marker_data(clicked_marker):  
    if clicked_marker:  
        marker = clicked_marker["marker"]
        return f'Clicked marker coords: {[ marker["lat"], marker["lon"] ]}'

# correr visualización
if __name__ == '__main__':
    app.run_server()

Más personalización e integración con otros componetes Dash

  • Dash Sylvereye puede ser combinado con Dash Core components y Dash Bootstrap Components para crear tableros más atractivos

  • Dash Sylvereye puede establecer el estilo de los enlaces (grosor, color y transparencia) en función de los datos almacenados en los enlaces

  • Dash Sylvereye permite la personalización de la escala de colores

Aplicaciones

Aplicaciones

Aplicaciones

Aplicaciones

Aplicaciones

Aplicaciones

Dash Sylvereye: Portal Web

¡Gracias!

 

 

agarcia@centrogeo.edu.mx

albertogarob.mx

Semana de las Ciencias de Información Geoespacial 2022

By Alberto Garcia-Robledo

Semana de las Ciencias de Información Geoespacial 2022

  • 158