484 lines
17 KiB
Python
484 lines
17 KiB
Python
from django.contrib.gis.db import models
|
|
from django.contrib.gis.geos import Point
|
|
from django import forms
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.contenttypes import generic
|
|
from django.db import connection
|
|
import json
|
|
from django.contrib.gis.measure import D
|
|
import datetime
|
|
from gtfs.gtfs_export import time_of
|
|
|
|
STOP_CHOICES = ( ('U','Up'),
|
|
('D', 'Down'),
|
|
)
|
|
|
|
DAYS = {
|
|
1: 'Monday',
|
|
2: 'Tuesday',
|
|
3: 'Wednesday',
|
|
4: 'Thursday',
|
|
5: 'Friday',
|
|
6: 'Saturday',
|
|
7: 'Sunday',
|
|
8: 'Holiday'
|
|
}
|
|
|
|
SCHED = {
|
|
'MS':[1, 2, 3, 4, 5, 6],
|
|
'HOL':[7, 8],
|
|
'SUN':[7],
|
|
'MF&HOL':[1, 2, 3, 4, 5, 8],
|
|
'SAT':[6],
|
|
'MF':[1, 2, 3, 4, 5],
|
|
'SH':[7, 8],
|
|
'AD':[1, 2, 3, 4, 5, 6, 7, 8],
|
|
'SAT&SUN':[6, 7],
|
|
'MS&HOL':[1, 2, 3, 4, 5, 6, 8],
|
|
'FW':[1, 2, 3, 4, 5, 6, 7],
|
|
'MS&SUN':[1, 2, 3, 4, 5, 6, 7],
|
|
'SAT/SH':[6, 7, 8],
|
|
'SAT&HOL':[6, 8],
|
|
'SAT&SH':[6, 7, 8],
|
|
'SAT/SUND&HOL':[6, 7, 8],
|
|
'S/H':[7, 8],
|
|
'SAT,SUN&HOL':[6, 7, 8],
|
|
'FH':[5, 8]
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Runtime start and end hour
|
|
# matching column `runtime$` where `$` = index % 4 + 1
|
|
RUNTIMES = (
|
|
(00, 07),
|
|
(07, 11),
|
|
(11, 17),
|
|
(17, 20),
|
|
(20, 24)
|
|
)
|
|
|
|
class TrigramSearchManager(models.GeoManager):
|
|
def __init__(self, trigram_columns=[]):
|
|
super(TrigramSearchManager, self).__init__()
|
|
self.trigram_columns = trigram_columns
|
|
|
|
def set_threshold(self, threshold):
|
|
"""Set the limit for trigram similarity matching."""
|
|
cursor = connection.cursor()
|
|
cursor.execute("""SELECT set_limit(%f)""" % threshold)
|
|
|
|
def find_approximate(self, text, match=0.5):
|
|
self.set_threshold(match)
|
|
similarity_measure = "greatest(%s)" % ",".join(["similarity(%s, %%s)" % col for col in self.trigram_columns])
|
|
similarity_filter = " OR ".join(["%s %%%% %%s" % col for col in self.trigram_columns])
|
|
text_values = [text] * len(self.trigram_columns)
|
|
qset = self.get_query_set()
|
|
# use the pg_trgm index via the % operator
|
|
qset = qset.extra(select={"similarity":similarity_measure},
|
|
select_params=text_values,
|
|
where=[similarity_filter],
|
|
params=text_values,
|
|
order_by=["-similarity"])
|
|
return qset
|
|
|
|
class Area(models.Model):
|
|
objects = TrigramSearchManager(("name", "name_mr", "display_name"))
|
|
code = models.IntegerField() #primary_key=True)
|
|
slug = models.SlugField(null=True)
|
|
name = models.TextField(blank=True, max_length=255)
|
|
name_mr= models.TextField(null=True, blank=True, max_length=512) #null=True,
|
|
display_name = models.TextField(blank=True, max_length=255)
|
|
geometry = models.PolygonField(blank=True, null=True)
|
|
alt_names = generic.GenericRelation("AlternativeName")
|
|
|
|
def get_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'code': self.code,
|
|
'slug': self.slug,
|
|
'name': self.name,
|
|
'name_mr': self.name_mr,
|
|
'display_name': self.display_name,
|
|
'url': self.get_absolute_url()
|
|
#FIXME add alt_names and geometry
|
|
}
|
|
|
|
def get_absolute_url(self):
|
|
return "/area/%s/" % self.name
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
#FIXME: ideally this would be done using the polygon of the area, but right now we take a random stop in the area, find all stops within x kms, and then return unique areas for those stops.
|
|
@property
|
|
def nearby_areas(self, distance=D(km=3)):
|
|
stop = self.stop_set.all()[0]
|
|
tup = (stop.point, distance,)
|
|
qset = Stop.objects.filter(point__distance_lte=tup).values('area').distinct()
|
|
area_ids = [val['area'] for val in qset]
|
|
return Area.objects.filter(pk__in=area_ids)
|
|
|
|
@property
|
|
def routes_passing(self):
|
|
return Route.objects.filter(routedetail__stop__area=self).distinct()
|
|
|
|
class Road(models.Model):
|
|
code = models.IntegerField()#primary_key=True)
|
|
slug = models.SlugField(null=True)
|
|
name = models.TextField(blank=True, max_length=255)
|
|
name_mr= models.TextField(null=True, blank=True, max_length=512)
|
|
display_name = models.TextField(blank=True, max_length=255)
|
|
geometry = models.LineStringField(blank=True, null=True)
|
|
alt_names = generic.GenericRelation("AlternativeName")
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class Fare(models.Model):
|
|
slab = models.DecimalField(max_digits=5, decimal_places=2)
|
|
ordinary = models.PositiveIntegerField()
|
|
limited = models.PositiveIntegerField()
|
|
express = models.PositiveIntegerField()
|
|
ac = models.PositiveIntegerField()
|
|
ac_express = models.PositiveIntegerField()
|
|
def __unicode__(self):
|
|
return str(self.slab)
|
|
|
|
class Stop(models.Model):
|
|
objects = TrigramSearchManager(("name", "name_mr", "display_name"))
|
|
code = models.IntegerField(db_index=True)
|
|
slug = models.SlugField(null=True)
|
|
name = models.TextField(blank=True, max_length=255, db_index=True)
|
|
display_name = models.TextField(blank=True, max_length=255)
|
|
dbdirection = models.CharField(null=True, blank=True, max_length=5, choices=STOP_CHOICES) #stopfl - > direction
|
|
chowki = models.NullBooleanField(null=True, blank=True) # this is nullable since in the next datafeed , they might have blank to represent a 0.
|
|
road = models.ForeignKey(Road, default=None, null=True, blank=True)
|
|
area = models.ForeignKey(Area, default=None, null=True, blank=True)
|
|
depot = models.ForeignKey("Depot", default=None, null=True, blank=True, related_name="is_depot_for") #models.CharField(null=True, blank=True, max_length=5)
|
|
name_mr= models.TextField(null=True, blank=True, max_length=512)#null=True
|
|
point = models.PointField(null=True)
|
|
alt_names = generic.GenericRelation("AlternativeName")
|
|
|
|
def get_dict(self):
|
|
routes = []
|
|
for r in self.routedetail_set.all():
|
|
if r.route is not None:
|
|
routes.append(r.route)
|
|
return {
|
|
'id': self.id,
|
|
'code': self.code,
|
|
'slug': self.slug,
|
|
'official_name': self.name,
|
|
'display_name': self.display_name,
|
|
'road': self.road.name,
|
|
'area': self.area.name,
|
|
'name_mr': self.name_mr,
|
|
'direction': self.dbdirection,
|
|
'routes': ", ".join([r.alias for r in routes]),
|
|
'alternative_names': ", ".join([a.name for a in self.alt_names.all()]),
|
|
'url': self.get_absolute_url()
|
|
}
|
|
|
|
def get_geojson(self, srid=4326):
|
|
# print srid
|
|
if self.point is not None:
|
|
geom = json.loads(self.point.transform(srid, True).geojson)
|
|
else:
|
|
geom = {}
|
|
|
|
properties = self.get_dict()
|
|
|
|
return {
|
|
'type': 'Feature',
|
|
'properties': properties,
|
|
'geometry': geom
|
|
}
|
|
|
|
def from_geojson(self, geojson, srid=4326):
|
|
geom = geojson['geometry']['coordinates']
|
|
data = geojson['properties']
|
|
point = Point(geom[0], geom[1], srid=srid).transform(4326, True)
|
|
if point:
|
|
self.point = point
|
|
self.display_name = data['display_name']
|
|
self.name_mr = data['name_mr']
|
|
if data.has_key('alternative_names') and data['alternative_names'].strip() != '':
|
|
for a in self.alt_names.all():
|
|
a.delete()
|
|
for a in data['alternative_names'].split(","):
|
|
alt_name = AlternativeName()
|
|
alt_name.name = a.strip()
|
|
alt_name.typ = 'common'
|
|
alt_name.content_object = self
|
|
alt_name.save()
|
|
self.alt_names.add(alt_name)
|
|
self.save()
|
|
return self.get_geojson(srid=srid)
|
|
|
|
@property
|
|
def nearby_stops(self, dist=D(km=1)):
|
|
tup = (self.point, dist,)
|
|
return Stop.objects.filter(point__distance_lte=tup)
|
|
|
|
@property
|
|
def routes(self):
|
|
return Route.objects.filter(routedetail__stop=self)
|
|
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
'''
|
|
check if point exists for stop
|
|
'''
|
|
def has_point(self):
|
|
if self.stoplocation_set.all():
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
has_point.boolean = True
|
|
|
|
def get_absolute_url(self):
|
|
return "/stop/%s" % self.slug
|
|
|
|
|
|
class Route(models.Model):
|
|
code = models.TextField(max_length=255, unique=True) #FIXME: Why is this a TextField??
|
|
slug = models.SlugField(null=True)
|
|
alias = models.TextField(max_length=255, db_index=True)
|
|
from_stop_txt = models.TextField(max_length=500)
|
|
to_stop_txt = models.TextField(max_length=500)
|
|
from_stop = models.ForeignKey(Stop, related_name='routes_from', default=None, null=True, blank=True)
|
|
to_stop = models.ForeignKey(Stop, related_name='routes_to', default=None, null=True, blank=True)
|
|
distance = models.DecimalField(max_digits=3, decimal_places=1)
|
|
stages = models.IntegerField()
|
|
route_type = models.ForeignKey('RouteType', default=0, null=True, blank=True)
|
|
code3 = models.CharField(max_length=5)
|
|
|
|
class Meta:
|
|
ordering = ['code']
|
|
|
|
def get_absolute_url(self):
|
|
return "/route/%s/" % self.alias
|
|
|
|
def __unicode__(self):
|
|
return self.alias
|
|
|
|
def get_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'code': self.code,
|
|
'alias': self.alias,
|
|
'slug': self.slug,
|
|
'distance': str(self.distance),
|
|
'url': self.get_absolute_url(),
|
|
'headway': self.headways()
|
|
}
|
|
|
|
def areas_passed(self):
|
|
return Area.objects.filter(stop__routedetail__route=self).distinct()
|
|
|
|
def headways(self, t=None):
|
|
# get time to be retrieved for
|
|
if not t:
|
|
t = datetime.datetime.now()
|
|
day = t.isoweekday()
|
|
|
|
# get the routeschedules for the route
|
|
scheds = []
|
|
for rs in RouteSchedule.objects.filter(unique_route__route=self):
|
|
# if holiday schedule,
|
|
# if 8 in SCHED[rs.schedule_type]:
|
|
# if Holiday.objects.filter(date=t)
|
|
|
|
# read route schedule and return headway for time period
|
|
if day in SCHED[rs.schedule_type]:
|
|
scheds.append(rs)
|
|
|
|
#(s.first_from if s.first_from < s.first_to else s.first_to)
|
|
TIMESPANS = ((None,"06:59:59"),
|
|
("07:00:00","10:59:59"),
|
|
("11:00:00","16:59:59"),
|
|
("17:00:00","19:59:59"),
|
|
("20:00:00", None))
|
|
|
|
freqs=[]
|
|
for s in scheds:
|
|
foo =s
|
|
tspan = TIMESPANS
|
|
if (t.time() > s.first_from or t.time() > s.first_to) and t.time() < time_of(tspan[0][1]): freqs.append(s.headway1)
|
|
if t.time() < time_of(tspan[1][1]) and t.time() > time_of(tspan[1][0]): freqs.append(s.headway2)
|
|
if t.time() < time_of(tspan[2][1]) and t.time() > time_of(tspan[2][0]): freqs.append(s.headway3)
|
|
if t.time() < time_of(tspan[3][1]) and t.time() > time_of(tspan[3][0]): freqs.append(s.headway4)
|
|
if (t.time() < s.last_from or t.time() < s.last_to) and t.time() > time_of(tspan[4][0]): freqs.append(s.headway5)
|
|
|
|
#avg = float(sum(freqs)/len(freqs))
|
|
if not freqs:
|
|
return None
|
|
|
|
frequencies = [x for x in freqs if x!=0]
|
|
return str(min(frequencies)) + "-" + str(max(frequencies)) if min(frequencies)!=max(frequencies) else str(max(frequencies))
|
|
|
|
|
|
#'scheds': [ (s.headway1, s.headway2, s.headway3, s.headway4, str(s.unique_route) ) for s in scheds]
|
|
|
|
class RouteDetail(models.Model):
|
|
route_code = models.TextField()
|
|
route = models.ForeignKey(Route, to_field="code", null=True, blank=True)
|
|
serial = models.PositiveIntegerField()
|
|
stop = models.ForeignKey(Stop, null=True, blank=True)
|
|
stage = models.NullBooleanField()
|
|
km = models.DecimalField(null=True, blank=True, max_digits=3, decimal_places=1)
|
|
|
|
class Meta:
|
|
verbose_name = 'Route Detail'
|
|
ordering = ['serial']
|
|
|
|
def __unicode__(self):
|
|
return str(self.route) + " : " + str(self.serial)
|
|
|
|
def stop_dir(self):
|
|
return str(self.stop.dbdirection)
|
|
|
|
class UniqueRoute(models.Model):
|
|
route = models.ForeignKey(Route)
|
|
from_stop_txt = models.CharField(max_length=255)
|
|
to_stop_txt = models.CharField(max_length=255)
|
|
from_stop = models.ForeignKey(Stop, related_name="unique_routes_from")
|
|
to_stop = models.ForeignKey(Stop, related_name="unique_routes_to")
|
|
distance = models.FloatField(blank=True, null=True)
|
|
is_full = models.BooleanField()
|
|
# from_stop.custom_filter_spec = True # this is used to identify the fields which use the custom filter
|
|
# to_stop.custom_filter_spec = True # this is used to identify the fields which use the custom filter
|
|
class Meta:
|
|
verbose_name = 'Atlas'
|
|
verbose_name_plural = 'Atlas'
|
|
|
|
def __unicode__(self):
|
|
return "%s: %s to %s" % (self.route.alias, self.from_stop_txt, self.to_stop_txt)
|
|
|
|
def get_stop_choices(self):
|
|
return Stop.objects.filter(routedetail__route=self.route).order_by('routedetail')
|
|
|
|
|
|
class FuzzyStopMatch(models.Model):
|
|
unr = models.OneToOneField(UniqueRoute)
|
|
checked = models.BooleanField(default=True)
|
|
|
|
|
|
|
|
class RouteSchedule(models.Model):
|
|
unique_route = models.ForeignKey(UniqueRoute)
|
|
schedule_type = models.CharField(max_length=16)
|
|
busesAM = models.IntegerField(blank=True, null=True)
|
|
busesN = models.IntegerField(blank=True, null=True)
|
|
busesPM = models.IntegerField(blank=True, null=True)
|
|
bus_type = models.CharField(max_length=3, default="SD", blank=True)
|
|
depot_txt = models.CharField(max_length=16, blank=True)
|
|
depot = models.ForeignKey("Depot", null=True, blank=True)
|
|
first_from = models.TimeField(blank=True, null=True)
|
|
last_from = models.TimeField(blank=True, null=True)
|
|
first_to = models.TimeField(blank=True, null=True)
|
|
last_to = models.TimeField(blank=True, null=True)
|
|
runtime1 = models.IntegerField(blank=True, null=True)
|
|
runtime2 = models.IntegerField(blank=True, null=True)
|
|
runtime3 = models.IntegerField(blank=True, null=True)
|
|
runtime4 = models.IntegerField(blank=True, null=True)
|
|
headway1 = models.IntegerField(blank=True, null=True)
|
|
headway2 = models.IntegerField(blank=True, null=True)
|
|
headway3 = models.IntegerField(blank=True, null=True)
|
|
headway4 = models.IntegerField(blank=True, null=True)
|
|
headway5 = models.IntegerField(blank=True, null=True)
|
|
|
|
def __unicode__(self):
|
|
return "%s: %s" % (unicode(self.unique_route), self.schedule_type,)
|
|
|
|
|
|
class RouteType(models.Model):
|
|
code = models.TextField(max_length=50)
|
|
rtype = models.TextField(max_length=50)
|
|
faretype = models.TextField(max_length=10)
|
|
|
|
def __unicode__(self):
|
|
return self.rtype
|
|
|
|
class Meta:
|
|
verbose_name = 'Route Type'
|
|
|
|
|
|
class HardCodedRoute(models.Model):
|
|
code = models.TextField(max_length=50)
|
|
alias = models.TextField(max_length=50)
|
|
faretype = models.TextField(max_length=10)
|
|
|
|
class Meta:
|
|
verbose_name = 'Hardcoded Route'
|
|
|
|
def __unicode__(self):
|
|
return self.code + " " +self.alias
|
|
|
|
|
|
class Landmark(models.Model):
|
|
slug = models.SlugField(null=True)
|
|
name = models.TextField(max_length=500, blank=True, null=True)
|
|
stops = models.ManyToManyField(Stop, related_name='is_near_to', blank=True)
|
|
name_mr = models.TextField(max_length=512, blank=True, null=True)
|
|
display_name = models.TextField(blank=True, max_length=255)
|
|
point = models.PointField(blank=True, null=True)
|
|
alt_names = generic.GenericRelation("AlternativeName")
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class StopLocation(models.Model):
|
|
stop = models.ForeignKey(Stop)
|
|
point = models.PointField()
|
|
direction = models.CharField(max_length=5, null=True, blank=True, choices=STOP_CHOICES)
|
|
|
|
def __unicode__(self):
|
|
return self.stop.name
|
|
|
|
|
|
class Depot(models.Model):
|
|
code = models.CharField(max_length=5) # should have unique=True ?
|
|
name = models.TextField(max_length=50)
|
|
stop = models.IntegerField()
|
|
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
class Holiday(models.Model):
|
|
date = models.DateField()
|
|
name = models.TextField(max_length=100)
|
|
operating_schedule=models.TextField(max_length=20)
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
|
|
ALT_TYPE_CHOICES = (
|
|
('alt', 'General Alternative Name'),
|
|
('old', 'Old Name'),
|
|
('common', 'Common Name')
|
|
)
|
|
|
|
class AlternativeName(models.Model):
|
|
objects = TrigramSearchManager(('name',))
|
|
name = models.CharField(max_length=512)
|
|
name_mr = models.CharField(max_length=512, blank=True)
|
|
typ = models.CharField(max_length=64, choices=ALT_TYPE_CHOICES, default="alt")
|
|
content_type = models.ForeignKey(ContentType)
|
|
object_id = models.PositiveIntegerField()
|
|
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
|
|
|
def __unicode__(self):
|
|
return self.name
|