def identify_locations(self): """Identify locations with user-provided home, work, school locations if possible.""" # TODO this function was written in a hurry and should be made more robust if self.home: for location in self.locations: if distance( location, self.home ) <= 150: # meters location.identify('home') if self.work: for location in self.locations: if distance( location, self.work ) <= 150: # meters location.identify('work') if self.school: for location in self.locations: if distance( location, self.school ) <= 150: # meters location.identify('school')
def observe_neighbors(self,indices=[]): """Get angle and distance measurements to and from adjacent points. Store in the point object. Operates on points, the inices of which are given in a list referring to the current point list.""" # for each point, except those first and last for i in indices: # skip first and last points if i <= 0 or i >= len(self.points)-1: continue point = self.points[i] # Find the nearest DIFFERENT geometries # first walk backwards to the next different point i_ante = i-1 while point.geom == self.points[i_ante].geom: if i_ante <= 0: break i_ante -= 1 # then forward to the next different point i_post = i+1 while point.geom == self.points[i_post].geom: if i_post >= len(self.points)-1: break i_post += 1 # distance to previous point point.d_ante = distance(point,self.points[i_ante]) # distance to next point point.d_post = distance(point,self.points[i_post]) # if either distance is zero, this is an end point and has no angle if point.d_ante * point.d_post == 0: point.angle = 180 else: # calculate the inner angle point.angle = inner_angle_sphere( self.points[i_ante], point, self.points[i_post] ) # is point is identical with both neighbors? if ( point.geom == self.points[i-1].geom and point.geom == self.points[i+1].geom ): point.inter = True
def pair_interpolation(self, other_point): """Given this and one other Point object, attempts to supply a list of interpolated points between the two such that gaps between the points are never greater than config.interpolation_distance.""" new_points = [self.copy()] # Why is this copied? dist = distance(self, other_point) if dist > config.interpolation_distance: time_dif = (other_point.time - self.time).seconds n_segs = ceil( dist / config.interpolation_distance ) seg_len = dist // n_segs x1, y1 = project(self.longitude, self.latitude) x2, y2 = project(other_point.longitude, other_point.latitude) dx, dy = (x2 - x1)/n_segs, (y2 - y1)/n_segs dt = time_dif / n_segs for np in range(1, n_segs): x0, y0 = x1 + np*dx, y1 + np*dy lng, lat = unproject(x0, y0) tstamp = self.time + timedelta(seconds=dt*np) ts = ts_str(tstamp, self.ts[-5:]) new_points.append(Point(ts, lng, lat, self.accuracy)) return new_points
def make_known_subsets(self): """Partition the trace points into sets for which we're confident we don't have substantial missing data. That is, exclude segments where it seems like we have no data, but substantial movement; for which trip and activity reconstruction would be impossible. TODO: Eventually we will need some much stricter checking here and eventually an explicit check foro subway trips.""" known_segments = [] segment = [ self.points[0] ] # iterate over all points (except the first). Test each point to see # whether we add it to the current segment or the one after. for i in range(1,len(self.points)): if ( # distance over 1 km? distance( self.points[i], self.points[i-1] ) > 1000 and # time gap > 2 hours? self.points[i].epoch - self.points[i-1].epoch > 1*3600 ): # append point to next segment known_segments.append( segment ) segment = [ self.points[i] ] else: # add this point to the current segment segment.append( self.points[i] ) if len(segment) > 1: known_segments.append( segment ) # check these segments for plausibility and append to the global property for segment in known_segments: if ( # at least one point len(segment) > 1 and # sufficient time between last and first points segment[-1].epoch - segment[0].epoch > 3600 ): # mininum time length of segment? self.known_subsets.append(segment) print( '\t',len(self.known_subsets)-1,'gap(s) found in data' )
def break_trips(self): """Use a Hidden Markov Model to classify all points as deriving from either time spent travelling or time spent at one of the potential activity locations. Allocate time to these sequences of activities accordingly.""" for point in self.all_interpolated_points: # get first-pass emission probabilities from all locations dists = [ distance(loc,point) for loc in self.locations ] dists = [ d - 100 for d in dists ] dists = [ 0 if d < 0 else d for d in dists ] point.emit_p = [ gaussian(d,100) for d in dists ] # standardize to one if necessary if sum(point.emit_p) > 1: point.emit_p = [ p / sum(point.emit_p) for p in point.emit_p ] # prepend travel probability as the difference from 1 if there is any point.emit_p = [1 - sum(point.emit_p)] + point.emit_p # make a list of starting probabilities (50/50 start travelling, start stationary) start_p = [0.5]+[(0.5/len(self.locations))]*len(self.locations) # get list of potential state indices # 0 is travel, others are then +1 from their list location states = range(0,len(self.locations)+1) # list of locations that actually get used used_locations = set() # simple transition probability matrix e.g.: # 0 1 2 # 0 .8 .1 .1 # 1 .2 .8 .0 # 2 .2 .0 .8 trans_p = [] for s0 in states: trans_p.append([]) for s1 in states: if s0 + s1 == 0: # travel -> travel trans_p[s0].append( 0.8 ) elif s0 == 0: # travel -> place trans_p[s0].append( 0.2 / len(self.locations) ) elif s1 == 0: # place -> travel trans_p[s0].append( 0.2 ) elif s0 == s1: # place -> same place trans_p[s0].append( 0.8 ) else: # place -> place (no travel) trans_p[s0].append( 0.0 ) # run the viterbi algorithm on each known subset for points in self.known_subsets_interpolated: # VITERBI ALGORITHM V = [{}] path = {} for state in states: # Initialize base cases (t == 0) V[0][state] = start_p[state] * points[0].emit_p[state] path[state] = [state] for t in range(1,len(points)): # Run Viterbi for t > 0 V.append({}) newpath = {} for s1 in states: (prob, state) = max( [ ( V[t-1][s0] * trans_p[s0][s1] * points[t].emit_p[s1], s0 ) for s0 in states ] ) V[t][s1] = prob newpath[s1] = path[state] + [s1] path = newpath # Don't need to remember the old paths (prob, final_state) = max( [ (V[len(points)-1][s], s) for s in states ] ) # get the optimal sequence of states state_path = path[final_state] # note which locations have been used TODO move this elsewhere for visited_id in set([s-1 for s in state_path if s != 0]): self.locations[visited_id].visited = True # now record the first activity episode self.episodes.append( Episode( points[0].time, # start_time # location, if any None if state_path[0] == 0 else self.locations[state_path[0]-1], False # unknown_time ) ) prev_state = state_path[0] start_time = points[0].epoch # for each point after the first for i in range(1,len(state_path)): # look for state changes if prev_state != state_path[i]: # assume the transition happens halfway between points transition_time = points[i-1].time + (points[i].time-points[i-1].time)/2 # output start of this new state # now record the first activity episode self.episodes.append( Episode( points[i].time, # start_time # location, if any None if state_path[i] == 0 else self.locations[state_path[i]-1], False # unknown_time ) ) prev_state = state_path[i] # now record the first activity episode self.episodes.append( Episode( points[-1].time, # start_time None, # no location, True # unknown time ends every segment ) ) print( '\tFound',len(self.episodes),'activities/trips' )