Difference between revisions of "Walkyourplace Django"

From stgo
Jump to: navigation, search
(Características nuevas)
Line 346: Line 346:
  
 
== Demographic group ==
 
== Demographic group ==
 +
 +
La idea es evaluar los POIs para distintos grupos demográficos, quien hace ese trabajo es la función de AggregationService.py, pero para que la petición llegue desde la interfaz también debe de llegarle a ManagementService.py, por lo que realizaremos estas modificaciones:
 +
 +
Primero creamos el modelo de datos que contendrá a las opciones de grupos demográficos en la base de datos, para ello editamos el archivo wyp/models.py y agregamos lo siguiente:
 +
 +
<source lang="python">
 +
from django.db import models
 +
 +
class Demographic(models.Model):
 +
value = models.CharField(max_length=255)                #Nombre de la opcion demografica
 +
bank = models.CharField(max_length=255)                #Pesos
 +
grocery = models.CharField(max_length=255)
 +
restaurant = models.CharField(max_length=255)
 +
shopping = models.CharField(max_length=255)
 +
entertainment = models.CharField(max_length=255)
 +
school = models.CharField(max_length=255)
 +
library = models.CharField(max_length=255)
 +
health = models.CharField(max_length=255)
 +
sum = models.FloatField(blank=True,default=0)          #Suma de los pesos anteriores
 +
score_factor = models.FloatField(blank=True,default=0)  #Valor de ponderacion
 +
 +
def __unicode__(self):
 +
return self.value
 +
 +
        #Generar el resultado de sum y score_factor automaticamente al guardar
 +
def save(self, *args, **kwargs):
 +
sumando = 0
 +
for i in ast.literal_eval(self.bank):
 +
sumando += i
 +
for i in ast.literal_eval(self.grocery):
 +
sumando += i
 +
for i in ast.literal_eval(self.restaurant):
 +
sumando += i
 +
for i in ast.literal_eval(self.shopping):
 +
sumando += i
 +
for i in ast.literal_eval(self.entertainment):
 +
sumando += i
 +
for i in ast.literal_eval(self.school):
 +
sumando += i
 +
for i in ast.literal_eval(self.library):
 +
sumando += i
 +
for i in ast.literal_eval(self.health):
 +
sumando += i
 +
self.sum = sumando
 +
self.score_factor = 100.0/float(sumando)
 +
super(Demographic,self).save(*args, **kwargs)
 +
</source>
 +
 +
Modificamos el archivo wyp/views.py para mandar estos datos a la vista html
 +
 +
<source lang="python">
 +
def inicio(request):
 +
  demographic = Demographic.objects.all()
 +
  return render_to_response('index.html',{'demographic':demographic},context_instance=RequestContext(request))
 +
</source>
 +
 +
Luego modificamos el archivo html que se encuentra en template para que éstos aparezcan dinámicamente como una lista de opciones. template/index.html
 +
 +
<source lang="html4strict">
 +
<h3>Cycling Model</h3>
 +
 +
<div>
 +
    <form id="call_wps_bike" method="post" action="#">
 +
        <fieldset>
 +
            <ul>
 +
                ...
 +
 +
                <li><input type="checkbox" id="distance_decay_function_bike"
 +
                          name="distance_decay_function"/>Distance
 +
                    Decay Function
 +
                </li>
 +
 +
                <li>
 +
                To:
 +
                  <select id="demographic_bike">
 +
                    {% for demo in demographic%}
 +
                    <option value="{{demo.value}}">{{demo.value}}</option>
 +
                    {% endfor %}
 +
                  </select>
 +
                </li>
 +
 +
                <input type="button" class="button" onclick="call_wps('bike')"
 +
                      value="Get Accessibility Score"/>
 +
            </ul>
 +
        </fieldset>
 +
    </form>
 +
</div>
 +
 +
<h3>Walking Model</h3>
 +
 +
<div>
 +
    <form id="call_wps_pedestrian" method="post" action="#">
 +
        <fieldset>
 +
            <ul>
 +
                <li>
 +
                    <input type="hidden" id="start_point_pedestrian" name="start_point_pedestrian"
 +
                          readonly/>
 +
                </li>
 +
                ...
 +
 +
                        <div id="walking_speed_pedestrian"
 +
                            style="width: 100px; height: 6px; margin:15px 10px auto 15px; display: inline-block;"></div>
 +
                        <span style="font-size: 10px;">Fast</span>
 +
                    </div>
 +
                </li>
 +
 +
                <li>
 +
                To:
 +
                <select id="demographic_pedestrian">
 +
                    {% for demo in demographic%}
 +
                    <option value="{{demo.value}}">{{demo.value}}</option>
 +
                    {% endfor %}
 +
                  </select>
 +
                  </li>
 +
 +
                <li><input type="checkbox" id="distance_decay_function_pedestrian"
 +
                          name="distance_decay_function"/>Distance
 +
                    Decay Function
 +
                </li>
 +
 +
 +
                <input type="button" class="button" onclick="call_wps('pedestrian')"
 +
                      value="Get Accessibility Score"/>
 +
            </ul>
 +
        </fieldset>
 +
    </form>
 +
</div>
 +
 +
<h3>Transit & Walking Model</h3>
 +
 +
<div>
 +
    <form id="call_wps_transit" method="post" action="#">
 +
        <fieldset>
 +
            <ul>
 +
                <li>
 +
                    <input type="hidden" id="start_point_transit" name="start_point_transit" readonly/>
 +
                </li>
 +
                ...
 +
 +
                                <li>
 +
                To:
 +
                  <select id="demographic_transit">
 +
                    {% for demo in demographic%}
 +
                    <option value="{{demo.value}}">{{demo.value}}</option>
 +
                    {% endfor %}
 +
                  </select>
 +
                </li>
 +
               
 +
                ...
 +
 +
                <input type="button" class="button" onclick="call_wps('transit')"
 +
                      value="Get Accessibility Score"/>
 +
            </ul>
 +
        </fieldset>
 +
    </form>
 +
</div>
 +
</source>
  
 
= Funcionamiento actual =
 
= Funcionamiento actual =
  
 
El código se puede encontrar acá: http://146.155.17.18:18080/csfuente/wyp-django/
 
El código se puede encontrar acá: http://146.155.17.18:18080/csfuente/wyp-django/

Revision as of 23:16, 8 September 2016

Update 08-09-2016

State of the art

EL código que se comenzó a editar se extrajo de los siguientes link:

De los cuales (1) es la interfaz gráfica, (2) es el servidor el cual crea los puertos para la comunicación y los demás son los módulos de geoserver para manejar las peticiones.

Migración a Django Python

Lo primero es crear un nuevo proyecto en Django

django-admin startproject wypdjango
cd wypdjango

En donde nos encontramos con:

  • manage.py
  • wypdjango/
    • __init__.py
    • settings.py
    • urls.py
    • wsgi.py

Manage.py es quien nos ayudará a correr la web, tanto en etapa de desarrollo como en producción, éste tomará en cuenta todas las carpetas que contengan el archivo __init__.py (el cual no cuenta con contenido), por lo mismo éste archivo no debe ser editado. Settings.py es en donde se encuentra toda la configuración que le asignemos, entre ella la conexión a la base de datos y la posición de los templates html. Urls.py será en donde listemos las URLs que queramos utilizar, además cumple la función de enlazar dicha url con una función desarrollada en lenguaje Python. Entonces, sabiendo esto hay que crear dos nuevas carpetas llamadas static y template, la primera para contener todos los archivos css y js que necesitemos y la segunda para retener todos los html, luego editamos el archivo wypdjango/settings.py con lo siguiente:

import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
PROJECT_PATH = os.path.realpath(os.path.dirname(__file__))
 
...
 
INSTALLED_APPS = (
    ...
    'django.contrib.staticfiles',
    'wyp',
)
...
STATIC_URL = '/static/'
 
TEMPLATE_DIRS = (
    PROJECT_PATH + '/../template/',
)
 
STATIC_ROOT = PROJECT_PATH + '/../static/'
 
ADMIN_MEDIA_PREFIX = '/static/admin/'
 
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'template'),
)

Luego se clona el repositorio git (1) dentro de la carpeta template

cd template
git clone https://github.com/mepa1363/wyp-client .

Volvemos a la raíz y creamos una app dentro del proyecto para manejar la aparición del templace

cd ..
django-admin startapp wyp

Con esto se crea una carpeta llamada wyp con:

  • __init__.py
  • admin.py
  • models.py
  • test.py
  • views.py

En donde en models.py creamos clases que corresponderán a los modelos en la base de datos, en views.py creamos las funciones que guiarán en la aparición de las vistas (Que posteriormente llamaremos con urls.py), admin.py que es donde se registran los modelos de la bse de datos que se quieran mostrar en el panel de administración. Test.py para realizar pruebas (nunca lo he ocupado) e __init__.py para que sea reconocido por manage.py.

En wyp/views.py editamos lo siguiente:

from django.shortcuts import render_to_response
from django.template.context import RequestContext
 
def inicio(request):
  return render_to_response('index.html',context_instance=RequestContext(request))

y en wypdjango/urls.py editamos:

from wyp.views import inicio
 
urlpatterns = patterns('',
...
    url(r'^admin/', include(admin.site.urls)),
    url(r'^$', inicio),
) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += staticfiles_urlpatterns()

Nos posicionamos en la raíz y ejecutamos los siguientes comandos:

python manage.py collectstatics #Rescata los archivos css y js del panel de administración y de los que tenga el template html
python manage.py makemigrations #Crea los archivos .py para editar la base de datos
python manage.py migrate        #Edita la base de datos con los cambios juntados en makemigrations
python manage.py runserver      #Corre un servidor local con la web

Con el último comando corriendo, nos dirigimos a un navegador web y entramos a http://localhost:8000/ y nos encontraremos con el index.html de walkyourplace.

Ya tenemos al menos la vista principal del proyecto, ahora debemos hacerlo funcionar.

Vemos que en la web inicial los request se solicitan a la url /call_wps.php en conjunto de los datos enviados vía GET, como ahora estamos trabajando en Django dicha url no está apuntando a nada ya que no se encuentra registrada en urls.py, por lo que el código php que junta esta información y realiza la petición de la información se debe construir esta vez en Python, para ello necesitamos los archivos que procesan la información (2) y pasarlos al proyecto.

Bike Model

Tomando de (2) los archivos de la carpeta Bike (AggregationService.py, CrimeService.py, ManagementService.py, POIService.py y config.py) y los copiamos dentro de la carpeta wyp. Tenemos que editarlos cada uno de ellos.

En AggregationService.py eliminamos las siguientes líneas:

from bottle import route, run, request
import bottle
 
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024
 
...
 
@route('/aggregation', method='POST')
def service():
    poi = request.POST.get('poi', default=None)
    crime = request.POST.get('crime', default=None)
    walkshed = request.POST.get('bikeshed', default=None)
    start_point = request.POST.get('start_point', default=None)
    distance_decay_function = request.POST.get('distance_decay_function', default=None).lower()
    walking_time_period = request.POST.get('biking_time_period', default=None)
    if start_point and poi and crime and walkshed and distance_decay_function and walking_time_period is not None:
        return aggregation(start_point, walkshed, crime, poi, distance_decay_function, walking_time_period)
 
 
run(host='0.0.0.0', port=7364, debug=True)

y modificamos la línea

        otp_url = "http://www.gisciencegroup.ucalgary.ca:8080/opentripplanner-api-webapp/ws/plan?arriveBy=false&time=6%3A58pm&ui_date=1%2F10%2F2013&mode=BICYCLE&optimize=QUICK&maxWalkDistance=5000&walkSpeed=1.38&date=2013-01-10&"

por

        otp_url = "http://146.155.17.18:23080/otp/routers/default/plan?arriveBy=false&time=6%3A58pm&ui_date=1%2F10%2F2013&mode=BICYCLE&optimize=QUICK&maxWalkDistance=5000&walkSpeed=1.38&date=2013-01-10&"


Para crime.py hay que hacer cambios drásticos ya que en Chile no contamos con la información geográfica de los crímenes de la ciudad, por lo que necesitamos que simplemente nos retorne 'NULL', aparte de eso debemos quitar todo lo relacionado con el paquete bottle. Por lo que eliminamos las líneas:

from bottle import route, run, request
import bottle
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024
 
@route('/crime')
def service():
    polygon = request.GET.get('bikeshed', default=None)
    if polygon is not None:
        return pointInPolygon(polygon)
 
run(host='0.0.0.0', port=7366, debug=True)

y agregamos después de la definición de la función:

def pointInPolygon(polygon):
    return '"NULL"'

En POIService.py también debemos eliminar lo relacionado con bottle, similar a los archivos anteriores, y debemos editar la línea:

    _overpass_url = 'http://overpass-api.de/api/interpreter'

por

    _overpass_url = 'http://146.155.17.18:24080/api/interpreter'

y en ManagementService.py primero le cambiaremos el nombre a ManagementServiceBike.py (mas adelante se verá porqué), luego eliminamos las siguientes líneas:

from bottle import route, run, request
import bottle
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024
 
...
 
@route('/management')
def service():
    start_point = request.GET.get('start_point', default=None)
    biking_time_period = request.GET.get('biking_time_period', default=None)
    distance_decay_function = request.GET.get('distance_decay_function', default=None)
 
    if start_point and biking_time_period is not None:
        return management(start_point, biking_time_period, distance_decay_function)
run(host='0.0.0.0', port=7363, debug=True)

y rehacemos por completo la función management(), ya que está configurada para enviar las solicitudes a geoserver, esta vez la configuraremos para que funcione dentro de Django.

def management(start_point, biking_time_period, distance_decay_function):
    otp_isochrone_url = 'http://146.155.17.18:23080/otp/routers/default/plan?&'
    otp_dataInputs = 'fromPlace=%s&date=2016/05/05&time=12:00:00&mode=BICYCLE&cutoffSec=%s' % (start_point,str(int(biking_time_period)*60))
    otp_url = otp_isochrone_url + otp_dataInputs
    try:
        bikeshed = urllib2.urlopen(otp_url).read()
    except:
        return '{"error":"OTP does not respond"}'
    bikeshed = removeNonAscii(bikeshed)
    bikeshed = bikeshed.replace('&','and')
    bikeshed = bikeshed.replace(';',' ')
    bikeshed = json.loads(bikeshed)
    bikeshed = '{"type":"Polygon","coordinates":'+str(bikeshed['features'][0]['geometry']['coordinates'][0])+'}'
    try:
        poi_data = getPOIs(bikeshed)
    except:
        return '{"error":"Overpass does not respond"}'
    crime_data = pointInPolygon(bikeshed)
    aggregation_data = aggregation(start_point,bikeshed,crime_data,poi_data,distance_decay_function,biking_time_period)
    result = '{"walkshed": %s, "poi": %s}' % (aggregation_data, poi_data)
    return result

Finalmente necesitamos enlazar todo este código, por lo que creamos una nueva función en el archivo wyp/views.py con:

from wyp.ManagementServiceBike import management as managementBike
def xml(request):
  approach = request.GET.get('wps',default=None)
  if approach == 'bike':
    start_point = request.GET.get('start_point', default=None)
    biking_time_period = request.GET.get('biking_time_period', default=None)
    distance_decay_function = request.GET.get('distance_decay_function', default=None)
    return HttpResponse(managementBike(start_point,biking_time_period,distance_decay_function))

y lo agregamos en wypdjango/urls.py

from wyp.views import inicio, xml
...
urlpatterns = patterns('',
...
    url(r'^call_wps.php',xml),
)

y finalmente ejecutamos el comando para correr el servidor y probamos que todo funciona correctamente.

python manage.py runserver

Pedestrian & Transit Model

Ahora podemos agregar la característica de caminata en el proyecto, para ello copiamos solo el archivo ManagementService.py que se encuentra en Pedestrian en (2) y le cambiamos el nombre a ManagementServicePedestrian.py y hacemos algo muy similar que el archivo de Bike, primero eliminamos todo lo relacionado con bottle:

from bottle import route, run, request
import bottle
bottle.BaseRequest.MEMFILE_MAX = 1024 * 1024
 
@route('/management')
def service():
    start_point = request.GET.get('start_point', default=None)
    walking_time_period = request.GET.get('walking_time_period', default=None)
    walking_speed = request.GET.get('walking_speed', default=None)
    distance_decay_function = request.GET.get('distance_decay_function', default=None)
    if start_point and walking_time_period and walking_speed and distance_decay_function is not None:
        return management(start_point, walking_time_period, walking_speed, distance_decay_function)
run(host='0.0.0.0', port=8363, debug=True)

y reacemos la función management(), la diferencia con Bike es que en éste se utiliza isochroneOLD para obtener el espacio posible, por lo que nuestra nueva función queda:

def management(start_point, walking_time_period, walking_speed, distance_decay_function):
    otp_isochroneOLD_url = 'http://146.155.17.18:23080/otp/routers/default/isochroneOld?'
    otp_dataInputs = 'fromPlace=%s&walkTime=%s&walkSpeed=%s&mode=WALK&toPlace=-33.5846161,-70.5410151&output=SHED' % (
        start_point, walking_time_period, walking_speed)
    otp_url = otp_isochroneOLD_url + otp_dataInputs
    try:
        walkshed = urllib2.urlopen(otp_url).read()
    except:
        return '{"error":"OTP does not respond"}'
    try:
        poi_data = getPOIs(walkshed)
    except:
        return '{"error":"Overpass does not respond"}'
    crime_data = pointInPolygon(walkshed)
    aggregation_data = aggregation(start_point,walkshed,crime_data,poi_data,distance_decay_function,walking_time_period)
    result = '{"walkshed": %s, "poi": %s}' % (aggregation_data, poi_data)
    return result

y editamos el archivo wyp/views.py para que acepte los datos de Pedestrian:

from ManagementServicePedestrian import management as managementPedestrian
 
def xml(request):
...
  elif approach == 'pedestrian':
    start_point = request.GET.get('start_point', default=None)
    walking_time_period = request.GET.get('walking_time_period', default=None)
    walking_speed = request.GET.get('walking_speed', default=None)
    distance_decay_function = request.GET.get('distance_decay_function', default=None)
    return HttpResponse(managementPedestrian(start_point,walking_time_period,walking_speed,distance_decay_function))

Y probamos como va funcionando con

python manage.py runserver

Para Transit hay que seguir pasos similares a Pedestrian, observar el código para ver como queda configurado.

Resultados

Finalmente el programa queda funcionando con muchos menos procesos y de forma más independiente, no requiere de una instalación de geoserver, tan solo Django Python, html, css y js.

Características nuevas

Gracias a Django fue posible crear un panel de administración para modificar ciertos valores dentro del código, además de agregar características nuevas que se explicarán a continuación:

Demographic group

La idea es evaluar los POIs para distintos grupos demográficos, quien hace ese trabajo es la función de AggregationService.py, pero para que la petición llegue desde la interfaz también debe de llegarle a ManagementService.py, por lo que realizaremos estas modificaciones:

Primero creamos el modelo de datos que contendrá a las opciones de grupos demográficos en la base de datos, para ello editamos el archivo wyp/models.py y agregamos lo siguiente:

from django.db import models
 
class Demographic(models.Model):
	value = models.CharField(max_length=255)                #Nombre de la opcion demografica
	bank = models.CharField(max_length=255)                 #Pesos
	grocery = models.CharField(max_length=255)
	restaurant = models.CharField(max_length=255)
	shopping = models.CharField(max_length=255)
	entertainment = models.CharField(max_length=255)
	school = models.CharField(max_length=255)
	library = models.CharField(max_length=255)
	health = models.CharField(max_length=255)
	sum = models.FloatField(blank=True,default=0)           #Suma de los pesos anteriores
	score_factor = models.FloatField(blank=True,default=0)  #Valor de ponderacion
 
	def __unicode__(self):
		return self.value
 
        #Generar el resultado de sum y score_factor automaticamente al guardar
	def save(self, *args, **kwargs):
		sumando = 0
		for i in ast.literal_eval(self.bank):
			sumando += i
		for i in ast.literal_eval(self.grocery):
			sumando += i
		for i in ast.literal_eval(self.restaurant):
			sumando += i
		for i in ast.literal_eval(self.shopping):
			sumando += i
		for i in ast.literal_eval(self.entertainment):
			sumando += i
		for i in ast.literal_eval(self.school):
			sumando += i
		for i in ast.literal_eval(self.library):
			sumando += i
		for i in ast.literal_eval(self.health):
			sumando += i
		self.sum = sumando
		self.score_factor = 100.0/float(sumando)
		super(Demographic,self).save(*args, **kwargs)

Modificamos el archivo wyp/views.py para mandar estos datos a la vista html

def inicio(request):
  demographic = Demographic.objects.all()
  return render_to_response('index.html',{'demographic':demographic},context_instance=RequestContext(request))

Luego modificamos el archivo html que se encuentra en template para que éstos aparezcan dinámicamente como una lista de opciones. template/index.html

<h3>Cycling Model</h3>
 
<div>
    <form id="call_wps_bike" method="post" action="#">
        <fieldset>
            <ul>
                ...
 
                <li><input type="checkbox" id="distance_decay_function_bike"
                           name="distance_decay_function"/>Distance
                    Decay Function
                </li>
 
                <li>
                To: 
                  <select id="demographic_bike">
                    {% for demo in demographic%}
                    <option value="{{demo.value}}">{{demo.value}}</option>
                    {% endfor %}
                  </select>
                </li>
 
                <input type="button" class="button" onclick="call_wps('bike')"
                       value="Get Accessibility Score"/>
            </ul>
        </fieldset>
    </form>
</div>
 
<h3>Walking Model</h3>
 
<div>
    <form id="call_wps_pedestrian" method="post" action="#">
        <fieldset>
            <ul>
                <li>
                    <input type="hidden" id="start_point_pedestrian" name="start_point_pedestrian"
                           readonly/>
                </li>
                ...
 
                        <div id="walking_speed_pedestrian"
                             style="width: 100px; height: 6px; margin:15px 10px auto 15px; display: inline-block;"></div>
                        <span style="font-size: 10px;">Fast</span>
                    </div>
                </li>
 
                <li>
                To: 
                <select id="demographic_pedestrian">
                    {% for demo in demographic%}
                    <option value="{{demo.value}}">{{demo.value}}</option>
                    {% endfor %}
                  </select>
                  </li>
 
                <li><input type="checkbox" id="distance_decay_function_pedestrian"
                           name="distance_decay_function"/>Distance
                    Decay Function
                </li>
 
 
                <input type="button" class="button" onclick="call_wps('pedestrian')"
                       value="Get Accessibility Score"/>
            </ul>
        </fieldset>
    </form>
</div>
 
<h3>Transit & Walking Model</h3>
 
<div>
    <form id="call_wps_transit" method="post" action="#">
        <fieldset>
            <ul>
                <li>
                    <input type="hidden" id="start_point_transit" name="start_point_transit" readonly/>
                </li>
                ...
 
                                <li>
                To: 
                  <select id="demographic_transit">
                    {% for demo in demographic%}
                    <option value="{{demo.value}}">{{demo.value}}</option>
                    {% endfor %}
                  </select>
                </li>
 
                ...
 
                <input type="button" class="button" onclick="call_wps('transit')"
                       value="Get Accessibility Score"/>
            </ul>
        </fieldset>
    </form>
</div>

Funcionamiento actual

El código se puede encontrar acá: http://146.155.17.18:18080/csfuente/wyp-django/