Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2013-2025 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 39784 : MSDevice_Taxi::insertOptions(OptionsCont& oc) {
82 39784 : oc.addOptionSubTopic("Taxi Device");
83 79568 : insertDefaultAssignmentOptions("taxi", "Taxi Device", oc);
84 :
85 79568 : oc.doRegister("device.taxi.dispatch-algorithm", new Option_String("greedy"));
86 79568 : oc.addDescription("device.taxi.dispatch-algorithm", "Taxi Device", TL("The dispatch algorithm [greedy|greedyClosest|greedyShared|routeExtension|traci]"));
87 :
88 39784 : oc.doRegister("device.taxi.dispatch-algorithm.output", new Option_FileName());
89 79568 : oc.addDescription("device.taxi.dispatch-algorithm.output", "Taxi Device", TL("Write information from the dispatch algorithm to FILE"));
90 :
91 79568 : oc.doRegister("device.taxi.dispatch-algorithm.params", new Option_String(""));
92 79568 : oc.addDescription("device.taxi.dispatch-algorithm.params", "Taxi Device", TL("Load dispatch algorithm parameters in format KEY1:VALUE1[,KEY2:VALUE]"));
93 :
94 79568 : oc.doRegister("device.taxi.dispatch-period", new Option_String("60", "TIME"));
95 79568 : oc.addDescription("device.taxi.dispatch-period", "Taxi Device", TL("The period between successive calls to the dispatcher"));
96 :
97 79568 : oc.doRegister("device.taxi.dispatch-keep-unreachable", new Option_String("3600", "TIME"));
98 79568 : oc.addDescription("device.taxi.dispatch-keep-unreachable", "Taxi Device", TL("The time before aborting unreachable reservations"));
99 :
100 79568 : oc.doRegister("device.taxi.idle-algorithm", new Option_String("stop"));
101 79568 : oc.addDescription("device.taxi.idle-algorithm", "Taxi Device", TL("The behavior of idle taxis [stop|randomCircling|taxistand]"));
102 :
103 39784 : oc.doRegister("device.taxi.idle-algorithm.output", new Option_FileName());
104 79568 : oc.addDescription("device.taxi.idle-algorithm.output", "Taxi Device", TL("Write information from the idling algorithm to FILE"));
105 39784 : }
106 :
107 :
108 : void
109 5371069 : MSDevice_Taxi::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
110 5371069 : OptionsCont& oc = OptionsCont::getOptions();
111 10742138 : if (equippedByDefaultAssignmentOptions(oc, "taxi", v, false)) {
112 : // build the device
113 6572 : MSDevice_Taxi* device = new MSDevice_Taxi(v, "taxi_" + v.getID());
114 6560 : into.push_back(device);
115 6560 : myFleet.push_back(device);
116 6560 : if (v.getParameter().line == "") {
117 : // automatically set the line so that persons are willing to enter
118 : // (see MSStageDriving::isWaitingFor)
119 6463 : const_cast<SUMOVehicleParameter&>(v.getParameter()).line = TAXI_SERVICE;
120 : }
121 6560 : if (v.getVClass() != SVC_TAXI && myVClassWarningVTypes.count(v.getVehicleType().getID()) == 0) {
122 162 : WRITE_WARNINGF(TL("Vehicle '%' with device.taxi should have vClass taxi instead of '%'."), v.getID(), toString(v.getVClass()));
123 54 : myVClassWarningVTypes.insert(v.getVehicleType().getID());
124 : }
125 6560 : const int personCapacity = v.getVehicleType().getPersonCapacity();
126 6560 : const int containerCapacity = v.getVehicleType().getContainerCapacity();
127 6560 : myMaxCapacity = MAX2(myMaxCapacity, personCapacity);
128 6560 : myMaxContainerCapacity = MAX2(myMaxContainerCapacity, containerCapacity);
129 6560 : if (personCapacity < 1 && containerCapacity < 1) {
130 0 : WRITE_WARNINGF(TL("Vehicle '%' with personCapacity % and containerCapacity % is not usable as taxi."), v.getID(), toString(personCapacity), toString(containerCapacity));
131 : }
132 : }
133 5371063 : }
134 :
135 :
136 : void
137 773 : MSDevice_Taxi::initDispatch() {
138 773 : OptionsCont& oc = OptionsCont::getOptions();
139 773 : myDispatchPeriod = string2time(oc.getString("device.taxi.dispatch-period"));
140 : // init dispatch algorithm
141 773 : std::string algo = oc.getString("device.taxi.dispatch-algorithm");
142 773 : Parameterised params;
143 1546 : params.setParametersStr(OptionsCont::getOptions().getString("device.taxi.dispatch-algorithm.params"), ":", ",");
144 773 : if (algo == "greedy") {
145 468 : myDispatcher = new MSDispatch_Greedy(params.getParametersMap());
146 305 : } else if (algo == "greedyClosest") {
147 36 : myDispatcher = new MSDispatch_GreedyClosest(params.getParametersMap());
148 287 : } else if (algo == "greedyShared") {
149 114 : myDispatcher = new MSDispatch_GreedyShared(params.getParametersMap());
150 173 : } else if (algo == "routeExtension") {
151 122 : myDispatcher = new MSDispatch_RouteExtension(params.getParametersMap());
152 112 : } else if (algo == "traci") {
153 224 : myDispatcher = new MSDispatch_TraCI(params.getParametersMap());
154 : } else {
155 0 : throw ProcessError(TLF("Dispatch algorithm '%' is not known", algo));
156 : }
157 773 : myDispatchCommand = new StaticCommand<MSDevice_Taxi>(&MSDevice_Taxi::triggerDispatch);
158 : // round to next multiple of myDispatchPeriod
159 773 : const SUMOTime now = MSNet::getInstance()->getCurrentTimeStep();
160 773 : const SUMOTime begin = string2time(oc.getString("begin"));
161 773 : const SUMOTime delay = (myDispatchPeriod - ((now - begin) % myDispatchPeriod)) % myDispatchPeriod;
162 773 : MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myDispatchCommand, now + delay);
163 1546 : }
164 :
165 : bool
166 79604 : MSDevice_Taxi::isReservation(const std::set<std::string>& lines) {
167 79604 : return lines.size() == 1 && (
168 79590 : *lines.begin() == TAXI_SERVICE
169 154096 : || StringUtils::startsWith(*lines.begin(), TAXI_SERVICE_PREFIX));
170 : }
171 :
172 : void
173 2609 : MSDevice_Taxi::addReservation(MSTransportable* person,
174 : const std::set<std::string>& lines,
175 : SUMOTime reservationTime,
176 : SUMOTime pickupTime,
177 : SUMOTime earliestPickupTime,
178 : const MSEdge* from, double fromPos,
179 : const MSStoppingPlace* fromStop,
180 : const MSEdge* to, double toPos,
181 : const MSStoppingPlace* toStop,
182 : const std::string& group) {
183 2609 : if (!isReservation(lines)) {
184 : return;
185 : }
186 2609 : if ((to->getPermissions() & SVC_TAXI) == 0) {
187 0 : throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
188 0 : + " '" + person->getID() + "' because destination edge '" + to->getID() + "'"
189 0 : + " does not permit taxi access");
190 : }
191 2609 : if ((from->getPermissions() & SVC_TAXI) == 0) {
192 0 : throw ProcessError("Cannot add taxi reservation for " + std::string(person->isPerson() ? "person" : "container")
193 0 : + " '" + person->getID() + "' because origin edge '" + from->getID() + "'"
194 0 : + " does not permit taxi access");
195 : }
196 2609 : if (myDispatchCommand == nullptr) {
197 773 : initDispatch();
198 : }
199 2609 : if (fromStop != nullptr && &fromStop->getLane().getEdge() == from) {
200 : // pickup position should be at the stop-endPos
201 885 : fromPos = fromStop->getEndLanePosition();
202 : }
203 5218 : myDispatcher->addReservation(person, reservationTime, pickupTime, earliestPickupTime, from, fromPos, fromStop, to, toPos, toStop, group, *lines.begin(), myMaxCapacity, myMaxContainerCapacity);
204 : }
205 :
206 : void
207 93 : MSDevice_Taxi::removeReservation(MSTransportable* person,
208 : const std::set<std::string>& lines,
209 : const MSEdge* from, double fromPos,
210 : const MSEdge* to, double toPos,
211 : const std::string& group) {
212 93 : if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
213 154 : myDispatcher->removeReservation(person, from, fromPos, to, toPos, group);
214 : }
215 93 : }
216 :
217 : void
218 162 : MSDevice_Taxi::updateReservationFromPos(MSTransportable* person,
219 : const std::set<std::string>& lines,
220 : const MSEdge* from, double fromPos,
221 : const MSEdge* to, double toPos,
222 : const std::string& group, double newFromPos) {
223 162 : if (myDispatcher != nullptr && lines.size() == 1 && *lines.begin() == TAXI_SERVICE) {
224 300 : myDispatcher->updateReservationFromPos(person, from, fromPos, to, toPos, group, newFromPos);
225 : }
226 162 : }
227 :
228 :
229 : SUMOTime
230 13038 : MSDevice_Taxi::triggerDispatch(SUMOTime currentTime) {
231 : std::vector<MSDevice_Taxi*> active;
232 33841 : for (MSDevice_Taxi* taxi : myFleet) {
233 20803 : if (taxi->getHolder().hasDeparted()) {
234 20705 : active.push_back(taxi);
235 : }
236 : }
237 13038 : myDispatcher->computeDispatch(currentTime, active);
238 13036 : return myDispatchPeriod;
239 13038 : }
240 :
241 : bool
242 3498633 : MSDevice_Taxi::hasServableReservations() {
243 3498633 : return myDispatcher != nullptr && myDispatcher->hasServableReservations();
244 : }
245 :
246 : void
247 38670 : MSDevice_Taxi::cleanup() {
248 38670 : if (myDispatcher != nullptr) {
249 773 : delete myDispatcher;
250 773 : myDispatcher = nullptr;
251 : }
252 38670 : myDispatchCommand = nullptr;
253 : myVClassWarningVTypes.clear();
254 38670 : }
255 :
256 : // ---------------------------------------------------------------------------
257 : // MSDevice_Taxi-methods
258 : // ---------------------------------------------------------------------------
259 6566 : MSDevice_Taxi::MSDevice_Taxi(SUMOVehicle& holder, const std::string& id) :
260 6566 : MSVehicleDevice(holder, id) {
261 6566 : std::string defaultServiceEnd = toString(1e15);
262 13138 : const std::string algo = holder.getStringParam("device.taxi.idle-algorithm");
263 6566 : if (algo == "stop") {
264 6400 : myIdleAlgorithm = new MSIdling_Stop();
265 166 : } else if (algo == "randomCircling") {
266 82 : myIdleAlgorithm = new MSIdling_RandomCircling();
267 : // make sure simulation terminates
268 82 : defaultServiceEnd = toString(STEPS2TIME(
269 : myHolder.getParameter().departProcedure == DepartDefinition::GIVEN
270 : ? myHolder.getParameter().depart
271 164 : : MSNet::getInstance()->getCurrentTimeStep()) + (3600 * 8));
272 84 : } else if (algo == "taxistand") {
273 156 : const std::string rerouterID = holder.getStringParam("device.taxi.stands-rerouter");
274 78 : if (rerouterID.empty()) {
275 0 : throw ProcessError("Idle algorithm '" + algo + "' requires a rerouter id to be defined using device param 'stands-rerouter' for vehicle '" + myHolder.getID() + "'");
276 : }
277 : if (MSTriggeredRerouter::getInstances().count(rerouterID) == 0) {
278 0 : throw ProcessError("Unknown rerouter '" + rerouterID + "' when loading taxi stands for vehicle '" + myHolder.getID() + "'");
279 : }
280 78 : MSTriggeredRerouter* rerouter = MSTriggeredRerouter::getInstances().find(rerouterID)->second;
281 78 : myIdleAlgorithm = new MSIdling_TaxiStand(rerouter);
282 : } else {
283 18 : throw ProcessError("Idle algorithm '" + algo + "' is not known for vehicle '" + myHolder.getID() + "'");
284 : }
285 6566 : myServiceEnd = string2time(holder.getStringParam("device.taxi.end", false, defaultServiceEnd));
286 6560 : myRoutingDevice = static_cast<MSDevice_Routing*>(myHolder.getDevice(typeid(MSDevice_Routing)));
287 6566 : }
288 :
289 :
290 13120 : MSDevice_Taxi::~MSDevice_Taxi() {
291 6560 : myFleet.erase(std::find(myFleet.begin(), myFleet.end(), this));
292 : // recompute myMaxCapacity
293 6560 : myMaxCapacity = 0;
294 6560 : myMaxContainerCapacity = 0;
295 16714 : for (MSDevice_Taxi* taxi : myFleet) {
296 10154 : myMaxCapacity = MAX2(myMaxCapacity, taxi->getHolder().getVehicleType().getPersonCapacity());
297 10154 : myMaxContainerCapacity = MAX2(myMaxContainerCapacity, taxi->getHolder().getVehicleType().getContainerCapacity());
298 : }
299 6560 : delete myIdleAlgorithm;
300 13120 : }
301 :
302 :
303 : bool
304 10904 : MSDevice_Taxi::hasFleet() {
305 10904 : return myFleet.size() > 0;;
306 : }
307 :
308 :
309 : void
310 1148 : MSDevice_Taxi::dispatch(const Reservation& res) {
311 1148 : dispatchShared({&res, &res});
312 1146 : }
313 :
314 :
315 : void
316 1489 : MSDevice_Taxi::dispatchShared(std::vector<const Reservation*> reservations) {
317 : #ifdef DEBUG_DISPATCH
318 : if (DEBUG_COND) {
319 : std::cout << SIMTIME << " taxi=" << myHolder.getID() << " dispatch:\n";
320 : for (const Reservation* res : reservations) {
321 : std::cout << " persons=" << toString(res->persons) << "\n";
322 : }
323 : }
324 : #endif
325 1489 : myLastDispatch = reservations;
326 : ConstMSEdgeVector tmpEdges;
327 : StopParVector stops;
328 1489 : double lastPos = myHolder.getPositionOnLane();
329 1489 : const MSEdge* rerouteOrigin = *myHolder.getRerouteOrigin();
330 1489 : if (isEmpty()) {
331 : // start fresh from the current edge
332 1398 : if (myHolder.isStoppedParking()) {
333 : // parking stop must be ended normally
334 732 : MSStop& stop = myHolder.getNextStopMutable();
335 732 : stop.duration = 0;
336 732 : lastPos = stop.pars.endPos;
337 732 : if (myHolder.isStoppedTriggered()) {
338 624 : stop.triggered = false;
339 624 : stop.containerTriggered = false;
340 624 : stop.joinTriggered = false;
341 624 : myHolder.unregisterWaiting();
342 : }
343 : // prevent unauthorized/premature entry
344 732 : const_cast<SUMOVehicleParameter::Stop&>(stop.pars).permitted.insert("");
345 756 : while (myHolder.getStops().size() > 1) {
346 24 : myHolder.abortNextStop(1);
347 : }
348 : } else {
349 1237 : while (myHolder.hasStops()) {
350 : // in meso there might be more than 1 stop at this point
351 571 : myHolder.abortNextStop();
352 : }
353 : assert(!myHolder.hasStops());
354 : }
355 1398 : tmpEdges.push_back(myHolder.getEdge());
356 1398 : 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 1485 : const SUMOTime t = MSNet::getInstance()->getCurrentTimeStep();
460 : bool hasPickup = false;
461 5861 : for (const Reservation* res : reservations) {
462 : myCurrentReservations.insert(res);
463 : bool isPickup = false;
464 8972 : 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 4376 : if (isPickup) {
472 6447 : prepareStop(tmpEdges, stops, lastPos, res->from, res->fromPos, res->fromStop, "pickup " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
473 4408 : for (const MSTransportable* const transportable : res->persons) {
474 2259 : if (transportable->isPerson()) {
475 2085 : 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 4408 : for (auto itr = persons.begin(); itr != persons.end(); itr++) {
484 2259 : stops.back().awaitedPersons.insert((*itr)->getID());
485 : }
486 :
487 2149 : stops.back().parametersSet |= STOP_PERMITTED_SET;
488 2149 : if (stops.back().duration == -1) {
489 : // keep dropOffDuration if the stop is dropOff and pickUp
490 1797 : stops.back().duration = TIME2STEPS(myHolder.getFloatParam("device.taxi.pickUpDuration", false, 0));
491 : }
492 : } else {
493 4454 : prepareStop(tmpEdges, stops, lastPos, res->to, res->toPos, res->toStop, "dropOff " + toString(res->persons) + " (" + res->id + ")", res, isPickup);
494 2227 : 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 2970 : 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 5008 : for (SUMOVehicleParameter::Stop& stop : stops) {
511 : std::string error;
512 3523 : myHolder.addStop(stop, error);
513 3523 : if (error != "") {
514 0 : WRITE_WARNINGF(TL("Could not add taxi stop, time=%, error=%"), myHolder.getID(), stop.actType, time2string(t), error)
515 : }
516 : }
517 1485 : SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
518 : // SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = myHolder.getInfluencer().getRouterTT(veh->getRNGIndex())
519 1485 : 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 1483 : if (hasPickup) {
526 1451 : myState |= PICKUP;
527 : }
528 1495 : }
529 :
530 :
531 : void
532 88 : MSDevice_Taxi::cancelCurrentCustomers() {
533 : // check if taxi has stopped
534 88 : 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 88 : onBoard.insert(myHolder.getPersons().begin(), myHolder.getPersons().end());
541 88 : onBoard.insert(myHolder.getContainers().begin(), myHolder.getContainers().end());
542 142 : 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 88 : if (!customersToBeRemoved.empty()) {
550 90 : WRITE_WARNINGF(TL("Taxi '%' aborts waiting for customers: % at time=%."),
551 : myHolder.getID(), toString(customersToBeRemoved), time2string(SIMSTEP));
552 : }
553 122 : for (auto t : customersToBeRemoved) {
554 34 : cancelCustomer(t);
555 : }
556 : }
557 :
558 :
559 : bool
560 44 : 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 132 : for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
574 : bool fulfilled = false;
575 88 : 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 220 : for (auto it = myLastDispatch.begin(); it != myLastDispatch.end();) {
583 176 : if (*it == res) {
584 88 : 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 44 : myDispatcher->fulfilledReservation(res);
593 : } else {
594 : ++resIt;
595 : }
596 : }
597 44 : myState &= ~PICKUP; // remove state PICKUP
598 88 : 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 132 : 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 44 : if (!MSGlobals::gUseMesoSim) {
615 36 : dispatchShared(myLastDispatch);
616 : }
617 : return true;
618 : }
619 :
620 :
621 : void
622 4376 : MSDevice_Taxi::prepareStop(ConstMSEdgeVector& edges,
623 : StopParVector& 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 4376 : if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
629 779 : stopPos = stopPlace->getEndLanePosition();
630 : }
631 4376 : if (stopPos < lastPos && stopPos + NUMERICAL_EPS >= lastPos) {
632 : stopPos = lastPos;
633 : }
634 : bool addedEdge = false;
635 :
636 4376 : if (stops.empty()) {
637 : // check brakeGap
638 1465 : double distToStop = stopPos - lastPos;
639 1465 : const double brakeGap = myHolder.getBrakeGap();
640 1465 : if (myHolder.getLane() != nullptr && myHolder.getLane()->isInternal()) {
641 4 : distToStop += myHolder.getLane()->getLength();
642 : }
643 1465 : if (stopEdge != edges.back()) {
644 1213 : distToStop += edges.back()->getLength();
645 1213 : if (distToStop < brakeGap) {
646 : // the distance between current edge and stop edge may be small
647 14 : SUMOAbstractRouter<MSEdge, SUMOVehicle>& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
648 : ConstMSEdgeVector toFirstStop;
649 7 : router.compute(edges.back(), stopEdge, &myHolder, SIMSTEP, toFirstStop, true);
650 31 : for (int i = 1; i < (int)toFirstStop.size() - 1; i++) {
651 24 : distToStop += toFirstStop[i]->getLength();
652 : }
653 7 : }
654 : }
655 1465 : if (distToStop < brakeGap) {
656 : // circle back to stopEdge
657 : //std::cout << SIMTIME << " taxi=" << getID() << " brakeGap=" << brakeGap << " distToStop=" << distToStop << "\n";
658 179 : edges.push_back(stopEdge);
659 : addedEdge = true;
660 : }
661 : }
662 :
663 4376 : if (stopEdge == edges.back() && !stops.empty()) {
664 1024 : if (stopPos >= lastPos && stopPos <= stops.back().endPos) {
665 : // no new stop and no adaption needed
666 853 : stops.back().actType += "," + action;
667 853 : return;
668 : }
669 171 : 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 3523 : if (!addedEdge && (stopEdge != edges.back() || stopPos < lastPos)) {
677 : //std::cout << SIMTIME << " stopPos=" << stopPos << " lastPos=" << lastPos << "\n";
678 3118 : edges.push_back(stopEdge);
679 : }
680 3523 : lastPos = stopPos;
681 3523 : SUMOVehicleParameter::Stop stop;
682 3523 : stop.lane = getStopLane(stopEdge, action)->getID();
683 3523 : if (stopPlace != nullptr && &stopPlace->getLane().getEdge() == stopEdge) {
684 546 : stop.startPos = stopPlace->getBeginLanePosition();
685 546 : stop.endPos = stopPlace->getEndLanePosition();
686 : const SumoXMLTag tag = stopPlace->getElement();
687 546 : if (tag == SUMO_TAG_BUS_STOP || tag == SUMO_TAG_TRAIN_STOP) {
688 : stop.busstop = stopPlace->getID();
689 245 : } else if (tag == SUMO_TAG_PARKING_AREA) {
690 : stop.parkingarea = stopPlace->getID();
691 240 : } else if (tag == SUMO_TAG_CONTAINER_STOP) {
692 : stop.containerstop = stopPlace->getID();
693 : }
694 : } else {
695 2977 : stop.startPos = stopPos;
696 5954 : stop.endPos = MAX2(stopPos, MIN2(myHolder.getVehicleType().getLength(), stopEdge->getLength()));
697 : }
698 7046 : stop.parking = SUMOVehicleParameter::parseParkingType(myHolder.getStringParam("device.taxi.parking", false, "true"));
699 : stop.actType = action;
700 3523 : stop.index = STOP_INDEX_END;
701 : // In case of prebooking if person is not there/ comes to late for pickup set maximum waiting time:
702 3523 : SUMOTime earliestPickupTime = res->earliestPickupTime;
703 3523 : if (isPickup && earliestPickupTime >= 0) {
704 138 : stop.waitUntil = earliestPickupTime;
705 : // TODO: replace hard coded extension with parameter
706 138 : stop.extension = static_cast<SUMOTime>(3 * 60 * 1000); // 3mins
707 : }
708 3523 : stops.push_back(stop);
709 3523 : }
710 :
711 :
712 : MSLane*
713 3523 : MSDevice_Taxi::getStopLane(const MSEdge* edge, const std::string& action) {
714 3523 : const std::vector<MSLane*>* allowedLanes = edge->allowedLanes(myHolder.getVClass());
715 3523 : if (allowedLanes == nullptr) {
716 0 : throw ProcessError("Taxi vehicle '" + myHolder.getID() + "' cannot stop on edge '" + edge->getID() + "' (" + action + ")");
717 : }
718 3523 : return allowedLanes->front();
719 : }
720 :
721 : bool
722 449520 : MSDevice_Taxi::isEmpty() {
723 449520 : return myState == EMPTY;
724 : }
725 :
726 :
727 : bool
728 27070 : MSDevice_Taxi::allowsBoarding(const MSTransportable* t) const {
729 27070 : return myCustomers.count(t) != 0;
730 : }
731 :
732 :
733 : void
734 358948 : MSDevice_Taxi::updateMove(const SUMOTime traveltime, const double travelledDist) {
735 358948 : if (myHolder.getPersonNumber() > 0 || myHolder.getContainerNumber() > 0) {
736 99331 : myOccupiedDistance += travelledDist;
737 99331 : myOccupiedTime += traveltime;
738 : }
739 358948 : if (isEmpty()) {
740 215599 : if (MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
741 213765 : myIdleAlgorithm->idle(this);
742 213765 : if (myRoutingDevice != nullptr) {
743 : // prevent rerouting during idling (#11079)
744 : myRoutingDevice->setActive(false);
745 : }
746 1834 : } else if (!myReachedServiceEnd) {
747 228 : WRITE_WARNINGF(TL("Taxi '%' reaches scheduled end of service at time=%."), myHolder.getID(), time2string(SIMSTEP));
748 76 : myReachedServiceEnd = true;
749 : }
750 143349 : } else if (myRoutingDevice != nullptr) {
751 : myRoutingDevice->setActive(true);
752 : }
753 358948 : if (myHolder.isStopped() && (isEmpty() || MSGlobals::gUseMesoSim) && myHolder.getNextStop().endBoarding > myServiceEnd) {
754 : // limit duration of stop (but only for idling-related stops)
755 2226 : myHolder.getNextStopMutable().endBoarding = myServiceEnd;
756 : }
757 : #ifdef DEBUG_DISPATCH
758 : if (DEBUG_COND && myIsStopped != myHolder.isStopped()) {
759 : std::cout << SIMTIME << " updateMove veh=" << myHolder.getID() << " myIsStopped=" << myIsStopped << " myHolderStopped=" << myHolder.isStopped() << " myState=" << myState << "\n";
760 : }
761 : #endif
762 358948 : myIsStopped = myHolder.isStopped();
763 358948 : }
764 :
765 :
766 : bool
767 339821 : MSDevice_Taxi::notifyMove(SUMOTrafficObject& /*tObject*/, double oldPos,
768 : double newPos, double /*newSpeed*/) {
769 339821 : updateMove(DELTA_T, newPos - oldPos);
770 339821 : return true; // keep the device
771 : }
772 :
773 :
774 : void
775 19127 : MSDevice_Taxi::notifyMoveInternal(const SUMOTrafficObject& /* veh */,
776 : const double /* frontOnLane */,
777 : const double timeOnLane,
778 : const double /* meanSpeedFrontOnLane */,
779 : const double /* meanSpeedVehicleOnLane */,
780 : const double travelledDistanceFrontOnLane,
781 : const double /* travelledDistanceVehicleOnLane */,
782 : const double /* meanLengthOnLane */) {
783 19127 : updateMove(TIME2STEPS(timeOnLane), travelledDistanceFrontOnLane);
784 19127 : }
785 :
786 :
787 : bool
788 60484 : MSDevice_Taxi::notifyEnter(SUMOTrafficObject& /*veh*/, MSMoveReminder::Notification /*reason*/, const MSLane* /* enteredLane */) {
789 60484 : if (isEmpty() && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
790 39858 : myIdleAlgorithm->idle(this);
791 : }
792 60484 : return true; // keep the device
793 : }
794 :
795 :
796 : void
797 2088 : MSDevice_Taxi::customerEntered(const MSTransportable* t) {
798 2088 : myState |= OCCUPIED;
799 2088 : if (!hasFuturePickup()) {
800 1620 : myState &= ~PICKUP;
801 : }
802 11126 : for (const Reservation* res : myCurrentReservations) {
803 16188 : for (const MSTransportable* cand : res->persons) {
804 9238 : if (cand == t) {
805 2088 : const_cast<Reservation*>(res)->state = Reservation::ONBOARD;
806 2088 : break;
807 : }
808 : }
809 : }
810 2088 : }
811 :
812 :
813 : void
814 2088 : MSDevice_Taxi::customerArrived(const MSTransportable* person) {
815 2088 : myCustomersServed++;
816 : myCustomers.erase(person);
817 2088 : if (myHolder.getPersonNumber() == 0 && myHolder.getContainerNumber() == 0) {
818 1367 : myState &= ~OCCUPIED;
819 1367 : if (myHolder.getStops().size() > 1 && (myState & PICKUP) == 0) {
820 0 : WRITE_WARNINGF(TL("All customers left vehicle '%' at time=% but there are % remaining stops"),
821 : myHolder.getID(), time2string(SIMSTEP), myHolder.getStops().size() - 1);
822 0 : while (myHolder.getStops().size() > 1) {
823 0 : myHolder.abortNextStop(1);
824 : }
825 : }
826 : }
827 2088 : if (isEmpty()) {
828 : // cleanup
829 2672 : for (const Reservation* res : myCurrentReservations) {
830 1342 : myDispatcher->fulfilledReservation(res);
831 : }
832 : myCurrentReservations.clear();
833 1330 : if (MSGlobals::gUseMesoSim && MSNet::getInstance()->getCurrentTimeStep() < myServiceEnd) {
834 405 : myIdleAlgorithm->idle(this);
835 : }
836 : } else {
837 : // check whether a single reservation has been fulfilled
838 5051 : for (auto resIt = myCurrentReservations.begin(); resIt != myCurrentReservations.end();) {
839 : bool fulfilled = true;
840 5124 : for (const MSTransportable* t : (*resIt)->persons) {
841 : if (myCustomers.count(t) != 0) {
842 : fulfilled = false;
843 : break;
844 : }
845 : }
846 4293 : if (fulfilled) {
847 648 : myDispatcher->fulfilledReservation(*resIt);
848 : resIt = myCurrentReservations.erase(resIt);
849 : } else {
850 : ++resIt;
851 : }
852 : }
853 : }
854 2088 : }
855 :
856 :
857 : bool
858 2088 : MSDevice_Taxi::hasFuturePickup() {
859 5984 : for (const auto& stop : myHolder.getStops()) {
860 4364 : if (stop.reached) {
861 2090 : continue;
862 : }
863 2274 : if (stop.pars.permitted.size() > 0) {
864 : return true;
865 : }
866 : }
867 : return false;
868 : }
869 :
870 : void
871 847 : MSDevice_Taxi::generateOutput(OutputDevice* tripinfoOut) const {
872 847 : if (tripinfoOut != nullptr) {
873 823 : tripinfoOut->openTag("taxi");
874 1646 : tripinfoOut->writeAttr("customers", toString(myCustomersServed));
875 1646 : tripinfoOut->writeAttr("occupiedDistance", toString(myOccupiedDistance));
876 1646 : tripinfoOut->writeAttr("occupiedTime", time2string(myOccupiedTime));
877 1646 : tripinfoOut->closeTag();
878 : }
879 847 : }
880 :
881 : std::string
882 1832 : MSDevice_Taxi::getParameter(const std::string& key) const {
883 1832 : if (key == "customers") {
884 0 : return toString(myCustomersServed);
885 1832 : } else if (key == "occupiedDistance") {
886 0 : return toString(myOccupiedDistance);
887 1832 : } else if (key == "occupiedTime") {
888 0 : return toString(STEPS2TIME(myOccupiedTime));
889 1832 : } else if (key == "state") {
890 1753 : return toString(myState);
891 79 : } else if (key == "currentCustomers") {
892 15 : return joinNamedToStringSorting(myCustomers, " ");
893 64 : } else if (key == "pickUpDuration") {
894 64 : return myHolder.getStringParam("device.taxi.pickUpDuration", false, "0");
895 32 : } else if (key == "dropOffDuration") {
896 64 : return myHolder.getStringParam("device.taxi.dropOffDuration", false, "60");
897 : }
898 0 : throw InvalidArgument("Parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
899 : }
900 :
901 :
902 : void
903 20 : MSDevice_Taxi::setParameter(const std::string& key, const std::string& value) {
904 : double doubleValue;
905 : try {
906 20 : doubleValue = StringUtils::toDouble(value);
907 0 : } catch (NumberFormatException&) {
908 0 : throw InvalidArgument("Setting parameter '" + key + "' requires a number for device of type '" + deviceName() + "'");
909 0 : }
910 20 : if (key == "pickUpDuration" || key == "dropOffDuration") {
911 : // store as generic vehicle parameters
912 20 : ((SUMOVehicleParameter&)myHolder.getParameter()).setParameter("device.taxi." + key, value);
913 : } else {
914 : UNUSED_PARAMETER(doubleValue);
915 0 : throw InvalidArgument("Setting parameter '" + key + "' is not supported for device of type '" + deviceName() + "'");
916 : }
917 20 : }
918 :
919 : bool
920 61982 : MSDevice_Taxi::compatibleLine(const std::string& taxiLine, const std::string& rideLine) {
921 70104 : return ((taxiLine == rideLine && StringUtils::startsWith(rideLine, "taxi") && StringUtils::startsWith(taxiLine, "taxi"))
922 115842 : || (taxiLine == TAXI_SERVICE && StringUtils::startsWith(rideLine, "taxi:"))
923 188115 : || (rideLine == TAXI_SERVICE && StringUtils::startsWith(taxiLine, "taxi:")));
924 : }
925 :
926 : bool
927 3570 : MSDevice_Taxi::compatibleLine(const Reservation* res) {
928 3570 : return compatibleLine(myHolder.getParameter().line, res->line);
929 : }
930 :
931 :
932 : /****************************************************************************/
|