This commit is contained in:
parent
e8b82c0829
commit
b3463cf9fc
45
README.md
45
README.md
@ -1,3 +1,46 @@
|
|||||||
# Flock Server
|
# Flock Server
|
||||||
|
|
||||||
Don't hide, hive!
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
114
model.py
Normal file
114
model.py
Normal file
@ -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 {}
|
||||||
|
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
flask
|
||||||
|
flask-mongoengine
|
||||||
|
road-agent==0.0.3
|
||||||
42
server.py
Normal file
42
server.py
Normal file
@ -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/<bike_id>/")
|
||||||
|
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())
|
||||||
47
test_model.py
Normal file
47
test_model.py
Normal file
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user