LCOV - code coverage report
Current view: top level - src/microsim - MSInsertionControl.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 90.3 % 216 195
Test Date: 2026-07-26 16:30:20 Functions: 96.0 % 25 24

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-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    MSInsertionControl.cpp
      15              : /// @author  Christian Roessel
      16              : /// @author  Daniel Krajzewicz
      17              : /// @author  Axel Wegener
      18              : /// @author  Michael Behrisch
      19              : /// @author  Jakob Erdmann
      20              : /// @author  Mirko Barthauer
      21              : /// @date    Mon, 12 Mar 2001
      22              : ///
      23              : // Inserts vehicles into the network when their departure time is reached
      24              : /****************************************************************************/
      25              : #include <config.h>
      26              : 
      27              : #include <iostream>
      28              : #include <algorithm>
      29              : #include <cassert>
      30              : #include <iterator>
      31              : #include <utils/router/IntermodalRouter.h>
      32              : #include <microsim/devices/MSDevice_Routing.h>
      33              : #include <microsim/devices/MSRoutingEngine.h>
      34              : #include "MSGlobals.h"
      35              : #include "MSVehicle.h"
      36              : #include "MSVehicleControl.h"
      37              : #include "MSLane.h"
      38              : #include "MSEdge.h"
      39              : #include "MSNet.h"
      40              : #include "MSRouteHandler.h"
      41              : #include "MSInsertionControl.h"
      42              : 
      43              : 
      44              : // ===========================================================================
      45              : // member method definitions
      46              : // ===========================================================================
      47        42780 : MSInsertionControl::MSInsertionControl(MSVehicleControl& vc,
      48              :                                        SUMOTime maxDepartDelay,
      49              :                                        bool eagerInsertionCheck,
      50              :                                        int maxVehicleNumber,
      51        42780 :                                        SUMOTime randomDepartOffset) :
      52        42780 :     myVehicleControl(vc),
      53        42780 :     myMaxDepartDelay(maxDepartDelay),
      54        42780 :     myEagerInsertionCheck(eagerInsertionCheck),
      55        42780 :     myMaxVehicleNumber(maxVehicleNumber),
      56        42780 :     myPendingEmitsUpdateTime(SUMOTime_MIN),
      57        85560 :     myFlowRNG("flow") {
      58        42780 :     myMaxRandomDepartOffset = randomDepartOffset;
      59        42780 :     RandHelper::initRandGlobal(&myFlowRNG);
      60        42780 : }
      61              : 
      62              : 
      63        42167 : MSInsertionControl::~MSInsertionControl() {
      64        44985 :     for (const Flow& f : myFlows) {
      65         2818 :         delete (f.pars);
      66              :     }
      67        84334 : }
      68              : 
      69              : 
      70              : void
      71      5264812 : MSInsertionControl::add(SUMOVehicle* veh) {
      72      5264812 :     myAllVeh.add(veh);
      73      5264812 : }
      74              : 
      75              : 
      76              : bool
      77        19615 : MSInsertionControl::addFlow(SUMOVehicleParameter* const pars, int index) {
      78        19615 :     if (myFlowIDs.count(pars->id) > 0) {
      79           39 :         return false;
      80              :     }
      81              :     const bool loadingFromState = index >= 0;
      82        19576 :     Flow flow{pars, loadingFromState ? index : 0, initScale(pars->vtypeid)};
      83        19576 :     if (!loadingFromState && pars->repetitionProbability < 0 && pars->repetitionOffset < 0) {
      84              :         // init poisson flow (but only the timing)
      85          753 :         flow.pars->incrementFlow(flow.scale, &myFlowRNG);
      86          753 :         flow.pars->repetitionsDone--;
      87              :     }
      88        19576 :     myFlows.emplace_back(flow);
      89        19576 :     myFlowIDs.insert(std::make_pair(pars->id, flow.index));
      90        19576 :     return true;
      91              : }
      92              : 
      93              : 
      94              : double
      95        19576 : MSInsertionControl::initScale(const std::string vtypeid) {
      96        19576 :     MSVehicleControl& vc = MSNet::getInstance()->getVehicleControl();
      97        19576 :     if (vc.hasVTypeDistribution(vtypeid)) {
      98              :         double result = -1;
      99          846 :         const RandomDistributor<MSVehicleType*>* dist = vc.getVTypeDistribution(vtypeid);
     100         2672 :         for (const MSVehicleType* t : dist->getVals()) {
     101         1826 :             if (result == -1) {
     102          846 :                 result = t->getParameter().scale;
     103          980 :             } else if (result != t->getParameter().scale) {
     104              :                 // unequal scales in distribution
     105              :                 return -1;
     106              :             }
     107              :         }
     108          846 :         return result;
     109              :     } else {
     110              :         // rng is not used since vtypeid is not a distribution
     111        18730 :         return vc.getVType(vtypeid, nullptr, true)->getParameter().scale;
     112              :     }
     113              : }
     114              : 
     115              : 
     116              : void
     117            5 : MSInsertionControl::updateScale(const std::string vtypeid) {
     118            5 :     for (Flow& f : myFlows) {
     119            0 :         if (f.pars->vtypeid == vtypeid) {
     120            0 :             f.scale = initScale(vtypeid);
     121              :         }
     122              :     }
     123            5 : }
     124              : 
     125              : 
     126              : int
     127     64200590 : MSInsertionControl::emitVehicles(SUMOTime time) {
     128              :     // check whether any vehicles shall be emitted within this time step
     129              :     const bool havePreChecked = MSRoutingEngine::isEnabled();
     130     64200590 :     if (myPendingEmits.empty() || (havePreChecked && myEmitCandidates.empty())) {
     131              :         return 0;
     132              :     }
     133              :     int numEmitted = 0;
     134              :     // we use buffering for the refused emits to save time
     135              :     //  for this, we have two lists; one contains previously refused emits, the second
     136              :     //  will be used to append those vehicles that will not be able to depart in this
     137              :     //  time step
     138              :     MSVehicleContainer::VehicleVector refusedEmits;
     139              : 
     140              :     // go through the list of previously refused vehicles, first
     141     19216752 :     MSNet::getInstance()->lockPendingEmits();
     142              :     MSVehicleContainer::VehicleVector::const_iterator veh;
     143   2152455134 :     for (veh = myPendingEmits.begin(); veh != myPendingEmits.end(); veh++) {
     144   2133238528 :         if (havePreChecked && (myEmitCandidates.count(*veh) == 0)) {
     145      4108640 :             refusedEmits.push_back(*veh);
     146              :         } else {
     147   2129129888 :             numEmitted += tryInsert(time, *veh, refusedEmits);
     148              :         }
     149              :     }
     150              :     myEmitCandidates.clear();
     151     19216606 :     myPendingEmits = refusedEmits;
     152     19216606 :     MSNet::getInstance()->unlockPendingEmits();
     153              :     return numEmitted;
     154     19216752 : }
     155              : 
     156              : 
     157              : int
     158   2129129888 : MSInsertionControl::tryInsert(SUMOTime time, SUMOVehicle* veh,
     159              :                               MSVehicleContainer::VehicleVector& refusedEmits) {
     160              :     assert(veh->getParameter().depart <= time);
     161   2129129888 :     const MSEdge& edge = *veh->getEdge();
     162   2129129888 :     if (veh->isOnRoad()) {
     163              :         return 1;
     164              :     }
     165       540408 :     if ((myMaxVehicleNumber < 0 || (int)MSNet::getInstance()->getVehicleControl().getRunningVehicleNo() < myMaxVehicleNumber)
     166   2129140871 :             && edge.insertVehicle(*veh, time, false, myEagerInsertionCheck || veh->getParameter().departProcedure == DepartDefinition::SPLIT)) {
     167              :         // Successful insertion
     168              :         return 1;
     169              :     }
     170   2124868849 :     if (myMaxDepartDelay >= 0 && time - veh->getParameter().depart > myMaxDepartDelay) {
     171              :         // remove vehicles waiting too long for departure
     172       825600 :         myVehicleControl.deleteVehicle(veh, true);
     173   2124043249 :     } else if (edge.isVaporizing()) {
     174              :         // remove vehicles if the edge shall be empty
     175        12818 :         myVehicleControl.deleteVehicle(veh, true);
     176   2124030431 :     } else if (myAbortedEmits.count(veh) > 0) {
     177              :         // remove vehicles which shall not be inserted for some reason
     178          458 :         myAbortedEmits.erase(veh);
     179          458 :         myVehicleControl.deleteVehicle(veh, true);
     180   2124029973 :     } else if ((veh->getRouteValidity(false) & (
     181              :                     MSBaseVehicle::ROUTE_START_INVALID_LANE
     182              :                     | MSBaseVehicle::ROUTE_START_INVALID_PERMISSIONS)) != 0) {
     183           46 :         myVehicleControl.deleteVehicle(veh, true);
     184              :     } else {
     185              :         // let the vehicle wait one step, we'll retry then
     186   2124029927 :         refusedEmits.push_back(veh);
     187              :     }
     188              :     edge.setLastFailedInsertionTime(time);
     189   2124868849 :     return 0;
     190              : }
     191              : 
     192              : 
     193              : void
     194     64200747 : MSInsertionControl::checkCandidates(SUMOTime time, const bool preCheck) {
     195     67988469 :     while (myAllVeh.anyWaitingBefore(time)) {
     196      3787722 :         const MSVehicleContainer::VehicleVector& top = myAllVeh.top();
     197      3787722 :         copy(top.begin(), top.end(), back_inserter(myPendingEmits));
     198      3787722 :         myAllVeh.pop();
     199              :     }
     200     64200747 :     if (preCheck) {
     201              :         MSVehicleContainer::VehicleVector::const_iterator veh;
     202    722884423 :         for (veh = myPendingEmits.begin(); veh != myPendingEmits.end(); veh++) {
     203    709517747 :             SUMOVehicle* const v = *veh;
     204    709517747 :             const MSEdge* const edge = v->getEdge();
     205    709517747 :             if (edge->insertVehicle(*v, time, true, myEagerInsertionCheck)) {
     206              :                 myEmitCandidates.insert(v);
     207              :             } else {
     208    103182636 :                 MSDevice_Routing* dev = static_cast<MSDevice_Routing*>(v->getDevice(typeid(MSDevice_Routing)));
     209              :                 if (dev != nullptr) {
     210              :                     dev->skipRouting(time);
     211              :                 }
     212              :             }
     213              :         }
     214              :     }
     215     64200709 : }
     216              : 
     217              : 
     218              : void
     219     64200756 : MSInsertionControl::determineCandidates(SUMOTime time) {
     220     64200756 :     MSVehicleControl& vehControl = MSNet::getInstance()->getVehicleControl();
     221              :     // for equidistant vehicles, up-scaling is done via repetitionOffset
     222     98364701 :     for (std::vector<Flow>::iterator i = myFlows.begin(); i != myFlows.end();) {
     223              :         MSVehicleType* vtype = nullptr;
     224     34163954 :         SUMOVehicleParameter* const pars = i->pars;
     225     34163954 :         double typeScale = i->scale;
     226     34163954 :         if (typeScale < 0) {
     227              :             // must sample from distribution to determine scale value
     228            0 :             vtype = vehControl.getVType(pars->vtypeid, MSRouteHandler::getParsingRNG());
     229            0 :             typeScale = vtype->getParameter().scale;
     230              :         }
     231     34163954 :         const double scale = vehControl.getScale() * typeScale;
     232     34163954 :         const long long int scaledRepetitions = pars->repetitionNumber == std::numeric_limits<long long int>::max() ? std::numeric_limits<long long int>::max() :
     233     22653942 :                                                 (long long int)((double)pars->repetitionNumber * scale + 0.5);
     234     34163954 :         bool tryEmitByProb = pars->repetitionProbability > 0;
     235     39059308 :         while (scale > 0 && ((pars->repetitionProbability < 0
     236     29402657 :                               && pars->repetitionsDone < scaledRepetitions
     237     29390266 :                               && pars->depart + pars->repetitionTotalOffset <= time)
     238     34722605 :                              || (tryEmitByProb
     239      9097991 :                                  && pars->depart <= time
     240      9078071 :                                  && pars->repetitionEnd > time
     241              :                                  // only call rand if all other conditions are met
     242      9074354 :                                  && RandHelper::rand(&myFlowRNG) < (pars->repetitionProbability * TS))
     243              :                             )) {
     244              :             tryEmitByProb = false; // only emit one per step
     245      4895363 :             SUMOVehicleParameter* const newPars = new SUMOVehicleParameter(*pars);
     246      9790726 :             newPars->id = pars->id + "." + toString(i->index);
     247      4895363 :             newPars->depart = pars->repetitionProbability > 0 ? time : pars->depart + pars->repetitionTotalOffset + computeRandomDepartOffset();
     248      4895363 :             pars->incrementFlow(scale, &myFlowRNG);
     249      4895363 :             myFlowIDs[pars->id] = i->index;
     250              :             //std::cout << SIMTIME << " flow=" << pars->id << " done=" << pars->repetitionsDone << " totalOffset=" << STEPS2TIME(pars->repetitionTotalOffset) << "\n";
     251              :             // try to build the vehicle
     252      4895363 :             if (vehControl.getVehicle(newPars->id) == nullptr) {
     253      4895363 :                 ConstMSRoutePtr const route = MSRoute::dictionary(pars->routeid);
     254      4895363 :                 if (vtype == nullptr) {
     255      4895363 :                     vtype = vehControl.getVType(pars->vtypeid, MSRouteHandler::getParsingRNG());
     256              :                 }
     257      4895363 :                 SUMOVehicle* const vehicle = vehControl.buildVehicle(newPars, route, vtype, !MSGlobals::gCheckRoutes);
     258              :                 // for equidistant vehicles, all scaling is done via repetitionOffset (to avoid artefacts, #11441)
     259              :                 // for probabilistic vehicles, we use the quota
     260      4895354 :                 int quota = pars->repetitionProbability < 0 ? 1 : vehControl.getQuota(scale);
     261       558660 :                 if (quota > 0) {
     262      4871588 :                     vehControl.addVehicle(newPars->id, vehicle);
     263      4871588 :                     if (pars->departProcedure == DepartDefinition::GIVEN || pars->departProcedure == DepartDefinition::BEGIN) {
     264      4871564 :                         add(vehicle);
     265              :                     }
     266      4871588 :                     i->index++;
     267      4928167 :                     while (--quota > 0) {
     268        56579 :                         SUMOVehicleParameter* const quotaPars = new SUMOVehicleParameter(*pars);
     269       113158 :                         quotaPars->id = pars->id + "." + toString(i->index);
     270        56579 :                         quotaPars->depart = pars->repetitionProbability > 0 ? time :
     271            0 :                                             pars->depart + pars->repetitionsDone * pars->repetitionTotalOffset + computeRandomDepartOffset();
     272        56588 :                         SUMOVehicle* const quotaVehicle = vehControl.buildVehicle(quotaPars, route, vtype, !MSGlobals::gCheckRoutes);
     273        56579 :                         vehControl.addVehicle(quotaPars->id, quotaVehicle);
     274        56579 :                         if (pars->departProcedure == DepartDefinition::GIVEN || pars->departProcedure == DepartDefinition::BEGIN) {
     275        56579 :                             add(quotaVehicle);
     276              :                         }
     277        56579 :                         pars->repetitionsDone++;
     278        56579 :                         i->index++;
     279              :                     }
     280              :                 } else {
     281        23766 :                     vehControl.deleteVehicle(vehicle, true);
     282              :                 }
     283              :             } else {
     284            0 :                 if (MSGlobals::gStateLoaded) {
     285              :                     /// @note probably obsolete since flows save their state
     286              :                     break;
     287              :                 }
     288            0 :                 throw ProcessError(TLF("Another vehicle with the id '%' exists.", newPars->id));
     289              :             }
     290              :             vtype = nullptr;
     291              :         }
     292     34163945 :         if (time >= pars->repetitionEnd || pars->repetitionsDone >= scaledRepetitions) {
     293        16737 :             i = myFlows.erase(i);
     294        16737 :             MSRoute::checkDist(pars->routeid);
     295        16737 :             delete pars;
     296              :         } else {
     297              :             ++i;
     298              :         }
     299              :     }
     300     64200747 :     checkCandidates(time, MSRoutingEngine::isEnabled());
     301     64200709 : }
     302              : 
     303              : 
     304              : int
     305     13657603 : MSInsertionControl::getWaitingVehicleNo() const {
     306     13657603 :     return (int)myPendingEmits.size();
     307              : }
     308              : 
     309              : 
     310              : int
     311     14745065 : MSInsertionControl::getPendingFlowCount() const {
     312     14745065 :     return (int)myFlows.size();
     313              : }
     314              : 
     315              : 
     316              : void
     317          471 : MSInsertionControl::descheduleDeparture(const SUMOVehicle* veh) {
     318          471 :     myAbortedEmits.insert(veh);
     319          471 : }
     320              : 
     321              : void
     322        15073 : MSInsertionControl::retractDescheduleDeparture(const SUMOVehicle* veh) {
     323        15073 :     myAbortedEmits.erase(veh);
     324        15073 : }
     325              : 
     326              : 
     327              : void
     328         2931 : MSInsertionControl::alreadyDeparted(SUMOVehicle* veh) {
     329         2931 :     myPendingEmits.erase(std::remove(myPendingEmits.begin(), myPendingEmits.end(), veh), myPendingEmits.end());
     330         2931 :     myAllVeh.remove(veh);
     331         2931 : }
     332              : 
     333              : 
     334              : void
     335            6 : MSInsertionControl::clearPendingVehicles(const std::string& route) {
     336              :     //clear out the refused vehicle list, deleting the vehicles entirely
     337              :     MSVehicleContainer::VehicleVector::iterator veh;
     338           11 :     for (veh = myPendingEmits.begin(); veh != myPendingEmits.end();) {
     339            5 :         if ((*veh)->getRoute().getID() == route || route == "") {
     340            5 :             myVehicleControl.deleteVehicle(*veh, true);
     341            5 :             veh = myPendingEmits.erase(veh);
     342              :         } else {
     343              :             ++veh;
     344              :         }
     345              :     }
     346            6 : }
     347              : 
     348              : 
     349              : int
     350            0 : MSInsertionControl::getPendingEmits(const MSLane* lane) {
     351            0 :     const MSNet* net = MSNet::getInstance();
     352            0 :     if (net->getCurrentTimeStep() != myPendingEmitsUpdateTime) {
     353            0 :         net->lockPendingEmits();
     354              :         // updated pending emits (only once per time step)
     355              :         myPendingEmitsForLane.clear();
     356            0 :         for (const SUMOVehicle* const veh : myPendingEmits) {
     357            0 :             const MSLane* const vlane = veh->getLane();
     358            0 :             if (vlane != nullptr) {
     359            0 :                 myPendingEmitsForLane[vlane]++;
     360              :             } else {
     361              :                 // no (tentative) departLane was set, increase count for all
     362              :                 // lanes of the depart edge
     363            0 :                 for (const MSLane* const l : veh->getEdge()->getLanes()) {
     364            0 :                     myPendingEmitsForLane[l]++;
     365              :                 }
     366              :             }
     367              :         }
     368            0 :         myPendingEmitsUpdateTime = MSNet::getInstance()->getCurrentTimeStep();
     369            0 :         net->unlockPendingEmits();
     370              :     }
     371            0 :     return myPendingEmitsForLane[lane];
     372              : }
     373              : 
     374              : 
     375              : void
     376         4940 : MSInsertionControl::adaptIntermodalRouter(MSTransportableRouter& router) const {
     377              :     // fill the public transport router with pre-parsed public transport lines
     378         7040 :     for (const Flow& f : myFlows) {
     379         2100 :         if (f.pars->line != "") {
     380          556 :             ConstMSRoutePtr const route = MSRoute::dictionary(f.pars->routeid);
     381          556 :             router.getNetwork()->addSchedule(*f.pars, route == nullptr ? nullptr : &route->getStops());
     382              :         }
     383              :     }
     384         4940 : }
     385              : 
     386              : 
     387              : void
     388          536 : MSInsertionControl::saveState(OutputDevice& out) {
     389              :     // save flow states
     390          630 :     for (const Flow& flow : myFlows) {
     391           94 :         flow.pars->write(out, OptionsCont::getOptions(), SUMO_TAG_FLOWSTATE,
     392           94 :                          flow.pars->vtypeid == DEFAULT_VTYPE_ID ? "" : flow.pars->vtypeid);
     393           94 :         if (flow.pars->repetitionProbability <= 0) {
     394           80 :             out.writeAttr(SUMO_ATTR_NEXT, STEPS2TIME(flow.pars->repetitionTotalOffset));
     395              :         }
     396           94 :         out.writeAttr(SUMO_ATTR_ROUTE, flow.pars->routeid);
     397           94 :         out.writeAttr(SUMO_ATTR_DONE, flow.pars->repetitionsDone);
     398           94 :         out.writeAttr(SUMO_ATTR_INDEX, flow.index);
     399           94 :         if (flow.pars->wasSet(VEHPARS_FORCE_REROUTE)) {
     400           30 :             out.writeAttr(SUMO_ATTR_REROUTE, true);
     401              :         }
     402          108 :         for (const SUMOVehicleParameter::Stop& stop : flow.pars->stops) {
     403           14 :             stop.write(out);
     404              :         }
     405          188 :         out.closeTag();
     406              :     }
     407          536 : }
     408              : 
     409              : 
     410              : void
     411          164 : MSInsertionControl::clearState() {
     412          164 :     for (const Flow& f : myFlows) {
     413            0 :         delete (f.pars);
     414              :     }
     415              :     myFlows.clear();
     416              :     myFlowIDs.clear();
     417          164 :     myAllVeh.clearState();
     418              :     myPendingEmits.clear();
     419              :     myEmitCandidates.clear();
     420          164 :     myAbortedEmits.clear();
     421              :     // myPendingEmitsForLane must not be cleared since it updates itself on the next call
     422          164 : }
     423              : 
     424              : 
     425              : SUMOTime
     426      5177351 : MSInsertionControl::computeRandomDepartOffset() const {
     427      5177351 :     if (myMaxRandomDepartOffset > 0) {
     428              :         // round to the closest usable simulation step
     429           90 :         return DELTA_T * ((RandHelper::rand(myMaxRandomDepartOffset, MSRouteHandler::getParsingRNG()) + DELTA_T / 2) / DELTA_T);
     430              :     }
     431              :     return 0;
     432              : }
     433              : 
     434              : const SUMOVehicleParameter*
     435           40 : MSInsertionControl::getFlowPars(const std::string& id) const {
     436              :     if (hasFlow(id)) {
     437           40 :         for (const Flow& f : myFlows) {
     438           40 :             if (f.pars->id == id) {
     439              :                 return f.pars;
     440              :             }
     441              :         }
     442              :     }
     443              :     return nullptr;
     444              : }
     445              : 
     446              : SUMOVehicle*
     447          808 : MSInsertionControl::getLastFlowVehicle(const std::string& id) const {
     448              :     const auto it = myFlowIDs.find(id);
     449          808 :     if (it != myFlowIDs.end()) {
     450         1616 :         const std::string vehID = id + "." + toString(it->second);
     451          808 :         return MSNet::getInstance()->getVehicleControl().getVehicle(vehID);
     452              :     }
     453              :     return nullptr;
     454              : }
     455              : 
     456              : 
     457              : bool
     458        10578 : MSInsertionControl::hasTaxiFlow() const {
     459        21156 :     SumoRNG tmp("tmp");
     460        18370 :     for (const Flow& flow : myFlows) {
     461        15664 :         if (flow.scale != 0 &&
     462        39160 :                 (StringUtils::toBool(flow.pars->getParameter("has.taxi.device", "false"))
     463         7832 :                  || hasTaxiDeviceType(flow.pars->vtypeid, tmp))) {
     464              :             return true;
     465              :         }
     466              :     }
     467              :     return false;
     468              : }
     469              : 
     470              : 
     471              : bool
     472         7832 : MSInsertionControl::hasTaxiDeviceType(const std::string& vtypeId, SumoRNG& rng) {
     473         7832 :     MSVehicleControl& vehControl = MSNet::getInstance()->getVehicleControl();
     474         7832 :     const MSVehicleType* vtype = vehControl.getVType(vtypeId, &rng);
     475        15664 :     return StringUtils::toBool(vtype->getParameter().getParameter("has.taxi.device", "false"));
     476              : }
     477              : 
     478              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1