LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_Taxi.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 91.9 % 442 406
Test Date: 2026-03-02 16:00:03 Functions: 100.0 % 37 37

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2013-2026 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    MSDevice_Taxi.cpp
      15              : /// @author  Jakob Erdmann
      16              : /// @date    16.12.2019
      17              : ///
      18              : // A device which controls a taxi
      19              : /****************************************************************************/
      20              : #include <config.h>
      21              : 
      22              : #include <utils/common/StringUtils.h>
      23              : #include <utils/common/StaticCommand.h>
      24              : #include <utils/common/StringTokenizer.h>
      25              : #include <utils/options/OptionsCont.h>
      26              : #include <utils/iodevices/OutputDevice.h>
      27              : #include <utils/vehicle/SUMOVehicle.h>
      28              : #include <utils/router/SUMOAbstractRouter.h>
      29              : #include <microsim/transportables/MSTransportable.h>
      30              : #include <microsim/MSEventControl.h>
      31              : #include <microsim/MSGlobals.h>
      32              : #include <microsim/MSVehicle.h>
      33              : #include <microsim/MSEdge.h>
      34              : #include <microsim/MSLane.h>
      35              : #include <microsim/MSStop.h>
      36              : #include <microsim/MSStoppingPlace.h>
      37              : #include <microsim/trigger/MSTriggeredRerouter.h>
      38              : 
      39              : #include "MSDispatch.h"
      40              : #include "MSDispatch_Greedy.h"
      41              : #include "MSDispatch_GreedyShared.h"
      42              : #include "MSDispatch_RouteExtension.h"
      43              : #include "MSDispatch_TraCI.h"
      44              : 
      45              : #include "MSIdling.h"
      46              : 
      47              : #include "MSRoutingEngine.h"
      48              : #include "MSDevice_Routing.h"
      49              : #include "MSDevice_Taxi.h"
      50              : 
      51              : //#define DEBUG_DISPATCH
      52              : //#define DEBUG_CANCEL
      53              : 
      54              : //#define DEBUG_COND (myHolder.isSelected())
      55              : #define DEBUG_COND (true)
      56              : 
      57              : // ===========================================================================
      58              : // static member variables
      59              : // ===========================================================================
      60              : SUMOTime MSDevice_Taxi::myDispatchPeriod(0);
      61              : /// @brief the dispatch algorithm
      62              : MSDispatch* MSDevice_Taxi::myDispatcher(nullptr);
      63              : /// @brief The repeated call to the dispatcher
      64              : Command* MSDevice_Taxi::myDispatchCommand(nullptr);
      65              : // @brief the list of available taxis
      66              : std::vector<MSDevice_Taxi*> MSDevice_Taxi::myFleet;
      67              : int MSDevice_Taxi::myMaxCapacity(0);
      68              : int MSDevice_Taxi::myMaxContainerCapacity(0);
      69              : std::set<std::string> MSDevice_Taxi::myVClassWarningVTypes;
      70              : 
      71              : #define TAXI_SERVICE "taxi"
      72              : #define TAXI_SERVICE_PREFIX "taxi:"
      73              : #define SWAP_THRESHOLD 5
      74              : 
      75              : // ===========================================================================
      76              : // method definitions
      77              : // ===========================================================================
      78              : // ---------------------------------------------------------------------------
      79              : // static initialisation methods
      80              : // ---------------------------------------------------------------------------
      81              : void
      82        42243 : MSDevice_Taxi::insertOptions(OptionsCont& oc) {
      83        42243 :     oc.addOptionSubTopic("Taxi Device");
      84        84486 :     insertDefaultAssignmentOptions("taxi", "Taxi Device", oc);
      85              : 
      86        84486 :     oc.doRegister("device.taxi.dispatch-algorithm", new Option_String("greedy"));
      87        84486 :     oc.addDescription("device.taxi.dispatch-algorithm", "Taxi Device", TL("The dispatch algorithm [greedy|greedyClosest|greedyShared|routeExtension|traci]"));
      88              : 
      89        42243 :     oc.doRegister("device.taxi.dispatch-algorithm.output", new Option_FileName());
      90        84486 :     oc.addDescription("device.taxi.dispatch-algorithm.output", "Taxi Device", TL("Write information from the dispatch algorithm to FILE"));
      91              : 
      92        84486 :     oc.doRegister("device.taxi.dispatch-algorithm.params", new Option_String(""));
      93        84486 :     oc.addDescription("device.taxi.dispatch-algorithm.params", "Taxi Device", TL("Load dispatch algorithm parameters in format KEY1:VALUE1[,KEY2:VALUE]"));
      94              : 
      95        84486 :     oc.doRegister("device.taxi.dispatch-period", new Option_String("60", "TIME"));
      96        84486 :     oc.addDescription("device.taxi.dispatch-period", "Taxi Device", TL("The period between successive calls to the dispatcher"));
      97              : 
      98        84486 :     oc.doRegister("device.taxi.dispatch-keep-unreachable", new Option_String("3600", "TIME"));
      99        84486 :     oc.addDescription("device.taxi.dispatch-keep-unreachable", "Taxi Device", TL("The time before aborting unreachable reservations"));
     100              : 
     101        84486 :     oc.doRegister("device.taxi.idle-algorithm", new Option_String("stop"));
     102        84486 :     oc.addDescription("device.taxi.idle-algorithm", "Taxi Device", TL("The behavior of idle taxis [stop|randomCircling|taxistand]"));
     103              : 
     104        42243 :     oc.doRegister("device.taxi.idle-algorithm.output", new Option_FileName());
     105        84486 :     oc.addDescription("device.taxi.idle-algorithm.output", "Taxi Device", TL("Write information from the idling algorithm to FILE"));
     106        42243 : }
     107              : 
     108              : 
     109              : void
     110      5537919 : MSDevice_Taxi::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
     111      5537919 :     OptionsCont& oc = OptionsCont::getOptions();
     112     11075838 :     if (equippedByDefaultAssignmentOptions(oc, "taxi", v, false)) {
     113              :         // build the device
     114         6733 :         MSDevice_Taxi* device = new MSDevice_Taxi(v, "taxi_" + v.getID());
     115         6721 :         into.push_back(device);
     116         6721 :         myFleet.push_back(device);
     117         6721 :         if (v.getParameter().line == "") {
     118              :             // automatically set the line so that persons are willing to enter
     119              :             // (see MSStageDriving::isWaitingFor)
     120         6624 :             const_cast<SUMOVehicleParameter&>(v.getParameter()).line = TAXI_SERVICE;
     121              :         }
     122         6721 :         if (v.getVClass() != SVC_TAXI && myVClassWarningVTypes.count(v.getVehicleType().getID()) == 0) {
     123          180 :             WRITE_WARNINGF(TL("Vehicle '%' with device.taxi should have vClass taxi instead of '%'."), v.getID(), toString(v.getVClass()));
     124           60 :             myVClassWarningVTypes.insert(v.getVehicleType().getID());
     125              :         }
     126         6721 :         const int personCapacity = v.getVehicleType().getPersonCapacity();
     127         6721 :         const int containerCapacity = v.getVehicleType().getContainerCapacity();
     128         6721 :         myMaxCapacity = MAX2(myMaxCapacity, personCapacity);
     129         6721 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, containerCapacity);
     130         6721 :         if (personCapacity < 1 && containerCapacity < 1) {
     131            0 :             WRITE_WARNINGF(TL("Vehicle '%' with personCapacity % and containerCapacity % is not usable as taxi."), v.getID(), toString(personCapacity), toString(containerCapacity));
     132              :         }
     133              :     }
     134      5537913 : }
     135              : 
     136              : 
     137              : void
     138          815 : MSDevice_Taxi::initDispatch() {
     139          815 :     OptionsCont& oc = OptionsCont::getOptions();
     140          815 :     myDispatchPeriod = string2time(oc.getString("device.taxi.dispatch-period"));
     141              :     // init dispatch algorithm
     142          815 :     std::string algo = oc.getString("device.taxi.dispatch-algorithm");
     143          815 :     Parameterised params;
     144         1630 :     params.setParametersStr(OptionsCont::getOptions().getString("device.taxi.dispatch-algorithm.params"), ":", ",");
     145          815 :     if (algo == "greedy") {
     146          510 :         myDispatcher = new MSDispatch_Greedy(params.getParametersMap());
     147          305 :     } else if (algo == "greedyClosest") {
     148           36 :         myDispatcher = new MSDispatch_GreedyClosest(params.getParametersMap());
     149          287 :     } else if (algo == "greedyShared") {
     150          114 :         myDispatcher = new MSDispatch_GreedyShared(params.getParametersMap());
     151          173 :     } else if (algo == "routeExtension") {
     152          122 :         myDispatcher = new MSDispatch_RouteExtension(params.getParametersMap());
     153          112 :     } else if (algo == "traci") {
     154          224 :         myDispatcher = new MSDispatch_TraCI(params.getParametersMap());
     155              :     } else {
     156            0 :         throw ProcessError(TLF("Dispatch algorithm '%' is not known", algo));
     157              :     }
     158          815 :     myDispatchCommand = new StaticCommand<MSDevice_Taxi>(&MSDevice_Taxi::triggerDispatch);
     159              :     // round to next multiple of myDispatchPeriod
     160          815 :     const SUMOTime now = MSNet::getInstance()->getCurrentTimeStep();
     161          815 :     const SUMOTime begin = string2time(oc.getString("begin"));
     162          815 :     const SUMOTime delay = (myDispatchPeriod - ((now - begin) % myDispatchPeriod)) % myDispatchPeriod;
     163          815 :     MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myDispatchCommand, now + delay);
     164         1630 : }
     165              : 
     166              : bool
     167        77967 : MSDevice_Taxi::isReservation(const std::set<std::string>& lines) {
     168        77967 :     return lines.size() == 1 && (
     169        77953 :                *lines.begin() == TAXI_SERVICE
     170       150348 :                || StringUtils::startsWith(*lines.begin(), TAXI_SERVICE_PREFIX));
     171              : }
     172              : 
     173              : void
     174         2846 : MSDevice_Taxi::addReservation(MSTransportable* person,
     175              :                               const std::set<std::string>& lines,
     176              :                               SUMOTime reservationTime,
     177              :                               SUMOTime pickupTime,
     178              :                               SUMOTime earliestPickupTime,
     179              :                               const MSEdge* from, double fromPos,
     180              :                               const MSStoppingPlace* fromStop,
     181              :                               const MSEdge* to, double toPos,
     182              :                               const MSStoppingPlace* toStop,
     183              :                               const std::string& group) {
     184         2846 :     if (!isReservation(lines)) {
     185              :         return;
     186              :     }
     187         2846 :     if ((to->getPermissions() & SVC_TAXI) == 0) {
     188            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     189            0 :                            + " '" + person->getID() + "' because destination edge '" + to->getID() + "'"
     190            0 :                            + " does not permit taxi access");
     191              :     }
     192         2846 :     if ((from->getPermissions() & SVC_TAXI) == 0) {
     193            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     194            0 :                            + " '" + person->getID() + "' because origin edge '" + from->getID() + "'"
     195            0 :                            + " does not permit taxi access");
     196              :     }
     197         2846 :     if (myDispatchCommand == nullptr) {
     198          815 :         initDispatch();
     199              :     }
     200         2846 :     if (fromStop != nullptr && &fromStop->getLane().getEdge() == from) {
     201              :         // pickup position should be at the stop-endPos
     202          888 :         fromPos = fromStop->getEndLanePosition();
     203              :     }
     204         5692 :     myDispatcher->addReservation(person, reservationTime, pickupTime, earliestPickupTime, from, fromPos, fromStop, to, toPos, toStop, group, *lines.begin(), myMaxCapacity, myMaxContainerCapacity);
     205              : }
     206              : 
     207              : void
     208           93 : MSDevice_Taxi::removeReservation(MSTransportable* person,
     209              :                                  const std::set<std::string>& lines,
     210              :                                  const MSEdge* from, double fromPos,
     211              :                                  const MSEdge* to, double toPos,
     212              :                                  const std::string& group) {
     213           93 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     214          154 :         myDispatcher->removeReservation(person, from, fromPos, to, toPos, group);
     215              :     }
     216           93 : }
     217              : 
     218              : void
     219          162 : MSDevice_Taxi::updateReservationFromPos(MSTransportable* person,
     220              :                                         const std::set<std::string>& lines,
     221              :                                         const MSEdge* from, double fromPos,
     222              :                                         const MSEdge* to, double toPos,
     223              :                                         const std::string& group, double newFromPos) {
     224          162 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     225          300 :         myDispatcher->updateReservationFromPos(person, from, fromPos, to, toPos, group, newFromPos);
     226              :     }
     227          162 : }
     228              : 
     229              : 
     230              : SUMOTime
     231        15222 : MSDevice_Taxi::triggerDispatch(SUMOTime currentTime) {
     232              :     std::vector<MSDevice_Taxi*> active;
     233        44165 :     for (MSDevice_Taxi* taxi : myFleet) {
     234        28943 :         if (taxi->getHolder().hasDeparted()) {
     235        28841 :             active.push_back(taxi);
     236              :         }
     237              :     }
     238        15222 :     myDispatcher->computeDispatch(currentTime, active);
     239        15220 :     return myDispatchPeriod;
     240        15222 : }
     241              : 
     242              : bool
     243      8734243 : MSDevice_Taxi::hasServableReservations() {
     244      8734243 :     return myDispatcher != nullptr && myDispatcher->hasServableReservations();
     245              : }
     246              : 
     247              : void
     248        40907 : MSDevice_Taxi::cleanup() {
     249        40907 :     if (myDispatcher != nullptr) {
     250          815 :         delete myDispatcher;
     251          815 :         myDispatcher = nullptr;
     252              :     }
     253        40907 :     myDispatchCommand = nullptr;
     254              :     myVClassWarningVTypes.clear();
     255        40907 : }
     256              : 
     257              : 
     258              : void
     259          591 : MSDevice_Taxi::allCustomersErased() {
     260         1480 :     for (MSDevice_Taxi* taxi : myFleet) {
     261              :         // disable taskSwap
     262          889 :         taxi->myState = EMPTY;
     263              :     }
     264          591 : }
     265              : 
     266              : // ---------------------------------------------------------------------------
     267              : // MSDevice_Taxi-methods
     268              : // ---------------------------------------------------------------------------
     269         6727 : MSDevice_Taxi::MSDevice_Taxi(SUMOVehicle& holder, const std::string& id) :
     270         6727 :     MSVehicleDevice(holder, id) {
     271         6727 :     std::string defaultServiceEnd = toString(1e15);
     272        13460 :     const std::string algo = holder.getStringParam("device.taxi.idle-algorithm");
     273         6727 :     if (algo == "stop") {
     274         6429 :         myIdleAlgorithm = new MSIdling_Stop();
     275          298 :     } else if (algo == "randomCircling") {
     276           94 :         myIdleAlgorithm = new MSIdling_RandomCircling();
     277              :         // make sure simulation terminates
     278           94 :         defaultServiceEnd = toString(STEPS2TIME(
     279              :                                          myHolder.getParameter().departProcedure == DepartDefinition::GIVEN
     280              :                                          ? myHolder.getParameter().depart
     281          188 :                                          : MSNet::getInstance()->getCurrentTimeStep()) + (3600 * 8));
     282          204 :     } else if (algo == "taxistand") {
     283          396 :         const std::string rerouterID = holder.getStringParam("device.taxi.stands-rerouter");
     284          198 :         if (rerouterID.empty()) {
     285            0 :             throw ProcessError("Idle algorithm '" + algo + "' requires a rerouter id to be defined using device param 'stands-rerouter' for vehicle '" + myHolder.getID() + "'");
     286              :         }
     287              :         if (MSTriggeredRerouter::getInstances().count(rerouterID) == 0) {
     288            0 :             throw ProcessError("Unknown rerouter '" + rerouterID + "' when loading taxi stands for vehicle '" + myHolder.getID() + "'");
     289              :         }
     290          198 :         MSTriggeredRerouter* rerouter = MSTriggeredRerouter::getInstances().find(rerouterID)->second;
     291          198 :         myIdleAlgorithm = new MSIdling_TaxiStand(rerouter);
     292              :     } else {
     293           18 :         throw ProcessError("Idle algorithm '" + algo + "' is not known for vehicle '" + myHolder.getID() + "'");
     294              :     }
     295         6727 :     myServiceEnd = string2time(holder.getStringParam("device.taxi.end", false, defaultServiceEnd));
     296         6721 :     myRoutingDevice = static_cast<MSDevice_Routing*>(myHolder.getDevice(typeid(MSDevice_Routing)));
     297         6727 : }
     298              : 
     299              : 
     300        13442 : MSDevice_Taxi::~MSDevice_Taxi() {
     301         6721 :     myFleet.erase(std::find(myFleet.begin(), myFleet.end(), this));
     302              :     // recompute myMaxCapacity
     303         6721 :     myMaxCapacity = 0;
     304         6721 :     myMaxContainerCapacity = 0;
     305        17282 :     for (MSDevice_Taxi* taxi : myFleet) {
     306        10561 :         myMaxCapacity = MAX2(myMaxCapacity, taxi->getHolder().getVehicleType().getPersonCapacity());
     307        10561 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, taxi->getHolder().getVehicleType().getContainerCapacity());
     308              :     }
     309         6721 :     delete myIdleAlgorithm;
     310        13442 : }
     311              : 
     312              : 
     313              : bool
     314        11404 : MSDevice_Taxi::hasFleet() {
     315        11404 :     return myFleet.size() > 0;;
     316              : }
     317              : 
     318              : 
     319              : void
     320         1315 : MSDevice_Taxi::dispatch(const Reservation& res) {
     321         1315 :     dispatchShared({&res, &res});
     322         1313 : }
     323              : 
     324              : 
     325              : void
     326         1660 : MSDevice_Taxi::dispatchShared(std::vector<const Reservation*> reservations) {
     327              : #ifdef DEBUG_DISPATCH
     328              :     if (DEBUG_COND) {
     329              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " dispatch:\n";
     330              :         for (const Reservation* res : reservations) {
     331              :             std::cout << "   persons=" << toString(res->persons) << "\n";
     332              :         }
     333              :     }
     334              : #endif
     335         1660 :     myLastDispatch = reservations;
     336              :     ConstMSEdgeVector tmpEdges;
     337              :     StopParVector stops;
     338         1660 :     double lastPos = myHolder.getPositionOnLane();
     339         1660 :     const MSEdge* rerouteOrigin = *myHolder.getRerouteOrigin();
     340         1660 :     if (isEmpty()) {
     341              :         // start fresh from the current edge
     342         1569 :         if (myHolder.isStoppedParking()) {
     343              :             // parking stop must be ended normally
     344          860 :             MSStop& stop = myHolder.getNextStopMutable();
     345          860 :             stop.duration = 0;
     346          860 :             lastPos = stop.pars.endPos;
     347          860 :             if (myHolder.isStoppedTriggered()) {
     348          748 :                 stop.triggered = false;
     349          748 :                 stop.containerTriggered = false;
     350          748 :                 stop.joinTriggered = false;
     351          748 :                 myHolder.unregisterWaiting();
     352              :             }
     353              :             // prevent unauthorized/premature entry
     354          860 :             const_cast<SUMOVehicleParameter::Stop&>(stop.pars).permitted.insert("");
     355          884 :             while (myHolder.getStops().size() > 1) {
     356           24 :                 myHolder.abortNextStop(1);
     357              :             }
     358              :         } else {
     359         1333 :             while (myHolder.hasStops()) {
     360              :                 // in meso there might be more than 1 stop at this point
     361          624 :                 myHolder.abortNextStop();
     362              :             }
     363              :             assert(!myHolder.hasStops());
     364              :         }
     365         1569 :         tmpEdges.push_back(myHolder.getEdge());
     366         1569 :         if (myHolder.getEdge() != rerouteOrigin) {
     367            8 :             tmpEdges.push_back(rerouteOrigin);
     368              :         }
     369              :     } else {
     370              :         assert(myHolder.hasStops());
     371              :         // check how often existing customers appear in the new reservations
     372              :         std::map<const MSTransportable*, int> nOccur;
     373          629 :         for (const Reservation* res : reservations) {
     374         1076 :             for (const MSTransportable* person : res->persons) {
     375              :                 if (myCustomers.count(person) != 0) {
     376          298 :                     nOccur[person] += 1;
     377              :                     if (myCurrentReservations.count(res) == 0) {
     378            0 :                         throw ProcessError(TLF("Invalid Re-dispatch for existing customer '%' with a new reservation", person->getID()));
     379              :                     }
     380              :                 }
     381              :             }
     382              :         }
     383              : #ifdef DEBUG_DISPATCH
     384              :         if (DEBUG_COND) {
     385              :             for (auto item : nOccur) {
     386              :                 std::cout << "   previousCustomer=" << item.first->getID() << " occurs=" << item.second << "\n";
     387              :             }
     388              :         }
     389              : #endif
     390           91 :         if (nOccur.size() == 0) {
     391              :             // no overlap with existing customers - extend route
     392           10 :             tmpEdges = myHolder.getRoute().getEdges();
     393           10 :             lastPos = myHolder.getStops().back().pars.endPos;
     394              : #ifdef DEBUG_DISPATCH
     395              :             if (DEBUG_COND) {
     396              :                 std::cout << " re-dispatch with route-extension\n";
     397              :             }
     398              : #endif
     399           81 :         } else if (nOccur.size() == myCustomers.size()) {
     400              :             // redefine route (verify correct number of mentions)
     401              :             std::set<const MSTransportable*> onBoard;
     402           81 :             const std::vector<MSTransportable*>& onBoardP = myHolder.getPersons();
     403           81 :             const std::vector<MSTransportable*>& onBoardC = myHolder.getContainers();
     404              :             onBoard.insert(onBoardP.begin(), onBoardP.end());
     405              :             onBoard.insert(onBoardC.begin(), onBoardC.end());
     406              :             std::set<const MSTransportable*> redundantPickup;
     407          244 :             for (auto item : nOccur) {
     408          167 :                 if (item.second == 1) {
     409              :                     // customers must already be on board
     410              :                     if (onBoard.count(item.first) == 0) {
     411           12 :                         throw ProcessError(TLF("Re-dispatch did not mention pickup for existing customer '%'", item.first->getID()));
     412              :                     }
     413          131 :                 } else if (item.second == 2) {
     414              :                     if (onBoard.count(item.first) == 0) {
     415              :                         // treat like a new customer
     416              :                         // TODO: need to be checked
     417              :                         myCustomers.erase(item.first);
     418              :                     } else {
     419              :                         redundantPickup.insert(item.first);
     420              :                     }
     421              :                 } else {
     422            0 :                     throw ProcessError("Re-dispatch mentions existing customer '" + item.first->getID() + "' " + toString(item.second) + " times");
     423              :                 }
     424              :             }
     425              :             // remove redundancy
     426           77 :             if (!redundantPickup.empty()) {
     427          183 :                 for (auto it = reservations.begin(); it != reservations.end();) {
     428              :                     bool isRedundant = false;
     429          262 :                     for (const MSTransportable* person : (*it)->persons) {
     430              :                         if (redundantPickup.count(person) != 0) {
     431              :                             isRedundant = true;
     432              :                             break;
     433              :                         }
     434              :                     }
     435          154 :                     if (isRedundant) {
     436           92 :                         for (const MSTransportable* person : (*it)->persons) {
     437              :                             redundantPickup.erase(person);
     438              :                         }
     439              :                         it = reservations.erase(it);
     440              :                     } else {
     441              :                         it++;
     442              :                     }
     443              :                 }
     444              :             }
     445          317 :             while (myHolder.hasStops()) {
     446          240 :                 myHolder.abortNextStop();
     447              :             }
     448           81 :             tmpEdges.push_back(myHolder.getEdge());
     449           77 :             if (myHolder.getEdge() != rerouteOrigin) {
     450           10 :                 tmpEdges.push_back(rerouteOrigin);
     451              :             }
     452              : #ifdef DEBUG_DISPATCH
     453              :             if (DEBUG_COND) {
     454              :                 std::cout << " re-dispatch from scratch\n";
     455              :             }
     456              : #endif
     457              :         } else {
     458              :             // inconsistent re-dispatch
     459              :             std::vector<std::string> missing;
     460            0 :             for (const MSTransportable* c : myCustomers) {
     461              :                 if (nOccur.count(c) == 0) {
     462            0 :                     missing.push_back(c->getID());
     463              :                 }
     464              :             }
     465            0 :             throw ProcessError("Re-dispatch did mention some customers but failed to mention " + joinToStringSorting(missing, " "));
     466            0 :         }
     467              :     }
     468              : 
     469         1656 :     const SUMOTime t = MSNet::getInstance()->getCurrentTimeStep();
     470              :     bool hasPickup = false;
     471         6374 :     for (const Reservation* res : reservations) {
     472              :         myCurrentReservations.insert(res);
     473              :         bool isPickup = false;
     474         9660 :         for (const MSTransportable* person : res->persons) {
     475              :             if (myCustomers.count(person) == 0) {
     476              :                 myCustomers.insert(person);
     477              :                 isPickup = true;
     478              :                 hasPickup = true;
     479              :             }
     480              :         }
     481         4718 :         if (isPickup) {
     482         6960 :             prepareStop(tmpEdges, stops, lastPos, res->from, res->fromPos, res->fromStop, "pickup " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     483         4752 :             for (const MSTransportable* const transportable : res->persons) {
     484         2432 :                 if (transportable->isPerson()) {
     485         2258 :                     stops.back().triggered = true;
     486              :                 } else {
     487          174 :                     stops.back().containerTriggered = true;
     488              :                 }
     489              :                 stops.back().permitted.insert(transportable->getID());
     490              :             }
     491              :             // proof this lines: Is needed for pre-booking?
     492              :             std::set<const MSTransportable*> persons = res->persons;
     493         4752 :             for (auto itr = persons.begin(); itr != persons.end(); itr++) {
     494         2432 :                 stops.back().awaitedPersons.insert((*itr)->getID());
     495              :             }
     496              : 
     497         2320 :             stops.back().parametersSet |= STOP_PERMITTED_SET;
     498         2320 :             if (stops.back().duration == -1) {
     499              :                 // keep dropOffDuration if the stop is dropOff and pickUp
     500         1966 :                 stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.pickUpDuration", false, 0));
     501              :             }
     502              :         } else {
     503         4796 :             prepareStop(tmpEdges, stops, lastPos, res->to, res->toPos, res->toStop, "dropOff " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     504         2398 :             stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.dropOffDuration", false, 60)); // pay and collect bags
     505              :         }
     506              :     }
     507              : #ifdef DEBUG_DISPATCH
     508              :     if (DEBUG_COND) {
     509              :         std::cout << "   tmpEdges=" << toString(tmpEdges) << "\n";
     510              :     }
     511              : #endif
     512         3312 :     if (!myHolder.replaceRouteEdges(tmpEdges, -1, 0, "taxi:prepare_dispatch", false, false, false)) {
     513            0 :         throw ProcessError("Route replacement for taxi dispatch failed for vehicle '" + myHolder.getID()
     514            0 :                            + "' at time=" + time2string(t) + ".");
     515              :     }
     516              : #ifdef DEBUG_DISPATCH
     517              :     if (DEBUG_COND) std::cout << "   replacedRoute=" << toString(tmpEdges)
     518              :                                   << "\n     actualRoute=" << toString(myHolder.getRoute().getEdges()) << "\n";
     519              : #endif
     520         5519 :     for (SUMOVehicleParameter::Stop& stop : stops) {
     521              :         std::string error;
     522         3863 :         myHolder.addStop(stop, error);
     523         3863 :         if (error != "") {
     524            0 :             WRITE_WARNINGF(TL("Could not add taxi stop, time=%, error=%"), myHolder.getID(), stop.actType, time2string(t), error)
     525              :         }
     526              :     }
     527         1656 :     SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     528              :     // SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myHolder.getInfluencer().getRouterTT(veh->getRNGIndex())
     529         1656 :     myHolder.reroute(t, "taxi:dispatch", router, false);
     530              : #ifdef DEBUG_DISPATCH
     531              :     if (DEBUG_COND) {
     532              :         std::cout << "\n      finalRoute=" << toString(myHolder.getRoute().getEdges()) << " routeIndex=" << myHolder.getRoutePosition() << "\n";
     533              :     }
     534              : #endif
     535         1654 :     if (hasPickup) {
     536         1622 :         myState |= PICKUP;
     537              :     }
     538         1666 : }
     539              : 
     540              : 
     541              : void
     542           88 : MSDevice_Taxi::cancelCurrentCustomers() {
     543              :     // check if taxi has stopped
     544           88 :     if (myHolder.getNextStopParameter() == nullptr) {
     545            0 :         return;
     546              :     }
     547              :     // find customers of the current stop
     548              :     std::set<const MSTransportable*> customersToBeRemoved;
     549              :     std::set<const MSTransportable*> onBoard;
     550           88 :     onBoard.insert(myHolder.getPersons().begin(), myHolder.getPersons().end());
     551           88 :     onBoard.insert(myHolder.getContainers().begin(), myHolder.getContainers().end());
     552          142 :     for (std::string tID : myHolder.getNextStopParameter()->permitted) {
     553          220 :         for (auto t : myCustomers) {
     554          166 :             if (t->getID() == tID && onBoard.count(t) == 0) {
     555              :                 customersToBeRemoved.insert(t);
     556              :             }
     557              :         }
     558              :     }
     559           88 :     if (!customersToBeRemoved.empty()) {
     560           90 :         WRITE_WARNINGF(TL("Taxi '%' aborts waiting for customers: % at time=%."),
     561              :                        myHolder.getID(), toString(customersToBeRemoved), time2string(SIMSTEP));
     562              :     }
     563          122 :     for (auto t : customersToBeRemoved) {
     564           34 :         cancelCustomer(t);
     565              :     }
     566              : }
     567              : 
     568              : 
     569              : bool
     570           44 : MSDevice_Taxi::cancelCustomer(const MSTransportable* t) {
     571              : #ifdef DEBUG_CANCEL
     572              :     if (DEBUG_COND) {
     573              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " cancelCustomer " << t->getID() << "\n";
     574              :     }
     575              : #endif
     576              : 
     577              :     // is the given transportable a customer of the reservations?
     578              :     if (myCustomers.count(t) == 0) {
     579              :         return false;
     580              :     }
     581              :     myCustomers.erase(t);
     582              :     // check whether a single reservation has been fulfilled or another customer is part of the reservation
     583          132 :     for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     584              :         bool fulfilled = false;
     585           88 :         if ((*resIt)->persons.size() == 1 && (*resIt)->persons.count(t) != 0) {
     586              :             // the reservation contains only the customer
     587              :             fulfilled = true;
     588              :         }
     589              :         if (fulfilled) {
     590              :             const Reservation* res = *resIt;
     591              :             // remove reservation from the current dispatch
     592          220 :             for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     593          176 :                 if (*it == res) {
     594           88 :                     it = myLastDispatch.erase(it);
     595              :                 } else {
     596              :                     ++it;
     597              :                 }
     598              :             }
     599              :             // remove reservation from the served reservations
     600              :             resIt = myCurrentReservations.erase(resIt);
     601              :             // delete the reservation
     602           44 :             myDispatcher->fulfilledReservation(res);
     603              :         } else {
     604              :             ++resIt;
     605              :         }
     606              :     }
     607           44 :     myState &= ~PICKUP;  // remove state PICKUP
     608           88 :     for (const Reservation* res : myCurrentReservations) {
     609              :         // if there is another pickup in the dispatch left, add the state PICKUP
     610           44 :         if (std::count(myLastDispatch.begin(), myLastDispatch.end(), res) == 2) {
     611           44 :             myState |= PICKUP;  // add state PICKUP
     612              :         }
     613              :     }
     614              :     // we also have to clean reservations from myLastDispatch where the customers arrived in the meantime
     615          132 :     for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     616              :         if (myCurrentReservations.count(*it) == 0) {
     617            0 :             it = myLastDispatch.erase(it);
     618              :         } else {
     619              :             ++it;
     620              :         }
     621              :     }
     622              :     // if there are reservations left, go on with the dispatch
     623              :     // in meso, wait for the next dispatch cycle to avoid updating stops in this stage
     624           44 :     if (!MSGlobals::gUseMesoSim) {
     625           36 :         dispatchShared(myLastDispatch);
     626              :     }
     627              :     return true;
     628              : }
     629              : 
     630              : 
     631              : void
     632           58 : MSDevice_Taxi::addCustomer(const MSTransportable* t, const Reservation* res) {
     633              :     myCustomers.insert(t);
     634           58 :     MSBaseVehicle& veh = dynamic_cast<MSBaseVehicle&>(myHolder);
     635          116 :     for (const MSStop& stop : veh.getStops()) {
     636              :         SUMOVehicleParameter::Stop& pars = const_cast<SUMOVehicleParameter::Stop&>(stop.pars);
     637              :         //std::cout << " sE=" << (*stop.edge)->getID() << " sStart=" << pars.startPos << " sEnd=" << pars.endPos << " rFrom=" <<
     638              :         //    res->from->getID() << " rTo=" << res->to->getID() << " rFromPos=" << res->fromPos << " resToPos=" << res->toPos << "\n";
     639          116 :         if (*stop.edge == res->from
     640           58 :                 && pars.startPos <= res->fromPos
     641          174 :                 && pars.endPos >= res->fromPos) {
     642           58 :             pars.awaitedPersons.insert(t->getID());
     643              :             pars.permitted.insert(t->getID());
     644          116 :             pars.actType += " +" + t->getID();
     645           58 :         } else if (*stop.edge == res->to
     646           58 :                 && pars.startPos <= res->toPos
     647          116 :                 && pars.endPos >= res->toPos) {
     648           58 :             pars.actType += " +" + t->getID();
     649           58 :             break;
     650              :         }
     651              :     }
     652           58 : }
     653              : 
     654              : 
     655              : void
     656         4718 : MSDevice_Taxi::prepareStop(ConstMSEdgeVector& edges,
     657              :                            StopParVector& stops,
     658              :                            double& lastPos, const MSEdge* stopEdge, double stopPos,
     659              :                            const MSStoppingPlace* stopPlace,
     660              :                            const std::string& action, const Reservation* res, const bool isPickup) {
     661              :     assert(!edges.empty());
     662         4718 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     663          863 :         stopPos = stopPlace->getEndLanePosition();
     664              :     }
     665         4718 :     if (stopPos < lastPos && stopPos + NUMERICAL_EPS >= lastPos) {
     666              :         stopPos = lastPos;
     667              :     }
     668              :     bool addedEdge = false;
     669              : 
     670         4718 :     if (stops.empty()) {
     671              :         // check brakeGap
     672         1636 :         double distToStop = stopPos - lastPos;
     673         1636 :         const double brakeGap = myHolder.getBrakeGap();
     674         1636 :         if (myHolder.getLane() != nullptr && myHolder.getLane()->isInternal()) {
     675            4 :             distToStop += myHolder.getLane()->getLength();
     676              :         }
     677         1636 :         if (stopEdge != edges.back()) {
     678         1376 :             distToStop += edges.back()->getLength();
     679         1376 :             if (distToStop < brakeGap) {
     680              :                 // the distance between current edge and stop edge may be small
     681           14 :                 SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     682              :                 ConstMSEdgeVector toFirstStop;
     683            7 :                 router.compute(edges.back(), stopEdge, &myHolder, SIMSTEP, toFirstStop, true);
     684           31 :                 for (int i = 1; i < (int)toFirstStop.size() - 1; i++) {
     685           24 :                     distToStop += toFirstStop[i]->getLength();
     686              :                 }
     687            7 :             }
     688              :         }
     689         1636 :         if (distToStop < brakeGap) {
     690              :             // circle back to stopEdge
     691              :             //std::cout << SIMTIME << " taxi=" << getID() << " brakeGap=" << brakeGap << " distToStop=" << distToStop << "\n";
     692          185 :             edges.push_back(stopEdge);
     693              :             addedEdge = true;
     694              :         }
     695              :     }
     696              : 
     697         4718 :     if (stopEdge == edges.back() && !stops.empty()) {
     698         1026 :         if (stopPos >= lastPos && stopPos <= stops.back().endPos) {
     699              :             // no new stop and no adaption needed
     700          855 :             stops.back().actType += "," + action;
     701          855 :             return;
     702              :         }
     703          171 :         if (stopPos >= lastPos && stopPos <= lastPos + myHolder.getVehicleType().getLength()) {
     704              :             // stop length adaption needed
     705            0 :             stops.back().endPos = MIN2(lastPos + myHolder.getVehicleType().getLength(), stopEdge->getLength());
     706            0 :             stops.back().actType += "," + action;
     707            0 :             return;
     708              :         }
     709              :     }
     710         3863 :     if (!addedEdge && (stopEdge != edges.back() || stopPos < lastPos)) {
     711              :         //std::cout << SIMTIME << " stopPos=" << stopPos << " lastPos=" << lastPos << "\n";
     712         3450 :         edges.push_back(stopEdge);
     713              :     }
     714         3863 :     lastPos = stopPos;
     715         3863 :     SUMOVehicleParameter::Stop stop;
     716         3863 :     stop.lane = getStopLane(stopEdge, action)->getID();
     717         3863 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     718          628 :         stop.startPos = stopPlace->getBeginLanePosition();
     719          628 :         stop.endPos = stopPlace->getEndLanePosition();
     720              :         const SumoXMLTag tag = stopPlace->getElement();
     721          628 :         if (tag == SUMO_TAG_BUS_STOP || tag == SUMO_TAG_TRAIN_STOP) {
     722              :             stop.busstop = stopPlace->getID();
     723          245 :         } else if (tag == SUMO_TAG_PARKING_AREA) {
     724              :             stop.parkingarea = stopPlace->getID();
     725          240 :         } else if (tag == SUMO_TAG_CONTAINER_STOP) {
     726              :             stop.containerstop = stopPlace->getID();
     727              :         }
     728              :     } else {
     729         3235 :         stop.startPos = stopPos;
     730         6470 :         stop.endPos = MAX2(stopPos, MIN2(myHolder.getVehicleType().getLength(), stopEdge->getLength()));
     731              :     }
     732         7726 :     stop.parking = SUMOVehicleParameter::parseParkingType(myHolder.getStringParam("device.taxi.parking", false, "true"));
     733              :     stop.actType = action;
     734         3863 :     stop.index = STOP_INDEX_END;
     735              :     // In case of prebooking if person is not there/ comes to late for pickup set maximum waiting time:
     736         3863 :     SUMOTime earliestPickupTime = res->earliestPickupTime;
     737         3863 :     if (isPickup && earliestPickupTime >= 0) {
     738          138 :         stop.waitUntil = earliestPickupTime;
     739              :         // TODO: replace hard coded extension with parameter
     740          138 :         stop.extension = static_cast<SUMOTime>(3 * 60 * 1000);  // 3mins
     741              :     }
     742         3863 :     stops.push_back(stop);
     743         3863 : }
     744              : 
     745              : 
     746              : MSLane*
     747         3863 : MSDevice_Taxi::getStopLane(const MSEdge* edge, const std::string& action) {
     748         3863 :     const std::vector<MSLane*>* allowedLanes = edge->allowedLanes(myHolder.getVClass());
     749         3863 :     if (allowedLanes == nullptr) {
     750            0 :         throw ProcessError("Taxi vehicle '" + myHolder.getID() + "' cannot stop on edge '" + edge->getID() + "' (" + action + ")");
     751              :     }
     752         3863 :     return allowedLanes->front();
     753              : }
     754              : 
     755              : bool
     756      1075058 : MSDevice_Taxi::isEmpty() {
     757      1075058 :     return myState == EMPTY;
     758              : }
     759              : 
     760              : 
     761              : bool
     762        27569 : MSDevice_Taxi::allowsBoarding(const MSTransportable* t) const {
     763        27569 :     return myCustomers.count(t) != 0;
     764              : }
     765              : 
     766              : 
     767              : void
     768       697266 : MSDevice_Taxi::updateMove(const SUMOTime traveltime, const double travelledDist) {
     769       697266 :     if (myHolder.getPersonNumber() > 0 || myHolder.getContainerNumber() > 0) {
     770       118499 :         myOccupiedDistance += travelledDist;
     771       118499 :         myOccupiedTime += traveltime;
     772              :     }
     773       697266 :     if (isEmpty()) {
     774       530409 :         if (MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     775       528575 :             myIdleAlgorithm->idle(this);
     776       528575 :             if (myRoutingDevice != nullptr) {
     777              :                 // prevent rerouting during idling (#11079)
     778              :                 myRoutingDevice->setActive(false);
     779              :             }
     780         1834 :         } else if (!myReachedServiceEnd) {
     781          228 :             WRITE_WARNINGF(TL("Taxi '%' reaches scheduled end of service at time=%."), myHolder.getID(), time2string(SIMSTEP));
     782           76 :             myReachedServiceEnd = true;
     783              :         }
     784       166857 :     } else if (myRoutingDevice != nullptr) {
     785              :         myRoutingDevice->setActive(true);
     786              :     }
     787       697266 :     if (myHolder.isStopped() && (isEmpty() || MSGlobals::gUseMesoSim) && myHolder.getNextStop().endBoarding > myServiceEnd) {
     788              :         // limit duration of stop (but only for idling-related stops)
     789         3277 :         myHolder.getNextStopMutable().endBoarding = myServiceEnd;
     790              :     }
     791              : #ifdef DEBUG_DISPATCH
     792              :     if (DEBUG_COND && myIsStopped != myHolder.isStopped()) {
     793              :         std::cout << SIMTIME << " updateMove veh=" << myHolder.getID() << " myIsStopped=" << myIsStopped << " myHolderStopped=" << myHolder.isStopped() << " myState=" << myState << "\n";
     794              :     }
     795              : #endif
     796       697266 :     myIsStopped = myHolder.isStopped();
     797       697266 : }
     798              : 
     799              : 
     800              : bool
     801       676393 : MSDevice_Taxi::notifyMove(SUMOTrafficObject& /*tObject*/, double oldPos,
     802              :                           double newPos, double /*newSpeed*/) {
     803       676393 :     updateMove(DELTA_T, newPos - oldPos);
     804       676393 :     return true; // keep the device
     805              : }
     806              : 
     807              : 
     808              : void
     809        20873 : MSDevice_Taxi::notifyMoveInternal(const SUMOTrafficObject& /* veh */,
     810              :                                   const double /* frontOnLane */,
     811              :                                   const double timeOnLane,
     812              :                                   const double /* meanSpeedFrontOnLane */,
     813              :                                   const double /* meanSpeedVehicleOnLane */,
     814              :                                   const double travelledDistanceFrontOnLane,
     815              :                                   const double /* travelledDistanceVehicleOnLane */,
     816              :                                   const double /* meanLengthOnLane */) {
     817        20873 :     updateMove(TIME2STEPS(timeOnLane), travelledDistanceFrontOnLane);
     818        20873 : }
     819              : 
     820              : 
     821              : bool
     822        67581 : MSDevice_Taxi::notifyEnter(SUMOTrafficObject& /*veh*/, MSMoveReminder::Notification /*reason*/, const MSLane* /* enteredLane */) {
     823        67581 :     if (isEmpty() && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     824        44649 :         myIdleAlgorithm->idle(this);
     825              :     }
     826        67581 :     return true; // keep the device
     827              : }
     828              : 
     829              : 
     830              : void
     831         2309 : MSDevice_Taxi::customerEntered(const MSTransportable* t) {
     832         2309 :     myState |= OCCUPIED;
     833         2309 :     if (!hasFuturePickup()) {
     834         1841 :         myState &= ~PICKUP;
     835              :     }
     836        11568 :     for (const Reservation* res : myCurrentReservations) {
     837        16685 :         for (const MSTransportable* cand : res->persons) {
     838         9735 :             if (cand == t) {
     839         2309 :                 const_cast<Reservation*>(res)->state = Reservation::ONBOARD;
     840         2309 :                 break;
     841              :             }
     842              :         }
     843              :     }
     844         2309 : }
     845              : 
     846              : 
     847              : void
     848         2309 : MSDevice_Taxi::customerArrived(const MSTransportable* person) {
     849         2309 :     myCustomersServed++;
     850              :     myCustomers.erase(person);
     851         2309 :     if (myHolder.getPersonNumber() == 0 && myHolder.getContainerNumber() == 0) {
     852         1528 :         myState &= ~OCCUPIED;
     853         1528 :         if (myHolder.getStops().size() > 1 && (myState & PICKUP) == 0) {
     854            0 :             WRITE_WARNINGF(TL("All customers left vehicle '%' at time=% but there are % remaining stops"),
     855              :                            myHolder.getID(), time2string(SIMSTEP), myHolder.getStops().size() - 1);
     856            0 :             while (myHolder.getStops().size() > 1) {
     857            0 :                 myHolder.abortNextStop(1);
     858              :             }
     859              :         }
     860              :     }
     861         2309 :     if (isEmpty()) {
     862              :         // cleanup
     863         2994 :         for (const Reservation* res : myCurrentReservations) {
     864         1503 :             myDispatcher->fulfilledReservation(res);
     865              :         }
     866              :         myCurrentReservations.clear();
     867         1491 :         checkTaskSwap();
     868         1491 :         if (isEmpty()) {
     869         1487 :             if (MSGlobals::gUseMesoSim && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     870          454 :                 myIdleAlgorithm->idle(this);
     871              :             }
     872              :         }
     873              :     } else {
     874              :         // check whether a single reservation has been fulfilled
     875         5171 :         for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     876              :             bool fulfilled = true;
     877         5443 :             for (const MSTransportable* t : (*resIt)->persons) {
     878              :                 if (myCustomers.count(t) != 0) {
     879              :                     fulfilled = false;
     880              :                     break;
     881              :                 }
     882              :             }
     883         4353 :             if (fulfilled) {
     884          648 :                 myDispatcher->fulfilledReservation(*resIt);
     885              :                 resIt = myCurrentReservations.erase(resIt);
     886              :             } else {
     887              :                 ++resIt;
     888              :             }
     889              :         }
     890              :     }
     891         2309 : }
     892              : 
     893              : 
     894              : void
     895         1491 : MSDevice_Taxi::checkTaskSwap() {
     896         2982 :     const std::string swapGroup = myHolder.getStringParam("device.taxi.swapGroup", false, "");
     897         1491 :     if (swapGroup != "") {
     898           12 :         SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myDispatcher->getRouter();
     899           12 :         const double stopTime = myHolder.isStopped() ? MAX2(0.0, STEPS2TIME(myHolder.getNextStop().duration)) : 0;
     900              :         double maxSaving = 0;
     901              :         MSDevice_Taxi* bestSwap = nullptr;
     902           36 :         for (MSDevice_Taxi* taxi : myFleet) {
     903           48 :             if (taxi->getHolder().hasDeparted() && taxi->getState() == PICKUP
     904           60 :                     && taxi->getHolder().getStringParam("device.taxi.swapGroup", false, "") == swapGroup) {
     905              :                 SUMOVehicle& veh = taxi->getHolder();
     906            4 :                 const MSStop& stop = veh.getNextStop();
     907            4 :                 ConstMSEdgeVector toPickup(veh.getCurrentRouteEdge(), stop.edge + 1);
     908            4 :                 const double cost = router.recomputeCostsPos(toPickup, &veh, veh.getPositionOnLane(), stop.pars.endPos, SIMSTEP);
     909              :                 ConstMSEdgeVector toPickup2;
     910            4 :                 router.compute(myHolder.getEdge(), myHolder.getPositionOnLane(), *stop.edge, stop.pars.endPos, &myHolder, SIMSTEP, toPickup2, true);
     911            4 :                 if (!toPickup2.empty()) {
     912            4 :                     const double cost2 = router.recomputeCostsPos(toPickup2, &myHolder, myHolder.getPositionOnLane(), stop.pars.endPos, SIMSTEP);
     913            4 :                     const double saving = cost - cost2 - stopTime;
     914              :                     //std::cout << SIMTIME << " taxi=" << getID() << " other=" << veh.getID() << " cost=" << cost << " cost2=" << cost2 << " stopTime=" << stopTime << " saving=" << saving << " route1=" << toString(toPickup) << " route2=" << toString(toPickup2) << "\n";
     915            4 :                     if (saving > maxSaving) {
     916              :                         maxSaving = saving;
     917              :                         bestSwap = taxi;
     918              :                     }
     919              :                 }
     920            4 :             }
     921              :         }
     922           12 :         if (maxSaving > SWAP_THRESHOLD) {
     923              : #ifdef DEBUG_DISPATCH
     924              :             if (DEBUG_COND) {
     925              :                 std::cout << SIMTIME << " taxi=" << myHolder.getID() << " swapWith=" << bestSwap->getHolder().getID() << " saving=" << maxSaving << " lastDispatch="; 
     926              :                 for (const Reservation* res : bestSwap->myLastDispatch) {
     927              :                     std::cout << toString(res->persons) << "; ";
     928              :                 }
     929              :                 std::cout << "\n";
     930              :             }
     931              : #endif
     932            4 :             dispatchShared(bestSwap->myLastDispatch);
     933              :             bestSwap->myCurrentReservations.clear();
     934              :             bestSwap->myCustomers.clear();
     935            4 :             bestSwap->myState = EMPTY;
     936           12 :             while (bestSwap->getHolder().hasStops()) {
     937            8 :                 bestSwap->getHolder().abortNextStop();
     938              :             }
     939            8 :             for (const Reservation* res : myCurrentReservations) {
     940            4 :                 myDispatcher->swappedRunning(res, this);
     941              :             }
     942              :         }
     943              :     }
     944         1491 : }
     945              : 
     946              : 
     947              : bool
     948         2309 : MSDevice_Taxi::hasFuturePickup() {
     949         6647 :     for (const auto& stop : myHolder.getStops()) {
     950         4806 :         if (stop.reached) {
     951         2311 :             continue;
     952              :         }
     953         2495 :         if (stop.pars.permitted.size() > 0) {
     954              :             return true;
     955              :         }
     956              :     }
     957              :     return false;
     958              : }
     959              : 
     960              : void
     961         1006 : MSDevice_Taxi::generateOutput(OutputDevice* tripinfoOut) const {
     962         1006 :     if (tripinfoOut != nullptr) {
     963          982 :         tripinfoOut->openTag("taxi");
     964         1964 :         tripinfoOut->writeAttr("customers", toString(myCustomersServed));
     965         1964 :         tripinfoOut->writeAttr("occupiedDistance", toString(myOccupiedDistance));
     966         1964 :         tripinfoOut->writeAttr("occupiedTime", time2string(myOccupiedTime));
     967         1964 :         tripinfoOut->closeTag();
     968              :     }
     969         1006 : }
     970              : 
     971              : std::string
     972         1884 : MSDevice_Taxi::getParameter(const std::string& key) const {
     973         1884 :     if (key == "customers") {
     974            0 :         return toString(myCustomersServed);
     975         1884 :     } else if (key == "occupiedDistance") {
     976            0 :         return toString(myOccupiedDistance);
     977         1884 :     } else if (key == "occupiedTime") {
     978            0 :         return toString(STEPS2TIME(myOccupiedTime));
     979         1884 :     } else if (key == "state") {
     980         1805 :         return toString(myState);
     981           79 :     } else if (key == "currentCustomers") {
     982           15 :         return joinNamedToStringSorting(myCustomers, " ");
     983           64 :     } else if (key == "pickUpDuration") {
     984           64 :         return myHolder.getStringParam("device.taxi.pickUpDuration", false, "0");
     985           32 :     } else if (key == "dropOffDuration") {
     986           64 :         return myHolder.getStringParam("device.taxi.dropOffDuration", false, "60");
     987              :     }
     988            0 :     throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     989              : }
     990              : 
     991              : 
     992              : void
     993           20 : MSDevice_Taxi::setParameter(const std::string& key, const std::string& value) {
     994              :     double doubleValue;
     995              :     try {
     996           20 :         doubleValue = StringUtils::toDouble(value);
     997            0 :     } catch (NumberFormatException&) {
     998            0 :         throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
     999            0 :     }
    1000           20 :     if (key == "pickUpDuration" || key == "dropOffDuration") {
    1001              :         // store as generic vehicle parameters
    1002           20 :         ((SUMOVehicleParameter&)myHolder.getParameter()).setParameter("device.taxi." + key, value);
    1003              :     } else {
    1004              :         UNUSED_PARAMETER(doubleValue);
    1005            0 :         throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
    1006              :     }
    1007           20 : }
    1008              : 
    1009              : bool
    1010        63143 : MSDevice_Taxi::compatibleLine(const std::string& taxiLine, const std::string& rideLine) {
    1011        72304 :     return ((taxiLine == rideLine && StringUtils::startsWith(rideLine, "taxi") && StringUtils::startsWith(taxiLine, "taxi"))
    1012       117125 :             || (taxiLine == TAXI_SERVICE && StringUtils::startsWith(rideLine, "taxi:"))
    1013       191598 :             || (rideLine == TAXI_SERVICE && StringUtils::startsWith(taxiLine, "taxi:")));
    1014              : }
    1015              : 
    1016              : bool
    1017         4332 : MSDevice_Taxi::compatibleLine(const Reservation* res) {
    1018         4332 :     return compatibleLine(myHolder.getParameter().line, res->line);
    1019              : }
    1020              : 
    1021              : 
    1022              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1