def test_simple_decimation_2():
    """ test_simple_decimation_2
  """
    s1_0 = State(0, 0.0)
    #s1_1 = State(1, 0.0)
    s1_2 = State(2, 0.0)
    s2_0 = State(0, 1.0)
    s2_1 = State(1, 1.0)
    s2_2 = State(2, 1.0)
    paths = [Path(s1_0, [0, 1], s2_1), \
             Path(s1_2, [2, 1], s2_1), \
             Path(s1_0, [0, 0], s2_0), \
             Path(s1_2, [2, 2], s2_2)]
    print paths
    trans1 = [(0, 0), (2, 1), (0, 2), (2, 3)]
    trans2 = [(0, 1), (1, 1), (2, 0), (3, 2)]
    start_mapping = {2: 0}
    end_mapping = {1: 0, 2: 1}
    (new_trans1, new_paths, new_trans2, paths_mapping) = \
      decimate_path_simple(start_mapping, trans1, paths, trans2, end_mapping)
    assert paths_mapping == {1: 0, 3: 1}, paths_mapping
    assert len(new_paths) == 2
    assert new_paths[0].links == [2, 1]
    assert new_paths[1].links == [2, 2]
    assert new_trans1 == [(0, 0), (0, 1)], new_trans1
    assert new_trans2 == [(0, 0), (1, 1)], new_trans2
def test_simple_decimation_1():
    """ test_simple_decimation_1
  """
    s1_0 = State(1, 0.0, None)
    s1_1 = State(2, 0.0, None)
    s1_2 = State(3, 0.0, None)
    s2_0 = State(1, 1.0, None)
    s2_1 = State(2, 2.0, None)
    s2_2 = State(3, 3.0, None)
    paths = [
        Path(s1_0, [1], s2_0),
        Path(s1_1, [2], s2_1),
        Path(s1_2, [3], s2_2)
    ]
    trans1 = [(0, 0), (1, 1), (2, 2)]
    trans2 = [(0, 0), (1, 1), (2, 2)]
    start_mapping = {1: 0}
    end_mapping = {1: 0}
    (new_trans1, new_paths, new_trans2, paths_mapping) = \
      decimate_path_simple(start_mapping, trans1, paths, trans2, end_mapping)
    assert paths_mapping == {1: 0}, paths_mapping
    assert new_trans1 == [(0, 0)], new_trans1
    assert new_trans2 == [(0, 0)], new_trans2
    assert len(new_paths) == 1
    assert new_paths[0].links == [2]
Exemple #3
0
 def getPaths(self, s1, s2, timeLimit=sys.maxsize):
     """ Returns a set of candidate paths between state s1 and state s3.
     Always includes the first and last link. 
     
     Arguments:
     - s1 : a State object
     - s2 : a State object
     - timeLimit: the maximum time allowed for a path, beyond which
                  none is returned
     """
     
     # if the same link, be sure that we're not going 
     # backwards along the link
     if (s1.link_id == s2.link_id):
         if (s2.offset < s1.offset):
             s2.offset = s1.offset
         path = Path(s1, [s1.link_id], s2)    
         return [path]
                     
     # sequence of link IDs
     linkSeq = self.getShortestPathLinkSequence(s1.link_id, s2.link_id, timeLimit=timeLimit)
             
     # return the path set
     path = Path(s1, linkSeq, s2)        
     return [path]
def test_merge_path_simple():
    """ test_merge_path_simple
  """
    s1 = State(1, 1.0, None)
    s2 = State(2, 3.0, None)
    s3 = State(3, 1.0, None)
    p1 = Path(s1, [1, 2], s2)
    p2 = Path(s2, [2, 3], s3)
    p = merge_path(p1, p2)
    assert p.start == s1
    assert p.end == s3
    assert p.links == [1, 2, 3]
def test_merge_path_sequence_2():
    """ test_merge_path_sequence_2
  """
    sa_0 = State(0, 0.0)
    sa_1 = State(1, 0.0)
    sb_0 = State(0, 1.0)
    sb_1 = State(1, 1.0)
    sc_0 = State(0, 2.0)
    sc_1 = State(1, 2.0)
    paths_a = [Path(sa_0, [0], sb_0), \
               Path(sa_0, [0, 1], sb_1), \
               Path(sa_1, [1, 0], sb_0), \
               Path(sa_1, [1], sb_1)]
    paths_b = [Path(sb_0, [0], sc_0), \
               Path(sb_0, [0, 1], sc_1), \
               Path(sb_1, [1, 0], sc_0), \
               Path(sb_1, [1], sc_1)]
    trans1_a = [(0, 0), (0, 1), (1, 2), (1, 3)]
    trans2_a = [(0, 0), (1, 1), (2, 0), (3, 1)]
    trans1_b = [(0, 0), (0, 1), (1, 2), (1, 3)]
    trans2_b = [(0, 0), (1, 1), (2, 0), (3, 1)]
    best_idx_a = 0
    best_idx_b = 1
    (new_trans1, new_paths, new_trans2, new_best_idx) = \
      merge_path_sequence(trans1_a, paths_a, trans2_a, trans1_b, paths_b, \
                          trans2_b, best_idx_a, best_idx_b)
    assert len(new_paths) == 8, len(new_paths)
    assert new_trans1 == [(0, 0), (0, 1), (0, 2), (0, 3), (1, 4), (1, 5),
                          (1, 6), (1, 7)], new_trans1
    assert new_trans2 == [(0, 0), (1, 1), (2, 0), (3, 1), (4, 0), (5, 1),
                          (6, 0), (7, 1)], new_trans2
    assert new_best_idx == 1, new_best_idx
def merge_path(path1, path2):
    """ Merges two path objects together.
  
  Assumes the paths are linked together by the same state.
  """
    assert path1.end == path2.start, (path1.end, path2.start, path1, path2)
    links = path1.links[:-1] + path2.links
    latlngs = None
    if path1.latlngs and path2.latlngs:
        latlngs = path1.latlngs[:-1] + path2.latlngs
    return Path(path1.start, links, path2.end, latlngs)
def test_merge_path_sequence_1():
    """ test_merge_path_sequence_1
  """
    sa_0 = State(0, 0.0, None)
    sa_1 = State(1, 0.0, None)
    sa_2 = State(2, 0.0, None)
    sb_0 = State(0, 1.0, None)
    sb_1 = State(1, 2.0, None)
    sb_2 = State(2, 3.0, None)
    sc_0 = State(0, 0.0, None)
    sc_1 = State(1, 0.0, None)
    sc_2 = State(2, 0.0, None)
    paths_a = [Path(sa_0, [0], sb_0), \
               Path(sa_1, [1], sb_1), \
               Path(sa_2, [2], sb_2)]
    paths_b = [Path(sb_0, [0], sc_0), \
               Path(sb_1, [1], sc_1), \
               Path(sb_2, [2], sc_2)]
    trans1_a = [(0, 0), (1, 1), (2, 2)]
    trans1_b = [(0, 0), (1, 1), (2, 2)]
    trans2_a = [(0, 0), (1, 1), (2, 2)]
    trans2_b = [(0, 0), (1, 1), (2, 2)]
    best_idx_a = 1
    best_idx_b = 1
    (new_trans1, new_paths, new_trans2, new_best_idx) = \
      merge_path_sequence(trans1_a, paths_a, trans2_a, trans1_b, paths_b, \
                          trans2_b, best_idx_a, best_idx_b)
    assert len(new_paths) == 3
    assert new_trans1 == [(0, 0), (1, 1), (2, 2)]
    assert new_trans2 == [(0, 0), (1, 1), (2, 2)]
    assert new_best_idx == 1
Exemple #8
0
    def initializeShortestPathsBetweenLinks(self):
        """
        Calculates the shortest paths between all link pairs and populates
        self.linkSkim and self.linkPred

        The paths between links consider turn restrictions or penalties based 
        on the movements in the network. 
        """
        
        # STEP 1: create a dictionary lookup between the node IDs and
        # the graph index
        self.l2i = {}
        self.i2l = {}
        
        i = 0
        for link in self.net.iterRoadLinks():   
            link_id = link.getId()
            self.l2i[link_id] = i
            self.i2l[i] = link_id
            i += 1
        num_links = i+1
        
        # STEP 2: create a compressed sparse matrix representation of the network, 
        # for use with scipy shortest path algorithms
        alinks = []
        blinks = []
        costs = []
        for movement in self.net.iterMovements():
            
            incomingLink = movement.getIncomingLink()
            outgoingLink = movement.getOutgoingLink()
                        
            # only keep if they are both road links
            if (incomingLink.isRoadLink() and outgoingLink.isRoadLink()):
            
                a = self.l2i[incomingLink.getId()]
                b = self.l2i[outgoingLink.getId()]

                # the cost is the travel time on the incoming link
                cost = 60.0 * movement.getFreeFlowTTInMin()
            
                # now we figure out if it is a right or left turn, 
                # and apply a penalty if it is
                if movement.isLeftTurn():
                    cost += self.LEFT_TURN_PENALTY
                elif movement.isRightTurn():
                    cost += self.RIGHT_TURN_PENALTY
                elif movement.isUTurn():
                    cost += self.U_TURN_PENALTY
            
                # and add to my lists
                alinks.append(a)
                blinks.append(b)
                costs.append(cost)
        
        num_movements = len(costs)
        
        alinks2 = np.array(alinks)
        blinks2 = np.array(blinks)
        costs2  = np.array(costs)
        
        print ('Creating network graph with %i links and %i movements ' %(num_links, num_movements)        
        graph = csr_matrix((costs2, (alinks2, blinks2)), shape=(num_links, num_links)) 
        
        
        # STEP 3: run the scipy algorithm
        (self.linkSkim, self.linkPred) = sp.sparse.csgraph.shortest_path(graph, 
                        method='auto', directed=True, return_predecessors=True)
        
    
    def project(self, gps_pos):
        """ (abstract) : takes a GPS position and returns a list of states.
        """
        
        return_tuple = self.findNRoadLinksNearestCoords(gps_pos.x, gps_pos.y, 
            n=self.PROJECT_NUM_LINKS, dist_limit=self.PROJECT_DIST_THRESHOLD)
            
        states = []
        for rt in return_tuple: 
            (roadlink, distance, t) = rt
            offset = t * roadlink.getLengthInCoordinateUnits()
            state = State(roadlink.getId(), offset, distFromGPS=distance)
            states.append(state)
                    
        return states

    
    def findNRoadLinksNearestCoords(self, x, y, n=1, dist_limit = sys.float_info.max):
        """
        Returns the *n* closest road links to the given (*x*, *y*) coordinates.
        
        If *n* = 1, returns a 3-tuple (*roadlink*, *distance*, *t*).  
        The *roadlink* is the closest :py:class:`RoadLink` instance to (*x*, *y*),
        the *distance* is the distance between (*x*, *y*) and the *roadlink*, and 
        *t* is in [0,1] and indicates how far along from the start point and end point
        of the *roadlink* lies the closest point to (*x*, *y*).
        
        If *n* > 1: returns a list of 3-tuples as described above, sorted by the *distance*
        values.
        
        Uses *dist_limit* (if passed) to return only those links within a 
        the specified distance. 
        
        Returns (None, None, None) if none found and *n* = 1, or an empty list for *n* > 1
                
        *x*,*y* and *dist_limit* are  in :py:attr:`Node.COORDINATE_UNITS`
        
        Implementation differs from that used in the dta.Network class in 
        that this one uses rtree for fast spatial indexing.  rtree uses
        a bounding box method, so it may occasionally miss one of the top N
        points, but as long as we have a few in the list, it shoudl be close
        enough. 
        """

        if (self.linkSpatialIndex==None):
            self.initializeSpatialIndex()

        return_tuples       = []
                        
        link_ids = self.linkSpatialIndex.nearest((x, x, y, y), n)

        for link_id in link_ids:
            link = self.net.getLinkForId(link_id)

            (dist, t) = link.getDistanceFromPoint(x,y)
            if dist < dist_limit:
                return_tuples.append( (link, dist, t))

        # sort
        return_tuples = sorted(return_tuples, key=operator.itemgetter(1))
        
        # kick out extras
        while len(return_tuples) > n:
            return_tuples.pop()
                    
        if n==1:
            if len(return_tuples) == 0: 
                return (None, None, None)
            return return_tuples[0]

        return return_tuples
        

    def initializeSpatialIndex(self):
        """
        Creates a spatial index for all links using rtree.  This must be called
        prior to calling findNRoadLinksNearestCoords().
        
        """

        #  The coordinate ordering for all functions are sensitive the the 
        # index’s interleaved data member. If interleaved is False, the 
        # coordinates must be in the form [xmin, xmax, ymin, ymax].
        self.linkSpatialIndex = rtree.index.Index(interleaved=False)

        for link in self.net.iterRoadLinks():
                        
            # draw a box around all shape points when doing this
            coords = link.getCenterLine(wholeLineShapePoints = True)
            x, y = zip(*coords)
            
            self.linkSpatialIndex.insert(link.getId(), (min(x), max(x), min(y), max(y)))
                        
        

    def getPaths(self, s1, s2, timeLimit=sys.maxint):
        """ Returns a set of candidate paths between state s1 and state s3.
        Always includes the first and last link. 
        
        Arguments:
        - s1 : a State object
        - s2 : a State object
        - timeLimit: the maximum time allowed for a path, beyond which
                     none is returned
        """
        
        # if the same link, be sure that we're not going 
        # backwards along the link
        if (s1.link_id == s2.link_id):
            if (s2.offset < s1.offset):
                s2.offset = s1.offset
            path = Path(s1, [s1.link_id], s2)    
            return [path]
                        
        # sequence of link IDs
        linkSeq = self.getShortestPathLinkSequence(s1.link_id, s2.link_id, timeLimit=timeLimit)
                
        # return the path set
        path = Path(s1, linkSeq, s2)        
        return [path]



    def getShortestPathLinkSequence(self, startLink, endLink, timeLimit=sys.maxint):
        """
        returns the sequence of link IDs that define the shortest
        path from the startLink to the endLink. 
        
        Considers movement restrictions or turn penalties. 
        
        - startLink: the start link ID (not index)
        - endLink: the end link ID (not index)
        - timeLimit: the maximum time allowed for a path, beyond which
                     none is returned
        """
        
        # use indices
        start = self.l2i[startLink]
        end = self.l2i[endLink]
        
        # if there is no valid path
        if (self.linkSkim[start, end] > timeLimit):
            return []
        
        # trace the path
        path = []
        j = end
        while (j != start):
            path.append(self.i2l[j])
            j = self.linkPred[start, j]
        path.append(self.i2l[start])
        
        # reverse the list, because we started from the end
        path.reverse()
            
        return path


    def getPathsBetweenCollections(self, sc1, sc2):
        """ Returns a set of candidate paths between all pairs of states
        in the two state collections.
        Arguments:
        - s1 : a StateCollection object
        - s2 : a StateCollection object
        """
        trans1 = []
        trans2 = []
        paths = []
        n1 = len(sc1.states)
        n2 = len(sc2.states)
        num_paths = 0
        for i1 in range(n1):
            for i2 in range(n2):
                
                # limit possible paths based on a max time diff
                timeDiff = (sc2.time - sc1.time).total_seconds()
                timeLimit = self.TIME_LIMIT_FACTOR * timeDiff
                timeLimit = max(self.TIME_LIMIT_MINIMUM, timeLimit)
                
                # get the paths
                ps = self.getPaths(sc1.states[i1], sc2.states[i2], timeLimit=timeLimit)
                for path in ps:
                    trans1.append((i1, num_paths))
                    trans2.append((num_paths, i2))
                    paths.append(path)
                    num_paths += 1
        return (trans1, paths, trans2)


    def getPathFreeFlowTTInSeconds(self, path):
        """ Returns the free-flow travel time of the path in seconds.
        
        Arguments: a path_inference.structures.Path object
        """
        
        # get the traversal ratios
        traversalRatios = self.getPathTraversalRatios(path)
        
        # frist get the total time across all links
        tot_tt = 0.0
        for i in range(0,len(path.links)):
            link = self.net.getLinkForId(path.links[i])
            tot_tt += 60.0 * link.getFreeFlowTTInMin() * traversalRatios[i]
                
        return tot_tt
    

    def getPathFreeFlowTTInSecondsWithTurnPenalties(self, path):
        """ Returns the free-flow travel time of the path in seconds, 
        including the cost of turn penalties.
        
        Arguments: a path_inference.structures.Path object
        """
        
        # the skim time includes turn penalties, so start from there
        startLinkId = path.links[0]
        endLinkId = path.links[-1]        
        skimTime = self.linkSkim[self.l2i[startLinkId], self.l2i[endLinkId]]
        
        # adjust the first element, only for the traversal portion of the travel time
        firstOffsetRatio = self.getLinkOffsetRatio(path.start)
        firstLink = self.net.getLinkForId(startLinkId)
        firstLinkTime = 60.0 * firstLink.getFreeFlowTTInMin()
        
        # adjust the last element, only for the traversal portion of the travel time
        lastOffsetRatio = self.getLinkOffsetRatio(path.end)
        lastLink = self.net.getLinkForId(endLinkId)
        lastLinkTime = 60.0 * lastLink.getFreeFlowTTInMin()
        
        tt = (skimTime 
            - (firstOffsetRatio * firstLinkTime) 
            - ((1.0 - lastOffsetRatio) * lastLinkTime))

        return tt


    def getLinkOffsetRatio(self, state):
        """ Returns the offset ratio         
        offset ratio is in [0,1] and indicates how far along from the 
        start point and end point
        """
        link = self.net.getLinkForId(state.link_id)
        dist = link.getLengthInCoordinateUnits()
        ratio = state.offset / dist
        return ratio
    
    
    def getPathTraversalRatios(self, path):
        """ Returns an array of traversal ratios, corresponding to each
        link in the path.  
                
        offset ratio is in [0,1] and indicates the fraction of the link
        that is actually traveled. 
        """
        
        if (len(path.links)==0):
            return []
                
        # start with an array of 1s
        ratios = [1.0] * len(path.links)
        
        # adjust the first element        
        firstOffsetRatio = self.getLinkOffsetRatio(path.start)
        ratios[0] = ratios[0] - firstOffsetRatio
        
        # adjust the last element
        lastOffsetRatio = self.getLinkOffsetRatio(path.end)
        ratios[len(path.links)-1] = ratios[len(path.links)-1] - (1.0-lastOffsetRatio)
        
        return ratios        
        

    def allocatePathTravelTimeToLinks(self, path, start_time, end_time):
        """ Returns three lists for: 
            
            (link_id, traversalRatio, travelTime)
            
            where traversalRatio is the fraction of the link actually traversed
            and travelTime is in seconds and the travel time to go across
            that fraction of the link.
            
            Note that for the first and last links, only a portion of
            the link may be traversed.  
        
        Arguments: a path_inference.structures.Path object
                   a datetime object for the start time
                   a datetime object for the end time
        """
                
        # get the traversal ratios
        traversalRatios = self.getPathTraversalRatios(path)
        
        # get the totals
        tot_tt = (end_time - start_time).total_seconds()
        tot_ff_time = self.getPathFreeFlowTTInSeconds(path)
        
        # allocate the travel time
        link_tt = []
        for i in range(0,len(path.links)):
            
            # if the vehicle is stopped, or effectively stopped
            # then allocate the travel time equally across all links
            if (tot_ff_time < 0.1): 
                tt = tot_tt * (1.0/len(path.links))

            # othwerwise make it proportional to the free-flow times
            else: 
                link = self.net.getLinkForId(path.links[i])
                ff_time = 60.0 * link.getFreeFlowTTInMin() * traversalRatios[i]
                tt = tot_tt * (ff_time / tot_ff_time)
                
            link_tt.append(tt)        
        
        return (path.links, traversalRatios, link_tt)
        
    
    def getRoadLinkDataFrame(self):
        """
        Returns a dataframe with one record for each road link, 
        containing key link attributes.  
        
        """

        data = []
        for link in self.net.iterRoadLinks():
            
            row = {}
            
            # ID fields
            row['ID']        = link.getId()
            row['ANODE']     = link.getStartNode().getId()
            row['BNODE']     = link.getEndNode().getId()
            
            # coordinates (can be more than two)
            coords = link.getCenterLine(wholeLineShapePoints = True)
            x, y = zip(*coords)
            row['X'] = x
            row['Y'] = y

            # attributes
            row['TYPE']     = 'RoadLink'
            row['LABEL']    = link.getLabel()
            row['FACTYPE']  = link.getFacilityType()
            row['LANES']    = link.getNumLanes()
            row['DIR']      = link.getDirection()
            row['LENGTH']   = link.getLength()
            row['FFSPEED']  = link.getFreeFlowSpeedInMPH()
            row['FFTIME']   = 60.0 * link.getFreeFlowTTInMin()

            data.append(row)
        
        df = pd.DataFrame(data)
        
        return df
        

    """   
    ----------------------------------------------------------------------------      
    The methods below are for calculating shortest paths by node sequences.
    
    They do not account for turn penalties, and thus are not used.  They are
    retained because they are more memory efficient, so may be useful for a
    bigger network. 
    ----------------------------------------------------------------------------   

  
    def initializeShortestPathsBetweenNodes(self):
        '''
        Calculates the shortest paths between all node pairs and populates
        self.nodeSkim and self.nodePred

        The shortest paths between nodes do not consider turn restrictions
        or turn penalties. 
        '''
        
        # STEP 1: create a dictionary lookup between the node IDs and
        # the graph index
        self.n2i = {}
        self.i2n = {}
        
        i = 0
        for node in self.net.iterNodes():   
            node_id = node.getId()
            self.n2i[node_id] = i
            self.i2n[i] = node_id
            i += 1
        num_nodes = i+1
        
        # STEP 2: create a compressed sparse matrix representation of the network, 
        # for use with scipy shortest path algorithms
        anodes = []
        bnodes = []
        costs = []
        for link in self.net.iterRoadLinks():
            a = self.n2i[link.getStartNodeId()]
            b = self.n2i[link.getEndNodeId()]
            cost = 60.0 * link.getFreeFlowTTInMin()
            
            anodes.append(a)
            bnodes.append(b)
            costs.append(cost)
            
        num_links = len(costs)
        
        anodes2 = np.array(anodes)
        bnodes2 = np.array(bnodes)
        costs2  = np.array(costs)
        
        print ('Creating network graph with %i nodes and %i links ' %(num_nodes, num_links))        
        graph = csr_matrix((costs2, (anodes2, bnodes2)), shape=(num_nodes, num_nodes)) 
        
        
        # STEP 3: run the scipy algorithm
        (self.nodeSkim, self.nodePred) = sp.sparse.csgraph.shortest_path(graph, 
                        method='auto', directed=True, return_predecessors=True)

    def getPathsUsingNodes(self, s1, s2):
        ''' Returns a set of candidate paths between state s1 and state s3.
        Always includes the first and last link. 
        
        Arguments:
        - s1 : a State object
        - s2 : a State object
        '''
        
        # if the same link, it's easy
        if (s1.link_id == s2.link_id):
            path = Path(s1, [s1.link_id], s2)    
            return [path]
        
        startNode = self.net.getLinkForId(s1.link_id).getEndNodeId()
        endNode   = self.net.getLinkForId(s2.link_id).getStartNodeId()
        
        # if there is no valid path
        cost = self.nodeSkim[self.n2i[startNode], self.n2i[endNode]]
        if np.isinf(cost):
            return [None]
        
        # sequence of node IDs
        nodeSeq = self.getShortestPathNodeSequence(startNode, endNode)
        
        # convert to a sequence of link IDs
        linkSeq = [s1.link_id]
        for i in range(1,len(nodeSeq)):
            a = nodeSeq[i-1]
            b = nodeSeq[i]        
            link_id = self.net.getLinkForNodeIdPair(a, b).getId()
            linkSeq.append(link_id)
        linkSeq.append(s2.link_id)
        
        # return the path set
        path = Path(s1, linkSeq, s2)        
        return [path]


    def getShortestPathNodeSequence(self, startNode, endNode):
        '''
        returns the sequence of node IDs that define the shortest
        path from the startNode to the endNode. 
        
        Does not consider movement restrictions or turn penalties. 
        
        - startNode: the start node ID (not index)
        - endNode: the end node ID (not index)
        '''
        
        # use indices
        start = self.n2i[startNode]
        end = self.n2i[endNode]
        
        # if there is no valid path
        cost = self.nodeSkim[start, end]
        if np.isinf(cost):
            return [None]
        
        # trace the path
        path = []
        j = end
        while (j != start):
            path.append(self.i2n[j])
            j = self.nodePred[start, j]
        path.append(self.i2n[start])
        
        # reverse the list, because we started from the end
        path.reverse()
            
        return path
  
    ------------------------------------------------------------------------------      
    """