LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_Taxi.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 90.8 % 393 357
Test Date: 2024-11-22 15:46:21 Functions: 100.0 % 34 34

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2013-2024 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              : 
      74              : // ===========================================================================
      75              : // method definitions
      76              : // ===========================================================================
      77              : // ---------------------------------------------------------------------------
      78              : // static initialisation methods
      79              : // ---------------------------------------------------------------------------
      80              : void
      81        43644 : MSDevice_Taxi::insertOptions(OptionsCont& oc) {
      82        43644 :     oc.addOptionSubTopic("Taxi Device");
      83        87288 :     insertDefaultAssignmentOptions("taxi", "Taxi Device", oc);
      84              : 
      85        87288 :     oc.doRegister("device.taxi.dispatch-algorithm", new Option_String("greedy"));
      86        87288 :     oc.addDescription("device.taxi.dispatch-algorithm", "Taxi Device", TL("The dispatch algorithm [greedy|greedyClosest|greedyShared|routeExtension|traci]"));
      87              : 
      88        43644 :     oc.doRegister("device.taxi.dispatch-algorithm.output", new Option_FileName());
      89        87288 :     oc.addDescription("device.taxi.dispatch-algorithm.output", "Taxi Device", TL("Write information from the dispatch algorithm to FILE"));
      90              : 
      91        87288 :     oc.doRegister("device.taxi.dispatch-algorithm.params", new Option_String(""));
      92        87288 :     oc.addDescription("device.taxi.dispatch-algorithm.params", "Taxi Device", TL("Load dispatch algorithm parameters in format KEY1:VALUE1[,KEY2:VALUE]"));
      93              : 
      94        87288 :     oc.doRegister("device.taxi.dispatch-period", new Option_String("60", "TIME"));
      95        87288 :     oc.addDescription("device.taxi.dispatch-period", "Taxi Device", TL("The period between successive calls to the dispatcher"));
      96              : 
      97        87288 :     oc.doRegister("device.taxi.idle-algorithm", new Option_String("stop"));
      98        87288 :     oc.addDescription("device.taxi.idle-algorithm", "Taxi Device", TL("The behavior of idle taxis [stop|randomCircling]"));
      99              : 
     100        43644 :     oc.doRegister("device.taxi.idle-algorithm.output", new Option_FileName());
     101        87288 :     oc.addDescription("device.taxi.idle-algorithm.output", "Taxi Device", TL("Write information from the idling algorithm to FILE"));
     102        43644 : }
     103              : 
     104              : 
     105              : void
     106      5104369 : MSDevice_Taxi::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
     107      5104369 :     OptionsCont& oc = OptionsCont::getOptions();
     108     10208738 :     if (equippedByDefaultAssignmentOptions(oc, "taxi", v, false)) {
     109              :         // build the device
     110         6367 :         MSDevice_Taxi* device = new MSDevice_Taxi(v, "taxi_" + v.getID());
     111         6355 :         into.push_back(device);
     112         6355 :         myFleet.push_back(device);
     113         6355 :         if (v.getParameter().line == "") {
     114              :             // automatically set the line so that persons are willing to enter
     115              :             // (see MSStageDriving::isWaitingFor)
     116         6258 :             const_cast<SUMOVehicleParameter&>(v.getParameter()).line = TAXI_SERVICE;
     117              :         }
     118         6355 :         if (v.getVClass() != SVC_TAXI && myVClassWarningVTypes.count(v.getVehicleType().getID()) == 0) {
     119          159 :             WRITE_WARNINGF(TL("Vehicle '%' with device.taxi should have vClass taxi instead of '%'."), v.getID(), toString(v.getVClass()));
     120           53 :             myVClassWarningVTypes.insert(v.getVehicleType().getID());
     121              :         }
     122         6355 :         const int personCapacity = v.getVehicleType().getPersonCapacity();
     123         6355 :         const int containerCapacity = v.getVehicleType().getContainerCapacity();
     124         6355 :         myMaxCapacity = MAX2(myMaxCapacity, personCapacity);
     125         6355 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, containerCapacity);
     126         6355 :         if (personCapacity < 1 && containerCapacity < 1) {
     127            0 :             WRITE_WARNINGF(TL("Vehicle '%' with personCapacity % and containerCapacity % is not usable as taxi."), v.getID(), toString(personCapacity), toString(containerCapacity));
     128              :         }
     129              :     }
     130      5104363 : }
     131              : 
     132              : 
     133              : void
     134          632 : MSDevice_Taxi::initDispatch() {
     135          632 :     OptionsCont& oc = OptionsCont::getOptions();
     136          632 :     myDispatchPeriod = string2time(oc.getString("device.taxi.dispatch-period"));
     137              :     // init dispatch algorithm
     138          632 :     std::string algo = oc.getString("device.taxi.dispatch-algorithm");
     139          632 :     Parameterised params;
     140         1264 :     params.setParametersStr(OptionsCont::getOptions().getString("device.taxi.dispatch-algorithm.params"), ":", ",");
     141          632 :     if (algo == "greedy") {
     142          390 :         myDispatcher = new MSDispatch_Greedy(params.getParametersMap());
     143          242 :     } else if (algo == "greedyClosest") {
     144           36 :         myDispatcher = new MSDispatch_GreedyClosest(params.getParametersMap());
     145          224 :     } else if (algo == "greedyShared") {
     146           96 :         myDispatcher = new MSDispatch_GreedyShared(params.getParametersMap());
     147          128 :     } else if (algo == "routeExtension") {
     148           74 :         myDispatcher = new MSDispatch_RouteExtension(params.getParametersMap());
     149           91 :     } else if (algo == "traci") {
     150          182 :         myDispatcher = new MSDispatch_TraCI(params.getParametersMap());
     151              :     } else {
     152            0 :         throw ProcessError(TLF("Dispatch algorithm '%' is not known", algo));
     153              :     }
     154          632 :     myDispatchCommand = new StaticCommand<MSDevice_Taxi>(&MSDevice_Taxi::triggerDispatch);
     155              :     // round to next multiple of myDispatchPeriod
     156          632 :     const SUMOTime now = MSNet::getInstance()->getCurrentTimeStep();
     157          632 :     const SUMOTime begin = string2time(oc.getString("begin"));
     158          632 :     const SUMOTime delay = (myDispatchPeriod - ((now - begin) % myDispatchPeriod)) % myDispatchPeriod;
     159          632 :     MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myDispatchCommand, now + delay);
     160         1264 : }
     161              : 
     162              : bool
     163        76205 : MSDevice_Taxi::isReservation(const std::set<std::string>& lines) {
     164        76205 :     return lines.size() == 1 && (
     165        76191 :                *lines.begin() == TAXI_SERVICE
     166       147776 :                || StringUtils::startsWith(*lines.begin(), TAXI_SERVICE_PREFIX));
     167              : }
     168              : 
     169              : void
     170         2370 : MSDevice_Taxi::addReservation(MSTransportable* person,
     171              :                               const std::set<std::string>& lines,
     172              :                               SUMOTime reservationTime,
     173              :                               SUMOTime pickupTime,
     174              :                               SUMOTime earliestPickupTime,
     175              :                               const MSEdge* from, double fromPos,
     176              :                               const MSStoppingPlace* fromStop,
     177              :                               const MSEdge* to, double toPos,
     178              :                               const MSStoppingPlace* toStop,
     179              :                               const std::string& group) {
     180         2370 :     if (!isReservation(lines)) {
     181              :         return;
     182              :     }
     183         2370 :     if ((to->getPermissions() & SVC_TAXI) == 0) {
     184            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     185            0 :                            + " '" + person->getID() + "' because destination edge '" + to->getID() + "'"
     186            0 :                            + " does not permit taxi access");
     187              :     }
     188         2370 :     if ((from->getPermissions() & SVC_TAXI) == 0) {
     189            0 :         throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
     190            0 :                            + " '" + person->getID() + "' because origin edge '" + from->getID() + "'"
     191            0 :                            + " does not permit taxi access");
     192              :     }
     193         2370 :     if (myDispatchCommand == nullptr) {
     194          632 :         initDispatch();
     195              :     }
     196         2370 :     if (fromStop != nullptr && &fromStop->getLane().getEdge() == from) {
     197              :         // pickup position should be at the stop-endPos
     198          857 :         fromPos = fromStop->getEndLanePosition();
     199              :     }
     200         4740 :     myDispatcher->addReservation(person, reservationTime, pickupTime, earliestPickupTime, from, fromPos, fromStop, to, toPos, toStop, group, *lines.begin(), myMaxCapacity, myMaxContainerCapacity);
     201              : }
     202              : 
     203              : void
     204           78 : MSDevice_Taxi::removeReservation(MSTransportable* person,
     205              :                                  const std::set<std::string>& lines,
     206              :                                  const MSEdge* from, double fromPos,
     207              :                                  const MSEdge* to, double toPos,
     208              :                                  const std::string& group) {
     209           78 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     210          124 :         myDispatcher->removeReservation(person, from, fromPos, to, toPos, group);
     211              :     }
     212           78 : }
     213              : 
     214              : void
     215          162 : MSDevice_Taxi::updateReservationFromPos(MSTransportable* person,
     216              :                                         const std::set<std::string>& lines,
     217              :                                         const MSEdge* from, double fromPos,
     218              :                                         const MSEdge* to, double toPos,
     219              :                                         const std::string& group, double newFromPos) {
     220          162 :     if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
     221          300 :         myDispatcher->updateReservationFromPos(person, from, fromPos, to, toPos, group, newFromPos);
     222              :     }
     223          162 : }
     224              : 
     225              : 
     226              : SUMOTime
     227         7129 : MSDevice_Taxi::triggerDispatch(SUMOTime currentTime) {
     228              :     std::vector<MSDevice_Taxi*> active;
     229        16729 :     for (MSDevice_Taxi* taxi : myFleet) {
     230         9600 :         if (taxi->getHolder().hasDeparted()) {
     231         9517 :             active.push_back(taxi);
     232              :         }
     233              :     }
     234         7129 :     myDispatcher->computeDispatch(currentTime, active);
     235         7127 :     return myDispatchPeriod;
     236         7129 : }
     237              : 
     238              : bool
     239        42035 : MSDevice_Taxi::hasServableReservations() {
     240        42035 :     return myDispatcher != nullptr && myDispatcher->hasServableReservations();
     241              : }
     242              : 
     243              : void
     244        40275 : MSDevice_Taxi::cleanup() {
     245        40275 :     if (myDispatcher != nullptr) {
     246          632 :         delete myDispatcher;
     247          632 :         myDispatcher = nullptr;
     248              :     }
     249        40275 :     myDispatchCommand = nullptr;
     250              :     myVClassWarningVTypes.clear();
     251        40275 : }
     252              : 
     253              : // ---------------------------------------------------------------------------
     254              : // MSDevice_Taxi-methods
     255              : // ---------------------------------------------------------------------------
     256         6361 : MSDevice_Taxi::MSDevice_Taxi(SUMOVehicle& holder, const std::string& id) :
     257         6361 :     MSVehicleDevice(holder, id) {
     258         6361 :     std::string defaultServiceEnd = toString(1e15);
     259        12728 :     const std::string algo = holder.getStringParam("device.taxi.idle-algorithm");
     260         6361 :     if (algo == "stop") {
     261         6221 :         myIdleAlgorithm = new MSIdling_Stop();
     262          140 :     } else if (algo == "randomCircling") {
     263           70 :         myIdleAlgorithm = new MSIdling_RandomCircling();
     264              :         // make sure simulation terminates
     265           70 :         defaultServiceEnd = toString(STEPS2TIME(
     266              :                                          myHolder.getParameter().departProcedure == DepartDefinition::GIVEN
     267              :                                          ? myHolder.getParameter().depart
     268          140 :                                          : MSNet::getInstance()->getCurrentTimeStep()) + (3600 * 8));
     269           70 :     } else if (algo == "taxistand") {
     270          128 :         const std::string rerouterID = holder.getStringParam("device.taxi.stands-rerouter");
     271           64 :         if (rerouterID.empty()) {
     272            0 :             throw ProcessError("Idle algorithm '" + algo + "' requires a rerouter id to be defined using device param 'stands-rerouter' for vehicle '" + myHolder.getID() + "'");
     273              :         }
     274              :         if (MSTriggeredRerouter::getInstances().count(rerouterID) == 0) {
     275            0 :             throw ProcessError("Unknown rerouter '" + rerouterID + "' when loading taxi stands for vehicle '" + myHolder.getID() + "'");
     276              :         }
     277           64 :         MSTriggeredRerouter* rerouter = MSTriggeredRerouter::getInstances().find(rerouterID)->second;
     278           64 :         myIdleAlgorithm = new MSIdling_TaxiStand(rerouter);
     279              :     } else {
     280           12 :         throw ProcessError("Idle algorithm '" + algo + "' is not known for vehicle '" + myHolder.getID() + "'");
     281              :     }
     282         6361 :     myServiceEnd = string2time(holder.getStringParam("device.taxi.end", false, defaultServiceEnd));
     283         6355 :     myRoutingDevice = static_cast<MSDevice_Routing*>(myHolder.getDevice(typeid(MSDevice_Routing)));
     284         6361 : }
     285              : 
     286              : 
     287        12710 : MSDevice_Taxi::~MSDevice_Taxi() {
     288         6355 :     myFleet.erase(std::find(myFleet.begin(), myFleet.end(), this));
     289              :     // recompute myMaxCapacity
     290         6355 :     myMaxCapacity = 0;
     291         6355 :     myMaxContainerCapacity = 0;
     292        16408 :     for (MSDevice_Taxi* taxi : myFleet) {
     293        10053 :         myMaxCapacity = MAX2(myMaxCapacity, taxi->getHolder().getVehicleType().getPersonCapacity());
     294        10053 :         myMaxContainerCapacity = MAX2(myMaxContainerCapacity, taxi->getHolder().getVehicleType().getContainerCapacity());
     295              :     }
     296         6355 :     delete myIdleAlgorithm;
     297        12710 : }
     298              : 
     299              : 
     300              : SUMOVehicle*
     301         3437 : MSDevice_Taxi::getTaxi() {
     302         3437 :     if (myFleet.size() > 0) {
     303          107 :         return &myFleet[0]->getHolder();
     304              :     } else {
     305              :         return nullptr;
     306              :     }
     307              : }
     308              : 
     309              : 
     310              : void
     311         1011 : MSDevice_Taxi::dispatch(const Reservation& res) {
     312         1011 :     dispatchShared({&res, &res});
     313         1009 : }
     314              : 
     315              : 
     316              : void
     317         1307 : MSDevice_Taxi::dispatchShared(std::vector<const Reservation*> reservations) {
     318              : #ifdef DEBUG_DISPATCH
     319              :     if (DEBUG_COND) {
     320              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " dispatch:\n";
     321              :         for (const Reservation* res : reservations) {
     322              :             std::cout << "   persons=" << toString(res->persons) << "\n";
     323              :         }
     324              :     }
     325              : #endif
     326         1307 :     myLastDispatch = reservations;
     327              :     ConstMSEdgeVector tmpEdges;
     328              :     std::vector<SUMOVehicleParameter::Stop> stops;
     329         1307 :     double lastPos = myHolder.getPositionOnLane();
     330         1307 :     const MSEdge* rerouteOrigin = *myHolder.getRerouteOrigin();
     331         1307 :     if (isEmpty()) {
     332              :         // start fresh from the current edge
     333         1216 :         if (myHolder.isStoppedParking()) {
     334              :             // parking stop must be ended normally
     335          654 :             MSStop& stop = myHolder.getNextStop();
     336          654 :             stop.duration = 0;
     337          654 :             lastPos = stop.pars.endPos;
     338          654 :             if (myHolder.isStoppedTriggered()) {
     339          550 :                 stop.triggered = false;
     340          550 :                 stop.containerTriggered = false;
     341          550 :                 stop.joinTriggered = false;
     342          550 :                 const_cast<SUMOVehicleParameter::Stop&>(stop.pars).permitted.insert("");
     343          550 :                 myHolder.unregisterWaiting();
     344              :             }
     345          678 :             while (myHolder.getStops().size() > 1) {
     346           24 :                 myHolder.abortNextStop(1);
     347              :             }
     348              :         } else {
     349         1031 :             while (myHolder.hasStops()) {
     350              :                 // in meso there might be more than 1 stop at this point
     351          469 :                 myHolder.abortNextStop();
     352              :             }
     353              :             assert(!myHolder.hasStops());
     354              :         }
     355         1216 :         tmpEdges.push_back(myHolder.getEdge());
     356         1216 :         if (myHolder.getEdge() != rerouteOrigin) {
     357            8 :             tmpEdges.push_back(rerouteOrigin);
     358              :         }
     359              :     } else {
     360              :         assert(myHolder.hasStops());
     361              :         // check how often existing customers appear in the new reservations
     362              :         std::map<const MSTransportable*, int> nOccur;
     363          629 :         for (const Reservation* res : reservations) {
     364         1076 :             for (const MSTransportable* person : res->persons) {
     365              :                 if (myCustomers.count(person) != 0) {
     366          298 :                     nOccur[person] += 1;
     367              :                     if (myCurrentReservations.count(res) == 0) {
     368            0 :                         throw ProcessError(TLF("Invalid Re-dispatch for existing customer '%' with a new reservation", person->getID()));
     369              :                     }
     370              :                 }
     371              :             }
     372              :         }
     373              : #ifdef DEBUG_DISPATCH
     374              :         if (DEBUG_COND) {
     375              :             for (auto item : nOccur) {
     376              :                 std::cout << "   previousCustomer=" << item.first->getID() << " occurs=" << item.second << "\n";
     377              :             }
     378              :         }
     379              : #endif
     380           91 :         if (nOccur.size() == 0) {
     381              :             // no overlap with existing customers - extend route
     382           10 :             tmpEdges = myHolder.getRoute().getEdges();
     383           10 :             lastPos = myHolder.getStops().back().pars.endPos;
     384              : #ifdef DEBUG_DISPATCH
     385              :             if (DEBUG_COND) {
     386              :                 std::cout << " re-dispatch with route-extension\n";
     387              :             }
     388              : #endif
     389           81 :         } else if (nOccur.size() == myCustomers.size()) {
     390              :             // redefine route (verify correct number of mentions)
     391              :             std::set<const MSTransportable*> onBoard;
     392           81 :             const std::vector<MSTransportable*>& onBoardP = myHolder.getPersons();
     393           81 :             const std::vector<MSTransportable*>& onBoardC = myHolder.getContainers();
     394              :             onBoard.insert(onBoardP.begin(), onBoardP.end());
     395              :             onBoard.insert(onBoardC.begin(), onBoardC.end());
     396              :             std::set<const MSTransportable*> redundantPickup;
     397          244 :             for (auto item : nOccur) {
     398          167 :                 if (item.second == 1) {
     399              :                     // customers must already be on board
     400              :                     if (onBoard.count(item.first) == 0) {
     401           12 :                         throw ProcessError(TLF("Re-dispatch did not mention pickup for existing customer '%'", item.first->getID()));
     402              :                     }
     403          131 :                 } else if (item.second == 2) {
     404              :                     if (onBoard.count(item.first) == 0) {
     405              :                         // treat like a new customer
     406              :                         // TODO: need to be checked
     407              :                         myCustomers.erase(item.first);
     408              :                     } else {
     409              :                         redundantPickup.insert(item.first);
     410              :                     }
     411              :                 } else {
     412            0 :                     throw ProcessError("Re-dispatch mentions existing customer '" + item.first->getID() + "' " + toString(item.second) + " times");
     413              :                 }
     414              :             }
     415              :             // remove redundancy
     416           77 :             if (!redundantPickup.empty()) {
     417          183 :                 for (auto it = reservations.begin(); it != reservations.end();) {
     418              :                     bool isRedundant = false;
     419          262 :                     for (const MSTransportable* person : (*it)->persons) {
     420              :                         if (redundantPickup.count(person) != 0) {
     421              :                             isRedundant = true;
     422              :                             break;
     423              :                         }
     424              :                     }
     425          154 :                     if (isRedundant) {
     426           92 :                         for (const MSTransportable* person : (*it)->persons) {
     427              :                             redundantPickup.erase(person);
     428              :                         }
     429              :                         it = reservations.erase(it);
     430              :                     } else {
     431              :                         it++;
     432              :                     }
     433              :                 }
     434              :             }
     435          321 :             while (myHolder.hasStops()) {
     436          244 :                 myHolder.abortNextStop();
     437              :             }
     438           81 :             tmpEdges.push_back(myHolder.getEdge());
     439           77 :             if (myHolder.getEdge() != rerouteOrigin) {
     440           10 :                 tmpEdges.push_back(rerouteOrigin);
     441              :             }
     442              : #ifdef DEBUG_DISPATCH
     443              :             if (DEBUG_COND) {
     444              :                 std::cout << " re-dispatch from scratch\n";
     445              :             }
     446              : #endif
     447              :         } else {
     448              :             // inconsistent re-dispatch
     449              :             std::vector<std::string> missing;
     450            0 :             for (const MSTransportable* c : myCustomers) {
     451              :                 if (nOccur.count(c) == 0) {
     452            0 :                     missing.push_back(c->getID());
     453              :                 }
     454              :             }
     455            0 :             throw ProcessError("Re-dispatch did mention some customers but failed to mention " + joinToStringSorting(missing, " "));
     456            0 :         }
     457              :     }
     458              : 
     459         1303 :     const SUMOTime t = MSNet::getInstance()->getCurrentTimeStep();
     460              :     bool hasPickup = false;
     461         5241 :     for (const Reservation* res : reservations) {
     462              :         myCurrentReservations.insert(res);
     463              :         bool isPickup = false;
     464         8100 :         for (const MSTransportable* person : res->persons) {
     465              :             if (myCustomers.count(person) == 0) {
     466              :                 myCustomers.insert(person);
     467              :                 isPickup = true;
     468              :                 hasPickup = true;
     469              :             }
     470              :         }
     471         3938 :         if (isPickup) {
     472         5790 :             prepareStop(tmpEdges, stops, lastPos, res->from, res->fromPos, res->fromStop, "pickup " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     473         3972 :             for (const MSTransportable* const transportable : res->persons) {
     474         2042 :                 if (transportable->isPerson()) {
     475         1868 :                     stops.back().triggered = true;
     476              :                 } else {
     477          174 :                     stops.back().containerTriggered = true;
     478              :                 }
     479              :                 stops.back().permitted.insert(transportable->getID());
     480              :             }
     481              :             // proof this lines: Is needed for pre-booking?
     482              :             std::set<const MSTransportable*> persons = res->persons;
     483         3972 :             for (auto itr = persons.begin(); itr != persons.end(); itr++) {
     484         2042 :                 stops.back().awaitedPersons.insert((*itr)->getID());
     485              :             }
     486              : 
     487         1930 :             stops.back().parametersSet |= STOP_PERMITTED_SET;
     488         1930 :             if (stops.back().duration == -1) {
     489              :                 // keep dropOffDuration if the stop is dropOff and pickUp
     490         1580 :                 stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.pickUpDuration", false, 0));
     491              :             }
     492              :         } else {
     493         4016 :             prepareStop(tmpEdges, stops, lastPos, res->to, res->toPos, res->toStop, "dropOff " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
     494         2008 :             stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.dropOffDuration", false, 60)); // pay and collect bags
     495              :         }
     496              :     }
     497              : #ifdef DEBUG_DISPATCH
     498              :     if (DEBUG_COND) {
     499              :         std::cout << "   tmpEdges=" << toString(tmpEdges) << "\n";
     500              :     }
     501              : #endif
     502         2606 :     if (!myHolder.replaceRouteEdges(tmpEdges, -1, 0, "taxi:prepare_dispatch", false, false, false)) {
     503            0 :         throw ProcessError("Route replacement for taxi dispatch failed for vehicle '" + myHolder.getID()
     504            0 :                            + "' at time=" + time2string(t) + ".");
     505              :     }
     506              : #ifdef DEBUG_DISPATCH
     507              :     if (DEBUG_COND) std::cout << "   replacedRoute=" << toString(tmpEdges)
     508              :                                   << "\n     actualRoute=" << toString(myHolder.getRoute().getEdges()) << "\n";
     509              : #endif
     510         4392 :     for (SUMOVehicleParameter::Stop& stop : stops) {
     511              :         std::string error;
     512         3089 :         myHolder.addStop(stop, error);
     513         3089 :         if (error != "") {
     514            0 :             WRITE_WARNINGF(TL("Could not add taxi stop, time=%, error=%"), myHolder.getID(), stop.actType, time2string(t), error)
     515              :         }
     516              :     }
     517         1303 :     SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     518              :     // SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myHolder.getInfluencer().getRouterTT(veh->getRNGIndex())
     519         1303 :     myHolder.reroute(t, "taxi:dispatch", router, false);
     520              : #ifdef DEBUG_DISPATCH
     521              :     if (DEBUG_COND) {
     522              :         std::cout << "\n      finalRoute=" << toString(myHolder.getRoute().getEdges()) << " routeIndex=" << myHolder.getRoutePosition() << "\n";
     523              :     }
     524              : #endif
     525         1301 :     if (hasPickup) {
     526         1277 :         myState |= PICKUP;
     527              :     }
     528         1313 : }
     529              : 
     530              : 
     531              : void
     532           82 : MSDevice_Taxi::cancelCurrentCustomers() {
     533              :     // check if taxi has stopped
     534           82 :     if (myHolder.getNextStopParameter() == nullptr) {
     535            0 :         return;
     536              :     }
     537              :     // find customers of the current stop
     538              :     std::set<const MSTransportable*> customersToBeRemoved;
     539              :     std::set<const MSTransportable*> onBoard;
     540           82 :     onBoard.insert(myHolder.getPersons().begin(), myHolder.getPersons().end());
     541           82 :     onBoard.insert(myHolder.getContainers().begin(), myHolder.getContainers().end());
     542          136 :     for (std::string tID : myHolder.getNextStopParameter()->permitted) {
     543          220 :         for (auto t : myCustomers) {
     544          166 :             if (t->getID() == tID && onBoard.count(t) == 0) {
     545              :                 customersToBeRemoved.insert(t);
     546              :             }
     547              :         }
     548              :     }
     549           82 :     if (!customersToBeRemoved.empty()) {
     550           90 :         WRITE_WARNINGF(TL("Taxi '%' aborts waiting for customers: % at time=%."),
     551              :                        myHolder.getID(), toString(customersToBeRemoved), time2string(SIMSTEP));
     552              :     }
     553          116 :     for (auto t : customersToBeRemoved) {
     554           34 :         cancelCustomer(t);
     555              :     }
     556              : }
     557              : 
     558              : 
     559              : bool
     560           34 : MSDevice_Taxi::cancelCustomer(const MSTransportable* t) {
     561              : #ifdef DEBUG_CANCEL
     562              :     if (DEBUG_COND) {
     563              :         std::cout << SIMTIME << " taxi=" << myHolder.getID() << " cancelCustomer " << t->getID() << "\n";
     564              :     }
     565              : #endif
     566              : 
     567              :     // is the given transportable a customer of the reservations?
     568              :     if (myCustomers.count(t) == 0) {
     569              :         return false;
     570              :     }
     571              :     myCustomers.erase(t);
     572              :     // check whether a single reservation has been fulfilled or another customer is part of the reservation
     573          112 :     for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     574              :         bool fulfilled = false;
     575           78 :         if ((*resIt)->persons.size() == 1 && (*resIt)->persons.count(t) != 0) {
     576              :             // the reservation contains only the customer
     577              :             fulfilled = true;
     578              :         }
     579              :         if (fulfilled) {
     580              :             const Reservation* res = *resIt;
     581              :             // remove reservation from the current dispatch
     582          190 :             for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     583          156 :                 if (*it == res) {
     584           68 :                     it = myLastDispatch.erase(it);
     585              :                 } else {
     586              :                     ++it;
     587              :                 }
     588              :             }
     589              :             // remove reservation from the served reservations
     590              :             resIt = myCurrentReservations.erase(resIt);
     591              :             // delete the reservation
     592           34 :             myDispatcher->fulfilledReservation(res);
     593              :         } else {
     594              :             ++resIt;
     595              :         }
     596              :     }
     597           34 :     myState &= ~PICKUP;  // remove state PICKUP
     598           78 :     for (const Reservation* res : myCurrentReservations) {
     599              :         // if there is another pickup in the dispatch left, add the state PICKUP
     600           44 :         if (std::count(myLastDispatch.begin(), myLastDispatch.end(), res) == 2) {
     601           44 :             myState |= PICKUP;  // add state PICKUP
     602              :         }
     603              :     }
     604              :     // we also have to clean reservations from myLastDispatch where the customers arrived in the meantime
     605          122 :     for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
     606              :         if (myCurrentReservations.count(*it) == 0) {
     607            0 :             it = myLastDispatch.erase(it);
     608              :         } else {
     609              :             ++it;
     610              :         }
     611              :     }
     612              :     // if there are reservations left, go on with the dispatch
     613              :     // in meso, wait for the next dispatch cycle to avoid updating stops in this stage
     614           34 :     if (!MSGlobals::gUseMesoSim) {
     615           28 :         dispatchShared(myLastDispatch);
     616              :     }
     617              :     return true;
     618              : }
     619              : 
     620              : 
     621              : void
     622         3938 : MSDevice_Taxi::prepareStop(ConstMSEdgeVector& edges,
     623              :                            std::vector<SUMOVehicleParameter::Stop>& stops,
     624              :                            double& lastPos, const MSEdge* stopEdge, double stopPos,
     625              :                            const MSStoppingPlace* stopPlace,
     626              :                            const std::string& action, const Reservation* res, const bool isPickup) {
     627              :     assert(!edges.empty());
     628         3938 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     629          736 :         stopPos = stopPlace->getEndLanePosition();
     630              :     }
     631         3938 :     if (stopPos < lastPos && stopPos + NUMERICAL_EPS >= lastPos) {
     632              :         stopPos = lastPos;
     633              :     }
     634              :     bool addedEdge = false;
     635              : 
     636         3938 :     if (stops.empty()) {
     637              :         // check brakeGap
     638         1291 :         double distToStop = stopPos - lastPos;
     639         1291 :         const double brakeGap = myHolder.getBrakeGap();
     640         1291 :         if (myHolder.getLane() != nullptr && myHolder.getLane()->isInternal()) {
     641            6 :             distToStop += myHolder.getLane()->getLength();
     642              :         }
     643         1291 :         if (stopEdge != edges.back()) {
     644         1101 :             distToStop += edges.back()->getLength();
     645         1101 :             if (distToStop < brakeGap) {
     646              :                 // the distance between current edge and stop edge may be small
     647            5 :                 SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     648              :                 ConstMSEdgeVector toFirstStop;
     649            5 :                 router.compute(edges.back(), stopEdge, &myHolder, SIMSTEP, toFirstStop, true);
     650           25 :                 for (int i = 1; i < (int)toFirstStop.size() - 1; i++) {
     651           20 :                     distToStop += toFirstStop[i]->getLength();
     652              :                 }
     653            5 :             }
     654              :         }
     655         1291 :         if (distToStop < brakeGap) {
     656              :             // circle back to stopEdge
     657              :             //std::cout << SIMTIME << " taxi=" << getID() << " brakeGap=" << brakeGap << " distToStop=" << distToStop << "\n";
     658          140 :             edges.push_back(stopEdge);
     659              :             addedEdge = true;
     660              :         }
     661              :     }
     662              : 
     663         3938 :     if (stopEdge == edges.back() && !stops.empty()) {
     664         1007 :         if (stopPos >= lastPos && stopPos <= stops.back().endPos) {
     665              :             // no new stop and no adaption needed
     666          849 :             stops.back().actType += "," + action;
     667          849 :             return;
     668              :         }
     669          158 :         if (stopPos >= lastPos && stopPos <= lastPos + myHolder.getVehicleType().getLength()) {
     670              :             // stop length adaption needed
     671            0 :             stops.back().endPos = MIN2(lastPos + myHolder.getVehicleType().getLength(), stopEdge->getLength());
     672            0 :             stops.back().actType += "," + action;
     673            0 :             return;
     674              :         }
     675              :     }
     676         3089 :     if (!addedEdge && (stopEdge != edges.back() || stopPos < lastPos)) {
     677              :         //std::cout << SIMTIME << " stopPos=" << stopPos << " lastPos=" << lastPos << "\n";
     678         2758 :         edges.push_back(stopEdge);
     679              :     }
     680         3089 :     lastPos = stopPos;
     681         3089 :     SUMOVehicleParameter::Stop stop;
     682         3089 :     stop.lane = getStopLane(stopEdge, action)->getID();
     683         3089 :     if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
     684          504 :         stop.startPos = stopPlace->getBeginLanePosition();
     685          504 :         stop.endPos = stopPlace->getEndLanePosition();
     686              :     } else {
     687         2585 :         stop.startPos = stopPos;
     688         5170 :         stop.endPos = MAX2(stopPos, MIN2(myHolder.getVehicleType().getLength(), stopEdge->getLength()));
     689              :     }
     690         6178 :     stop.parking = SUMOVehicleParameter::parseParkingType(myHolder.getStringParam("device.taxi.parking", false, "true"));
     691              :     stop.actType = action;
     692         3089 :     stop.index = STOP_INDEX_END;
     693              :     // In case of prebooking if person is not there/ comes to late for pickup set maximum waiting time:
     694         3089 :     SUMOTime earliestPickupTime = res->earliestPickupTime;
     695         3089 :     if (isPickup && earliestPickupTime >= 0) {
     696          138 :         stop.waitUntil = earliestPickupTime;
     697              :         // TODO: replace hard coded extension with parameter
     698          138 :         stop.extension = static_cast<SUMOTime>(3 * 60 * 1000);  // 3mins
     699              :     }
     700         3089 :     stops.push_back(stop);
     701         3089 : }
     702              : 
     703              : 
     704              : MSLane*
     705         3089 : MSDevice_Taxi::getStopLane(const MSEdge* edge, const std::string& action) {
     706         3089 :     const std::vector<MSLane*>* allowedLanes = edge->allowedLanes(myHolder.getVClass());
     707         3089 :     if (allowedLanes == nullptr) {
     708            0 :         throw ProcessError("Taxi vehicle '" + myHolder.getID() + "' cannot stop on edge '" + edge->getID() + "' (" + action + ")");
     709              :     }
     710         3089 :     return allowedLanes->front();
     711              : }
     712              : 
     713              : bool
     714       267176 : MSDevice_Taxi::isEmpty() {
     715       267176 :     return myState == EMPTY;
     716              : }
     717              : 
     718              : 
     719              : bool
     720        23252 : MSDevice_Taxi::allowsBoarding(const MSTransportable* t) const {
     721        23252 :     return myCustomers.count(t) != 0;
     722              : }
     723              : 
     724              : 
     725              : void
     726       213926 : MSDevice_Taxi::updateMove(const SUMOTime traveltime, const double travelledDist) {
     727       213926 :     if (myHolder.getPersonNumber() > 0 || myHolder.getContainerNumber() > 0) {
     728        85046 :         myOccupiedDistance += travelledDist;
     729        85046 :         myOccupiedTime += traveltime;
     730              :     }
     731       213926 :     if (isEmpty()) {
     732        91243 :         if (MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     733        89803 :             myIdleAlgorithm->idle(this);
     734        89803 :             if (myRoutingDevice != nullptr) {
     735              :                 // prevent rerouting during idling (#11079)
     736              :                 myRoutingDevice->setActive(false);
     737              :             }
     738         1440 :         } else if (!myReachedServiceEnd) {
     739          192 :             WRITE_WARNINGF(TL("Taxi '%' reaches scheduled end of service at time=%."), myHolder.getID(), time2string(SIMSTEP));
     740           64 :             myReachedServiceEnd = true;
     741              :         }
     742       122683 :     } else if (myRoutingDevice != nullptr) {
     743              :         myRoutingDevice->setActive(true);
     744              :     }
     745       213926 :     if (myHolder.isStopped() && (isEmpty() || MSGlobals::gUseMesoSim) && myHolder.getNextStop().endBoarding > myServiceEnd) {
     746              :         // limit duration of stop (but only for idling-related stops)
     747         1893 :         myHolder.getNextStop().endBoarding = myServiceEnd;
     748              :     }
     749              : #ifdef DEBUG_DISPATCH
     750              :     if (DEBUG_COND && myIsStopped != myHolder.isStopped()) {
     751              :         std::cout << SIMTIME << " updateMove veh=" << myHolder.getID() << " myIsStopped=" << myIsStopped << " myHolderStopped=" << myHolder.isStopped() << " myState=" << myState << "\n";
     752              :     }
     753              : #endif
     754       213926 :     myIsStopped = myHolder.isStopped();
     755       213926 : }
     756              : 
     757              : 
     758              : bool
     759       203968 : MSDevice_Taxi::notifyMove(SUMOTrafficObject& /*tObject*/, double oldPos,
     760              :                           double newPos, double /*newSpeed*/) {
     761       203968 :     updateMove(DELTA_T, newPos - oldPos);
     762       203968 :     return true; // keep the device
     763              : }
     764              : 
     765              : 
     766              : void
     767         9958 : MSDevice_Taxi::notifyMoveInternal(const SUMOTrafficObject& /* veh */,
     768              :                                   const double /* frontOnLane */,
     769              :                                   const double timeOnLane,
     770              :                                   const double /* meanSpeedFrontOnLane */,
     771              :                                   const double /* meanSpeedVehicleOnLane */,
     772              :                                   const double travelledDistanceFrontOnLane,
     773              :                                   const double /* travelledDistanceVehicleOnLane */,
     774              :                                   const double /* meanLengthOnLane */) {
     775         9958 :     updateMove(TIME2STEPS(timeOnLane), travelledDistanceFrontOnLane);
     776         9958 : }
     777              : 
     778              : 
     779              : bool
     780        35353 : MSDevice_Taxi::notifyEnter(SUMOTrafficObject& /*veh*/, MSMoveReminder::Notification /*reason*/, const MSLane* /* enteredLane */) {
     781        35353 :     if (isEmpty() && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     782        16946 :         myIdleAlgorithm->idle(this);
     783              :     }
     784        35353 :     return true; // keep the device
     785              : }
     786              : 
     787              : 
     788              : void
     789         1881 : MSDevice_Taxi::customerEntered(const MSTransportable* t) {
     790         1881 :     myState |= OCCUPIED;
     791         1881 :     if (!hasFuturePickup()) {
     792         1469 :         myState &= ~PICKUP;
     793              :     }
     794        10546 :     for (const Reservation* res : myCurrentReservations) {
     795        15651 :         for (const MSTransportable* cand : res->persons) {
     796         8867 :             if (cand == t) {
     797         1881 :                 const_cast<Reservation*>(res)->state = Reservation::ONBOARD;
     798         1881 :                 break;
     799              :             }
     800              :         }
     801              :     }
     802         1881 : }
     803              : 
     804              : 
     805              : void
     806         1881 : MSDevice_Taxi::customerArrived(const MSTransportable* person) {
     807         1881 :     myCustomersServed++;
     808              :     myCustomers.erase(person);
     809         1881 :     if (myHolder.getPersonNumber() == 0 && myHolder.getContainerNumber() == 0) {
     810         1200 :         myState &= ~OCCUPIED;
     811         1200 :         if (myHolder.getStops().size() > 1 && (myState & PICKUP) == 0) {
     812            0 :             WRITE_WARNINGF(TL("All customers left vehicle '%' at time=% but there are % remaining stops"),
     813              :                            myHolder.getID(), time2string(SIMSTEP), myHolder.getStops().size() - 1);
     814            0 :             while (myHolder.getStops().size() > 1) {
     815            0 :                 myHolder.abortNextStop(1);
     816              :             }
     817              :         }
     818              :     }
     819         1881 :     if (isEmpty()) {
     820              :         // cleanup
     821         2344 :         for (const Reservation* res : myCurrentReservations) {
     822         1178 :             myDispatcher->fulfilledReservation(res);
     823              :         }
     824              :         myCurrentReservations.clear();
     825         1166 :         if (MSGlobals::gUseMesoSim && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
     826          362 :             myIdleAlgorithm->idle(this);
     827              :         }
     828              :     } else {
     829              :         // check whether a single reservation has been fulfilled
     830         4878 :         for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
     831              :             bool fulfilled = true;
     832         4958 :             for (const MSTransportable* t : (*resIt)->persons) {
     833              :                 if (myCustomers.count(t) != 0) {
     834              :                     fulfilled = false;
     835              :                     break;
     836              :                 }
     837              :             }
     838         4163 :             if (fulfilled) {
     839          603 :                 myDispatcher->fulfilledReservation(*resIt);
     840              :                 resIt = myCurrentReservations.erase(resIt);
     841              :             } else {
     842              :                 ++resIt;
     843              :             }
     844              :         }
     845              :     }
     846         1881 : }
     847              : 
     848              : 
     849              : bool
     850         1881 : MSDevice_Taxi::hasFuturePickup() {
     851         5377 :     for (const auto& stop : myHolder.getStops()) {
     852         3908 :         if (stop.reached) {
     853         1883 :             continue;
     854              :         }
     855         2025 :         if (stop.pars.permitted.size() > 0) {
     856              :             return true;
     857              :         }
     858              :     }
     859              :     return false;
     860              : }
     861              : 
     862              : void
     863          741 : MSDevice_Taxi::generateOutput(OutputDevice* tripinfoOut) const {
     864          741 :     if (tripinfoOut != nullptr) {
     865          741 :         tripinfoOut->openTag("taxi");
     866         1482 :         tripinfoOut->writeAttr("customers", toString(myCustomersServed));
     867         1482 :         tripinfoOut->writeAttr("occupiedDistance", toString(myOccupiedDistance));
     868         1482 :         tripinfoOut->writeAttr("occupiedTime", time2string(myOccupiedTime));
     869         1482 :         tripinfoOut->closeTag();
     870              :     }
     871          741 : }
     872              : 
     873              : std::string
     874         1348 : MSDevice_Taxi::getParameter(const std::string& key) const {
     875         1348 :     if (key == "customers") {
     876            0 :         return toString(myCustomersServed);
     877         1348 :     } else if (key == "occupiedDistance") {
     878            0 :         return toString(myOccupiedDistance);
     879         1348 :     } else if (key == "occupiedTime") {
     880            0 :         return toString(STEPS2TIME(myOccupiedTime));
     881         1348 :     } else if (key == "state") {
     882         1290 :         return toString(myState);
     883           58 :     } else if (key == "currentCustomers") {
     884           14 :         return joinNamedToStringSorting(myCustomers, " ");
     885           44 :     } else if (key == "pickUpDuration") {
     886           44 :         return myHolder.getStringParam("device.taxi.pickUpDuration", false, "0");
     887           22 :     } else if (key == "dropOffDuration") {
     888           44 :         return myHolder.getStringParam("device.taxi.dropOffDuration", false, "60");
     889              :     }
     890            0 :     throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     891              : }
     892              : 
     893              : 
     894              : void
     895           10 : MSDevice_Taxi::setParameter(const std::string& key, const std::string& value) {
     896              :     double doubleValue;
     897              :     try {
     898           10 :         doubleValue = StringUtils::toDouble(value);
     899            0 :     } catch (NumberFormatException&) {
     900            0 :         throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
     901            0 :     }
     902           10 :     if (key == "pickUpDuration" || key == "dropOffDuration") {
     903              :         // store as generic vehicle parameters
     904           10 :         ((SUMOVehicleParameter&)myHolder.getParameter()).setParameter("device.taxi." + key, value);
     905              :     } else {
     906              :         UNUSED_PARAMETER(doubleValue);
     907            0 :         throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
     908              :     }
     909           10 : }
     910              : 
     911              : bool
     912       131139 : MSDevice_Taxi::compatibleLine(const std::string& taxiLine, const std::string& rideLine) {
     913       208302 :     return ((taxiLine == rideLine && StringUtils::startsWith(rideLine, "taxi") && StringUtils::startsWith(taxiLine, "taxi"))
     914       185704 :             || (taxiLine == TAXI_SERVICE && StringUtils::startsWith(rideLine, "taxi:"))
     915       395222 :             || (rideLine == TAXI_SERVICE && StringUtils::startsWith(taxiLine, "taxi:")));
     916              : }
     917              : 
     918              : bool
     919         2440 : MSDevice_Taxi::compatibleLine(const Reservation* res) {
     920         2440 :     return compatibleLine(myHolder.getParameter().line, res->line);
     921              : }
     922              : 
     923              : 
     924              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1