LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_Taxi.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 90.0 % 538 484
Test Date: 2026-05-06 15:47:47 Functions: 100.0 % 42 42

            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/transportables/MSTransportableControl.h>
      31              : #include <microsim/MSEventControl.h>
      32              : #include <microsim/MSGlobals.h>
      33              : #include <microsim/MSVehicle.h>
      34              : #include <microsim/MSEdge.h>
      35              : #include <microsim/MSLane.h>
      36              : #include <microsim/MSStop.h>
      37              : #include <microsim/MSStoppingPlace.h>
      38              : #include <microsim/trigger/MSTriggeredRerouter.h>
      39              : 
      40              : #include "MSDispatch.h"
      41              : #include "MSDispatch_Greedy.h"
      42              : #include "MSDispatch_GreedyShared.h"
      43              : #include "MSDispatch_RouteExtension.h"
      44              : #include "MSDispatch_TraCI.h"
      45              : 
      46              : #include "MSIdling.h"
      47              : 
      48              : #include "MSRoutingEngine.h"
      49              : #include "MSDevice_Routing.h"
      50              : #include "MSDevice_Taxi.h"
      51              : 
      52              : //#define DEBUG_DISPATCH
      53              : //#define DEBUG_CANCEL
      54              : 
      55              : //#define DEBUG_COND (myHolder.isSelected())
      56              : #define DEBUG_COND (true)
      57              : 
      58              : // ===========================================================================
      59              : // static member variables
      60              : // ===========================================================================
      61              : SUMOTime MSDevice_Taxi::myDispatchPeriod(0);
      62              : /// @brief the dispatch algorithm
      63              : MSDispatch* MSDevice_Taxi::myDispatcher(nullptr);
      64              : /// @brief The repeated call to the dispatcher
      65              : Command* MSDevice_Taxi::myDispatchCommand(nullptr);
      66              : // @brief the list of available taxis
      67              : std::vector<MSDevice_Taxi*> MSDevice_Taxi::myFleet;
      68              : int MSDevice_Taxi::myMaxCapacity(0);
      69              : int MSDevice_Taxi::myMaxContainerCapacity(0);
      70              : std::map<SUMOVehicleClass, std::string> MSDevice_Taxi::myTaxiTypes;
      71              : SUMOTime MSDevice_Taxi::myNextDispatchTime(-1);
      72              : std::map<std::string, MSDevice_Taxi*> MSDevice_Taxi::myStateLoadedCustomers;
      73              : std::map<std::string, MSDevice_Taxi*> MSDevice_Taxi::myStateLoadedReservations;
      74              : 
      75              : #define TAXI_SERVICE "taxi"
      76              : #define TAXI_SERVICE_PREFIX "taxi:"
      77              : #define SWAP_THRESHOLD 5
      78              : 
      79              : // ===========================================================================
      80              : // method definitions
      81              : // ===========================================================================
      82              : // ---------------------------------------------------------------------------
      83              : // static initialisation methods
      84              : // ---------------------------------------------------------------------------
      85              : void
      86        44671 : MSDevice_Taxi::insertOptions(OptionsCont& oc) {
      87        44671 :     oc.addOptionSubTopic("Taxi Device");
      88        89342 :     insertDefaultAssignmentOptions("taxi", "Taxi Device", oc);
      89              : 
      90        89342 :     oc.doRegister("device.taxi.dispatch-algorithm", new Option_String("greedy"));
      91        89342 :     oc.addDescription("device.taxi.dispatch-algorithm", "Taxi Device", TL("The dispatch algorithm [greedy|greedyClosest|greedyShared|routeExtension|traci]"));
      92              : 
      93        44671 :     oc.doRegister("device.taxi.dispatch-algorithm.output", new Option_FileName());
      94        89342 :     oc.addDescription("device.taxi.dispatch-algorithm.output", "Taxi Device", TL("Write information from the dispatch algorithm to FILE"));
      95              : 
      96        89342 :     oc.doRegister("device.taxi.dispatch-algorithm.params", new Option_String(""));
      97        89342 :     oc.addDescription("device.taxi.dispatch-algorithm.params", "Taxi Device", TL("Load dispatch algorithm parameters in format KEY1:VALUE1[,KEY2:VALUE]"));
      98              : 
      99        89342 :     oc.doRegister("device.taxi.dispatch-period", new Option_String("60", "TIME"));
     100        89342 :     oc.addDescription("device.taxi.dispatch-period", "Taxi Device", TL("The period between successive calls to the dispatcher"));
     101              : 
     102        89342 :     oc.doRegister("device.taxi.dispatch-keep-unreachable", new Option_String("3600", "TIME"));
     103        89342 :     oc.addDescription("device.taxi.dispatch-keep-unreachable", "Taxi Device", TL("The time before aborting unreachable reservations"));
     104              : 
     105        89342 :     oc.doRegister("device.taxi.idle-algorithm", new Option_String("stop"));
     106        89342 :     oc.addDescription("device.taxi.idle-algorithm", "Taxi Device", TL("The behavior of idle taxis [stop|randomCircling|taxistand]"));
     107              : 
     108        44671 :     oc.doRegister("device.taxi.idle-algorithm.output", new Option_FileName());
     109        89342 :     oc.addDescription("device.taxi.idle-algorithm.output", "Taxi Device", TL("Write information from the idling algorithm to FILE"));
     110              : 
     111        89342 :     oc.doRegister("device.taxi.vclasses", new Option_StringVector({"taxi"}));
     112        89342 :     oc.addSynonyme("device.taxi.vclasses", "taxi.vclasses");
     113        89342 :     oc.addDescription("device.taxi.vclasses", "Taxi Device", TL("Network permissions that can be accessed by taxis"));
     114        44671 : }
     115              : 
     116              : 
     117              : void
     118      5232261 : MSDevice_Taxi::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
     119      5232261 :     OptionsCont& oc = OptionsCont::getOptions();
     120     10464522 :     if (equippedByDefaultAssignmentOptions(oc, "taxi", v, false)) {
     121              :         // build the device
     122         7013 :         MSDevice_Taxi* device = new MSDevice_Taxi(v, "taxi_" + v.getID());
     123         7001 :         into.push_back(device);
     124         7001 :         myFleet.push_back(device);
     125         7001 :         if (v.getParameter().line == "") {
     126              :             // automatically set the line so that persons are willing to enter
     127              :             // (see MSStageDriving::isWaitingFor)
     128         6724 :             const_cast<SUMOVehicleParameter&>(v.getParameter()).line = TAXI_SERVICE;
     129              :         }
     130         7001 :         const int personCapacity = v.getVehicleType().getPersonCapacity();
     131         7001 :         const int containerCapacity = v.getVehicleType().getContainerCapacity();
     132         7001 :         myMaxCapacity = MAX2(myMaxCapacity, personCapacity);
     133         7001 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, containerCapacity);
     134         7001 :         if (myTaxiTypes[v.getVClass()] == "") {
     135         1748 :             myTaxiTypes[v.getVClass()] = v.getVehicleType().getID();
     136              :         }
     137         7001 :         if ((gTaxiClasses & v.getVClass()) == 0) {
     138           80 :             gTaxiClasses |= v.getVClass();
     139           80 :             MSNet::getInstance()->resetIntermodalRouter();
     140              :         }
     141         7001 :         if (personCapacity < 1 && containerCapacity < 1) {
     142            0 :             WRITE_WARNINGF(TL("Vehicle '%' with personCapacity % and containerCapacity % is not usable as taxi."), v.getID(), toString(personCapacity), toString(containerCapacity));
     143              :         }
     144              :     }
     145      5232255 : }
     146              : 
     147              : SUMOTime
     148           16 : MSDevice_Taxi::getNextDispatchTime() {
     149           16 :     return myNextDispatchTime;
     150              : }
     151              : 
     152              : void
     153          907 : MSDevice_Taxi::initDispatch(SUMOTime next) {
     154          907 :     OptionsCont& oc = OptionsCont::getOptions();
     155          907 :     myDispatchPeriod = string2time(oc.getString("device.taxi.dispatch-period"));
     156              :     // init dispatch algorithm
     157          907 :     std::string algo = oc.getString("device.taxi.dispatch-algorithm");
     158          907 :     Parameterised params;
     159         1814 :     params.setParametersStr(OptionsCont::getOptions().getString("device.taxi.dispatch-algorithm.params"), ":", ",");
     160          907 :     if (algo == "greedy") {
     161          603 :         myDispatcher = new MSDispatch_Greedy(params.getParametersMap());
     162          304 :     } else if (algo == "greedyClosest") {
     163           36 :         myDispatcher = new MSDispatch_GreedyClosest(params.getParametersMap());
     164          286 :     } else if (algo == "greedyShared") {
     165          114 :         myDispatcher = new MSDispatch_GreedyShared(params.getParametersMap());
     166          172 :     } else if (algo == "routeExtension") {
     167          122 :         myDispatcher = new MSDispatch_RouteExtension(params.getParametersMap());
     168          111 :     } else if (algo == "traci") {
     169          222 :         myDispatcher = new MSDispatch_TraCI(params.getParametersMap());
     170              :     } else {
     171            0 :         throw ProcessError(TLF("Dispatch algorithm '%' is not known", algo));
     172              :     }
     173          907 :     myDispatchCommand = new StaticCommand<MSDevice_Taxi>(&MSDevice_Taxi::triggerDispatch);
     174              :     // round to next multiple of myDispatchPeriod
     175          907 :     if (next < 0) {
     176          891 :         const SUMOTime now = MSNet::getInstance()->getCurrentTimeStep();
     177          891 :         const SUMOTime begin = string2time(oc.getString("begin"));
     178          891 :         const SUMOTime delay = (myDispatchPeriod - ((now - begin) % myDispatchPeriod)) % myDispatchPeriod;
     179          891 :         next = now + delay;
     180              :     }
     181          907 :     myNextDispatchTime = next;
     182          907 :     MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myDispatchCommand, next);
     183         1814 : }
     184              : 
     185              : bool
     186        78724 : MSDevice_Taxi::isReservation(const std::set<std::string>& lines) {
     187        78724 :     return lines.size() == 1 && (
     188        78710 :                *lines.begin() == TAXI_SERVICE
     189       151058 :                || StringUtils::startsWith(*lines.begin(), TAXI_SERVICE_PREFIX));
     190              : }
     191              : 
     192              : void
     193         3248 : MSDevice_Taxi::addReservation(MSTransportable* person,
     194              :                               const std::set<std::string>& lines,
     195              :                               SUMOTime reservationTime,
     196              :                               SUMOTime pickupTime,
     197              :                               SUMOTime earliestPickupTime,
     198              :                               const MSEdge* from, double fromPos,
     199              :                               const MSStoppingPlace* fromStop,
     200              :                               const MSEdge* to, double toPos,
     201              :                               const MSStoppingPlace* toStop,
     202              :                               const std::string& group) {
     203         3248 :     if (!isReservation(lines)) {
     204              :         return;
     205              :     }
     206         3248 :     if ((to->getPermissions() & gTaxiClasses) == 0) {
     207            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     208            0 :                            + " '" + person->getID() + "' because destination edge '" + to->getID() + "'"
     209            0 :                            + " does not permit taxi access");
     210              :     }
     211         3248 :     if ((from->getPermissions() & gTaxiClasses) == 0) {
     212            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     213            0 :                            + " '" + person->getID() + "' because origin edge '" + from->getID() + "'"
     214            0 :                            + " does not permit taxi access");
     215              :     }
     216         3248 :     if (myDispatchCommand == nullptr) {
     217          891 :         initDispatch();
     218              :     }
     219         3248 :     if (fromStop != nullptr && &fromStop->getLane().getEdge() == from) {
     220              :         // pickup position should be at the stop-endPos
     221          930 :         fromPos = fromStop->getEndLanePosition();
     222              :     }
     223         6496 :     Reservation* res = myDispatcher->addReservation(person, reservationTime, pickupTime, earliestPickupTime, from, fromPos, fromStop, to, toPos, toStop, group, *lines.begin(), myMaxCapacity, myMaxContainerCapacity);
     224         3248 :     if (myStateLoadedCustomers.size() > 0) {
     225              :         auto it = myStateLoadedCustomers.find(person->getID());
     226           76 :         if (it != myStateLoadedCustomers.end()) {
     227              :             //std::cout << SIMTIME << " loadedServed p=" << person->getID() << " res=" << res->getID() << " taxi=" << it->second->getID() << "\n";
     228           28 :             myDispatcher->servedReservation(res, it->second);
     229              :         }
     230              :     }
     231              : }
     232              : 
     233              : void
     234           93 : MSDevice_Taxi::removeReservation(MSTransportable* person,
     235              :                                  const std::set<std::string>& lines,
     236              :                                  const MSEdge* from, double fromPos,
     237              :                                  const MSEdge* to, double toPos,
     238              :                                  const std::string& group) {
     239           93 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     240          154 :         myDispatcher->removeReservation(person, from, fromPos, to, toPos, group);
     241              :     }
     242           93 : }
     243              : 
     244              : void
     245          162 : MSDevice_Taxi::updateReservationFromPos(MSTransportable* person,
     246              :                                         const std::set<std::string>& lines,
     247              :                                         const MSEdge* from, double fromPos,
     248              :                                         const MSEdge* to, double toPos,
     249              :                                         const std::string& group, double newFromPos) {
     250          162 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     251          300 :         myDispatcher->updateReservationFromPos(person, from, fromPos, to, toPos, group, newFromPos);
     252              :     }
     253          162 : }
     254              : 
     255              : 
     256              : SUMOTime
     257        15996 : MSDevice_Taxi::triggerDispatch(SUMOTime currentTime) {
     258              :     std::vector<MSDevice_Taxi*> active;
     259        46641 :     for (MSDevice_Taxi* taxi : myFleet) {
     260        30645 :         if (taxi->getHolder().hasDeparted()) {
     261        30542 :             active.push_back(taxi);
     262              :         }
     263              :     }
     264        15996 :     myDispatcher->computeDispatch(currentTime, active);
     265        15993 :     myNextDispatchTime = currentTime + myDispatchPeriod;
     266        15993 :     return myDispatchPeriod;
     267        15996 : }
     268              : 
     269              : bool
     270      8650485 : MSDevice_Taxi::hasServableReservations() {
     271      8650485 :     return myDispatcher != nullptr && myDispatcher->hasServableReservations();
     272              : }
     273              : 
     274              : void
     275        41529 : MSDevice_Taxi::cleanup() {
     276        41529 :     if (myDispatcher != nullptr) {
     277          907 :         delete myDispatcher;
     278          907 :         myDispatcher = nullptr;
     279              :     }
     280        41529 :     myDispatchCommand = nullptr;
     281              :     myTaxiTypes.clear();
     282        41529 : }
     283              : 
     284              : 
     285              : void
     286          640 : MSDevice_Taxi::allCustomersErased() {
     287         1606 :     for (MSDevice_Taxi* taxi : myFleet) {
     288              :         // disable taskSwap
     289          966 :         taxi->myState = EMPTY;
     290              :     }
     291          640 : }
     292              : 
     293              : 
     294              : const std::map<SUMOVehicleClass, std::string>&
     295          229 : MSDevice_Taxi::getTaxiTypes() {
     296          229 :     if (myTaxiTypes.size() < std::bitset<64>(gTaxiClasses).count()) {
     297          240 :         for (const std::string& vClassName : OptionsCont::getOptions().getStringVector("device.taxi.vclasses")) {
     298           80 :             SUMOVehicleClass svc = (SUMOVehicleClass)parseVehicleClasses(vClassName);
     299           80 :             if (myTaxiTypes[svc] == "") {
     300           80 :                 switch(svc) {
     301              :                     // @see MSVehicleControl::initDefaultTypes()
     302           80 :                     case SVC_TAXI:
     303           80 :                         myTaxiTypes[svc] = DEFAULT_TAXITYPE_ID;
     304              :                         break;
     305            0 :                     case SVC_RAIL:
     306            0 :                         myTaxiTypes[svc] = DEFAULT_RAILTYPE_ID;
     307              :                         break;
     308            0 :                     case SVC_BICYCLE:
     309            0 :                         myTaxiTypes[svc] = DEFAULT_BIKETYPE_ID;
     310              :                         break;
     311            0 :                     case SVC_PASSENGER:
     312            0 :                         myTaxiTypes[svc] = DEFAULT_VTYPE_ID;
     313              :                         break;
     314            0 :                     default: {
     315            0 :                         const std::string typeID = "DEFAULT_" + StringUtils::to_upper_case(vClassName) + "TYPE";
     316            0 :                         MSVehicleControl& vc = MSNet::getInstance()->getVehicleControl();
     317            0 :                         if (!vc.hasVType(typeID)) {
     318            0 :                             SUMOVTypeParameter tp(typeID, svc);
     319            0 :                             MSVehicleType* t = MSVehicleType::build(tp);
     320            0 :                             vc.addVType(t);
     321            0 :                         }
     322            0 :                         myTaxiTypes[svc] = typeID;;
     323              :                     }
     324              :                 }
     325              :             }
     326              :         }
     327              :     }
     328          229 :     return myTaxiTypes;
     329              : }
     330              : 
     331              : 
     332              : // ---------------------------------------------------------------------------
     333              : // MSDevice_Taxi-methods
     334              : // ---------------------------------------------------------------------------
     335         7007 : MSDevice_Taxi::MSDevice_Taxi(SUMOVehicle& holder, const std::string& id) :
     336         7007 :     MSVehicleDevice(holder, id) {
     337         7007 :     std::string defaultServiceEnd = toString(1e15);
     338        14020 :     const std::string algo = holder.getStringParam("device.taxi.idle-algorithm");
     339         7007 :     if (algo == "stop") {
     340         6694 :         myIdleAlgorithm = new MSIdling_Stop();
     341          313 :     } else if (algo == "randomCircling") {
     342          108 :         myIdleAlgorithm = new MSIdling_RandomCircling();
     343              :         // make sure simulation terminates
     344          108 :         defaultServiceEnd = toString(STEPS2TIME(
     345              :                                          myHolder.getParameter().departProcedure == DepartDefinition::GIVEN
     346              :                                          ? myHolder.getParameter().depart
     347          216 :                                          : MSNet::getInstance()->getCurrentTimeStep()) + (3600 * 8));
     348          205 :     } else if (algo == "taxistand") {
     349          398 :         const std::string rerouterID = holder.getStringParam("device.taxi.stands-rerouter");
     350          199 :         if (rerouterID.empty()) {
     351            0 :             throw ProcessError("Idle algorithm '" + algo + "' requires a rerouter id to be defined using device param 'stands-rerouter' for vehicle '" + myHolder.getID() + "'");
     352              :         }
     353              :         if (MSTriggeredRerouter::getInstances().count(rerouterID) == 0) {
     354            0 :             throw ProcessError("Unknown rerouter '" + rerouterID + "' when loading taxi stands for vehicle '" + myHolder.getID() + "'");
     355              :         }
     356          199 :         MSTriggeredRerouter* rerouter = MSTriggeredRerouter::getInstances().find(rerouterID)->second;
     357          199 :         myIdleAlgorithm = new MSIdling_TaxiStand(rerouter);
     358              :     } else {
     359           18 :         throw ProcessError("Idle algorithm '" + algo + "' is not known for vehicle '" + myHolder.getID() + "'");
     360              :     }
     361         7007 :     myServiceEnd = string2time(holder.getStringParam("device.taxi.end", false, defaultServiceEnd));
     362         7001 :     myRoutingDevice = static_cast<MSDevice_Routing*>(myHolder.getDevice(typeid(MSDevice_Routing)));
     363         7007 : }
     364              : 
     365              : 
     366        14002 : MSDevice_Taxi::~MSDevice_Taxi() {
     367         7001 :     myFleet.erase(std::find(myFleet.begin(), myFleet.end(), this));
     368              :     // recompute myMaxCapacity
     369         7001 :     myMaxCapacity = 0;
     370         7001 :     myMaxContainerCapacity = 0;
     371        17828 :     for (MSDevice_Taxi* taxi : myFleet) {
     372        10827 :         myMaxCapacity = MAX2(myMaxCapacity, taxi->getHolder().getVehicleType().getPersonCapacity());
     373        10827 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, taxi->getHolder().getVehicleType().getContainerCapacity());
     374              :     }
     375         7001 :     delete myIdleAlgorithm;
     376        14002 : }
     377              : 
     378              : 
     379              : bool
     380        11606 : MSDevice_Taxi::hasFleet() {
     381        11606 :     return myFleet.size() > 0;;
     382              : }
     383              : 
     384              : 
     385              : void
     386         1608 : MSDevice_Taxi::dispatch(const Reservation& res) {
     387         1608 :     dispatchShared({&res, &res});
     388         1605 : }
     389              : 
     390              : 
     391              : void
     392         1952 : MSDevice_Taxi::dispatchShared(std::vector<const Reservation*> reservations) {
     393              : #ifdef DEBUG_DISPATCH
     394              :     if (DEBUG_COND) {
     395              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " dispatch:\n";
     396              :         for (const Reservation* res : reservations) {
     397              :             std::cout << "   res=" << res->getID();
     398              :             std::cout << " persons=" << toString(res->persons) << "\n";
     399              :         }
     400              :     }
     401              : #endif
     402         1952 :     myLastDispatch = reservations;
     403              :     ConstMSEdgeVector tmpEdges;
     404              :     StopParVector stops;
     405         1952 :     double lastPos = myHolder.getPositionOnLane();
     406         1952 :     const MSEdge* rerouteOrigin = *myHolder.getRerouteOrigin();
     407         1952 :     if (isEmpty()) {
     408              :         // start fresh from the current edge
     409         1861 :         if (myHolder.isStoppedParking()) {
     410              :             // parking stop must be ended normally
     411         1110 :             MSStop& stop = myHolder.getNextStopMutable();
     412         1110 :             stop.duration = 0;
     413         1110 :             lastPos = stop.pars.endPos;
     414         1110 :             if (myHolder.isStoppedTriggered()) {
     415          994 :                 stop.triggered = false;
     416          994 :                 stop.containerTriggered = false;
     417          994 :                 stop.joinTriggered = false;
     418          994 :                 myHolder.unregisterWaiting();
     419              :             }
     420              :             // prevent unauthorized/premature entry
     421         1110 :             const_cast<SUMOVehicleParameter::Stop&>(stop.pars).permitted.insert("");
     422         1134 :             while (myHolder.getStops().size() > 1) {
     423           24 :                 myHolder.abortNextStop(1);
     424              :             }
     425              :         } else {
     426         1416 :             while (myHolder.hasStops()) {
     427              :                 // in meso there might be more than 1 stop at this point
     428          665 :                 myHolder.abortNextStop();
     429              :             }
     430              :             assert(!myHolder.hasStops());
     431              :         }
     432         1861 :         tmpEdges.push_back(myHolder.getEdge());
     433         1861 :         if (myHolder.getEdge() != rerouteOrigin) {
     434            8 :             tmpEdges.push_back(rerouteOrigin);
     435              :         }
     436              :     } else {
     437              :         assert(myHolder.hasStops());
     438              :         // check how often existing customers appear in the new reservations
     439              :         std::map<const MSTransportable*, int> nOccur;
     440          629 :         for (const Reservation* res : reservations) {
     441         1076 :             for (const MSTransportable* person : res->persons) {
     442              :                 if (myCustomers.count(person) != 0) {
     443          298 :                     nOccur[person] += 1;
     444              :                     if (myCurrentReservations.count(res) == 0) {
     445            0 :                         throw ProcessError(TLF("Invalid Re-dispatch for existing customer '%' with a new reservation", person->getID()));
     446              :                     }
     447              :                 }
     448              :             }
     449              :         }
     450              : #ifdef DEBUG_DISPATCH
     451              :         if (DEBUG_COND) {
     452              :             for (auto item : nOccur) {
     453              :                 std::cout << "   previousCustomer=" << item.first->getID() << " occurs=" << item.second << "\n";
     454              :             }
     455              :         }
     456              : #endif
     457           91 :         if (nOccur.size() == 0) {
     458              :             // no overlap with existing customers - extend route
     459           10 :             tmpEdges = myHolder.getRoute().getEdges();
     460           10 :             lastPos = myHolder.getStops().back().pars.endPos;
     461              : #ifdef DEBUG_DISPATCH
     462              :             if (DEBUG_COND) {
     463              :                 std::cout << " re-dispatch with route-extension\n";
     464              :             }
     465              : #endif
     466           81 :         } else if (nOccur.size() == myCustomers.size()) {
     467              :             // redefine route (verify correct number of mentions)
     468              :             std::set<const MSTransportable*> onBoard;
     469           81 :             const std::vector<MSTransportable*>& onBoardP = myHolder.getPersons();
     470           81 :             const std::vector<MSTransportable*>& onBoardC = myHolder.getContainers();
     471              :             onBoard.insert(onBoardP.begin(), onBoardP.end());
     472              :             onBoard.insert(onBoardC.begin(), onBoardC.end());
     473              :             std::set<const MSTransportable*> redundantPickup;
     474          244 :             for (auto item : nOccur) {
     475          167 :                 if (item.second == 1) {
     476              :                     // customers must already be on board
     477              :                     if (onBoard.count(item.first) == 0) {
     478           12 :                         throw ProcessError(TLF("Re-dispatch did not mention pickup for existing customer '%'", item.first->getID()));
     479              :                     }
     480          131 :                 } else if (item.second == 2) {
     481              :                     if (onBoard.count(item.first) == 0) {
     482              :                         // treat like a new customer
     483              :                         // TODO: need to be checked
     484              :                         myCustomers.erase(item.first);
     485              :                     } else {
     486              :                         redundantPickup.insert(item.first);
     487              :                     }
     488              :                 } else {
     489            0 :                     throw ProcessError("Re-dispatch mentions existing customer '" + item.first->getID() + "' " + toString(item.second) + " times");
     490              :                 }
     491              :             }
     492              :             // remove redundancy
     493           77 :             if (!redundantPickup.empty()) {
     494          183 :                 for (auto it = reservations.begin(); it != reservations.end();) {
     495              :                     bool isRedundant = false;
     496          262 :                     for (const MSTransportable* person : (*it)->persons) {
     497              :                         if (redundantPickup.count(person) != 0) {
     498              :                             isRedundant = true;
     499              :                             break;
     500              :                         }
     501              :                     }
     502          154 :                     if (isRedundant) {
     503           92 :                         for (const MSTransportable* person : (*it)->persons) {
     504              :                             redundantPickup.erase(person);
     505              :                         }
     506              :                         it = reservations.erase(it);
     507              :                     } else {
     508              :                         it++;
     509              :                     }
     510              :                 }
     511              :             }
     512          321 :             while (myHolder.hasStops()) {
     513          244 :                 myHolder.abortNextStop();
     514              :             }
     515           81 :             tmpEdges.push_back(myHolder.getEdge());
     516           77 :             if (myHolder.getEdge() != rerouteOrigin) {
     517           10 :                 tmpEdges.push_back(rerouteOrigin);
     518              :             }
     519              : #ifdef DEBUG_DISPATCH
     520              :             if (DEBUG_COND) {
     521              :                 std::cout << " re-dispatch from scratch\n";
     522              :             }
     523              : #endif
     524              :         } else {
     525              :             // inconsistent re-dispatch
     526              :             std::vector<std::string> missing;
     527            0 :             for (const MSTransportable* c : myCustomers) {
     528              :                 if (nOccur.count(c) == 0) {
     529            0 :                     missing.push_back(c->getID());
     530              :                 }
     531              :             }
     532            0 :             throw ProcessError("Re-dispatch did mention some customers but failed to mention " + joinToStringSorting(missing, " "));
     533            0 :         }
     534              :     }
     535              : 
     536              :     bool hasPickup = false;
     537         7244 :     for (const Reservation* res : reservations) {
     538              :         myCurrentReservations.insert(res);
     539              :         bool isPickup = false;
     540        10952 :         for (const MSTransportable* person : res->persons) {
     541              :             if (myCustomers.count(person) == 0) {
     542              :                 myCustomers.insert(person);
     543              :                 isPickup = true;
     544              :                 hasPickup = true;
     545              :             }
     546              :         }
     547         5296 :         if (isPickup) {
     548         7827 :             prepareStop(tmpEdges, stops, lastPos, res->from, res->fromPos, res->fromStop, "pickup " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     549         5398 :             for (const MSTransportable* const transportable : res->persons) {
     550         2789 :                 if (transportable->isPerson()) {
     551         2615 :                     stops.back().triggered = true;
     552              :                 } else {
     553          174 :                     stops.back().containerTriggered = true;
     554              :                 }
     555              :                 stops.back().permitted.insert(transportable->getID());
     556         2789 :                 stops.back().parametersSet |= STOP_PERMITTED_SET | STOP_TRIGGER_SET;
     557              :             }
     558         5398 :             for (const MSTransportable* t : res->persons) {
     559              :                 stops.back().awaitedPersons.insert(t->getID());
     560              :             }
     561              : 
     562         2609 :             if (stops.back().duration == -1) {
     563              :                 // keep dropOffDuration if the stop is dropOff and pickUp
     564         2256 :                 stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.pickUpDuration", false, 0));
     565              :             }
     566              :         } else {
     567         5374 :             prepareStop(tmpEdges, stops, lastPos, res->to, res->toPos, res->toStop, "dropOff " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     568         2687 :             stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.dropOffDuration", false, 60)); // pay and collect bags
     569              :         }
     570         5296 :         stops.back().parametersSet |= STOP_DURATION_SET | STOP_PARKING_SET;
     571              :     }
     572              : #ifdef DEBUG_DISPATCH
     573              :     if (DEBUG_COND) {
     574              :         std::cout << "   tmpEdges=" << toString(tmpEdges) << "\n";
     575              :     }
     576              : #endif
     577         1948 :     const SUMOTime t = SIMSTEP;
     578         3896 :     if (!myHolder.replaceRouteEdges(tmpEdges, -1, 0, "taxi:prepare_dispatch", false, false, false)) {
     579            0 :         throw ProcessError("Route replacement for taxi dispatch failed for vehicle '" + myHolder.getID()
     580            0 :                            + "' at time=" + time2string(t) + ".");
     581              :     }
     582              : #ifdef DEBUG_DISPATCH
     583              :     if (DEBUG_COND) std::cout << "   replacedRoute=" << toString(tmpEdges)
     584              :                                   << "\n     actualRoute=" << toString(myHolder.getRoute().getEdges()) << "\n";
     585              : #endif
     586         6392 :     for (SUMOVehicleParameter::Stop& stop : stops) {
     587              :         std::string error;
     588         4444 :         if (!myHolder.addStop(stop, error)) {
     589            0 :             WRITE_WARNINGF(TL("Could not add taxi stop for %, desc=% time=% error=%"), myHolder.getID(), stop.actType, time2string(t), error)
     590              :         }
     591              :     }
     592         1948 :     SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     593              :     // SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myHolder.getInfluencer().getRouterTT(veh->getRNGIndex())
     594         1948 :     myHolder.reroute(t, "taxi:dispatch", router, false);
     595              : #ifdef DEBUG_DISPATCH
     596              :     if (DEBUG_COND) {
     597              :         std::cout << "\n      finalRoute=" << toString(myHolder.getRoute().getEdges()) << " routeIndex=" << myHolder.getRoutePosition() << "\n";
     598              :     }
     599              : #endif
     600         1945 :     if (hasPickup) {
     601         1913 :         myState |= PICKUP;
     602              :     }
     603         1959 : }
     604              : 
     605              : 
     606              : void
     607           88 : MSDevice_Taxi::cancelCurrentCustomers() {
     608              :     // check if taxi has stopped
     609           88 :     if (myHolder.getNextStopParameter() == nullptr) {
     610            0 :         return;
     611              :     }
     612              :     // find customers of the current stop
     613              :     std::set<const MSTransportable*> customersToBeRemoved;
     614              :     std::set<const MSTransportable*> onBoard;
     615           88 :     onBoard.insert(myHolder.getPersons().begin(), myHolder.getPersons().end());
     616           88 :     onBoard.insert(myHolder.getContainers().begin(), myHolder.getContainers().end());
     617          142 :     for (std::string tID : myHolder.getNextStopParameter()->permitted) {
     618          220 :         for (auto t : myCustomers) {
     619          166 :             if (t->getID() == tID && onBoard.count(t) == 0) {
     620              :                 customersToBeRemoved.insert(t);
     621              :             }
     622              :         }
     623              :     }
     624           88 :     if (!customersToBeRemoved.empty()) {
     625           90 :         WRITE_WARNINGF(TL("Taxi '%' aborts waiting for customers: % at time=%."),
     626              :                        myHolder.getID(), toString(customersToBeRemoved), time2string(SIMSTEP));
     627              :     }
     628          122 :     for (auto t : customersToBeRemoved) {
     629           34 :         cancelCustomer(t);
     630              :     }
     631              : }
     632              : 
     633              : 
     634              : bool
     635           44 : MSDevice_Taxi::cancelCustomer(const MSTransportable* t) {
     636              : #ifdef DEBUG_CANCEL
     637              :     if (DEBUG_COND) {
     638              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " cancelCustomer " << t->getID() << "\n";
     639              :     }
     640              : #endif
     641              : 
     642              :     // is the given transportable a customer of the reservations?
     643              :     if (myCustomers.count(t) == 0) {
     644              :         return false;
     645              :     }
     646              :     myCustomers.erase(t);
     647              :     // check whether a single reservation has been fulfilled or another customer is part of the reservation
     648          132 :     for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     649              :         bool fulfilled = false;
     650           88 :         if ((*resIt)->persons.size() == 1 && (*resIt)->persons.count(t) != 0) {
     651              :             // the reservation contains only the customer
     652              :             fulfilled = true;
     653              :         }
     654              :         if (fulfilled) {
     655           44 :             const Reservation* res = *resIt;
     656              :             // remove reservation from the current dispatch
     657          220 :             for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     658          176 :                 if (*it == res) {
     659           88 :                     it = myLastDispatch.erase(it);
     660              :                 } else {
     661              :                     ++it;
     662              :                 }
     663              :             }
     664              :             // remove reservation from the served reservations
     665              :             resIt = myCurrentReservations.erase(resIt);
     666              :             // delete the reservation
     667           44 :             myDispatcher->fulfilledReservation(res);
     668              :         } else {
     669              :             ++resIt;
     670              :         }
     671              :     }
     672           44 :     myState &= ~PICKUP;  // remove state PICKUP
     673           88 :     for (const Reservation* res : myCurrentReservations) {
     674              :         // if there is another pickup in the dispatch left, add the state PICKUP
     675           44 :         if (std::count(myLastDispatch.begin(), myLastDispatch.end(), res) == 2) {
     676           44 :             myState |= PICKUP;  // add state PICKUP
     677              :         }
     678              :     }
     679              :     // we also have to clean reservations from myLastDispatch where the customers arrived in the meantime
     680          132 :     for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     681              :         if (myCurrentReservations.count(*it) == 0) {
     682            0 :             it = myLastDispatch.erase(it);
     683              :         } else {
     684              :             ++it;
     685              :         }
     686              :     }
     687              :     // if there are reservations left, go on with the dispatch
     688              :     // in meso, wait for the next dispatch cycle to avoid updating stops in this stage
     689           44 :     if (!MSGlobals::gUseMesoSim) {
     690           36 :         dispatchShared(myLastDispatch);
     691              :     }
     692              :     return true;
     693              : }
     694              : 
     695              : 
     696              : void
     697           60 : MSDevice_Taxi::addCustomer(const MSTransportable* t, const Reservation* res) {
     698              :     myCustomers.insert(t);
     699           60 :     MSBaseVehicle& veh = dynamic_cast<MSBaseVehicle&>(myHolder);
     700          122 :     for (const MSStop& stop : veh.getStops()) {
     701              :         SUMOVehicleParameter::Stop& pars = const_cast<SUMOVehicleParameter::Stop&>(stop.pars);
     702              :         //std::cout << " sE=" << (*stop.edge)->getID() << " sStart=" << pars.startPos << " sEnd=" << pars.endPos << " rFrom=" <<
     703              :         //    res->from->getID() << " rTo=" << res->to->getID() << " rFromPos=" << res->fromPos << " resToPos=" << res->toPos << "\n";
     704          122 :         if (*stop.edge == res->from
     705           60 :                 && pars.startPos <= res->fromPos
     706          182 :                 && pars.endPos >= res->fromPos) {
     707           60 :             pars.awaitedPersons.insert(t->getID());
     708              :             pars.permitted.insert(t->getID());
     709          120 :             pars.actType += " +" + t->getID();
     710           62 :         } else if (*stop.edge == res->to
     711           60 :                 && pars.startPos <= res->toPos
     712          122 :                 && pars.endPos >= res->toPos) {
     713           60 :             pars.actType += " +" + t->getID();
     714           60 :             break;
     715              :         }
     716              :     }
     717           60 : }
     718              : 
     719              : 
     720              : void
     721         5296 : MSDevice_Taxi::prepareStop(ConstMSEdgeVector& edges,
     722              :                            StopParVector& stops,
     723              :                            double& lastPos, const MSEdge* stopEdge, double stopPos,
     724              :                            const MSStoppingPlace* stopPlace,
     725              :                            const std::string& action, const Reservation* res, const bool isPickup) {
     726              :     assert(!edges.empty());
     727         5296 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     728          907 :         stopPos = stopPlace->getEndLanePosition();
     729              :     }
     730         5296 :     if (stopPos < lastPos && stopPos + NUMERICAL_EPS >= lastPos) {
     731              :         stopPos = lastPos;
     732              :     }
     733              :     bool addedEdge = false;
     734              : 
     735         5296 :     if (stops.empty()) {
     736              :         // check brakeGap
     737         1928 :         double distToStop = stopPos - lastPos;
     738         1928 :         const double brakeGap = myHolder.getBrakeGap();
     739         1928 :         if (myHolder.getLane() != nullptr && myHolder.getLane()->isInternal()) {
     740            6 :             distToStop += myHolder.getLane()->getLength();
     741              :         }
     742         1928 :         if (stopEdge != edges.back()) {
     743         1654 :             distToStop += edges.back()->getLength();
     744         1654 :             if (distToStop < brakeGap) {
     745              :                 // the distance between current edge and stop edge may be small
     746           22 :                 SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     747              :                 ConstMSEdgeVector toFirstStop;
     748           11 :                 router.compute(edges.back(), stopEdge, &myHolder, SIMSTEP, toFirstStop, true);
     749           43 :                 for (int i = 1; i < (int)toFirstStop.size() - 1; i++) {
     750           32 :                     distToStop += toFirstStop[i]->getLength();
     751              :                 }
     752           11 :             }
     753              :         }
     754         1928 :         if (distToStop < brakeGap) {
     755              :             // circle back to stopEdge
     756              :             //std::cout << SIMTIME << " taxi=" << getID() << " brakeGap=" << brakeGap << " distToStop=" << distToStop << "\n";
     757          190 :             edges.push_back(stopEdge);
     758              :             addedEdge = true;
     759              :         }
     760              :     }
     761              : 
     762         5296 :     if (stopEdge == edges.back() && !stops.empty()) {
     763         1023 :         if (stopPos >= lastPos && stopPos <= stops.back().endPos) {
     764              :             // no new stop and no adaption needed
     765          852 :             stops.back().actType += "," + action;
     766          852 :             return;
     767              :         }
     768          171 :         if (stopPos >= lastPos && stopPos <= lastPos + myHolder.getVehicleType().getLength()) {
     769              :             // stop length adaption needed
     770            0 :             stops.back().endPos = MIN2(lastPos + myHolder.getVehicleType().getLength(), stopEdge->getLength());
     771            0 :             stops.back().actType += "," + action;
     772            0 :             return;
     773              :         }
     774              :     }
     775         4444 :     if (!addedEdge && (stopEdge != edges.back() || stopPos < lastPos)) {
     776              :         //std::cout << SIMTIME << " stopPos=" << stopPos << " lastPos=" << lastPos << "\n";
     777         4017 :         edges.push_back(stopEdge);
     778              :     }
     779         4444 :     lastPos = stopPos;
     780         4444 :     SUMOVehicleParameter::Stop stop;
     781         4444 :     stop.lane = getStopLane(stopEdge, action)->getID();
     782         4444 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     783          672 :         stop.startPos = stopPlace->getBeginLanePosition();
     784          672 :         stop.endPos = stopPlace->getEndLanePosition();
     785              :         const SumoXMLTag tag = stopPlace->getElement();
     786          672 :         if (tag == SUMO_TAG_BUS_STOP || tag == SUMO_TAG_TRAIN_STOP) {
     787              :             stop.busstop = stopPlace->getID();
     788          245 :         } else if (tag == SUMO_TAG_PARKING_AREA) {
     789              :             stop.parkingarea = stopPlace->getID();
     790          240 :         } else if (tag == SUMO_TAG_CONTAINER_STOP) {
     791              :             stop.containerstop = stopPlace->getID();
     792              :         }
     793              :     } else {
     794         3772 :         stop.startPos = stopPos;
     795         3772 :         stop.endPos = MAX2(stopPos, MIN2(myHolder.getVehicleType().getLength(), stopEdge->getLength()));
     796         3772 :         stop.parametersSet |= STOP_START_SET | STOP_END_SET;
     797              :     }
     798         8888 :     stop.parking = SUMOVehicleParameter::parseParkingType(myHolder.getStringParam("device.taxi.parking", false, "true"));
     799              :     stop.actType = action;
     800         4444 :     stop.index = STOP_INDEX_END;
     801              :     // In case of prebooking if person is not there/ comes to late for pickup set maximum waiting time:
     802         4444 :     SUMOTime earliestPickupTime = res->earliestPickupTime;
     803         4444 :     if (isPickup && earliestPickupTime >= 0) {
     804          138 :         stop.waitUntil = earliestPickupTime;
     805              :         // TODO: replace hard coded extension with parameter
     806          138 :         stop.extension = static_cast<SUMOTime>(3 * 60 * 1000);  // 3mins
     807              :     }
     808         4444 :     stops.push_back(stop);
     809         4444 : }
     810              : 
     811              : 
     812              : MSLane*
     813         4444 : MSDevice_Taxi::getStopLane(const MSEdge* edge, const std::string& action) {
     814         4444 :     const std::vector<MSLane*>* allowedLanes = edge->allowedLanes(myHolder.getVClass());
     815         4444 :     if (allowedLanes == nullptr) {
     816            0 :         throw ProcessError("Taxi vehicle '" + myHolder.getID() + "' cannot stop on edge '" + edge->getID() + "' (" + action + ")");
     817              :     }
     818         4444 :     return allowedLanes->front();
     819              : }
     820              : 
     821              : bool
     822      1134195 : MSDevice_Taxi::isEmpty() {
     823      1134195 :     return myState == EMPTY;
     824              : }
     825              : 
     826              : 
     827              : bool
     828        29249 : MSDevice_Taxi::allowsBoarding(const MSTransportable* t) const {
     829        29249 :     return myCustomers.count(t) != 0;
     830              : }
     831              : 
     832              : 
     833              : void
     834       737573 : MSDevice_Taxi::updateMove(const SUMOTime traveltime, const double travelledDist) {
     835       737573 :     if (myHolder.getPersonNumber() > 0 || myHolder.getContainerNumber() > 0) {
     836       138504 :         myOccupiedDistance += travelledDist;
     837       138504 :         myOccupiedTime += traveltime;
     838              :     }
     839       737573 :     if (isEmpty()) {
     840       539594 :         if (MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     841       537536 :             myIdleAlgorithm->idle(this);
     842       537535 :             if (myRoutingDevice != nullptr) {
     843              :                 // prevent rerouting during idling (#11079)
     844              :                 myRoutingDevice->setActive(false);
     845              :             }
     846         2058 :         } else if (!myReachedServiceEnd) {
     847          252 :             WRITE_WARNINGF(TL("Taxi '%' reaches scheduled end of service at time=%."), myHolder.getID(), time2string(SIMSTEP));
     848           84 :             myReachedServiceEnd = true;
     849              :         }
     850       197979 :     } else if (myRoutingDevice != nullptr) {
     851              :         myRoutingDevice->setActive(true);
     852              :     }
     853       737572 :     if (myHolder.isStopped() && (isEmpty() || MSGlobals::gUseMesoSim) && myHolder.getNextStop().endBoarding > myServiceEnd) {
     854              :         // limit duration of stop (but only for idling-related stops)
     855         3722 :         myHolder.getNextStopMutable().endBoarding = myServiceEnd;
     856              :     }
     857              : #ifdef DEBUG_DISPATCH
     858              :     if (DEBUG_COND && myIsStopped != myHolder.isStopped()) {
     859              :         std::cout << SIMTIME << " updateMove veh=" << myHolder.getID() << " myIsStopped=" << myIsStopped << " myHolderStopped=" << myHolder.isStopped() << " myState=" << myState << "\n";
     860              :     }
     861              : #endif
     862       737572 :     myIsStopped = myHolder.isStopped();
     863       737572 : }
     864              : 
     865              : 
     866              : bool
     867       705118 : MSDevice_Taxi::notifyMove(SUMOTrafficObject& /*tObject*/, double oldPos,
     868              :                           double newPos, double /*newSpeed*/) {
     869       705118 :     updateMove(DELTA_T, newPos - oldPos);
     870       705117 :     return true; // keep the device
     871              : }
     872              : 
     873              : 
     874              : void
     875        32455 : MSDevice_Taxi::notifyMoveInternal(const SUMOTrafficObject& /* veh */,
     876              :                                   const double /* frontOnLane */,
     877              :                                   const double timeOnLane,
     878              :                                   const double /* meanSpeedFrontOnLane */,
     879              :                                   const double /* meanSpeedVehicleOnLane */,
     880              :                                   const double travelledDistanceFrontOnLane,
     881              :                                   const double /* travelledDistanceVehicleOnLane */,
     882              :                                   const double /* meanLengthOnLane */) {
     883        32455 :     updateMove(TIME2STEPS(timeOnLane), travelledDistanceFrontOnLane);
     884        32455 : }
     885              : 
     886              : 
     887              : bool
     888        83003 : MSDevice_Taxi::notifyEnter(SUMOTrafficObject& /*veh*/, MSMoveReminder::Notification /*reason*/, const MSLane* /* enteredLane */) {
     889        83003 :     if (isEmpty() && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     890        51364 :         myIdleAlgorithm->idle(this);
     891              :     }
     892        83003 :     return true; // keep the device
     893              : }
     894              : 
     895              : 
     896              : void
     897         2693 : MSDevice_Taxi::customerEntered(const MSTransportable* t) {
     898         2693 :     myState |= OCCUPIED;
     899         2693 :     if (!hasFuturePickup()) {
     900         2228 :         myState &= ~PICKUP;
     901              :     }
     902        12311 :     for (const Reservation* res : myCurrentReservations) {
     903        17130 :         for (const MSTransportable* cand : res->persons) {
     904        10186 :             if (cand == t) {
     905         2674 :                 const_cast<Reservation*>(res)->state = Reservation::ONBOARD;
     906         2674 :                 break;
     907              :             }
     908              :         }
     909              :     }
     910         2693 : }
     911              : 
     912              : 
     913              : void
     914         2693 : MSDevice_Taxi::customerArrived(const MSTransportable* person) {
     915         2693 :     myCustomersServed++;
     916              :     myCustomers.erase(person);
     917         2693 :     if (myHolder.getPersonNumber() == 0 && myHolder.getContainerNumber() == 0) {
     918         1836 :         myState &= ~OCCUPIED;
     919         1836 :         if (myHolder.getStops().size() > 1 && (myState & PICKUP) == 0) {
     920            0 :             WRITE_WARNINGF(TL("All customers left vehicle '%' at time=% but there are % remaining stops"),
     921              :                            myHolder.getID(), time2string(SIMSTEP), myHolder.getStops().size() - 1);
     922            0 :             while (myHolder.getStops().size() > 1) {
     923            0 :                 myHolder.abortNextStop(1);
     924              :             }
     925              :         }
     926              :     }
     927         2693 :     if (isEmpty()) {
     928              :         // cleanup
     929         3612 :         for (const Reservation* res : myCurrentReservations) {
     930         1812 :             myDispatcher->fulfilledReservation(res);
     931              :         }
     932              :         myCurrentReservations.clear();
     933         1800 :         checkTaskSwap();
     934         1800 :         if (isEmpty()) {
     935         1796 :             if (MSGlobals::gUseMesoSim && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     936          580 :                 myIdleAlgorithm->idle(this);
     937              :             }
     938              :         }
     939              :     } else {
     940              :         // check whether a single reservation has been fulfilled
     941         5315 :         for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     942              :             bool fulfilled = true;
     943         5619 :             for (const MSTransportable* t : (*resIt)->persons) {
     944              :                 if (myCustomers.count(t) != 0) {
     945              :                     fulfilled = false;
     946              :                     break;
     947              :                 }
     948              :             }
     949         4422 :             if (fulfilled) {
     950          645 :                 myDispatcher->fulfilledReservation(*resIt);
     951              :                 resIt = myCurrentReservations.erase(resIt);
     952              :             } else {
     953              :                 ++resIt;
     954              :             }
     955              :         }
     956              :     }
     957         2693 : }
     958              : 
     959              : 
     960              : void
     961         1800 : MSDevice_Taxi::checkTaskSwap() {
     962         3600 :     const std::string swapGroup = myHolder.getStringParam("device.taxi.swapGroup", false, "");
     963         1800 :     if (swapGroup != "") {
     964           12 :         SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myDispatcher->getRouter();
     965           12 :         const double stopTime = myHolder.isStopped() ? MAX2(0.0, STEPS2TIME(myHolder.getNextStop().duration)) : 0;
     966              :         double maxSaving = 0;
     967              :         MSDevice_Taxi* bestSwap = nullptr;
     968           36 :         for (MSDevice_Taxi* taxi : myFleet) {
     969           48 :             if (taxi->getHolder().hasDeparted() && taxi->getState() == PICKUP
     970           60 :                     && taxi->getHolder().getStringParam("device.taxi.swapGroup", false, "") == swapGroup) {
     971              :                 SUMOVehicle& veh = taxi->getHolder();
     972            4 :                 const MSStop& stop = veh.getNextStop();
     973            4 :                 ConstMSEdgeVector toPickup(veh.getCurrentRouteEdge(), stop.edge + 1);
     974            4 :                 const double cost = router.recomputeCostsPos(toPickup, &veh, veh.getPositionOnLane(), stop.pars.endPos, SIMSTEP);
     975              :                 ConstMSEdgeVector toPickup2;
     976            4 :                 router.compute(myHolder.getEdge(), myHolder.getPositionOnLane(), *stop.edge, stop.pars.endPos, &myHolder, SIMSTEP, toPickup2, true);
     977            4 :                 if (!toPickup2.empty()) {
     978            4 :                     const double cost2 = router.recomputeCostsPos(toPickup2, &myHolder, myHolder.getPositionOnLane(), stop.pars.endPos, SIMSTEP);
     979            4 :                     const double saving = cost - cost2 - stopTime;
     980              :                     //std::cout << SIMTIME << " taxi=" << getID() << " other=" << veh.getID() << " cost=" << cost << " cost2=" << cost2 << " stopTime=" << stopTime << " saving=" << saving << " route1=" << toString(toPickup) << " route2=" << toString(toPickup2) << "\n";
     981            4 :                     if (saving > maxSaving) {
     982              :                         maxSaving = saving;
     983              :                         bestSwap = taxi;
     984              :                     }
     985              :                 }
     986            4 :             }
     987              :         }
     988           12 :         if (maxSaving > SWAP_THRESHOLD) {
     989              : #ifdef DEBUG_DISPATCH
     990              :             if (DEBUG_COND) {
     991              :                 std::cout << SIMTIME << " taxi=" << myHolder.getID() << " swapWith=" << bestSwap->getHolder().getID() << " saving=" << maxSaving << " lastDispatch=";
     992              :                 for (const Reservation* res : bestSwap->myLastDispatch) {
     993              :                     std::cout << toString(res->persons) << "; ";
     994              :                 }
     995              :                 std::cout << "\n";
     996              :             }
     997              : #endif
     998            4 :             dispatchShared(bestSwap->myLastDispatch);
     999              :             bestSwap->myCurrentReservations.clear();
    1000              :             bestSwap->myCustomers.clear();
    1001            4 :             bestSwap->myState = EMPTY;
    1002           12 :             while (bestSwap->getHolder().hasStops()) {
    1003            8 :                 bestSwap->getHolder().abortNextStop();
    1004              :             }
    1005            8 :             for (const Reservation* res : myCurrentReservations) {
    1006            4 :                 myDispatcher->swappedRunning(res, this);
    1007              :             }
    1008              :         }
    1009              :     }
    1010         1800 : }
    1011              : 
    1012              : 
    1013              : bool
    1014         2693 : MSDevice_Taxi::hasFuturePickup() {
    1015         7788 :     for (const auto& stop : myHolder.getStops()) {
    1016         5560 :         if (stop.reached) {
    1017         2683 :             continue;
    1018              :         }
    1019         2877 :         if (stop.pars.permitted.size() > 0) {
    1020              :             return true;
    1021              :         }
    1022              :     }
    1023              :     return false;
    1024              : }
    1025              : 
    1026              : void
    1027         1084 : MSDevice_Taxi::generateOutput(OutputDevice* tripinfoOut) const {
    1028         1084 :     if (tripinfoOut != nullptr) {
    1029         1060 :         tripinfoOut->openTag("taxi");
    1030         1060 :         tripinfoOut->writeAttr("customers", toString(myCustomersServed));
    1031         1060 :         tripinfoOut->writeAttr("occupiedDistance", toString(myOccupiedDistance));
    1032         1060 :         tripinfoOut->writeAttr("occupiedTime", time2string(myOccupiedTime));
    1033         2120 :         tripinfoOut->closeTag();
    1034              :     }
    1035         1084 : }
    1036              : 
    1037              : std::string
    1038         1724 : MSDevice_Taxi::getParameter(const std::string& key) const {
    1039         1724 :     if (key == "customers") {
    1040            0 :         return toString(myCustomersServed);
    1041         1724 :     } else if (key == "occupiedDistance") {
    1042            0 :         return toString(myOccupiedDistance);
    1043         1724 :     } else if (key == "occupiedTime") {
    1044            0 :         return toString(STEPS2TIME(myOccupiedTime));
    1045         1724 :     } else if (key == "state") {
    1046         1646 :         return toString(myState);
    1047           78 :     } else if (key == "currentCustomers") {
    1048           14 :         return joinNamedToStringSorting(myCustomers, " ");
    1049           64 :     } else if (key == "pickUpDuration") {
    1050           64 :         return myHolder.getStringParam("device.taxi.pickUpDuration", false, "0");
    1051           32 :     } else if (key == "dropOffDuration") {
    1052           64 :         return myHolder.getStringParam("device.taxi.dropOffDuration", false, "60");
    1053              :     }
    1054            0 :     throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
    1055              : }
    1056              : 
    1057              : 
    1058              : void
    1059           20 : MSDevice_Taxi::setParameter(const std::string& key, const std::string& value) {
    1060              :     double doubleValue;
    1061              :     try {
    1062           20 :         doubleValue = StringUtils::toDouble(value);
    1063            0 :     } catch (NumberFormatException&) {
    1064            0 :         throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
    1065            0 :     }
    1066           20 :     if (key == "pickUpDuration" || key == "dropOffDuration") {
    1067              :         // store as generic vehicle parameters
    1068           20 :         ((SUMOVehicleParameter&)myHolder.getParameter()).setParameter("device.taxi." + key, value);
    1069              :     } else {
    1070              :         UNUSED_PARAMETER(doubleValue);
    1071            0 :         throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
    1072              :     }
    1073           20 : }
    1074              : 
    1075              : 
    1076              : void
    1077           31 : MSDevice_Taxi::saveState(OutputDevice& out) const {
    1078           31 :     out.openTag(SUMO_TAG_DEVICE);
    1079           31 :     out.writeAttr(SUMO_ATTR_ID, getID());
    1080           31 :     std::ostringstream internals;
    1081           31 :     internals << myState
    1082           31 :         << " " << myCustomersServed
    1083           62 :         << " " << myOccupiedDistance
    1084           31 :         << " " << myOccupiedTime;
    1085           31 :     out.writeAttr(SUMO_ATTR_STATE, internals.str());
    1086           31 :     if (myCustomers.size() > 0) {
    1087           20 :         out.writeAttr(SUMO_ATTR_CUSTOMERS, joinNamedToStringSorting(myCustomers, " "));
    1088              :     }
    1089           31 :     if (myCurrentReservations.size() > 0) {
    1090           20 :         out.writeAttr(SUMO_ATTR_RESERVATIONS, joinNamedToStringSorting(myCurrentReservations, " "));
    1091              :     }
    1092           31 :     out.closeTag();
    1093           31 : }
    1094              : 
    1095              : 
    1096              : void
    1097           31 : MSDevice_Taxi::loadState(const SUMOSAXAttributes& attrs) {
    1098           31 :     bool ok = true;
    1099           31 :     std::istringstream bis(attrs.getString(SUMO_ATTR_STATE));
    1100           31 :     bis >> myState;
    1101           31 :     bis >> myCustomersServed;
    1102           31 :     bis >> myOccupiedDistance;
    1103           31 :     bis >> myOccupiedTime;
    1104           31 :     if (attrs.hasAttribute(SUMO_ATTR_CUSTOMERS)) {
    1105           48 :         for (const std::string& id : attrs.get<std::vector<std::string> >(SUMO_ATTR_CUSTOMERS, getID().c_str(), ok)) {
    1106           28 :             myStateLoadedCustomers[id] = this;
    1107           20 :         }
    1108              :     }
    1109           31 :     if (attrs.hasAttribute(SUMO_ATTR_RESERVATIONS)) {
    1110           40 :         for (const std::string& id : attrs.get<std::vector<std::string> >(SUMO_ATTR_RESERVATIONS, getID().c_str(), ok)) {
    1111           20 :             myStateLoadedReservations[id] = this;
    1112           20 :         }
    1113              :     }
    1114           31 : }
    1115              : 
    1116              : 
    1117              : void
    1118          439 : MSDevice_Taxi::finalizeLoadState() {
    1119          439 :     if (myStateLoadedCustomers.size() > 0) {
    1120           14 :         MSNet* net = MSNet::getInstance();
    1121           14 :         MSTransportableControl* pc = net->hasPersons() ? &net->getPersonControl() : nullptr;
    1122           14 :         MSTransportableControl* cc = net->hasContainers() ? &net->getContainerControl() : nullptr;
    1123           42 :         for (auto item : myStateLoadedCustomers) {
    1124           28 :             MSTransportable* t = nullptr;
    1125           28 :             if (pc != nullptr) {
    1126           28 :                 t = pc->get(item.first);
    1127              :             }
    1128           28 :             if (t == nullptr && cc != nullptr) {
    1129            0 :                 t = cc->get(item.first);
    1130              :             }
    1131           28 :             if (t == nullptr) {
    1132            0 :                 WRITE_ERRORF("Could not find taxi customer '%'. Ensure state contains transportables", item.first);
    1133              :             } else {
    1134           28 :                 item.second->myCustomers.insert(t);
    1135              :             }
    1136              :         }
    1137              :     }
    1138          439 :     if (myStateLoadedReservations.size() > 0) {
    1139              :         assert(myDispatcher != nullptr);
    1140              :         std::map<std::string, const Reservation*> resLookup;
    1141           62 :         for (const Reservation* res : myDispatcher->getReservations()) {
    1142           48 :             resLookup[res->getID()] = res;
    1143           14 :         }
    1144           42 :         for (const Reservation* res : myDispatcher->getRunningReservations()) {
    1145           28 :             resLookup[res->getID()] = res;
    1146           14 :         }
    1147           34 :         for (auto item : myStateLoadedReservations) {
    1148              :             auto it = resLookup.find(item.first);
    1149           20 :             if (it == resLookup.end()) {
    1150            0 :                 WRITE_ERRORF("Could not find taxi reservation '%'.", item.first);
    1151              :             } else {
    1152           20 :                 item.second->myCurrentReservations.insert(it->second);
    1153              :             }
    1154              :         }
    1155              :     }
    1156              :     myStateLoadedCustomers.clear();
    1157              :     myStateLoadedReservations.clear();
    1158          439 : }
    1159              : 
    1160              : 
    1161              : bool
    1162        64078 : MSDevice_Taxi::compatibleLine(const std::string& taxiLine, const std::string& rideLine) {
    1163        74540 :     return ((taxiLine == rideLine && StringUtils::startsWith(rideLine, "taxi") && StringUtils::startsWith(taxiLine, "taxi"))
    1164       117694 :             || (taxiLine == TAXI_SERVICE && StringUtils::startsWith(rideLine, "taxi:"))
    1165       194411 :             || (rideLine == TAXI_SERVICE && StringUtils::startsWith(taxiLine, "taxi:")));
    1166              : }
    1167              : 
    1168              : bool
    1169         4667 : MSDevice_Taxi::compatibleLine(const Reservation* res) {
    1170         4667 :     return compatibleLine(myHolder.getParameter().line, res->line);
    1171              : }
    1172              : 
    1173              : 
    1174              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1