flock-server/model.py

150 lines
4.9 KiB
Python

"""
Object-Document Map for bike model
"""
from mongoengine import Document, DateTimeField, GeoPointField, \
FloatField, connection
import bson
import datetime
from LatLon23 import LatLon, Latitude, Longitude
from geopy import distance
# TODO: instead of centroid use speed and bearing of flockers to create a vector, point rider towards it
# use time elapsed since last update, speed vector and current point to compute future location
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()
bearings = list()
n = 0
for b in bikes:
# next point is determined by the distance travelled since the last update
dt = datetime.datetime.now() - b['last_update']
progress = (b['speed'] * dt.seconds) / 1000 # in km
point = LatLon(b['point'][1], b['point'][0])
next_point = point.offset(b['bearing'], progress)
# collect all speeds, bearings and next points, to find centroid and average speed
speeds.append(b['speed'])
bearings.append(b.get('bearing', 0))
lats.append(float(next_point.lat))
lons.append(float(next_point.lon))
n += 1
self.mean_speed = 0
self.size = 0
self.centroid = None
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)))
class Bike(Document):
point = GeoPointField()
destination = GeoPointField(required=True)
speed = FloatField(default=0)
bearing = FloatField(default=0)
last_update = DateTimeField(default=datetime.datetime.utcnow)
def __init__(self, *args, **kwargs):
super(Document, self).__init__(*args, **kwargs)
self.db = connection.get_db()
def update(self, lat, lon, speed, bearing):
self.point = (lon, lat)
self.speed = speed
self.bearing = bearing
self.last_update = datetime.datetime.now()
self.save()
def heading_to(self, target):
s = LatLon(Latitude(self.point[1]),
Longitude(self.point[0]))
if type(target) is list:
t = LatLon(Latitude(target[0]),
Longitude(target[1]))
else:
t = target
return s.heading_initial(t)
def distance_to(self, point):
return distance.geodesic(self.get_point(),
(point[1],
point[0])).meters
def get_point(self):
return (self.point[1],
self.point[0])
def get_dest(self):
return (self.destination[1],
self.destination[0])
def find_flock(self, point_altruism=0.1, dest_altruism=0.2):
"""
:return: list of Bike objects
"""
trip_len = distance.geodesic(self.get_point(), self.get_dest()).meters
local_radius = trip_len * point_altruism
destination_radius = trip_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}},
'last_update': {
'$gte': datetime.datetime.now() - datetime.timedelta(minutes=15)}, # TODO: adjust this
'_id': {'$ne': bson.objectid.ObjectId(self.id)}})]
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 > 0:
flock_lat = float(flock.centroid.lat)
flock_lon = float(flock.centroid.lon)
return {'flock_lat': flock_lat,
'flock_lon': flock_lon,
'flock_avg_speed': flock.mean_speed,
'flock_size': flock.size}
else:
return {}