use 'latest' library versions for maximum lifespan as an example
[gae-samples.git] / 24hrsinsf / models.py
blob9b96e2c80d9c566174d8e787c1eb2192aa0b694b
1 import logging
2 import math
3 from google.appengine.ext import db
4 from google.appengine.ext.db import djangoforms
6 import geobox
8 RADIUS = 6378100
10 GEOBOX_CONFIGS = (
11 (4, 5, True),
12 (3, 2, True),
13 (3, 8, False),
14 (3, 16, False),
15 (2, 5, False),
18 _DAY_DICTIONARY = {
19 'Monday': 'hr_mon',
20 'Tuesday': 'hr_tues',
21 'Wednesday': 'hr_weds',
22 'Thursday': 'hr_thurs',
23 'Friday': 'hr_fri',
24 'Saturday': 'hr_sat',
25 'Sunday': 'hr_sun'
27 def _make_hours(store_hours):
28 """Store hours is a dictionary that maps a DOW to different open/close times
29 Since it's easy to represent disjoing hours, we'll do this by default
30 Such as, if a store is open from 11am-2pm and then 5pm-10pm
31 We'll slice the times in to a list of floats representing 30 minute intevals
32 So for monday, let's assume we have the store hours from 10am - 3pm
33 We represent this as
34 monday = [10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5]
35 """
36 week_hrs = {}
37 for dow in store_hours.keys():
38 dow_hours = []
39 for hour_set in store_hours[dow]:
40 if len(hour_set) < 2:
41 open_hr = 0.0
42 close_hr = 24.0
43 else:
44 open_hr = float(hour_set[0])
45 close_hr = float(hour_set[1])
46 if close_hr < open_hr:
47 tmp = close_hr
48 close_hr = open_hr
49 open_hr = tmp
50 current_hr_it = open_hr
51 while((close_hr - current_hr_it) >= .5):
52 dow_hours.append(current_hr_it)
53 current_hr_it += .5
54 week_hrs[dow] = dow_hours
55 return week_hrs
57 def _earth_distance(lat1, lon1, lat2, lon2):
58 lat1, lon1 = math.radians(float(lat1)), math.radians(float(lon1))
59 lat2, lon2 = math.radians(float(lat2)), math.radians(float(lon2))
60 return RADIUS * math.acos(math.sin(lat1) * math.sin(lat2) +
61 math.cos(lat1) * math.cos(lat2) * math.cos(lon2 - lon1))
63 class Store(db.Model):
64 name = db.StringProperty()
65 pretty_address = db.TextProperty()
66 pretty_description = db.TextProperty()
67 pretty_hours = db.TextProperty()
68 location = db.GeoPtProperty()
69 geoboxes = db.StringListProperty()
70 hr_mon = db.ListProperty(float)
71 hr_tues = db.ListProperty(float)
72 hr_weds = db.ListProperty(float)
73 hr_thurs = db.ListProperty(float)
74 hr_fri = db.ListProperty(float)
75 hr_sat = db.ListProperty(float)
76 hr_sun = db.ListProperty(float)
77 holidays = db.StringListProperty()
78 categories = db.StringListProperty()
79 phone_numbers = db.StringProperty()
81 @classmethod
82 def add(self, **kwargs):
83 lat = kwargs.pop('lat')
84 lon = kwargs.pop('lon')
85 location = db.GeoPt(lat, lon)
86 name = kwargs['name']
87 new_store = Store(name=name, location=location)
88 all_boxes = []
89 new_store.pretty_address = kwargs['address']
90 for (resolution, slice, use_set) in GEOBOX_CONFIGS:
91 if use_set:
92 all_boxes.extend(geobox.compute_set(lat, lon, resolution, slice))
93 else:
94 all_boxes.append(geobox.compute(lat, lon, resolution, slice))
95 new_store.geoboxes = all_boxes
96 store_hour_dict = _make_hours(kwargs['store_hours'])
97 for day, prop in _DAY_DICTIONARY.iteritems():
98 setattr(new_store, prop, store_hour_dict[day])
100 new_store.categories = kwargs['categories']
101 new_store.pretty_description = kwargs['description']
102 new_store.put()
104 @classmethod
105 def query(self, time, dow, lat, lon, max_results, min_params):
106 """Queries for Muni stops repeatedly until max results or scope is reached.
107 Args:
108 system: The transit system to query.
109 lat, lon: Coordinates of the agent querying.
110 max_results: Maximum number of stops to find.
111 min_params: Tuple (resolution, slice) of the minimum resolution to allow.
113 Returns:
114 List of (distance, MuniStop) tuples, ordered by minimum distance first.
115 There will be no duplicates in these results. Distance is in meters.
117 # Maps stop_ids to MuniStop instances.
118 found_stores = {}
120 # Do concentric queries until the max number of results is reached.
121 dow_query_string = _DAY_DICTIONARY[dow] + ' ='
122 for params in GEOBOX_CONFIGS:
123 if len(found_stores) >= max_results:
124 break
125 if params < min_params:
126 break
128 resolution, slice, unused = params
129 box = geobox.compute(lat, lon, resolution, slice)
130 logging.info("Searching for box=%s at resolution=%s, slice=%s",
131 box, resolution, slice)
132 query = self.all().filter("geoboxes =", box).filter(dow_query_string, time)
133 results = query.fetch(50)
134 logging.info("Found %d results", len(results))
136 # De-dupe results.
137 for result in results:
138 if result.name not in found_stores:
139 found_stores[result.name] = result
141 # Now compute distances and sort by distance.
142 stores_by_distance = []
143 for store in found_stores.itervalues():
144 distance = _earth_distance(lat, lon, store.location.lat, store.location.lon)
145 stores_by_distance.append((distance, store))
146 stores_by_distance.sort()
148 return stores_by_distance
150 class UserProfile(db.Model):
151 user = db.UserProperty(required=True)
153 class CommentIndex(db.Model):
154 max_index = db.IntegerProperty(default=0, required=True)
156 class Comment(db.Model):
157 index = db.IntegerProperty(required=True)
158 reviewer = db.ReferenceProperty(UserProfile, required=True)
159 store = db.ReferenceProperty(Store, required=True)
160 review = db.TextProperty(required=True)
161 rating = db.IntegerProperty(choices=set([1,2,3,4,5]))
162 posted_on = db.DateTimeProperty(auto_now_add=True)
163 disabled = db.BooleanProperty(default=False)