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 : /****************************************************************************/
|