static double getPickupDistance_maxPickupConstr_route(Route * pRoute, Request * pRequest, int pickupOrder) {
     
     // ensure there are two existing pickups
     assert( pRoute->getPickupEvents()->size() == 2 );
     
     const time_t reqTime = pRequest->getReqTime();
             
     const Event * pDispatch = pRoute->getDispatchEvent();
     RouteEvent * pFirstSchedPick  = pRoute->getPickupEvents()->front();
     RouteEvent * pSecondSchedPick = pRoute->getPickupEvents()->back();
     
     // case 1: the request is to be the first pickup in the new route
     if ( pickupOrder == 1 ) {
         // 1.A the request occurs BEFORE the first sched pickup
         if( reqTime < pFirstSchedPick->getEventTime() ) {
             LatLng currLoc = Utility::estLocationByLinearProxy(reqTime, pDispatch->timeT, pDispatch->lat, pDispatch->lng, pFirstSchedPick->getEventTime(), pFirstSchedPick->getLat(), pFirstSchedPick->getLng());
             double distToCurrReq = Utility::computeGreatCircleDistance(currLoc.getLat(), currLoc.getLng(), pRequest->getActualPickupEvent()->lat, pRequest->getActualPickupEvent()->lng);
             return distToCurrReq;
         }
         // 1.B the request already occurred and request is in between first and second scheduled pickup
         else if( (pFirstSchedPick->getEventTime() <= reqTime) && (reqTime <= pSecondSchedPick->getEventTime()) ) {
             LatLng currLoc = Utility::estLocationByLinearProxy(reqTime, pFirstSchedPick->getEventTime(), pFirstSchedPick->getLat(), pFirstSchedPick->getLng(), pSecondSchedPick->getEventTime(), pSecondSchedPick->getLat(), pSecondSchedPick->getLng());
             double distToCurrReq = Utility::computeGreatCircleDistance(currLoc.getLat(), currLoc.getLng(), pRequest->getActualPickupEvent()->lat, pRequest->getActualPickupEvent()->lng);
             return distToCurrReq;
         }
     }
     
     // case 2: the request is not to be the first pickup in the new route
     else {
         // get prior pickup event
         RouteEvent * pPriorPickup = pRoute->getPickupEvents()->at(pickupOrder-2); // e.g. if the 2nd pickup, then the prior is the 1st pickup which is index 0
         
         // 2.A: the request occurs BEFORE the pickup of the prior event
         if( reqTime < pPriorPickup->getEventTime() ) {
             double distToCurrReq = Utility::computeGreatCircleDistance(pPriorPickup->getLat(), pPriorPickup->getLng(), pRequest->getActualPickupEvent()->lat, pRequest->getActualPickupEvent()->lng);
             return distToCurrReq;
         }
         
         // 2.B: the requests occurs AFTER pickup of the prior event
         else {
             RouteEvent * pNextEvent = (pickupOrder == 2) ? pRoute->getPickupEvents()->back() : pRoute->getDropoffEvents()->front();
             LatLng currLoc = Utility::estLocationByLinearProxy(reqTime, pPriorPickup->getEventTime(), pPriorPickup->getLat(), pPriorPickup->getLng(), pNextEvent->getEventTime(), pNextEvent->getLat(), pNextEvent->getLng());
             double distToCurrReq = Utility::computeGreatCircleDistance(currLoc.getLat(), currLoc.getLng(), pRequest->getActualPickupEvent()->lat, pRequest->getActualPickupEvent()->lng);
             return distToCurrReq;
         }
         
     }
     
     cout << "** unhandled case when checking for pickup distance to minion req **\n" << endl;
     exit(1);
     return 0.0;
 }
 static std::string convertToLatLngStr(const LatLng &loc, int prec) {
     std::string locStr = "";
     locStr += Utility::truncateDouble(loc.getLat(), prec);
     locStr += ",";
     locStr += Utility::truncateDouble(loc.getLng(), prec);
     return locStr;
 }    
 //static double getPickupDistanceAtTimeOfMinionRequest(Request * pRequest, OpenTrip * pOpenTrip) {
 static double getPickupDistanceAtTimeOfMinionRequest_maxPickupConstr(
         const time_t minionReqTime,         // request time
         const double minionPickupLat,       // request lat
         const double minionPickupLng,       // request lng
         const time_t masterPickupTime,      // previous event time
         const double masterPickupLat,       // previous event start lat
         const double masterPickupLng,       // previous event start lng
         const time_t masterDropoffTime,     // next event time
         const double masterDropLat,         // next event end lat
         const double masterDropLng,         // next event end lng
         const time_t masterDispatchTime,    // dispatch time
         const double masterDispatchLat,     // dispatch lat
         const double masterDispatchLng      // dispatch lng
         ) {
     
     // case 1: pickup has NOT occurred (non-extended)
     if( minionReqTime <= masterPickupTime ) {
         
         // pickup distance from current location to minion origin
         LatLng estMasterLocation = Utility::estLocationByLinearProxy(minionReqTime, masterDispatchTime, masterDispatchLat, masterDispatchLng, masterPickupTime, masterPickupLat, masterPickupLng);
         double pickupDistance_driverToMaster = Utility::computeGreatCircleDistance(estMasterLocation.getLat(), estMasterLocation.getLng(), masterPickupLat, masterPickupLng);
         
         // pickup distance from master to minion
         double pickupDistance_masterToMinion = Utility::computeGreatCircleDistance(masterPickupLat, masterPickupLng, minionPickupLat, minionPickupLng);
         
         double pickupDistance_total = pickupDistance_driverToMaster + pickupDistance_masterToMinion;
         return pickupDistance_total;
     }
     
     // case 2: pickup HAS occurred (extended)
     else {
         // estimate location     
         LatLng estLocAtTimeOfRequest = Utility::estLocationByLinearProxy(minionReqTime, masterPickupTime, masterPickupLat, masterPickupLng, masterDropoffTime, masterDropLat, masterDropLng);
         
         // pickup distance is 
         double pickupDistance = Utility::computeGreatCircleDistance(estLocAtTimeOfRequest.getLat(), estLocAtTimeOfRequest.getLng(), minionPickupLat, minionPickupLng);
         
         return pickupDistance;
     }                
 }
NearestEdgeInfo GraphStorage::GetNearestEdge(const LatLng &source, const double &radius) const
{
    // Get all nodes within a radius
    vector<int> nodes = kd_tree.RadiusSearch(source, radius);

    // Get all edges within that radius
    // We want to get a set of edges and avoid adding
    // the same A->B and B->A edge twice
    set<Edge, function<bool(const Edge &, const Edge &)>>
                edges(
                    // Custom compare function to avoid adding A->B and B->A edges twice
                    [](const Edge &e1, const Edge &e2)
                    {
                        const int x = min(e1.u, e1.v);
                        const int y = min(e2.u, e2.v);
                        if (x < y)
                            return true;
                        else if (y > x)
                            return false;
                        else
                            return max(e1.u, e1.v) < max(e2.u, e2.v);
                    }
                );

    // Fill in a set of (undirected) edges
    // For each node
    for (int i = 0; i < nodes.size(); ++i)
    {
        const int A = nodes[i];

        // For each edge that starts from this node
        for (int j = 0; j < adjacency_lists[A].size(); ++j)
        {
            const auto &e = adjacency_lists[A][j];
            if (e.is_startpoint) // only startpoints
            {
                const int B = e.v;
                if (e.edge_type == Edge::BACKWARD_EDGE)
                    edges.insert(Edge(B, A, e.w, e.edge_type));
                else
                    edges.insert(Edge(A, B, e.w, e.edge_type));
            }
        }
    }

    auto current_best = NearestEdgeInfo();

    if (edges.empty())
    {
        return current_best;
    }

    // Find nearest edge
    const double    source_lat    = source.getLat();
    const double    source_lng    = source.getLng();
    double          best_distance = numeric_limits<double>::max();
    for (const auto &e : edges)
    {
        double clamped_ratio;
        LatLng projected_point;

        tie(clamped_ratio, projected_point) = LatLng::ProjectOnSegment(source_lat, source_lng,
                                                                       coordinates[e.u], coordinates[e.v]);
        const double current_distance = LatLng::DistanceInMeters(source_lat, source_lng,
                                                                 projected_point.getLat(), projected_point.getLng());
        // Update nearest edge information
        if (current_distance < best_distance)
        {
            best_distance = current_distance;
            current_best  = NearestEdgeInfo(e, projected_point, clamped_ratio, true);
        }
    }
    return current_best;
}
 // compute pick up distance
 static double computePickupDistance_savingsConstr(
         const time_t masterETA, const double masterOrigLat, const double masterOrigLng, 
         const time_t masterETD, const double masterDestLat, const double masterDestLng, 
         const time_t minionReqTime, const double minionOrigLat, const double minionOrigLng, 
         int inclInitDistExtendedMatches
 ) {                
     double pickupDist = 0.0;
     
     bool isExtended = (masterETA <= minionReqTime);
     
     // case 1: non-extended match
     if( !isExtended ) {
         pickupDist = Utility::computeGreatCircleDistance(masterOrigLat, masterOrigLng, minionOrigLat, minionOrigLng);
     }
     
     // case 2: extended match
     else {
         // get the master location at time of minion request
         LatLng estLocAtMinReq = Utility::estLocationByLinearProxy(minionReqTime, masterETA, masterOrigLat, masterOrigLng, masterETD, masterDestLat, masterDestLng);
         
         // compute dist between master origin and curr location
         const double distFromOrigToCurrLoc = (inclInitDistExtendedMatches) ? Utility::computeGreatCircleDistance(masterOrigLat, masterOrigLng, estLocAtMinReq.getLat(), estLocAtMinReq.getLng()) : 0.0;
         
         // compute dist between curr location and minion origin
         const double distFromCurrLocToMinion = Utility::computeGreatCircleDistance(estLocAtMinReq.getLat(), estLocAtMinReq.getLng(), minionOrigLat, minionOrigLng);
         
         pickupDist = distFromOrigToCurrLoc + distFromCurrLocToMinion;               
     }
     
     return pickupDist;
 }