Aprende historia gracias a geocodificación inversa, mapping y wikipedia

El otro día, mientras esperaba en Juan Bravo a un amigo, tuve algo de tiempo para divagar y entre esas divagaciones junté Juan Bravo, Python, Internet, geolocalización, historia,... En fin, que estaba en Juan Bravo, no tenía ni idea de quien era ese señor (llamadme ignorante), tenía algo de tiempo y se me ocurrió poder obtener información de calles a partir de un mapa y de la wikipedia y, de aquellos polvos, estos lodos, y nació map2wiki.

¿Qué es map2wiki?

En pocas palabras, es una aplicación web que te permite buscar una calle/avenida/plaza/... en un mapa y obtener información sobre lo que le da nombre a esa dirección.

¿Por qué no buscarlo directamente en la wikipedia?

Porque eso no es tan divertido y no hubiera aprendido nada sobre Flask, Jinja2, geocodificación inversa, OpenStreetMap, Nominatim, OpenLayers, Javascript, Brython, la API de la wikipedia, mis nulas aptitudes para el diseño web (de forma expresa no quería usar cosas como bootstrap para mantenerlo lo más simple posible) aunque podría ser peor, el infierno CSS...

CSS hell

Pero vayamos por partes...

¿Qué es la geocodificación inversa?

La geocodificación inversa (reverse geocoding en inglés) permite, a partir de unas coordenadas geográficas, obtener información sobre una dirección, por ejemplo, u otras cosas que se encuentren en esas coordenadas o cerca.

OpenStreetMap ofrece una API, Nominatim, que permite hacer eso mismo, a partir de unas coordenadas geográficas se obtiene información de su base de datos.

¿Cómo funciona?

En este post voy a relatar un poco el Así se hizo sin ver código, que a veces es más interesante que la película en sí. En otro post comentaré un poco el código por si alguien quiere utilizar alguna parte para otros proyectos.

Existen varias partes que se conectan entre sí.

  • Accedes a una página servida por Flask que ofrece un mapa, gracias a openlayers + openstreetmap.
  • En el mapa, nos podemos mover hasta una dirección que debe estar en español, ya que solo está pensada para ser usada en español. En el frontend tenemos la dirección central del mapa almacenada en una variable para la latitud y otra para la longitud (gracias a Brython/JS).
  • Con la dirección definida, pulsamos sobre el botón de buscar en la wikipedia. Este botón conecta un formulario en HTML, en el cliente, con Flask, en el servidor. Tenemos un formulario con algunos campos ocultos al que le pasaremos las coordenadas obtenidas en el frontend para que sean manejadas en Python gracias a Flask.
  • Una vez que tenemos las coordenadas en Python hacemos varias cosas:
    • Primero, vamos a la API de Nominatim y le metemos las coordenadas para que nos devuelva un JSON con la información de la dirección relacionada con esas coordenadas.
    • De ese JSON extraemos la información relevante a la dirección y la 'limpiamos' (sanitizar no está en el diccionario de la RAE). En la dirección se eliminan los siguientes términos junto con los posibles preposiciones y/o artículos que pueden acompañar a esa dirección ('calle de ...', 'avenida de los ...',...)
 
["alameda", "avenida", "bulevar", "calle", "camino", 
 "carrera", "cuesta", "pasaje", "pasadizo", "paseo", "plaza", 
 "rambla", "ronda", "travesia", "via"]
  • Seguimos:
    • Con la dirección sanitizada solo nos debería quedar el nombre de la dirección ya limpio. Por ejemplo, 'Paseo de la Marquesa de Python' debería quedar como 'Marquesa de Python'. Esa dirección ya limpia se la pasamos a la API de la Wikipedia usando la librería wikipedia que podéis encontrar en pypi. Si es posible encontrar algo en la wikipedia usando el valor que le hemos pasado nos devolverá un objeto con la información relevante del artículo.
    • Con el objeto con la información de la wikipedia obtenido extraemos parte de la información y la formateamos para mostrarla en la página.
    • Una vez tenemos la información de Nominatim (el JSON con la información de la dirección) y la información devuelta por la Wikipedia tenemos todo listo para que, mediante Flask, pasar esa información a una plantilla Jinja2, que construirá el HTML final con la información del JSON obtenido y de la Wikipedia, en caso de que sea posible, o un mensaje de error, en el caso de que no sea posible.

Y este es, principalmente, todo el proceso.

En el próximo artículo nos meteremos un poco más en las tripas para poder entender mejor como se unen las piezas. Lo que veamos no pretenderá ser algo exhaustivo sobre Flask, Jinja2 u otras tecnologías.

Espero que a alguien le resulte útil:

  1. la aplicación en sí, para aprender algo de historia,
  2. la explicación del como se hizo la aplicación, para entender como se juntan las piezas del rompecabezas en una aplicación con una estructura extremadamente simple y sin base de datos detrás.

Hasta la próxima entrada.

BryPlot y el triángulo de Sierpinski

Esta vez vamos a ver como usar una pequeña librería que he creado para poder dibujar en el canvas de HTML5 usando python (vía Brython).

La librería la podéis encontrar en este repo alojado en bitbucket. Solo voy a usar el módulo base y para no complicar el tema lo voy a pegar como código directamente aquí y así no habrá que importarlo.

Pero primero algunos apuntes:

¿Para qué otra librería para dibujar?

Por varios motivos:

  • Porque encontré algo de tiempo.
  • Porque me da la gana :-P
  • Para aprender a usar canvas.
  • Para aprender sobre el DOM, eventos,...
  • Para aprender.

Preparativos antes de empezar.

Para poder usar este engendro dentro del notebook de IPython vamos a usar una extensión creada para ello. La extensión se llama brythonmagic (¡qué derroche de creatividad!) y permite usar brython internamente en el notebook de IPython.

Para instalarla solo tenéis que hacer:

%install_ext https://raw.github.com/kikocorreoso/brythonmagic/master/brythonmagic.py

Y para poder usarla hacemos:

%load_ext brythonmagic

Además, hemos de cargar la librería javascript brython.

%%HTML
<script src="http://brython.info/src/brython_dist.js"></script>

Cargamos el módulo base directamente en el notebook.

Una vez listo para empezar a usar brython en el notebook vamos a meter en la siguiente celda el módulo base, citado anteriormente, y que contiene una serie de clases para poder dibujar texto y formas simples (círculos, cuadrados, polilíneas,...) en el canvas.

%%brython -s base
from browser import document as doc
import math

## Base classes for higher level objects
class Figure:
    """
    Base class to create other elements.
    """
    def __init__(self, canvasid, 
                       facecolor = "white", 
                       edgecolor = "black", 
                       borderwidth = None):
        """        
        Parameters
        ----------
        *canvasid*: String
            String indicating the canvas id where the image should be 
            rendered.
        *facecolor*: String
            String value containing a valid HTML color
        *edgecolor*: String
            String value containing a valid HTML color
        *borderwidth*: Integer
            Value indicating the width of the border in pixels.
            If not provided it will 0 and the edgecolor will not be
            visible
        """

        if isinstance(canvasid, str):
            self.id = canvasid
        else:
            raise Exception("The canvasid parameter should be a string")
             
        try:
            self.canvas = doc[self.id]
        except:
            raise Exception("No HTML element with id=%s" %
                            self.id)
        
        try:
            self._W = self.canvas.width
            self._H = self.canvas.height
            self._ctx = self.canvas.getContext("2d")
        except:
            raise Exception("You must provide the ID of a  element")
        
        self.facecolor = facecolor
        self.borderwidth = borderwidth
        self.edgecolor = edgecolor
        self.clf()
    
    def clf(self):
        "clear the figure"
        self._ctx.save()
        
        # The following line should clear the canvas but I found a
        # problem when I use beginPath ¿¿¿jQuery2030011017107678294003_1414965477473?
        #self._ctx.clearRect(0, 0, self._W, self._H)
        # So I use the following line tat is less performant but
        # this operation shouldn't be done very often...
        self.canvas.width = self.canvas.width
        
        self._ctx.fillStyle = self.facecolor
        self._ctx.fillRect(0, 0, self._W, self._H)
        self._ctx.fill()
        if self.borderwidth:
            self._ctx.lineWidth = self.borderwidth
            self._ctx.strokeStyle = self.edgecolor
            self._ctx.strokeRect(0, 0, self._W, self._H)
            self._ctx.stroke()
        self._ctx.restore()
        

class Text:
    """
    Base class for text
    """
    def __init__(self, context, x, y, s, 
                       font = "Verdana", fontsize = 12,
                       horizontalalignment='center',
                       verticalalignment='middle',
                       color = "black",
                       alpha = 1,
                       rotate = 0):
        """
        Parameters
        ----------
        *context: a canvas context
            a valid canvas context where the text will be rendered
        *x*: int or float
            x value for location in pixels
        *y*: int or float
            y value for location in pixels
        *s*: String
            String value with the text to be rendered
        *font*: String
            String value with the font type
        *fontsize*: int or float
            Size of the font in pixels
        *horizontalalignment*: String
            ``left``, ``right`` or ``center``
        *verticalalignment*: String
            ``top``, ``bottom``, ``middle``
        *color*: String
            A string with a valid HTML color
        *alpha*: int or float
            Value between 0 (transparent) and 1 (opaque) to set the
            transparency of the text
        *rotate*: int or float
            Value indicating an angle to rotate the text in the
            clockwise direction
        """
        self._ctx = context
        self.x = x
        self.y = y
        self.s = s
        self.font = font
        self.fontsize = fontsize
        self.font_complete = "{0}pt {1} sans-serif".format(fontsize,
                                                            font)
        self.horizontalalignment = horizontalalignment
        self.verticalalignment = verticalalignment
        self.color = color
        self.alpha = alpha
        self.rotate = rotate
        self.draw()
    
    def draw(self):
        self._ctx.save()
        self._ctx.translate(self.x, self.y)
        self._ctx.rotate(self.rotate * math.pi/ 180.)
        self._ctx.textAlign = self.horizontalalignment
        self._ctx.textBaseline = self.verticalalignment
        self._ctx.font = self.font_complete
        self._ctx.globalAlpha = self.alpha
        self._ctx.fillStyle = self.color
        self._ctx.fillText(self.s, 0, 0)
        _ = self._ctx.measureText(self.s)
        self.text_width = _.width
        self._ctx.restore()
    
    @property
    def transparency(self):
        return self.alpha
    
    @transparency.setter
    def transparency(self, alpha):
        if alpha >= 0 and alpha <= 1:
            self.alpha = alpha
        else:
            print("alpha value must be between 0 and 1")
    
    # create more setters and getters for other properties?
        
class Shape:
    """
    Base class to create other elements.
    """
    def __init__(self, context, x, y,
                       facecolor = "black", 
                       edgecolor = "black",
                       alpha = 1,
                       borderwidth = None):
        """        
        Parameters
        ----------
        *context*: a canvas context
            a valid canvas context where the text will be rendered
        *x*: int or float
            x value for location in pixels
        *y*: int or float
            y value for location in pixels
        *facecolor*: String
            String value containing a valid HTML color
        *edgecolor*: String
            String value containing a valid HTML color
        *alpha*: int or float
            Value between 0 (transparent) and 1 (opaque) to set the
            transparency of the text
        *borderwidth*: Integer
            Value indicating the width of the border in pixels.
            If not provided it will 0 and the edgecolor will not be
            visible
        """
        self._ctx = context
        self.x = x
        self.y = y
        self.facecolor = facecolor
        self.borderwidth = borderwidth
        self.edgecolor = edgecolor
        self.alpha = alpha

class Rectangle(Shape):
    def __init__(self, *args, size = (0,0), rotation = 0, **kwargs):
        """
        Parameters
        ----------
        *size*: tuple
            (width, height) size of the rectangle in pixels.
        *rotation*: int or float
            Value indicating an angle to rotate the shape in the
            clockwise direction         
        """
        Shape.__init__(self, *args, **kwargs)
        self.x_size = size[0]
        self.y_size = size[1]
        self.rotation = rotation
        self.draw()
    
    def draw(self):
        self._ctx.save()
        self._ctx.globalAlpha = self.alpha
        x0 = -self.x_size / 2.
        y0 = -self.y_size / 2.        
        self._ctx.translate(self.x, self.y)
        self._ctx.rotate(self.rotation * math.pi / 180.)
        self._ctx.fillStyle = self.facecolor
        self._ctx.fillRect(x0, y0, self.x_size, self.y_size)
        self._ctx.fill()
        if self.borderwidth:
            self._ctx.lineWidth = self.borderwidth
            self._ctx.strokeStyle = self.edgecolor
            self._ctx.strokeRect(x0, y0, self.x_size, self.y_size)
            self._ctx.stroke()
        self._ctx.restore()

class Circle(Shape):
    def __init__(self, *args, radius = 10, **kwargs):
        """
        Parameters
        ----------
        *radius*: int or float
            radius of the circle in pixels.
        """
        Shape.__init__(self, *args, **kwargs)
        self.r = radius
        self.draw()
    
    def draw(self):
        self._ctx.save()
        self._ctx.globalAlpha = self.alpha
        self._ctx.beginPath()
        self._ctx.fillStyle = self.facecolor
        self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)
        self._ctx.fill()
        if self.borderwidth:
            self._ctx.lineWidth = self.borderwidth
            self._ctx.strokeStyle = self.edgecolor
            self._ctx.arc(self.x, self.y, self.r, 0, 2 * math.pi)
            self._ctx.stroke()
        self._ctx.closePath()
        self._ctx.restore()

class Wedge(Shape):
    def __init__(self, *args, radius = 10, angle = 30, rotation = 0, **kwargs):
        """
        Parameters
        ----------
        *radius*: int or float
            radius of the pie wedge in pixels.
        *angle*: int or float
            angle width in degrees.
        *rotation*: int or float
            Value indicating an angle to rotate the shape in the
            clockwise direction 
        """
        Shape.__init__(self, *args, **kwargs)
        self.r = radius
        self.angle = angle
        self.rotation = rotation
        self.draw()
    
    def draw(self):
        self._ctx.save()
        self._ctx.globalAlpha = self.alpha
        self._ctx.fillStyle = self.facecolor
        self._ctx.beginPath()
        self._ctx.arc(self.x, self.y, self.r, 
                      (self.rotation - self.angle / 2 - 90) * math.pi / 180.,
                      (self.rotation + self.angle / 2 - 90) * math.pi / 180., 
                      False)
        self._ctx.lineTo(self.x, self.y)
        self._ctx.closePath()
        self._ctx.fill()
        if self.borderwidth:
            self._ctx.lineWidth = self.borderwidth
            self._ctx.strokeStyle = self.edgecolor
            self._ctx.arc(self.x, self.y, self.r, 
                      (self.rotation - self.angle / 2 - 90) * math.pi / 180.,
                      (self.rotation + self.angle / 2 - 90) * math.pi / 180., 
                      False)
            self._ctx.stroke()
        self._ctx.restore()

class Line(Shape):
    def __init__(self, *args, polygon = False, borderwidth = 2, **kwargs):
        Shape.__init__(self, *args, **kwargs)
        self.borderwidth = borderwidth
        self.polygon = polygon
        self.draw()
    
    def draw(self):
        self._ctx.save()
        self._ctx.globalAlpha = self.alpha
        self._ctx.beginPath()
        self._ctx.moveTo(self.x[0], self.y[0])
        for i in range(len(self.x)):
            self._ctx.lineTo(self.x[i], self.y[i])
        if self.polygon:
            self._ctx.closePath()
            if self.facecolor:
                self._ctx.fillStyle = self.facecolor
                self._ctx.fill()
        if self.borderwidth:
            self._ctx.lineWidth = self.borderwidth
            self._ctx.strokeStyle = self.edgecolor
            self._ctx.stroke()
        self._ctx.restore()

class Polygon(Line):
    def __init__(self, *args, polygon = True, 
                 facecolor = None, **kwargs):
        Line.__init__(self, *args, **kwargs)
        self.polygon = polygon
        self.facecolor = facecolor
        self.draw()

Básicamente, el módulo que acabamos de cargar nos permite usar el canvas de HTML5 a un nivel más alto que usando la API oficial y con una sintaxis pythónica.

Para ver todas las posibilidades actuales de la librería podéis usar este notebook que está en el mismo repo de la librería.

Ejemplo de uso, el triángulo de Sierpinski.

El triángulo de Sierpinski es un fractal y es lo que vamos a dibujar para ver las ¿capacidades? de Bryplot.

Primero hemos de crear el elemento HTML donde vamos a dibujar y que posteriormente le pasaremos al script Brython para que lo use.

HTML = """<div><canvas id="cnvs01" width=500 height=433></canvas></div>"""

Y ahora vamos a crear simplemente la figura, que es donde pintaremos todo lo que queramos, con un fondo negro:

%%brython -S base -h HTML
fig = Figure('cnvs01', facecolor = "black")

BRYPLOT01

Ahora vamos a dar un pequeño paso más y vamos a dibujar un triángulo usando la clase Polygon. A esta clase le hemos de pasar el contexto del canvas que, dicho de forma muy simplificada, es lo que nos permite realmente dibujar en el canvas. Además, le hemos de pasar los puntos que delimitan el polígono y luego podemos pasarle diferentes valores opcionales.

HTML = """<div><canvas id="cnvs02" width=500 height=433></canvas></div>"""
%%brython -S base -h HTML
fig = Figure('cnvs02', facecolor = "#bbb")
ctx = fig._ctx
width = fig._W
height = fig._H

def triangle(ctx, p1, p2, p3, color = "yellow"):
    x = [p1[0], p2[0], p3[0]]
    y = [p1[1], p2[1], p3[1]]
    Polygon(ctx, x, y, facecolor = color)
    
p1 = [0,height]
p2 = [width, height]
p3 = [width / 2, 0]

triangle(ctx, p1, p2, p3)

BRYPLOT02

Como he comentado anteriormente, tenéis más clases y con un poco de maña podéis crear vuestras propias figuras como las que tenéis en el README del repo.

Por último, vamos a dibujar un fractal usando recursión. No voy a explicar el ejemplo y si os hace falta alguna explicación podéis preguntar en los comentarios del blog puesto que este notebook se convertirá en post.

HTML = """<div><canvas id="cnvs03" width=500 height=433></canvas></div>"""
%%brython -S base -h HTML
from __random import randint

fig = Figure('cnvs03', facecolor = "#bbb")
ctx = fig._ctx
width = fig._W
height = fig._H

def triangle(ctx, p1, p2, p3, color = "yellow"):
    x = [p1[0], p2[0], p3[0]]
    y = [p1[1], p2[1], p3[1]]
    Polygon(ctx, x, y, facecolor = color)
    
def mitad(p1,p2):
    return [abs((p2[0]+p1[0]) / 2.), abs((p2[1]+p1[1]) / 2.)]
    
def sierpinski(ctx, p1, p2, p3, degree):
    if degree > 7:
        raise Exception("Degree should be <= 8")
    COLORS = ('#0000FF', '#008000', '#FF0000', '#00BFBF',
              '#BF00BF', '#BFBF00', '#000000', '#FFFFFF')
    triangle(ctx, p1, p2, p3, color = COLORS[degree])
    if degree > 0:
        sierpinski(ctx, p1, mitad(p1,p2), mitad(p1,p3), degree-1)
        sierpinski(ctx, p2, mitad(p1,p2), mitad(p3,p2), degree-1)
        sierpinski(ctx, p3, mitad(p3,p2), mitad(p1,p3), degree-1)
        
p1 = [0,0]
p2 = [width, 0]
p3 = [width / 2, height]
sierpinski(ctx, p1, p2, p3, 6)

BRYPLOT03

Y eso es todo por hoy.

¿Nos vemos en la PyConES?

P.D.: Cómo siempre, tenéis el notebook aquí.

Palabras usadas en Python (palabras clave y builtins)

Hace poco estuve incluyendo autocompletado de código Python3 en un editor online (pyschool.net) destinado a la educación que se está desarrollando dentro del proyecto Brython.

El editor online que se usa es Ace (el notebook de IPython usa codemirror). El modo Python de Ace incluye palabras de Python2 y Brython implementa Python3 por lo que el resaltado de código y autocompletado oficial incluido con Ace no se ajusta a lo que se quería usar en pyschool.net. Para solventar esto creé el modo Python3 que se usa en el editor online (y que próximamente también podréis ver y usar en el editor oficial en Brython.info). Pero había que saber qué palabras incluir en el resaltado y autocompletado de Ace (ya estoy llegando a lo que quería mostrar). Y como Python es tan increible y tiene módulos para todo podéis hacer lo siguiente para obtener las palabras clave (keywords):

import keyword
print(keyword.kwlist)

Que dará como resultado:

['False', 'None', 'True', 'and', 'as', 'assert', 
'break', 'class', 'continue', 'def', 'del', 'elif', 
'else', 'except', 'finally', 'for', 'from', 'global', 
'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 
'not', 'or', 'pass', 'raise', 'return', 'try', 
'while', 'with', 'yield']

Y podéis obtener las funciones integradas (builtins) usando:

import builtins
print(dir(builtins))

Que dará como resultado:

['ArithmeticError', 'AssertionError', 'AttributeError', 
'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 
'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 
'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 
'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 
'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 
'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError',
'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 
'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 
'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 
'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 
'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 
'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 
'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 
'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 
'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', 
'__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', 
'__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 
'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 
'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 
'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 
'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 
'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 
'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 
'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

Limpio, fácil, rápido. Es tan eficiente que me ha sobrado tiempo para escribir esta entrada!!!

Espero que a alguien le sirva en algún momento.

Saludos.

P.D.: Si alguien quiere incluir el modo Python3 que hemos incluido en Ace dentro de Brython lo puede encontrar aquí.

Tutorial de Highcharts usando IPython, brython y brythonmagic

Hoy volvemos a hablar de una librería Javascript (ver tutorial de Openlayers) pero lo vamos a hacer desde un punto de vista un poco más pythónico (aunque por debajo no dejará de ser Javascript).

En el siguiente frame tenéis un tutorial de Highcharts (librería para hacer gráficos interactivos en el navegador):

Si lo queréis ver a pantalla completa podéis pulsar aquí.

Si alguien tiene alguna duda que pregunte en los comentarios.

Saludos a todos.

Tutorial de OpenLayers usando IPython, Brython y brythonmagic

No, no habéis leído mal, hoy vamos a hablar de OpenLayers, una librería javascript para hacer 'mapping' en el cliente (navegador).

Como sabéis, hablamos principalmente de Python porque nos gusta y porque nos divierte y este tutorial de una librería javascript lo vamos a realizar usando una sintaxis pythónica y, mientras aprendemos el uso básico de OpenLayers, también veremos algo de sintaxis javascript.

Para poder seguir este tutorial necesitaréis tener una versión moderna de IPython notebook instalada e instalar brythonmagic del que ya hablamos por aquí hace poco. El tutorial lo podéis descargar desde aquí.

Finalmente, quería agradecer a Roger Veciana por ofrecerse a revisar el tutorial y aportar mejoras importantes al mismo. Gracias monstruo.

Saludos.

P.D.: El tutorial está en inglés, si alguien no es capaz de seguirlo que avise por aquí y si el tiempo lo permite intentaré hacer una versión en la lengua de Cervantes.

Presentando brythonmagic

En esta entrada os voy a enseñar una extensión que he creado que funciona sobre el IPython notebook, es decir, está pensada para funcionar en un navegador. Si no sabéis muy bien que son las 'cell magics' (no sé muy bien como traducir esto, podría ser algo así como magia que aplica sobre celdas) le podéis echar un ojo a esta entrada donde ya creamos alguna función mágica ('line magics') de ayuda. De todas formas, entre mis planes está el hacer una parte de la serie '157 cosas de IPython...' que hable sobre las 'cell magics'. No han salido nuevos capítulos ya que estoy viendo como evoluciona IPython 2.0.0 y adaptar las entradas a la nueva versión de IPython para que no se queden desfasadas ya de salida...

Bueno, a lo que vamos,... ¿Qué es eso de brythonmagic? Brythonmagic pretende ayudar a los programadores Python a crear un puente entre el mundo de la programación científica en Python y el de la visualización interactiva en javascript a través del IPython notebook y con la ayuda de Brython, una implementación de Python3 hecha en javascript y en Python y que funciona en el lado del cliente (navegador web). De esa forma, escribiendo en Python/subconjunto de Python/seudoPython/... (i.e., escribiendo en Brython) podemos acceder al navegador y a interesantes librerías javascript que funcionan en el navegador.

Brython es un proyecto que ya empieza a madurar y nos permite hacer cosas bastante interesantes que hasta ahora estaban vetadas para los pythonistas. Mediante brythonmagic he llevado las capacidades de Brython al notebook de IPython creando una 'cell magic', %%brython, que permite ejecutar código Brython dentro del navegador y nos permite manejar documentos HTML, acceder al DOM,...

El funcionamiento es muy sencillo, añades la extensión al notebook de IPython, añades las librerías de Brython que vayas a necesitar y después ya puedes ejecutar celdas de código Brython dentro del notebook y ver el resultado al momento.

A día de hoy permite una serie de opciones:

  • Obtener, mediante pantalla, el html que se usa para el output de la celda de código
  • Indicar que librerías javascript vamos a usar en la celda de código
  • Usar datos de Python creados en otras celdas y que queramos usar en la celda de código Brython
  • Indicar el nombre del contenedor (elemento HTML div) de la salida.

En el siguiente vídeo podéis echarle un vistazo rápido a lo que se puede hacer con brythonmagic. En el vídeo, entre otras cosas, accedemos a las librerías d3js (gráficos interactivos) y openlayers (mapas interactivos):

El notebook usado en el vídeo está en el repositorio por si lo queréis ejecutar de forma local.

El repo de Brython lo tenéis aquí.

Además de para usarse para lo comentado anteriormente también lo podéis usar para probar cosas de Brython en un entorno amigable como el notebook de IPython (resaltado de sintáxis, autocompletado, formateo del código, comentarios en Markdown,...), es decir, si eres un 'frontender' y quieres probar Brython puedes usar brythonmagic y IPython para ello :-).

Saludos.

P.D.: Se agradecen ideas para mejorar brythonmagic.

Gráficas interactivas en el navegador

Últimamente sigo con mucha atención varios desarrollos relacionados con la posibilidad de usar 'python' en el navegador, siempre persiguiendo quimeras :-P Entre ellos se encuentra brython, cuyo nombre proviene de 'browser python'.

He estado estudiando un poco de brython y un poco sobre el uso del elemento 'canvas' en HTML5, el cual permite dibujar en el navegador y, a pesar de lo cutre del código rápido y sucio que he hecho y a pesar de no tener mucha idea de HTML5, del DOM, de brython, de javascript, he obtenido un resultado que me ha parecido ilusionante. Mi propósito era poder hacer un gráfico como lo que se obtiene con pyplot.plot de matplotlib pero en el navegador y con un poco de interactividad.

El resultado lo podéis visualizar en este enlace. El código lo podéis ver viendo el código html de la página.

¿Alguien vería interesante desarrollar una biblioteca sobre brython para 'pintar' en el navegador?, ¿alguien ve factible la idea detrás de brython?, ¿los navegadores podrían traer brython o sucedáneos ya integrados?, ¿sigo persiguiendo quimeras?

P.D.: En cuanto tenga tiempo edito esta entrada explicando el código (igual lo refactorizo antes si encuentro el tiempo necesario), simplemente lo quería compartir ya porque igual no puedo tocarlo en semanas y se diluye en mi cabeza.