Skip to content
Snippets Groups Projects
app.py 4.78 KiB
import os
from flask import Flask, jsonify, make_response, render_template, request, abort
from flask.ext.bower import Bower
from jinja2.exceptions import TemplateNotFound
from sqlalchemy.orm import joinedload
from models import db, Agency
from datetime import datetime

app = Flask(__name__, instance_relative_config=True)

# Load environment-specific settings from config.py
env = os.environ.get('BUSMAP_ENV', 'prod')
app.config.from_object('config.{0}Config'.format(env.capitalize()))

# Load deployment-specific settings from instance/config.cfg
app.config.from_pyfile('config.py', silent=True)

# Database init
db.init_app(app)

Bower(app)

# Flask Web Routes
@app.route('/')
def map():
    # TODO: serve different agency depending on cookie (or special domain)
    agency_tag = app.config['AGENCIES'][0]
    agency = db.session.query(Agency).filter(Agency.tag==agency_tag).one()
    r = make_response(render_template('map.html', agency=agency, config=app.config))
    r.headers.add('X-Frame-Options', 'DENY')
    return r

@app.route('/e')
def map_embed():
    # TODO: serve different agency depending on cookie (or special domain)
    agency_tag = app.config['AGENCIES'][0]
    agency = db.session.query(Agency).filter(Agency.tag==agency_tag).one()
    return render_template('map.html', agency=agency, config=app.config, embed=True)

@app.route('/ajax')
def ajax():
    """ Handle all async requests (from JS). """
    query = request.args.get('query')
    agency_tag = app.config['AGENCIES'][0]
    agency = db.session.query(Agency).filter(Agency.tag==agency_tag).one()
    def modal(name):
        """ Serve contents of a modal popup """
        if not name.isalnum():
            return abort(404)
        template_name = 'modal-' + name + '.html'
        try:
            return render_template(template_name, agency=agency, config=app.config)
        except TemplateNotFound:
            return abort(404)
    def routes():
        """ Serve the route configuration (routes, stops)  """
        from models import Route, RouteStop, Stop
        routes = db.session.query(Route).join(Agency)\
            .filter(Agency.tag==agency_tag).all()
        stops = db.session.query(Stop).options(joinedload(Stop.routes))\
            .filter(Stop.routes.any(Route.id.in_([r.id for r in routes]))).all()
        return {
            "routes": {r.tag: r.serialize() for r in routes},
            "stops": {s.id: s.serialize() for s in stops}
        }

    def vehicles():
        """ Serve the current vehicle locations and arrival predictions. """
        from models import Route, VehicleLocation, Prediction
        # TODO: OPTIMIZE
        # Over 1sec/request just to get predictions? Fuck that noise.
        # TODO: Somehow bundle these queries into the object model definitions? So messy :(
        # maybe VehicleLocation.get_latest_for_agency(a)?
        # 1. Select the latest vehicle locations for each vehicle. (The DB may have old ones too).
        v_inner = db.session.query(VehicleLocation.vehicle,
                                db.func.max(VehicleLocation.time).label("time"))\
                            .group_by(VehicleLocation.vehicle).subquery()
        vehicle_locations = db.session.query(VehicleLocation).join(v_inner, db.and_(
                v_inner.c.vehicle == VehicleLocation.vehicle,
                v_inner.c.time == VehicleLocation.time
            )).filter(Agency.tag==agency_tag).all()
        # 2. Select the predictions for each vehicle:stop pair which came from the most recent
        # API call for that vehicle:stop pair. Old predictions may be stored but we don't want them.
        now = datetime.now()
        p_inner = db.session.query(Prediction.vehicle, Prediction.stop_id,
                                   db.func.max(Prediction.api_call_id).label("api_call_id"))\
                            .group_by(Prediction.vehicle, Prediction.stop_id)\
                            .subquery()
        predictions = db.session.query(Prediction).join(p_inner, db.and_(
                p_inner.c.api_call_id == Prediction.api_call_id,
                p_inner.c.vehicle == Prediction.vehicle,
                p_inner.c.stop_id == Prediction.stop_id
            )).filter(
                Agency.tag==agency_tag,
                Prediction.prediction >= now)\
            .group_by(Prediction.id, Prediction.vehicle, Prediction.stop_id)\
            .all()
        return {
                "locations": {v.vehicle: v.serialize() for v in vehicle_locations},
                "predictions": {p.id: p.serialize() for p in predictions}
        }

    if query == "routes":
        return jsonify(routes())
    elif query == "vehicles":
        return jsonify(vehicles())
    elif query == "modal":
        modal_name = request.args.get('modal_name')
        return modal(modal_name)

if __name__ == '__main__':
    # Run Flask
    app.run(host='0.0.0.0')