martes, 13 de mayo de 2014

Aplicación de Datos: Python, Flask, pip y virtualenvs

Flask un micro framework para crear aplicaciones web en python. Usar pip para instalar Flask

$ pip install Flask

Crear directorio en donde pondremos archivos con código de la aplicación

$ mkdir mi-app

Adentro crear archivo llamado app.py, donde configuremos Flask y servira como backend de nuestro proyecto. Abrir app.py en un editor

from flask import Flask
app = Flask(__name__)

Configurar para hacer la raíz de tu sitio. Usaremos un archivo llamado 'index.html' para colocar la información. Tenemos que especificarlo en app.py

from flask import Flask
from flask import render_template
app = Flask(__name__)

@app.route("/")
def index():
  return render_template('index.html')

Crear directorio "templates" dentro del directorio de nuestro proyecto.

$ mkdir templates

Crear "index.html" dentro del directorio "templates" y colocarle cualquier texto.

¡Hola Mundo!

Volver a editar app.py y agregarle lo siguiente para poder levantar el servidor local de Flask

from flask import Flask
from flask import render_template
app = Flask(__name__)

@app.route("/")
def index():
  return render_template('index.html')

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Correr app.py en la linea de comandos. Mirar el resultado en un navegador en http://localhost:8000

$ python app.py

Comenzar nuevamente editando "index.html". Reemplazar el contenido por el esqueleto de un archivo HTML


<!--doctype html-->
<html lang="es"> 
  <head></head>
  <body> 
    <h1>Incendios en España entre 2004 y 2014</h1>
  </body>
</html>

Crear directorio para guardar archivos estáticos. Aqui guardaremos el CSV con nuestros datos. Descargalo

$ mkdir static

Abrir app.py en editor de texto. Usaremos libreria csv para acceder a nuestros datos y pasarle la lista de datos del csv a index.html

from flask import Flask
from flask import render_template
app = Flask(__name__)

csv_path = './static/incendios.csv'
csv_obj = csv.DictReader(open(csv_path, 'r'))
csv_list = list(csv_obj)

@app.route("/")
def index():
  return render_template('index.html',
     object_list=csv_list,
  )

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Guardar app.py y editar index.html. Colocar la lista del csv en el index.html. En linea de comandos correr app.py y visitar http://localhost:8000 nuevamente

 
<!--doctype html--> 
<html lang="es"> 
  <head></head> 
  <body> 
    <h1>Incendios en España entre 2004 y 2014</h1> 
    {{ object_list }} 
  </body>
</html>

Vamos a darle formato a los datos en index.html. Estamos usando el lenguaje de templating jinja de Flask

 
<!--doctype html--> 
<html lang="es"> 
  <head></head> 
  <body> 
  <h1>Incendios en España entre 2004 y 2014</h1>
  <table border=1 cellpadding=7>
    <tr>
      <th>IDPIF</th>
      <th>Superficie Forestal Quemada</th>
      <th>Fecha</th>
      <th>Muertos</th>
      <th>Heridos</th>
      <th>Comunidad</th>
      <th>Provincia</th>
      <th>Comarca</th>
      <th>Causa</th>
      <th>Perdidas</th>
    </tr>
    {% for obj in object_list %}
      <tr>
        <td><a href="{{ obj['IDPIF'] }}/">{{ obj['IDPIF'] }}</a></td>
        <td>{{ obj['SUPQUEMADA'] }}</td>
        <td>{{ obj['FECHA'] }}</td>
        <td>{{ obj['MUERTOS'] }}</td>
        <td>{{ obj['HERIDOS'] }}</td>
        <td>{{ obj['COMUNIDAD'].decode('UTF-8') }}</td>
        <td>{{ obj['PROVINCIA'].decode('UTF-8') }}</td>
        <td>{{ obj['COMARCA'].decode('UTF-8') }}</td>
        <td>{{ obj['CAUSA'].decode('UTF-8') }}</td>
        <td>{{ obj['PERDIDAS'] }}</td> 
      </tr>
    {% endfor %} 
  </table>
  </body>
</html>

Ahora página de detalles por incendio. Agregamos una nueva ruta en archivo "app.py"

@app.route('//')
def detail(number):
  return render_template('detail.html')

Agreguemos el template "detail.html". Editamos el archivo detail.html. Agregar algo simple. Y levantamos la página en el navegador con cualquier número. http://localhost:8000/2004210126/

¡Hola Detalle!

Buscamos el incendio. Conectamos el número en la URL con el número identificador real del incendio en el CSV. Editamos app.py y convertimos la lista que tenemos en algo donde el incendio sea fácilmente buscable por identificador.

Editamos la función detail en app.py para que conecte el número en la URL con el registro correspondiente. Quedaría:

import csv
from flask import Flask
from flask import render_template
app = Flask(__name__)

csv_path = './static/incendios.csv'
csv_obj = csv.DictReader(open(csv_path, 'r'))
csv_list = list(csv_obj)
csv_dict = dict([[o['IDPIF'], o] for o in csv_list])

@app.route("/")
def index():
  return render_template('index.html',
     object_list=csv_list,
  )

@app.route('//')
def detail(number):
  return render_template('detail.html',
    object=csv_dict[number],
  )

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Volver a editar detail.html para agregarle datos desde nuestro CSV


<!--doctype html-->
<html lang="es">
  <head> <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  </head>
  <body>
    <h1>{{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  </body>
</html>

Editar "index.html" para enlazar la página del incendio. En la tag table reemplazar la fila de

<td><a href="{{ obj['IDPIF'] }}/">{{ obj['IDPIF'] }}</a></td>

Agregar el resto de los campos del incendio en detail.html


<!--doctype html-->
<html lang="es">
<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script>
</head>
<body>
  <div id="map" style="width:100%; height:300px;"></div>
  <h1>Incendio de {{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  <p>En la comunidad {{ object['COMUNIDAD'].decode('UTF-8') }}, provincia {{ object['PROVINCIA'].decode('UTF-8') }}, comarca {{ object['COMARCA'].decode('UTF-8') }}, municipio {{ object['MUNICIPIO'].decode('UTF-8') }} se quemó una superficie forestal de {{ object['SUPQUEMADA'] }}. Hubieron {{ object['MUERTOS'] }} muertos y {{ object['HERIDOS'] }} heridos. Se detectó en la fecha {{ object['FECHA'] }}. Se pudo controlar en {{ object['TIME_CTRL'] }} minutos y extinguir en {{ object['TIME_EXT'] }} minutos. La causa del incendio fue {{ object['CAUSA'].decode('UTF-8') }}. En la extinción del incendio participaron {{ object['PERSONAL'] }} personas, {{ object['PESADOS'] }} vehiculos pesados y {{ object['AEREOS'] }} medios aereos.</p>
  <script type="text/javascript">
    var map = L.map('map').setView([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}], 16);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
      subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
    var marker = L.marker([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}]).addTo(map);
  </script>
</body>
</html>

Javascript. Ahora vamos a colocar un mapa con los incendios usando una libreria de Javascript llamada Leaflet. Primero hay que importarla en el archivo "index.html"


<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script> 
</head>
<body>
...
</body>

Crear un elemento HTML para el mapa


...
<body>
  <h1>Incendios en España entre 2004 y 2014</h1>
  <div id="map" style="width:100%; height:300px;"></div>
...
</body>

Y usar Leaflet para centrarlo en España. Inicializamos el mapa con las coordenadas de España y nivel de zoom


  <script type="text/javascript">
    var map = L.map('map').setView([40.2085,-3.713], 6);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
        subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
  </script>

Agregamos la capa de tiles desde mapquest, que utiliza open street map. Se le pasa la URL para las imagenes de tiles, el nivel máximo de zoom (18) y el texto sobre de donde vienen los mapas.


  <script type="text/javascript">
    var map = L.map('map').setView([40.2085,-3.713], 6);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
        subdomains: ['otile1','otile2','otile3','otile4']
    });
  </script>

Le agregamos el layer al elemento del mapa


  <script type="text/javascript">
  ...
  map.addLayer(mapquestLayer);
  </script>

Transformar los datos del CSV que tenemos a GeoJSON. GeoJSON es un formato para guardar estructuras de datos geograficos junto a información no geografica


  <script type="text/javascript">
  ...
    var data = {
      "type": "FeatureCollection",
      "features": [
        {% for obj in object_list %}
        {
          "type": "Feature",
          "properties": {
            "causa": "{{ obj['CAUSA'].decode('UTF-8') }}",
            "id": "{{ obj['IDPIF'] }}"
          },
          "geometry": {
            "type": "Point",
            "coordinates": [{{ obj['LONGITUD'] }}, {{ obj['LATITUD'] }}]
          }
        }{% if not loop.last %},{% endif %}
        {% endfor %}
      ]
    };
    var dataLayer = L.geoJson(data);
    map.addLayer(dataLayer);
  </script>

Agregar una ventanita popup que muestre información sobre el incendio. Sustituir var dataLayer


  <script type="text/javascript">
  ...

    var dataLayer = L.geoJson(data, {
        onEachFeature: function(feature, layer) {
            layer.bindPopup(
              '<a href="' + feature.properties.id + '/">' +
                feature.properties.causa +
              '</a>')
        }
    });
    map.addLayer(dataLayer);
  </script>

Hacemos lo mismo en "detail.html" agregamos un mapa del incendio. Quedaria:


<!--doctype html-->
<html lang="es">
<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script>
</head>
<body>
  <div id="map" style="width:100%; height:300px;"></div>
  <h1>Incendio de {{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  <p>En la comunidad {{ object['COMUNIDAD'].decode('UTF-8') }}, provincia {{ object['PROVINCIA'].decode('UTF-8') }}, comarca {{ object['COMARCA'].decode('UTF-8') }}, municipio {{ object['MUNICIPIO'].decode('UTF-8') }} se quemó una superficie forestal de {{ object['SUPQUEMADA'] }}. Hubieron {{ object['MUERTOS'] }} muertos y {{ object['HERIDOS'] }} heridos. Se detectó en la fecha {{ object['FECHA'] }}. Se pudo controlar en {{ object['TIME_CTRL'] }} minutos y extinguir en {{ object['TIME_EXT'] }} minutos. La causa del incendio fue {{ object['CAUSA'].decode('UTF-8') }}. En la extinción del incendio participaron {{ object['PERSONAL'] }} personas, {{ object['PESADOS'] }} vehiculos pesados y {{ object['AEREOS'] }} medios aereos.</p>
  <script type="text/javascript">
    var map = L.map('map').setView([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}], 16);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
      subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
    var marker = L.marker([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}]).addTo(map);
  </script>
</body>
</html>




1 comentario: