diff --git a/README.md b/README.md index 7b74aab..a49b665 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,46 @@ # Flock Server -Don't hide, hive! \ No newline at end of file +This program is the server component of the [Social Cycling](https://gitlab.com/rgarcia-herrera/social-cycling) mobile app. + + +## Installation and running + +Clone or download this repo. +Install Python libraries, like so: + + $ pip install -r requirements.txt + [...] + +### MongoDB setup + +[MongoDB](https://www.mongodb.com/) is used for data +persistence. Geographical queries require the creation of indexes for +the **point** and **destination** fields. + + $ mongodb + [...] + > use test + switched to db test + > db.bike.createIndex({point:"2dsphere"}); + [...] + > db.bike.createIndex({destination:"2dsphere"}); + [...] + + +### Development webserver + +In a production environment Flask apps must be deployed with a +dedicated webserver. Instructions for doing so are documented +[elsewhere](http://flask.pocoo.org/docs/1.0/deploying/). + +For simplicity the app may be run with the included development +server, using the following commands: + + $ export FLASK_APP=server.py + $ flask run + * Serving Flask app "server.py" + [...] + * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) + + + diff --git a/model.py b/model.py new file mode 100644 index 0000000..8f639fb --- /dev/null +++ b/model.py @@ -0,0 +1,114 @@ +""" +Object-Document Map for bike model +""" +from mongoengine import Document, DateTimeField, GeoPointField, \ + FloatField, connection +from road_agent import Agent +import datetime +from LatLon import LatLon, Latitude, Longitude + + +class Flock: + """ + Flock object is created from a list of agents + and has a useful centroid + """ + def __init__(self, bikes): + lats = list() + lons = list() + speeds = list() + n = 0 + for b in bikes: + speeds.append(b['speed']) + lats.append(float(b['point'][1])) + lons.append(float(b['point'][0])) + n += 1 + + if n > 0: + self.size = n + self.mean_speed = sum(speeds) / float(len(speeds)) + self.centroid = LatLon(Latitude(sum(lats) / len(lats)), + Longitude(sum(lons) / len(lons))) + else: + self.mean_speed = 0 + self.size = 0 + self.centroid = None + + +class Bike(Document): + point = GeoPointField() + destination = GeoPointField(required=True) + speed = FloatField(default=0) + last_update = DateTimeField(default=datetime.datetime.utcnow) + + def __init__(self, *args, **kwargs): + super(Document, self).__init__(*args, **kwargs) + + self.agent = Agent(point=LatLon(self.point[1], + self.point[0]), + dest=LatLon(self.destination[1], + self.destination[0]), + router=None) + + self.db = connection.get_db() + + def update(self, lat, lon): + self.point = (lon, lat) + + tdelta = datetime.datetime.now() - self.last_update + seconds = tdelta.total_seconds() + new_point = LatLon(Latitude(lat), Longitude(lon)) + distance = abs(self.agent.point().distance(new_point)) / 1000.0 + self.agent.update(new_point) + + self.speed = distance / seconds + self.save() + + def find_flock(self, point_altruism=0.1, dest_altruism=0.2): + """ + :return: list of Bike objects + """ + + voyage_len = self.agent.distance_to(self.agent.destination()) + + local_radius = voyage_len * point_altruism + destination_radius = voyage_len * dest_altruism + + # these are bikes around me + local = [bike for bike in + self.db.bike.find( + {'point': + {'$near': + {'$geometry': {'type': 'Point', + 'coordinates': self.point}, + '$maxDistance': local_radius}}})] + local_ids = set([bike['_id'] for bike in local]) + + # these are going near my destination + remote = [bike for bike in + self.db.bike.find( + {'destination': + {'$near': + {'$geometry': {'type': 'Point', + 'coordinates': self.destination}, + '$maxDistance': destination_radius}}})] + remote_ids = set([bike['_id'] for bike in remote]) + + # intersect them! + flock_ids = local_ids.intersection(remote_ids) + + return (bike for bike in local if bike['_id'] in flock_ids) + + def flock_data(self): + """ + :return: heading from point to flock centroid, in degrees + """ + flock = Flock(self.find_flock()) + if flock.size > 1: # no flocking with self! + return {'flock_heading': self.agent.heading_to(flock.centroid), + 'flock_distance': self.agent.distance_to(flock.centroid), + 'flock_avg_speed': flock.mean_speed, + 'flock_size': flock.size} + else: + return {} + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..aba8ac3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +flask-mongoengine +road-agent==0.0.3 diff --git a/server.py b/server.py new file mode 100644 index 0000000..b68f06e --- /dev/null +++ b/server.py @@ -0,0 +1,42 @@ +from flask import Flask +from flask import request, jsonify +from flask_mongoengine import MongoEngine +from model import Bike + +app = Flask(__name__) + +db = MongoEngine(app) + + +@app.route("/register/") +def register(): + dest_lat = request.args.get('dest_lat', None) + dest_lon = request.args.get('dest_lon', None) + + assert dest_lat is not None and dest_lon is not None + + dest = (float(dest_lon), float(dest_lat)) + + bike = Bike(point=(0, 0), + destination=dest) + + bike.save() + + return jsonify(bike_id=str(bike.id)) + + +@app.route("/update//") +def update(bike_id): + + bike = Bike.objects.get(id=bike_id) + + lat = float(request.args.get('lat', None)) + lon = float(request.args.get('lon', None)) + + bike.update(lat, lon) + + return jsonify( + dest_heading=bike.agent.heading_to(bike.agent.destination()), + dest_distance=bike.agent.distance_to(bike.agent.destination()), + speed=bike.speed, + **bike.flock_data()) diff --git a/test_model.py b/test_model.py new file mode 100644 index 0000000..385c07a --- /dev/null +++ b/test_model.py @@ -0,0 +1,47 @@ +import pytest +import model + +from mongoengine import connect +connect('test') + + +juarez = (-98.88038, 19.50963) +# scope='module' +@pytest.fixture() +def four_bikers(): + """ + create four bikers with same destination + three of them closeby, one too far to hive with + """ + r = model.Bike(point=(-98.87011, 19.48790), + destination=juarez) + r.save() + + k = model.Bike(point=(-98.87086, 19.48838), + destination=juarez) + k.save() + + m = model.Bike(point=(-98.87004, 19.48737), + destination=juarez) + m.save() + + t = model.Bike(point=(-98.87312, 19.48658), + destination=juarez) + t.save() + + yield [r, k, m, t] + + # teardown + model.Bike.objects.delete() + + +def test_flock_data(four_bikers): + r = four_bikers[0] + assert r.flock_data() == {'flock_avg_speed': 0.0, + 'flock_distance': 23.86718565123822, + 'flock_heading': -94.43342715427826, + 'flock_size': 3} + + +# http://localhost:5000/register/?dest_lat=19.50963&dest_lon=-98.88038 +# http://localhost:5000/update/5c4239198849af1ee43edb2c/?lon=-98.87011&lat=19.48790