Skip to content
Snippets Groups Projects
Commit da9dd1ee authored by Anton Sarukhanov's avatar Anton Sarukhanov
Browse files

AJAX is implemented, but its SLOW.

parent 66a68b25
No related branches found
No related tags found
No related merge requests found
import os
from flask import Flask, render_template
from flask import Flask, jsonify, render_template, request
from flask.ext.bower import Bower
from sqlalchemy.orm import joinedload
from models import db
from datetime import datetime
app = Flask(__name__, instance_relative_config=True)
......@@ -26,6 +28,39 @@ def map():
agency = db.session.query(Agency).filter(Agency.tag==agency_tag).one()
return render_template('map.html', agency=agency, config=app.config)
@app.route('/ajax')
def ajax():
print("BEGIN AJAX {0}".format(datetime.now()))
dataset = request.args.get('dataset')
agency = request.args.get('agency')
def routes():
from models import Agency, Route
routes = db.session.query(Route).join(Agency)\
.filter(Agency.tag==agency).all()
return {r.tag: r.serialize() for r in routes}
def vehicles():
from models import Agency, Route, VehicleLocation, Prediction
print("BEGIN VEHICLES")
vehicle_locations = db.session.query(VehicleLocation)\
.join(VehicleLocation.route).join(Route.agency)\
.filter(Agency.tag==agency).all()
predictions = db.session.query(Prediction)\
.join(Prediction.route).join(Route.agency)\
.filter(Agency.tag==agency).all()
z = {
"locations": {v.vehicle: v.serialize() for v in vehicle_locations},
"predictions": {p.vehicle: p.serialize() for p in predictions}
}
return z
if dataset == "routes":
r = jsonify(routes())
elif dataset == "vehicles":
r = jsonify(vehicles())
print("END AJAX {0}".format(datetime.now()))
return r
if __name__ == '__main__':
# Run Flask
app.run(host='0.0.0.0')
......
from werkzeug.contrib.profiler import ProfilerMiddleware
from app import app
app.config['PROFILE'] = True
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
app.run(host='0.0.0.0', debug = True)
......@@ -14,6 +14,7 @@
"tests"
],
"dependencies": {
"leaflet": "~0.7.7"
"leaflet": "~0.7.7",
"jquery": "~2.1.4"
}
}
......@@ -4,7 +4,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm import object_session, column_property
from sqlalchemy.orm import backref, column_property
from sqlalchemy.dialects import postgresql
from datetime import datetime
......@@ -58,14 +58,75 @@ class Agency(db.Model, BMModel):
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="agencies")
@property
def center(self):
"""
Centerpoint coordinates for this agency.
"""
lat = (self.lat_max + self.lat_min) / 2
lon = (self.lon_max + self.lon_min) / 2
return [lat, lon]
def serialize(self):
return {
'tag': self.tag,
'title': self.title,
'short_title': self.short_title,
}
class ApiCall(db.Model, BMModel):
"""
A retrieval of data from a data source.
"""
__tablename__ = "api_call"
id = db.Column(db.Integer, primary_key=True)
# Full URL of request
url = db.Column(db.String)
# Size of the dataset in bytes
size = db.Column(db.Integer, default=0)
# HTTP response code
status = db.Column(db.Integer)
# Any error text returned by the API
error = db.Column(db.String)
# Where the data came from
source = db.Column(db.Enum('Nextbus', name="source", native_enum=False), default='Nextbus')
# When this data was fetched
time = db.Column(db.DateTime, default=datetime.now)
# Parameters (request variables) of this API call
params = db.Column(postgresql.JSON)
class Direction(db.Model, BMModel):
"""
A direction of a route. "Eastbound" / "Westbound", "Inbound" / "Outbound".
"""
__tablename__ = "direction"
__table_args__ = (
db.UniqueConstraint('tag', 'route_id'),
)
id = db.Column(db.Integer, primary_key=True)
# route_id - The agency which operates this route
route_id = db.Column(db.Integer, db.ForeignKey('route.id', ondelete="cascade"))
# tag - Unique alphanumeric identifier (a.k.a. "machine name")
tag = db.Column(db.String)
# title
title = db.Column(db.String)
# name = A simplified/normalized name (for indexing; some routes may share this)
name= db.Column(db.String)
# API Request which was used to retrieve this data
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="directions")
def serialize(self):
return {
'tag': self.tag,
'title': self.title,
}
class Prediction(db.Model, BMModel):
"""
......@@ -108,6 +169,17 @@ class Prediction(db.Model, BMModel):
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="predictions")
def serialize(self):
return {
'route': self.route.tag,
'prediction': self.prediction,
'created': self.created,
'is_departure': self.is_departure,
'has_layover': self.has_layover,
'direction': self.direction.tag if self.direction else None,
'vehicle': self.vehicle,
}
class Region(db.Model, BMModel):
"""
......@@ -183,32 +255,23 @@ class Route(db.Model, BMModel):
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="routes")
class Direction(db.Model, BMModel):
"""
A direction of a route. "Eastbound" / "Westbound", "Inbound" / "Outbound".
"""
__tablename__ = "direction"
__table_args__ = (
db.UniqueConstraint('tag', 'route_id'),
)
id = db.Column(db.Integer, primary_key=True)
# route_id - The agency which operates this route
route_id = db.Column(db.Integer, db.ForeignKey('route.id', ondelete="cascade"))
# tag - Unique alphanumeric identifier (a.k.a. "machine name")
tag = db.Column(db.String)
# title
title = db.Column(db.String)
# name = A simplified/normalized name (for indexing; some routes may share this)
name= db.Column(db.String)
# API Request which was used to retrieve this data
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="directions")
def serialize(self):
return {
'agency': self.agency.serialize(),
'tag': self.tag,
'title': self.title,
'short_title': self.short_title,
'color': self.color,
'opposite_color': self.opposite_color,
'bounds': {
'lat_min': self.lat_min,
'lat_max': self.lat_max,
'lon_min': self.lon_min,
'lon_max': self.lon_max,
},
'directions': {d.tag: d.serialize() for d in self.directions},
'stops': {s.tag: s.serialize() for s_tag, s in self.stops.items()},
}
class Stop(db.Model, BMModel):
......@@ -246,6 +309,14 @@ class Stop(db.Model, BMModel):
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="stops")
def serialize(self):
return {
'tag': self.tag,
'title': self.title,
'lat': self.lat,
'lon': self.lon,
}
class VehicleLocation(db.Model, BMModel):
"""
......@@ -286,38 +357,19 @@ class VehicleLocation(db.Model, BMModel):
api_call_id = db.Column(db.Integer, db.ForeignKey('api_call.id', ondelete="set null"))
api_call = db.relationship("ApiCall", backref="vehicle_locations")
class ApiCall(db.Model, BMModel):
"""
A retrieval of data from a data source.
"""
__tablename__ = "api_call"
id = db.Column(db.Integer, primary_key=True)
# Full URL of request
url = db.Column(db.String)
# Size of the dataset in bytes
size = db.Column(db.Integer, default=0)
# HTTP response code
status = db.Column(db.Integer)
# Any error text returned by the API
error = db.Column(db.String)
# Where the data came from
source = db.Column(db.Enum('Nextbus', name="source", native_enum=False), default='Nextbus')
# When this data was fetched
time = db.Column(db.DateTime, default=datetime.now)
# Parameters (request variables) of this API call
params = db.Column(postgresql.JSON)
def serialize(self):
return {
'vehicle': self.vehicle,
'route': self.route.tag,
'direction': self.direction.tag if self.direction else None,
'lat': self.lat,
'lon': self.lon,
'time': self.time,
'speed': self.speed,
}
# Hybrid properties (can't be defined until relevant classes are defined)
# Agency boundaries (derived from Route boundaries)
Agency.lat_min = column_property(db.select([db.func.min(Route.lat_min)])\
.where(Route.agency_id==Agency.id))
......
/* The primary class for this project */
var BusMap = {};
/*
Creates and manipulates a Leaflet map, abstracting away the ugly parts.
Updating the map (Vehicles, Routes) is also handled here.
*/
BusMap.Map = function(opts) {
this.opts = opts;
var that = this;
init();
/* Constructor - create/initialize the map */
function init() {
// Create Map
var mapOptions = {
zoomControl: false,
};
var boundsOptions = {
animate: false,
reset: true,
};
that.leaflet = L.map(that.opts.mapElement, mapOptions)
.fitBounds(that.opts.bounds)
.setMaxBounds(that.opts.bounds, boundsOptions);
if (that.opts.center) { that.leaflet.setView(that.opts.center); }
else {that.leaflet.fitBounds(that.opts.bounds)}
if (that.opts.zoom) that.leaflet.setZoom(that.opts.zoom);
// Configure and apply the map tile layer
var tileUrl = that.opts.tileUrl;
var tileOptions = {}
if (that.opts.tileOptions) { tileOptions = that.opts.tileOptions; }
L.tileLayer(tileUrl, tileOptions).addTo(that.leaflet);
// Fetch initial data
updateRoutes();
updateVehicles();
// Begin timed data updates
if (that.opts.refresh.routes) {
setInterval(updateRoutes, that.opts.refresh.routes * 1000);
}
if (that.opts.refresh.vehicles) {
setInterval(updateVehicles, that.opts.refresh.vehicles * 1000);
}
};
/* Get Routes (and Stops, and Directions) */
function updateRoutes() {
var url = "ajax";
var params = {
"dataset": "routes",
"agency": that.opts.agency,
};
$.getJSON(url, params)
.done(function(data) {
console.log(data);
});
// cb: update that.routes && update map (and UI?). data bindings??
// todo
return that;
};
/* Get Vehicles (and Predictions) */
function updateVehicles() {
var url = "ajax";
var params = {
"dataset": "vehicles",
"agency": that.opts.agency,
};
$.getJSON(url, params)
.done(function(data) {
console.log(data);
});
// todo
return that;
};
return that;
};
......@@ -8,34 +8,31 @@
{% block body %}
<div id="map"></div>
<script src="bower/leaflet/dist/leaflet.js"></script>
<script src="bower/jquery/dist/jquery.min.js"></script>
<script src="static/js/map.js"></script>
<script>
var mapDivId = "map";
var mapOptions = {
zoomControl: false,
};
var center = [{{ agency.center[0]|round(5) }}, {{ agency.center[1]|round(5) }}];
var bounds = [
[{{ (agency.lat_min - config['MAP_LAT_PADDING'])|round(5) }},
{{ (agency.lon_min - config['MAP_LON_PADDING'])|round(5) }}],
[{{ (agency.lat_max + config['MAP_LAT_PADDING'])|round(5) }},
{{ (agency.lon_max + config['MAP_LON_PADDING'])|round(5) }}]
];
var boundsOptions = {
animate: false,
reset: true,
};
var tileUrl = '{{ config['MAP_TILE_URL']|safe }}';
var tileOptions = {
subdomains: {{ config['MAP_TILE_SUBDOMAINS']|tojson|safe }},
attribution: '{{ config['MAP_CUSTOM_ATTRIBUTION']|safe }}'
+ '<br>Map Data &copy; {{ config['MAP_DATA_ATTRIBUTION']|safe }}'
+ '<br>Bus Data &copy; {{ agency.title }}',
tileset: '{{ config['MAP_TILESET']|safe }}',
errorTileUrl: '{{ config['MAP_ERROR_TILE_URL']|safe }}',
};
var map = L.map(mapDivId, mapOptions)
.fitBounds(bounds)
.setMaxBounds(bounds, boundsOptions);
L.tileLayer(tileUrl, tileOptions).addTo(map);
var map = BusMap.Map({
agency: {{ agency.tag|tojson|safe }},
mapElement: $("#map").get(0),
tileUrl: '{{ config['MAP_TILE_URL']|safe }}',
tileOptions: {
subdomains: {{ config['MAP_TILE_SUBDOMAINS']|tojson|safe }},
attribution: '{{ config['MAP_CUSTOM_ATTRIBUTION']|safe }}'
+ '<br>Map Data &copy; {{ config['MAP_DATA_ATTRIBUTION']|safe }}'
+ '<br>Bus Data &copy; {{ agency.title }}',
tileset: '{{ config['MAP_TILESET']|safe }}',
errorTileUrl: '{{ config['MAP_ERROR_TILE_URL']|safe }}',
},
bounds: [
[{{ (agency.lat_min - config['MAP_LAT_PADDING'])|round(5) }},
{{ (agency.lon_min - config['MAP_LON_PADDING'])|round(5) }}],
[{{ (agency.lat_max + config['MAP_LAT_PADDING'])|round(5) }},
{{ (agency.lon_max + config['MAP_LON_PADDING'])|round(5) }}]
],
refresh: {
routes: 60,
vehicles: 5,
},
});
</script>
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment