Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2001-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 RORouteHandler.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Jakob Erdmann
17 : /// @author Sascha Krieg
18 : /// @author Michael Behrisch
19 : /// @date Mon, 9 Jul 2001
20 : ///
21 : // Parser and container for routes during their loading
22 : /****************************************************************************/
23 : #include <config.h>
24 :
25 : #include <string>
26 : #include <map>
27 : #include <vector>
28 : #include <iostream>
29 : #include <utils/iodevices/OutputDevice.h>
30 : #include <utils/xml/SUMOSAXHandler.h>
31 : #include <utils/xml/SUMOXMLDefinitions.h>
32 : #include <utils/geom/GeoConvHelper.h>
33 : #include <utils/common/FileHelpers.h>
34 : #include <utils/common/MsgHandler.h>
35 : #include <utils/common/StringTokenizer.h>
36 : #include <utils/common/UtilExceptions.h>
37 : #include <utils/options/OptionsCont.h>
38 : #include <utils/vehicle/SUMOVehicleParserHelper.h>
39 : #include <utils/xml/SUMOSAXReader.h>
40 : #include <utils/xml/XMLSubSys.h>
41 : #include <utils/iodevices/OutputDevice_String.h>
42 : #include "RONet.h"
43 : #include "ROEdge.h"
44 : #include "ROLane.h"
45 : #include "RORouteDef.h"
46 : #include "RORouteHandler.h"
47 :
48 : // ===========================================================================
49 : // method definitions
50 : // ===========================================================================
51 15579 : RORouteHandler::RORouteHandler(RONet& net, const std::string& file,
52 : const bool tryRepair,
53 : const bool emptyDestinationsAllowed,
54 : const bool ignoreErrors,
55 15579 : const bool checkSchema) :
56 : SUMORouteHandler(file, checkSchema ? "routes" : "", true),
57 31158 : MapMatcher(OptionsCont::getOptions().getBool("mapmatch.junctions"),
58 31158 : OptionsCont::getOptions().getBool("mapmatch.taz"),
59 15579 : OptionsCont::getOptions().getFloat("mapmatch.distance"),
60 15579 : ignoreErrors ? MsgHandler::getWarningInstance() : MsgHandler::getErrorInstance()),
61 15579 : myNet(net),
62 15579 : myActiveRouteRepeat(0),
63 15579 : myActiveRoutePeriod(0),
64 15579 : myActivePlan(nullptr),
65 15579 : myActiveContainerPlan(nullptr),
66 15579 : myActiveContainerPlanSize(0),
67 15579 : myTryRepair(tryRepair),
68 15579 : myEmptyDestinationsAllowed(emptyDestinationsAllowed),
69 15579 : myErrorOutput(ignoreErrors ? MsgHandler::getWarningInstance() : MsgHandler::getErrorInstance()),
70 15579 : myBegin(string2time(OptionsCont::getOptions().getString("begin"))),
71 15579 : myKeepVTypeDist(OptionsCont::getOptions().getBool("keep-vtype-distributions")),
72 31158 : myUnsortedInput(OptionsCont::getOptions().exists("unsorted-input") && OptionsCont::getOptions().getBool("unsorted-input")),
73 15579 : myCurrentVTypeDistribution(nullptr),
74 69044 : myCurrentAlternatives(nullptr) {
75 15579 : myActiveRoute.reserve(100);
76 15579 : }
77 :
78 :
79 31158 : RORouteHandler::~RORouteHandler() {
80 15579 : delete myCurrentAlternatives;
81 46737 : }
82 :
83 :
84 : void
85 352 : RORouteHandler::deleteActivePlanAndVehicleParameter() {
86 352 : if (myActivePlan != nullptr) {
87 4 : for (ROPerson::PlanItem* const it : *myActivePlan) {
88 0 : delete it;
89 : }
90 4 : delete myActivePlan;
91 4 : myActivePlan = nullptr;
92 : }
93 352 : delete myActiveContainerPlan;
94 352 : myActiveContainerPlan = nullptr;
95 352 : delete myVehicleParameter;
96 352 : myVehicleParameter = nullptr;
97 352 : }
98 :
99 :
100 : void
101 41358 : RORouteHandler::parseFromViaTo(SumoXMLTag tag, const SUMOSAXAttributes& attrs, bool& ok) {
102 41358 : const std::string element = toString(tag);
103 : myActiveRoute.clear();
104 41358 : bool useTaz = OptionsCont::getOptions().getBool("with-taz");
105 41358 : if (useTaz && !myVehicleParameter->wasSet(VEHPARS_FROM_TAZ_SET) && !myVehicleParameter->wasSet(VEHPARS_TO_TAZ_SET)) {
106 1344 : WRITE_WARNINGF(TL("Taz usage was requested but no taz present in % '%'!"), element, myVehicleParameter->id);
107 : useTaz = false;
108 : }
109 : SUMOVehicleClass vClass = SVC_PASSENGER;
110 41358 : if (!myNet.getVTypeDistribution(myVehicleParameter->vtypeid)) {
111 41326 : SUMOVTypeParameter* type = myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid);
112 41326 : if (type != nullptr) {
113 41252 : vClass = type->vehicleClass;
114 : }
115 : }
116 : // from-attributes
117 41358 : const std::string rid = "for " + element + " '" + myVehicleParameter->id + "'";
118 55516 : if ((useTaz || !attrs.hasAttribute(SUMO_ATTR_FROM)) &&
119 26938 : (attrs.hasAttribute(SUMO_ATTR_FROM_TAZ) || attrs.hasAttribute(SUMO_ATTR_FROM_JUNCTION))) {
120 12360 : const bool useJunction = attrs.hasAttribute(SUMO_ATTR_FROM_JUNCTION);
121 13738 : const std::string tazType = useJunction ? "junction" : "taz";
122 13738 : const std::string tazID = attrs.get<std::string>(useJunction ? SUMO_ATTR_FROM_JUNCTION : SUMO_ATTR_FROM_TAZ, myVehicleParameter->id.c_str(), ok, true);
123 24720 : const ROEdge* fromTaz = myNet.getEdge(tazID + "-source");
124 12360 : if (fromTaz == nullptr) {
125 16 : myErrorOutput->inform("Source " + tazType + " '" + tazID + "' not known for " + element + " '" + myVehicleParameter->id + "'!"
126 8 : + (useJunction ? JUNCTION_TAZ_MISSING_HELP : ""));
127 8 : ok = false;
128 12352 : } else if (fromTaz->getNumSuccessors() == 0 && tag != SUMO_TAG_PERSON) {
129 0 : myErrorOutput->inform("Source " + tazType + " '" + tazID + "' has no outgoing edges for " + element + " '" + myVehicleParameter->id + "'!");
130 0 : ok = false;
131 : } else {
132 12352 : myActiveRoute.push_back(fromTaz);
133 : }
134 28998 : } else if (attrs.hasAttribute(SUMO_ATTR_FROMXY)) {
135 98 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_FROMXY, myVehicleParameter->id.c_str(), ok), false, vClass, myActiveRoute, rid, true, ok);
136 98 : if (myMapMatchTAZ && ok) {
137 20 : myVehicleParameter->fromTaz = myActiveRoute.back()->getID();
138 20 : myVehicleParameter->parametersSet |= VEHPARS_FROM_TAZ_SET;
139 : }
140 28900 : } else if (attrs.hasAttribute(SUMO_ATTR_FROMLONLAT)) {
141 24 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_FROMLONLAT, myVehicleParameter->id.c_str(), ok), true, vClass, myActiveRoute, rid, true, ok);
142 24 : if (myMapMatchTAZ && ok) {
143 0 : myVehicleParameter->fromTaz = myActiveRoute.back()->getID();
144 0 : myVehicleParameter->parametersSet |= VEHPARS_FROM_TAZ_SET;
145 : }
146 : } else {
147 57752 : parseEdges(attrs.getOpt<std::string>(SUMO_ATTR_FROM, myVehicleParameter->id.c_str(), ok), myActiveRoute, rid, ok);
148 : }
149 41358 : if (!attrs.hasAttribute(SUMO_ATTR_VIA) && !attrs.hasAttribute(SUMO_ATTR_VIALONLAT) && !attrs.hasAttribute(SUMO_ATTR_VIAXY)) {
150 34008 : myInsertStopEdgesAt = (int)myActiveRoute.size();
151 : }
152 :
153 : // via-attributes
154 : ConstROEdgeVector viaEdges;
155 41358 : if (attrs.hasAttribute(SUMO_ATTR_VIAXY)) {
156 32 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_VIAXY, myVehicleParameter->id.c_str(), ok), false, vClass, viaEdges, rid, false, ok);
157 41326 : } else if (attrs.hasAttribute(SUMO_ATTR_VIALONLAT)) {
158 4 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_VIALONLAT, myVehicleParameter->id.c_str(), ok), true, vClass, viaEdges, rid, false, ok);
159 41322 : } else if (attrs.hasAttribute(SUMO_ATTR_VIAJUNCTIONS)) {
160 24 : for (std::string junctionID : attrs.get<std::vector<std::string> >(SUMO_ATTR_VIAJUNCTIONS, myVehicleParameter->id.c_str(), ok)) {
161 32 : const ROEdge* viaSink = myNet.getEdge(junctionID + "-sink");
162 16 : if (viaSink == nullptr) {
163 0 : myErrorOutput->inform("Junction-taz '" + junctionID + "' not found." + JUNCTION_TAZ_MISSING_HELP);
164 0 : ok = false;
165 : } else {
166 16 : viaEdges.push_back(viaSink);
167 : }
168 8 : }
169 : } else {
170 82628 : parseEdges(attrs.getOpt<std::string>(SUMO_ATTR_VIA, myVehicleParameter->id.c_str(), ok, "", true), viaEdges, rid, ok);
171 : }
172 58191 : for (const ROEdge* e : viaEdges) {
173 16833 : myActiveRoute.push_back(e);
174 16833 : myVehicleParameter->via.push_back(e->getID());
175 : }
176 :
177 : // to-attributes
178 56092 : if ((useTaz || !attrs.hasAttribute(SUMO_ATTR_TO)) &&
179 28090 : (attrs.hasAttribute(SUMO_ATTR_TO_TAZ) || attrs.hasAttribute(SUMO_ATTR_TO_JUNCTION))) {
180 12352 : const bool useJunction = attrs.hasAttribute(SUMO_ATTR_TO_JUNCTION);
181 13730 : const std::string tazType = useJunction ? "junction" : "taz";
182 13730 : const std::string tazID = attrs.get<std::string>(useJunction ? SUMO_ATTR_TO_JUNCTION : SUMO_ATTR_TO_TAZ, myVehicleParameter->id.c_str(), ok, true);
183 24704 : const ROEdge* toTaz = myNet.getEdge(tazID + "-sink");
184 12352 : if (toTaz == nullptr) {
185 0 : myErrorOutput->inform("Sink " + tazType + " '" + tazID + "' not known for " + element + " '" + myVehicleParameter->id + "'!"
186 0 : + (useJunction ? JUNCTION_TAZ_MISSING_HELP : ""));
187 0 : ok = false;
188 12352 : } else if (toTaz->getNumPredecessors() == 0 && tag != SUMO_TAG_PERSON) {
189 0 : myErrorOutput->inform("Sink " + tazType + " '" + tazID + "' has no incoming edges for " + element + " '" + myVehicleParameter->id + "'!");
190 0 : ok = false;
191 : } else {
192 12352 : myActiveRoute.push_back(toTaz);
193 : }
194 29006 : } else if (attrs.hasAttribute(SUMO_ATTR_TOXY)) {
195 98 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_TOXY, myVehicleParameter->id.c_str(), ok, true), false, vClass, myActiveRoute, rid, false, ok);
196 98 : if (myMapMatchTAZ && ok) {
197 20 : myVehicleParameter->toTaz = myActiveRoute.back()->getID();
198 20 : myVehicleParameter->parametersSet |= VEHPARS_TO_TAZ_SET;
199 : }
200 28908 : } else if (attrs.hasAttribute(SUMO_ATTR_TOLONLAT)) {
201 24 : parseGeoEdges(attrs.get<PositionVector>(SUMO_ATTR_TOLONLAT, myVehicleParameter->id.c_str(), ok, true), true, vClass, myActiveRoute, rid, false, ok);
202 24 : if (myMapMatchTAZ && ok) {
203 0 : myVehicleParameter->toTaz = myActiveRoute.back()->getID();
204 0 : myVehicleParameter->parametersSet |= VEHPARS_TO_TAZ_SET;
205 : }
206 : } else {
207 57768 : parseEdges(attrs.getOpt<std::string>(SUMO_ATTR_TO, myVehicleParameter->id.c_str(), ok, "", true), myActiveRoute, rid, ok);
208 : }
209 41358 : myActiveRouteID = "!" + myVehicleParameter->id;
210 41358 : if (myVehicleParameter->routeid == "") {
211 : myVehicleParameter->routeid = myActiveRouteID;
212 : }
213 82716 : }
214 :
215 :
216 : void
217 512144 : RORouteHandler::myStartElement(int element,
218 : const SUMOSAXAttributes& attrs) {
219 : try {
220 512144 : if (myActivePlan != nullptr && myActivePlan->empty() && myVehicleParameter->departProcedure == DepartDefinition::TRIGGERED && element != SUMO_TAG_RIDE && element != SUMO_TAG_PARAM) {
221 12 : throw ProcessError(TLF("Triggered departure for person '%' requires starting with a ride.", myVehicleParameter->id));
222 512140 : } else if (myActiveContainerPlan != nullptr && myActiveContainerPlanSize == 0 && myVehicleParameter->departProcedure == DepartDefinition::TRIGGERED && element != SUMO_TAG_TRANSPORT && element != SUMO_TAG_PARAM) {
223 12 : throw ProcessError(TLF("Triggered departure for container '%' requires starting with a transport.", myVehicleParameter->id));
224 : }
225 512136 : SUMORouteHandler::myStartElement(element, attrs);
226 511796 : bool ok = true;
227 511796 : switch (element) {
228 1355 : case SUMO_TAG_PERSON:
229 : case SUMO_TAG_PERSONFLOW: {
230 1355 : myActivePlan = new std::vector<ROPerson::PlanItem*>();
231 1355 : break;
232 : }
233 : case SUMO_TAG_RIDE:
234 : break; // handled in addRide, called from SUMORouteHandler::myStartElement
235 64 : case SUMO_TAG_CONTAINER:
236 : case SUMO_TAG_CONTAINERFLOW:
237 64 : myActiveContainerPlan = new OutputDevice_String(1);
238 64 : myActiveContainerPlanSize = 0;
239 64 : myActiveContainerPlan->openTag((SumoXMLTag)element);
240 64 : (*myActiveContainerPlan) << attrs;
241 : break;
242 60 : case SUMO_TAG_TRANSPORT:
243 : case SUMO_TAG_TRANSHIP:
244 60 : if (myActiveContainerPlan == nullptr) {
245 8 : throw ProcessError(TLF("Found % outside container element", toString((SumoXMLTag)element)));
246 : }
247 : // copy container elements
248 56 : myActiveContainerPlan->openTag((SumoXMLTag)element);
249 56 : (*myActiveContainerPlan) << attrs;
250 56 : myActiveContainerPlan->closeTag();
251 56 : myActiveContainerPlanSize++;
252 56 : break;
253 3465 : case SUMO_TAG_FLOW:
254 3465 : myActiveRouteProbability = DEFAULT_VEH_PROB;
255 3465 : parseFromViaTo((SumoXMLTag)element, attrs, ok);
256 : break;
257 36598 : case SUMO_TAG_TRIP:
258 36598 : myActiveRouteProbability = DEFAULT_VEH_PROB;
259 36598 : parseFromViaTo((SumoXMLTag)element, attrs, ok);
260 : break;
261 : default:
262 : break;
263 : }
264 352 : } catch (ProcessError&) {
265 352 : deleteActivePlanAndVehicleParameter();
266 352 : throw;
267 352 : }
268 511792 : }
269 :
270 :
271 : void
272 52 : RORouteHandler::openVehicleTypeDistribution(const SUMOSAXAttributes& attrs) {
273 52 : bool ok = true;
274 52 : myCurrentVTypeDistributionID = attrs.get<std::string>(SUMO_ATTR_ID, nullptr, ok);
275 52 : if (ok) {
276 52 : myCurrentVTypeDistribution = new RandomDistributor<SUMOVTypeParameter*>();
277 52 : if (attrs.hasAttribute(SUMO_ATTR_VTYPES)) {
278 : std::vector<double> probs;
279 36 : if (attrs.hasAttribute(SUMO_ATTR_PROBS)) {
280 8 : StringTokenizer st(attrs.get<std::string>(SUMO_ATTR_PROBS, myCurrentVTypeDistributionID.c_str(), ok));
281 24 : while (st.hasNext()) {
282 32 : probs.push_back(StringUtils::toDoubleSecure(st.next(), 1.0));
283 : }
284 8 : }
285 36 : const std::string vTypes = attrs.get<std::string>(SUMO_ATTR_VTYPES, myCurrentVTypeDistributionID.c_str(), ok);
286 36 : StringTokenizer st(vTypes);
287 36 : int probIndex = 0;
288 84 : while (st.hasNext()) {
289 48 : const std::string typeID = st.next();
290 48 : const RandomDistributor<SUMOVTypeParameter*>* const dist = myNet.getVTypeDistribution(typeID);
291 8 : if (dist != nullptr) {
292 8 : const double distProb = ((int)probs.size() > probIndex ? probs[probIndex] : 1.) / dist->getOverallProb();
293 : std::vector<double>::const_iterator probIt = dist->getProbs().begin();
294 24 : for (SUMOVTypeParameter* const type : dist->getVals()) {
295 16 : myCurrentVTypeDistribution->add(type, distProb * *probIt);
296 : probIt++;
297 : }
298 : } else {
299 40 : SUMOVTypeParameter* const type = myNet.getVehicleTypeSecure(typeID);
300 40 : if (type == nullptr) {
301 24 : myErrorOutput->inform("Unknown vehicle type '" + typeID + "' in distribution '" + myCurrentVTypeDistributionID + "'.");
302 : } else {
303 32 : const double prob = ((int)probs.size() > probIndex ? probs[probIndex] : type->defaultProbability);
304 32 : myCurrentVTypeDistribution->add(type, prob);
305 : }
306 : }
307 48 : probIndex++;
308 : }
309 36 : if (probs.size() > 0 && probIndex != (int)probs.size()) {
310 0 : WRITE_WARNING("Got " + toString(probs.size()) + " probabilities for " + toString(probIndex) +
311 : " types in vTypeDistribution '" + myCurrentVTypeDistributionID + "'");
312 : }
313 72 : }
314 : }
315 52 : }
316 :
317 :
318 : void
319 52 : RORouteHandler::closeVehicleTypeDistribution() {
320 52 : if (myCurrentVTypeDistribution != nullptr) {
321 52 : if (myCurrentVTypeDistribution->getOverallProb() == 0) {
322 8 : delete myCurrentVTypeDistribution;
323 24 : myErrorOutput->inform("Vehicle type distribution '" + myCurrentVTypeDistributionID + "' is empty.");
324 44 : } else if (!myNet.addVTypeDistribution(myCurrentVTypeDistributionID, myCurrentVTypeDistribution)) {
325 0 : delete myCurrentVTypeDistribution;
326 0 : myErrorOutput->inform("Another vehicle type (or distribution) with the id '" + myCurrentVTypeDistributionID + "' exists.");
327 : }
328 52 : myCurrentVTypeDistribution = nullptr;
329 : }
330 52 : }
331 :
332 :
333 : void
334 242614 : RORouteHandler::openRoute(const SUMOSAXAttributes& attrs) {
335 : myActiveRoute.clear();
336 242614 : myInsertStopEdgesAt = -1;
337 : // check whether the id is really necessary
338 : std::string rid;
339 242614 : if (myCurrentAlternatives != nullptr) {
340 232222 : myActiveRouteID = myCurrentAlternatives->getID();
341 696666 : rid = "distribution '" + myCurrentAlternatives->getID() + "'";
342 10392 : } else if (myVehicleParameter != nullptr) {
343 : // ok, a vehicle is wrapping the route,
344 : // we may use this vehicle's id as default
345 3493 : myVehicleParameter->routeid = myActiveRouteID = "!" + myVehicleParameter->id; // !!! document this
346 3493 : if (attrs.hasAttribute(SUMO_ATTR_ID)) {
347 0 : WRITE_WARNINGF(TL("Ids of internal routes are ignored (vehicle '%')."), myVehicleParameter->id);
348 : }
349 : } else {
350 6899 : bool ok = true;
351 6899 : myActiveRouteID = attrs.get<std::string>(SUMO_ATTR_ID, nullptr, ok);
352 6899 : if (!ok) {
353 16 : return;
354 : }
355 20649 : rid = "'" + myActiveRouteID + "'";
356 : }
357 242598 : if (myVehicleParameter != nullptr) { // have to do this here for nested route distributions
358 704085 : rid = "for vehicle '" + myVehicleParameter->id + "'";
359 : }
360 242598 : bool ok = true;
361 242598 : if (attrs.hasAttribute(SUMO_ATTR_EDGES)) {
362 485180 : parseEdges(attrs.get<std::string>(SUMO_ATTR_EDGES, myActiveRouteID.c_str(), ok), myActiveRoute, rid, ok);
363 : }
364 485196 : myActiveRouteRefID = attrs.getOpt<std::string>(SUMO_ATTR_REFID, myActiveRouteID.c_str(), ok, "");
365 242598 : if (myActiveRouteRefID != "" && myNet.getRouteDef(myActiveRouteRefID) == nullptr) {
366 0 : myErrorOutput->inform("Invalid reference to route '" + myActiveRouteRefID + "' in route " + rid + ".");
367 : }
368 242598 : if (myCurrentAlternatives != nullptr && !attrs.hasAttribute(SUMO_ATTR_PROB)) {
369 48 : WRITE_WARNINGF(TL("No probability for route %, using default."), rid);
370 : }
371 242598 : myActiveRouteProbability = attrs.getOpt<double>(SUMO_ATTR_PROB, myActiveRouteID.c_str(), ok, DEFAULT_VEH_PROB);
372 242598 : if (ok && myActiveRouteProbability < 0) {
373 24 : myErrorOutput->inform("Invalid probability for route '" + myActiveRouteID + "'.");
374 : }
375 242598 : myActiveRouteColor = attrs.hasAttribute(SUMO_ATTR_COLOR) ? new RGBColor(attrs.get<RGBColor>(SUMO_ATTR_COLOR, myActiveRouteID.c_str(), ok)) : nullptr;
376 242598 : ok = true;
377 242598 : myActiveRouteRepeat = attrs.getOpt<int>(SUMO_ATTR_REPEAT, myActiveRouteID.c_str(), ok, 0);
378 242598 : myActiveRoutePeriod = attrs.getOptSUMOTimeReporting(SUMO_ATTR_CYCLETIME, myActiveRouteID.c_str(), ok, 0);
379 242598 : if (myActiveRouteRepeat > 0) {
380 : SUMOVehicleClass vClass = SVC_IGNORING;
381 8 : if (myVehicleParameter != nullptr) {
382 0 : SUMOVTypeParameter* type = myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid);
383 0 : if (type != nullptr) {
384 0 : vClass = type->vehicleClass;
385 : }
386 : }
387 8 : if (myActiveRoute.size() > 0 && !myActiveRoute.back()->isConnectedTo(*myActiveRoute.front(), vClass)) {
388 0 : myErrorOutput->inform("Disconnected route " + rid + " when repeating.");
389 : }
390 : }
391 242598 : myCurrentCosts = attrs.getOpt<double>(SUMO_ATTR_COST, myActiveRouteID.c_str(), ok, -1);
392 242598 : if (ok && myCurrentCosts != -1 && myCurrentCosts < 0) {
393 24 : myErrorOutput->inform("Invalid cost for route '" + myActiveRouteID + "'.");
394 : }
395 : }
396 :
397 :
398 : void
399 2088 : RORouteHandler::openFlow(const SUMOSAXAttributes& /*attrs*/) {
400 : // currently unused
401 2088 : }
402 :
403 :
404 : void
405 1377 : RORouteHandler::openRouteFlow(const SUMOSAXAttributes& /*attrs*/) {
406 : // currently unused
407 1377 : }
408 :
409 :
410 : void
411 36598 : RORouteHandler::openTrip(const SUMOSAXAttributes& /*attrs*/) {
412 : // currently unused
413 36598 : }
414 :
415 :
416 : void
417 280472 : RORouteHandler::closeRoute(const bool mayBeDisconnected) {
418 280472 : const bool mustReroute = myActiveRoute.size() == 0 && myActiveRouteStops.size() != 0;
419 : if (mustReroute) {
420 : // implicit route from stops
421 20 : for (const SUMOVehicleParameter::Stop& stop : myActiveRouteStops) {
422 16 : ROEdge* edge = myNet.getEdge(stop.edge);
423 16 : myActiveRoute.push_back(edge);
424 : }
425 : }
426 280472 : if (myActiveRoute.size() == 0) {
427 546 : if (myActiveRouteRefID != "" && myCurrentAlternatives != nullptr) {
428 8 : myCurrentAlternatives->addAlternativeDef(myNet.getRouteDef(myActiveRouteRefID));
429 4 : myActiveRouteID = "";
430 : myActiveRouteRefID = "";
431 4 : return;
432 : }
433 542 : if (myVehicleParameter != nullptr) {
434 1578 : myErrorOutput->inform("The route for vehicle '" + myVehicleParameter->id + "' has no edges.");
435 : } else {
436 48 : myErrorOutput->inform("Route '" + myActiveRouteID + "' has no edges.");
437 : }
438 542 : myActiveRouteID = "";
439 : myActiveRouteStops.clear();
440 542 : return;
441 : }
442 279926 : if (myActiveRoute.size() == 1 && myActiveRoute.front()->isTazConnector()) {
443 60 : myErrorOutput->inform("The routing information for vehicle '" + myVehicleParameter->id + "' is insufficient.");
444 30 : myActiveRouteID = "";
445 : myActiveRouteStops.clear();
446 30 : return;
447 : }
448 765086 : if (!mayBeDisconnected && OptionsCont::getOptions().exists("no-internal-links") && !OptionsCont::getOptions().getBool("no-internal-links")) {
449 : // fix internal edges which did not get parsed
450 : const ROEdge* last = nullptr;
451 : ConstROEdgeVector fullRoute;
452 1549362 : for (const ROEdge* roe : myActiveRoute) {
453 1306770 : if (last != nullptr) {
454 2617771 : for (const ROEdge* intern : last->getSuccessors()) {
455 1553593 : if (intern->isInternal() && intern->getSuccessors().size() == 1 && intern->getSuccessors().front() == roe) {
456 0 : fullRoute.push_back(intern);
457 : }
458 : }
459 : }
460 1306770 : fullRoute.push_back(roe);
461 : last = roe;
462 : }
463 242592 : myActiveRoute = fullRoute;
464 242592 : }
465 279896 : if (myActiveRouteRepeat > 0) {
466 : // duplicate route
467 8 : ConstROEdgeVector tmpEdges = myActiveRoute;
468 8 : auto tmpStops = myActiveRouteStops;
469 24 : for (int i = 0; i < myActiveRouteRepeat; i++) {
470 16 : myActiveRoute.insert(myActiveRoute.begin(), tmpEdges.begin(), tmpEdges.end());
471 80 : for (SUMOVehicleParameter::Stop stop : tmpStops) {
472 64 : if (stop.until > 0) {
473 32 : if (myActiveRoutePeriod <= 0) {
474 0 : const std::string description = myVehicleParameter != nullptr
475 0 : ? "for vehicle '" + myVehicleParameter->id + "'"
476 0 : : "'" + myActiveRouteID + "'";
477 0 : throw ProcessError(TLF("Cannot repeat stops with 'until' in route % because no cycleTime is defined.", description));
478 : }
479 32 : stop.until += myActiveRoutePeriod * (i + 1);
480 32 : stop.arrival += myActiveRoutePeriod * (i + 1);
481 : }
482 64 : myActiveRouteStops.push_back(stop);
483 64 : }
484 : }
485 8 : }
486 279896 : RORoute* route = new RORoute(myActiveRouteID, myCurrentCosts, myActiveRouteProbability, myActiveRoute,
487 279896 : myActiveRouteColor, myActiveRouteStops);
488 : myActiveRoute.clear();
489 279896 : if (myCurrentAlternatives == nullptr) {
490 47678 : if (myNet.getRouteDef(myActiveRouteID) != nullptr) {
491 0 : delete route;
492 0 : if (myVehicleParameter != nullptr) {
493 0 : myErrorOutput->inform("Another route for vehicle '" + myVehicleParameter->id + "' exists.");
494 : } else {
495 0 : myErrorOutput->inform("Another route (or distribution) with the id '" + myActiveRouteID + "' exists.");
496 : }
497 : myActiveRouteID = "";
498 : myActiveRouteStops.clear();
499 0 : return;
500 : } else {
501 47678 : myCurrentAlternatives = new RORouteDef(myActiveRouteID, 0, mayBeDisconnected || myTryRepair, mayBeDisconnected);
502 47678 : myCurrentAlternatives->addLoadedAlternative(route);
503 47678 : myNet.addRouteDef(myCurrentAlternatives);
504 47678 : myCurrentAlternatives = nullptr;
505 : }
506 : } else {
507 232218 : myCurrentAlternatives->addLoadedAlternative(route);
508 : }
509 : myActiveRouteID = "";
510 : myActiveRouteStops.clear();
511 : }
512 :
513 :
514 : void
515 90881 : RORouteHandler::openRouteDistribution(const SUMOSAXAttributes& attrs) {
516 : // check whether the id is really necessary
517 90881 : bool ok = true;
518 : std::string id;
519 90881 : if (myVehicleParameter != nullptr) {
520 : // ok, a vehicle is wrapping the route,
521 : // we may use this vehicle's id as default
522 89857 : myVehicleParameter->routeid = id = "!" + myVehicleParameter->id; // !!! document this
523 89857 : if (attrs.hasAttribute(SUMO_ATTR_ID)) {
524 0 : WRITE_WARNINGF(TL("Ids of internal route distributions are ignored (vehicle '%')."), myVehicleParameter->id);
525 : }
526 : } else {
527 1024 : id = attrs.get<std::string>(SUMO_ATTR_ID, nullptr, ok);
528 1024 : if (!ok) {
529 : return;
530 : }
531 : }
532 : // try to get the index of the last element
533 90873 : int index = attrs.getOpt<int>(SUMO_ATTR_LAST, id.c_str(), ok, 0);
534 90873 : if (ok && index < 0) {
535 16 : myErrorOutput->inform("Negative index of a route alternative (id='" + id + "').");
536 8 : return;
537 : }
538 : // build the alternative cont
539 90865 : myCurrentAlternatives = new RORouteDef(id, index, myTryRepair, false);
540 90865 : if (attrs.hasAttribute(SUMO_ATTR_ROUTES)) {
541 0 : ok = true;
542 0 : StringTokenizer st(attrs.get<std::string>(SUMO_ATTR_ROUTES, id.c_str(), ok));
543 0 : while (st.hasNext()) {
544 0 : const std::string routeID = st.next();
545 0 : const RORouteDef* route = myNet.getRouteDef(routeID);
546 0 : if (route == nullptr) {
547 0 : myErrorOutput->inform("Unknown route '" + routeID + "' in distribution '" + id + "'.");
548 : } else {
549 0 : myCurrentAlternatives->addAlternativeDef(route);
550 : }
551 : }
552 0 : }
553 : }
554 :
555 :
556 : void
557 90865 : RORouteHandler::closeRouteDistribution() {
558 90865 : if (myCurrentAlternatives != nullptr) {
559 90849 : if (myCurrentAlternatives->getOverallProb() == 0) {
560 0 : myErrorOutput->inform("Route distribution '" + myCurrentAlternatives->getID() + "' is empty.");
561 0 : delete myCurrentAlternatives;
562 90849 : } else if (!myNet.addRouteDef(myCurrentAlternatives)) {
563 0 : myErrorOutput->inform("Another route (or distribution) with the id '" + myCurrentAlternatives->getID() + "' exists.");
564 0 : delete myCurrentAlternatives;
565 : }
566 90849 : myCurrentAlternatives = nullptr;
567 : }
568 90865 : }
569 :
570 :
571 : void
572 130105 : RORouteHandler::closeVehicle() {
573 130105 : checkLastDepart();
574 : // get the vehicle id
575 130105 : if (myVehicleParameter->departProcedure == DepartDefinition::GIVEN && myVehicleParameter->depart < myBegin) {
576 : return;
577 : }
578 : // get vehicle type
579 130105 : SUMOVTypeParameter* type = myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid);
580 130105 : if (type == nullptr) {
581 272 : myErrorOutput->inform("The vehicle type '" + myVehicleParameter->vtypeid + "' for vehicle '" + myVehicleParameter->id + "' is not known.");
582 136 : type = myNet.getVehicleTypeSecure(DEFAULT_VTYPE_ID);
583 : } else {
584 129969 : if (!myKeepVTypeDist) {
585 : // fix the type id in case we used a distribution
586 129965 : myVehicleParameter->vtypeid = type->id;
587 : }
588 : }
589 130105 : if (type->vehicleClass == SVC_PEDESTRIAN) {
590 120 : WRITE_WARNINGF(TL("Vehicle type '%' with vClass=pedestrian should only be used for persons and not for vehicle '%'."), type->id, myVehicleParameter->id);
591 : }
592 : // get the route
593 130105 : RORouteDef* route = myNet.getRouteDef(myVehicleParameter->routeid);
594 129741 : if (route == nullptr) {
595 728 : myErrorOutput->inform("The route of the vehicle '" + myVehicleParameter->id + "' is not known.");
596 364 : return;
597 : }
598 129741 : if (MsgHandler::getErrorInstance()->wasInformed()) {
599 : return;
600 : }
601 129073 : const bool needCopy = route->getID()[0] != '!';
602 129073 : if (needCopy) {
603 3694 : route = route->copy("!" + myVehicleParameter->id, myVehicleParameter->depart);
604 : }
605 : // build the vehicle
606 129073 : ROVehicle* veh = new ROVehicle(*myVehicleParameter, route, type, &myNet, myErrorOutput);
607 129073 : if (myNet.addVehicle(myVehicleParameter->id, veh)) {
608 129069 : registerLastDepart();
609 4 : } else if (needCopy) {
610 4 : delete route;
611 : }
612 129073 : delete myVehicleParameter;
613 129073 : myVehicleParameter = nullptr;
614 : }
615 :
616 :
617 : void
618 1781 : RORouteHandler::closeVType() {
619 1781 : if (myNet.addVehicleType(myCurrentVType)) {
620 1781 : if (myCurrentVTypeDistribution != nullptr) {
621 32 : myCurrentVTypeDistribution->add(myCurrentVType, myCurrentVType->defaultProbability);
622 : }
623 : }
624 3562 : if (OptionsCont::getOptions().isSet("restriction-params")) {
625 12 : const std::vector<std::string> paramKeys = OptionsCont::getOptions().getStringVector("restriction-params");
626 12 : myCurrentVType->cacheParamRestrictions(paramKeys);
627 12 : }
628 1781 : myCurrentVType = nullptr;
629 1781 : }
630 :
631 :
632 : void
633 1323 : RORouteHandler::closePerson() {
634 1323 : SUMOVTypeParameter* type = myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid);
635 1323 : if (type == nullptr) {
636 0 : myErrorOutput->inform("The vehicle type '" + myVehicleParameter->vtypeid + "' for person '" + myVehicleParameter->id + "' is not known.");
637 0 : type = myNet.getVehicleTypeSecure(DEFAULT_PEDTYPE_ID);
638 : }
639 1323 : if (myActivePlan == nullptr || myActivePlan->empty()) {
640 40 : WRITE_WARNINGF(TL("Discarding person '%' because her plan is empty"), myVehicleParameter->id);
641 : } else {
642 1303 : ROPerson* person = new ROPerson(*myVehicleParameter, type);
643 2726 : for (ROPerson::PlanItem* item : *myActivePlan) {
644 1423 : person->getPlan().push_back(item);
645 : }
646 1303 : if (myNet.addPerson(person)) {
647 1303 : checkLastDepart();
648 1303 : registerLastDepart();
649 : }
650 : }
651 1323 : delete myVehicleParameter;
652 1323 : myVehicleParameter = nullptr;
653 1323 : delete myActivePlan;
654 1323 : myActivePlan = nullptr;
655 1323 : }
656 :
657 :
658 : void
659 28 : RORouteHandler::closePersonFlow() {
660 : std::string typeID = DEFAULT_PEDTYPE_ID;
661 28 : if (myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid) == nullptr) {
662 0 : myErrorOutput->inform("The vehicle type '" + myVehicleParameter->vtypeid + "' for personFlow '" + myVehicleParameter->id + "' is not known.");
663 : } else {
664 28 : typeID = myVehicleParameter->vtypeid;
665 : }
666 28 : if (myActivePlan == nullptr || myActivePlan->empty()) {
667 0 : WRITE_WARNINGF(TL("Discarding personFlow '%' because their plan is empty"), myVehicleParameter->id);
668 : } else {
669 28 : checkLastDepart();
670 : // instantiate all persons of this flow
671 : int i = 0;
672 28 : std::string baseID = myVehicleParameter->id;
673 28 : if (myVehicleParameter->repetitionProbability > 0) {
674 4 : if (myVehicleParameter->repetitionEnd == SUMOTime_MAX) {
675 0 : throw ProcessError(TLF("probabilistic personFlow '%' must specify end time", myVehicleParameter->id));
676 : } else {
677 44 : for (SUMOTime t = myVehicleParameter->depart; t < myVehicleParameter->repetitionEnd; t += TIME2STEPS(1)) {
678 40 : if (RandHelper::rand() < myVehicleParameter->repetitionProbability) {
679 20 : addFlowPerson(typeID, t, baseID, i++);
680 : }
681 : }
682 : }
683 : } else {
684 24 : SUMOTime depart = myVehicleParameter->depart;
685 : // uniform sampling of departures from range is equivalent to poisson flow (encoded by negative offset)
686 48 : if (OptionsCont::getOptions().getBool("randomize-flows") && myVehicleParameter->repetitionOffset >= 0) {
687 : std::vector<SUMOTime> departures;
688 4 : const SUMOTime range = myVehicleParameter->repetitionNumber * myVehicleParameter->repetitionOffset;
689 24 : for (int j = 0; j < myVehicleParameter->repetitionNumber; ++j) {
690 20 : departures.push_back(depart + RandHelper::rand(range));
691 : }
692 4 : std::sort(departures.begin(), departures.end());
693 : std::reverse(departures.begin(), departures.end());
694 24 : for (; i < myVehicleParameter->repetitionNumber; i++) {
695 20 : addFlowPerson(typeID, departures[i], baseID, i);
696 : depart += myVehicleParameter->repetitionOffset;
697 : }
698 4 : } else {
699 20 : const bool triggered = myVehicleParameter->departProcedure == DepartDefinition::TRIGGERED;
700 20 : if (myVehicleParameter->repetitionOffset < 0) {
701 : // poisson: randomize first depart
702 4 : myVehicleParameter->incrementFlow(1);
703 : }
704 172 : for (; i < myVehicleParameter->repetitionNumber && (triggered || depart + myVehicleParameter->repetitionTotalOffset <= myVehicleParameter->repetitionEnd); i++) {
705 152 : addFlowPerson(typeID, depart + myVehicleParameter->repetitionTotalOffset, baseID, i);
706 152 : if (myVehicleParameter->departProcedure != DepartDefinition::TRIGGERED) {
707 152 : myVehicleParameter->incrementFlow(1);
708 : }
709 : }
710 : }
711 : }
712 : }
713 28 : delete myVehicleParameter;
714 28 : myVehicleParameter = nullptr;
715 56 : for (ROPerson::PlanItem* const it : *myActivePlan) {
716 28 : delete it;
717 : }
718 28 : delete myActivePlan;
719 28 : myActivePlan = nullptr;
720 28 : }
721 :
722 :
723 : void
724 192 : RORouteHandler::addFlowPerson(const std::string& typeID, SUMOTime depart, const std::string& baseID, int i) {
725 192 : SUMOVehicleParameter pars = *myVehicleParameter;
726 384 : pars.id = baseID + "." + toString(i);
727 192 : pars.depart = depart;
728 192 : const SUMOVTypeParameter* const type = myNet.getVehicleTypeSecure(typeID);
729 192 : if (!myKeepVTypeDist) {
730 192 : pars.vtypeid = type->id;
731 : }
732 192 : ROPerson* person = new ROPerson(pars, type);
733 384 : for (ROPerson::PlanItem* item : *myActivePlan) {
734 192 : person->getPlan().push_back(item->clone());
735 : }
736 192 : if (myNet.addPerson(person)) {
737 192 : if (i == 0) {
738 28 : registerLastDepart();
739 : }
740 : }
741 192 : }
742 :
743 :
744 : void
745 36 : RORouteHandler::closeContainer() {
746 36 : myActiveContainerPlan->closeTag();
747 36 : if (myActiveContainerPlanSize > 0) {
748 36 : myNet.addContainer(myVehicleParameter->depart, myActiveContainerPlan->getString());
749 36 : checkLastDepart();
750 36 : registerLastDepart();
751 : } else {
752 0 : WRITE_WARNINGF(TL("Discarding container '%' because its plan is empty"), myVehicleParameter->id);
753 : }
754 36 : delete myVehicleParameter;
755 36 : myVehicleParameter = nullptr;
756 36 : delete myActiveContainerPlan;
757 36 : myActiveContainerPlan = nullptr;
758 36 : myActiveContainerPlanSize = 0;
759 36 : }
760 :
761 :
762 20 : void RORouteHandler::closeContainerFlow() {
763 20 : myActiveContainerPlan->closeTag();
764 20 : if (myActiveContainerPlanSize > 0) {
765 20 : myNet.addContainer(myVehicleParameter->depart, myActiveContainerPlan->getString());
766 20 : checkLastDepart();
767 20 : registerLastDepart();
768 : } else {
769 0 : WRITE_WARNINGF(TL("Discarding containerFlow '%' because its plan is empty"), myVehicleParameter->id);
770 : }
771 20 : delete myVehicleParameter;
772 20 : myVehicleParameter = nullptr;
773 20 : delete myActiveContainerPlan;
774 20 : myActiveContainerPlan = nullptr;
775 20 : myActiveContainerPlanSize = 0;
776 20 : }
777 :
778 :
779 : void
780 3465 : RORouteHandler::closeFlow() {
781 3465 : checkLastDepart();
782 : // @todo: consider myScale?
783 3465 : if (myVehicleParameter->repetitionNumber == 0) {
784 8 : delete myVehicleParameter;
785 8 : myVehicleParameter = nullptr;
786 8 : return;
787 : }
788 : // let's check whether vehicles had to depart before the simulation starts
789 3457 : myVehicleParameter->repetitionsDone = 0;
790 3457 : const SUMOTime offsetToBegin = myBegin - myVehicleParameter->depart;
791 3641 : while (myVehicleParameter->repetitionTotalOffset < offsetToBegin) {
792 186 : myVehicleParameter->incrementFlow(1);
793 186 : if (myVehicleParameter->repetitionsDone == myVehicleParameter->repetitionNumber) {
794 2 : delete myVehicleParameter;
795 2 : myVehicleParameter = nullptr;
796 2 : return;
797 : }
798 : }
799 3455 : if (myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid) == nullptr) {
800 54 : myErrorOutput->inform("The vehicle type '" + myVehicleParameter->vtypeid + "' for flow '" + myVehicleParameter->id + "' is not known.");
801 : }
802 3455 : if (myVehicleParameter->routeid[0] == '!' && myNet.getRouteDef(myVehicleParameter->routeid) == nullptr) {
803 1516 : closeRoute(true);
804 : }
805 3455 : if (myNet.getRouteDef(myVehicleParameter->routeid) == nullptr) {
806 32 : myErrorOutput->inform("The route '" + myVehicleParameter->routeid + "' for flow '" + myVehicleParameter->id + "' is not known.");
807 16 : delete myVehicleParameter;
808 16 : myVehicleParameter = nullptr;
809 16 : return;
810 : }
811 3439 : myActiveRouteID = "";
812 3439 : if (!MsgHandler::getErrorInstance()->wasInformed()) {
813 6860 : if (myNet.addFlow(myVehicleParameter, OptionsCont::getOptions().getBool("randomize-flows"))) {
814 3426 : registerLastDepart();
815 : } else {
816 8 : myErrorOutput->inform("Another flow with the id '" + myVehicleParameter->id + "' exists.");
817 4 : delete myVehicleParameter;
818 : }
819 : } else {
820 9 : delete myVehicleParameter;
821 : }
822 3439 : myVehicleParameter = nullptr;
823 3439 : myInsertStopEdgesAt = -1;
824 : }
825 :
826 :
827 : void
828 36342 : RORouteHandler::closeTrip() {
829 36342 : closeRoute(true);
830 36342 : closeVehicle();
831 36342 : }
832 :
833 :
834 : const SUMOVehicleParameter::Stop*
835 10098 : RORouteHandler::retrieveStoppingPlace(const SUMOSAXAttributes& attrs, const std::string& errorSuffix, std::string& id, const SUMOVehicleParameter::Stop* stopParam) {
836 : // dummy stop parameter to hold the attributes
837 10098 : SUMOVehicleParameter::Stop stop;
838 10098 : if (stopParam != nullptr) {
839 8731 : stop = *stopParam;
840 : } else {
841 1367 : bool ok = true;
842 2734 : stop.busstop = attrs.getOpt<std::string>(SUMO_ATTR_BUS_STOP, nullptr, ok, "");
843 1367 : stop.busstop = attrs.getOpt<std::string>(SUMO_ATTR_TRAIN_STOP, nullptr, ok, stop.busstop); // alias
844 1367 : stop.chargingStation = attrs.getOpt<std::string>(SUMO_ATTR_CHARGING_STATION, nullptr, ok, "");
845 1367 : stop.overheadWireSegment = attrs.getOpt<std::string>(SUMO_ATTR_OVERHEAD_WIRE_SEGMENT, nullptr, ok, "");
846 1367 : stop.containerstop = attrs.getOpt<std::string>(SUMO_ATTR_CONTAINER_STOP, nullptr, ok, "");
847 2734 : stop.parkingarea = attrs.getOpt<std::string>(SUMO_ATTR_PARKING_AREA, nullptr, ok, "");
848 : }
849 : const SUMOVehicleParameter::Stop* toStop = nullptr;
850 10098 : if (stop.busstop != "") {
851 1754 : toStop = myNet.getStoppingPlace(stop.busstop, SUMO_TAG_BUS_STOP);
852 : id = stop.busstop;
853 1754 : if (toStop == nullptr) {
854 0 : WRITE_ERROR("The busStop '" + stop.busstop + "' is not known" + errorSuffix);
855 : }
856 8344 : } else if (stop.containerstop != "") {
857 32 : toStop = myNet.getStoppingPlace(stop.containerstop, SUMO_TAG_CONTAINER_STOP);
858 : id = stop.containerstop;
859 32 : if (toStop == nullptr) {
860 0 : WRITE_ERROR("The containerStop '" + stop.containerstop + "' is not known" + errorSuffix);
861 : }
862 8312 : } else if (stop.parkingarea != "") {
863 24 : toStop = myNet.getStoppingPlace(stop.parkingarea, SUMO_TAG_PARKING_AREA);
864 : id = stop.parkingarea;
865 24 : if (toStop == nullptr) {
866 0 : WRITE_ERROR("The parkingArea '" + stop.parkingarea + "' is not known" + errorSuffix);
867 : }
868 8288 : } else if (stop.chargingStation != "") {
869 : // ok, we have a charging station
870 20 : toStop = myNet.getStoppingPlace(stop.chargingStation, SUMO_TAG_CHARGING_STATION);
871 : id = stop.chargingStation;
872 20 : if (toStop == nullptr) {
873 0 : WRITE_ERROR("The chargingStation '" + stop.chargingStation + "' is not known" + errorSuffix);
874 : }
875 8268 : } else if (stop.overheadWireSegment != "") {
876 : // ok, we have an overhead wire segment
877 0 : toStop = myNet.getStoppingPlace(stop.overheadWireSegment, SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
878 : id = stop.overheadWireSegment;
879 0 : if (toStop == nullptr) {
880 0 : WRITE_ERROR("The overhead wire segment '" + stop.overheadWireSegment + "' is not known" + errorSuffix);
881 : }
882 : }
883 10098 : return toStop;
884 10098 : }
885 :
886 : Parameterised*
887 8747 : RORouteHandler::addStop(const SUMOSAXAttributes& attrs) {
888 : Parameterised* result = nullptr;
889 8747 : if (myActiveContainerPlan != nullptr) {
890 16 : myActiveContainerPlan->openTag(SUMO_TAG_STOP);
891 16 : (*myActiveContainerPlan) << attrs;
892 16 : myActiveContainerPlan->closeTag();
893 16 : myActiveContainerPlanSize++;
894 16 : return result;
895 : }
896 : std::string errorSuffix;
897 8731 : if (myActivePlan != nullptr) {
898 324 : errorSuffix = " in person '" + myVehicleParameter->id + "'.";
899 : } else if (myActiveContainerPlan != nullptr) {
900 : errorSuffix = " in container '" + myVehicleParameter->id + "'.";
901 8623 : } else if (myVehicleParameter != nullptr) {
902 22674 : errorSuffix = " in vehicle '" + myVehicleParameter->id + "'.";
903 : } else {
904 3195 : errorSuffix = " in route '" + myActiveRouteID + "'.";
905 : }
906 8731 : SUMOVehicleParameter::Stop stop;
907 17462 : bool ok = parseStop(stop, attrs, errorSuffix, myErrorOutput);
908 8731 : if (!ok) {
909 : return result;
910 : }
911 : // try to parse the assigned bus stop
912 8731 : const ROEdge* edge = nullptr;
913 : std::string stoppingPlaceID;
914 8731 : const SUMOVehicleParameter::Stop* stoppingPlace = retrieveStoppingPlace(attrs, errorSuffix, stoppingPlaceID, &stop);
915 : bool hasPos = false;
916 8731 : if (stoppingPlace != nullptr) {
917 1666 : stop.lane = stoppingPlace->lane;
918 1666 : stop.endPos = stoppingPlace->endPos;
919 1666 : stop.startPos = stoppingPlace->startPos;
920 6664 : edge = myNet.getEdge(SUMOXMLDefinitions::getEdgeIDFromLane(stop.lane));
921 : } else {
922 : // no, the lane and the position should be given
923 7065 : stop.lane = attrs.getOpt<std::string>(SUMO_ATTR_LANE, nullptr, ok, "");
924 7065 : stop.edge = attrs.getOpt<std::string>(SUMO_ATTR_EDGE, nullptr, ok, "");
925 7065 : if (ok && stop.edge != "") {
926 28 : edge = myNet.getEdge(stop.edge);
927 28 : if (edge == nullptr) {
928 0 : myErrorOutput->inform("The edge '" + stop.edge + "' for a stop is not known" + errorSuffix);
929 0 : return result;
930 : }
931 7037 : } else if (ok && stop.lane != "") {
932 21015 : edge = myNet.getEdge(SUMOXMLDefinitions::getEdgeIDFromLane(stop.lane));
933 7005 : if (edge == nullptr) {
934 0 : myErrorOutput->inform("The lane '" + stop.lane + "' for a stop is not known" + errorSuffix);
935 0 : return result;
936 : }
937 32 : } else if (ok && ((attrs.hasAttribute(SUMO_ATTR_X) && attrs.hasAttribute(SUMO_ATTR_Y))
938 20 : || (attrs.hasAttribute(SUMO_ATTR_LON) && attrs.hasAttribute(SUMO_ATTR_LAT)))) {
939 : Position pos;
940 : bool geo = false;
941 32 : if (attrs.hasAttribute(SUMO_ATTR_X) && attrs.hasAttribute(SUMO_ATTR_Y)) {
942 12 : pos = Position(attrs.get<double>(SUMO_ATTR_X, myVehicleParameter->id.c_str(), ok), attrs.get<double>(SUMO_ATTR_Y, myVehicleParameter->id.c_str(), ok));
943 : } else {
944 20 : pos = Position(attrs.get<double>(SUMO_ATTR_LON, myVehicleParameter->id.c_str(), ok), attrs.get<double>(SUMO_ATTR_LAT, myVehicleParameter->id.c_str(), ok));
945 : geo = true;
946 : }
947 32 : PositionVector positions;
948 32 : positions.push_back(pos);
949 : ConstROEdgeVector geoEdges;
950 : SUMOVehicleClass vClass = SVC_PASSENGER;
951 32 : if (!myNet.getVTypeDistribution(myVehicleParameter->vtypeid)) {
952 32 : SUMOVTypeParameter* type = myNet.getVehicleTypeSecure(myVehicleParameter->vtypeid);
953 32 : if (type != nullptr) {
954 32 : vClass = type->vehicleClass;
955 : }
956 : }
957 32 : parseGeoEdges(positions, geo, vClass, geoEdges, myVehicleParameter->id, true, ok, true);
958 32 : if (ok) {
959 32 : edge = geoEdges.front();
960 : hasPos = true;
961 32 : if (geo) {
962 20 : GeoConvHelper::getFinal().x2cartesian_const(pos);
963 : }
964 32 : stop.parametersSet |= STOP_END_SET;
965 32 : stop.endPos = edge->getLanes()[0]->getShape().nearest_offset_to_point2D(pos, false);
966 : } else {
967 : return result;
968 : }
969 32 : } else if (!ok || (stop.lane == "" && stop.edge == "")) {
970 0 : myErrorOutput->inform("A stop must be placed on a bus stop, a container stop, a parking area, an edge or a lane" + errorSuffix);
971 0 : return result;
972 : }
973 : if (!hasPos) {
974 7033 : stop.endPos = attrs.getOpt<double>(SUMO_ATTR_ENDPOS, nullptr, ok, edge->getLength());
975 : }
976 7065 : stop.startPos = attrs.getOpt<double>(SUMO_ATTR_STARTPOS, nullptr, ok, stop.endPos - 2 * POSITION_EPS);
977 7086 : const bool friendlyPos = attrs.getOpt<bool>(SUMO_ATTR_FRIENDLY_POS, nullptr, ok, !attrs.hasAttribute(SUMO_ATTR_STARTPOS) && !attrs.hasAttribute(SUMO_ATTR_ENDPOS));
978 7065 : const double endPosOffset = edge->isInternal() ? edge->getNormalBefore()->getLength() : 0;
979 7065 : if (!ok || (checkStopPos(stop.startPos, stop.endPos, edge->getLength() + endPosOffset, POSITION_EPS, friendlyPos) != SUMORouteHandler::StopPos::STOPPOS_VALID)) {
980 0 : myErrorOutput->inform("Invalid start or end position for stop" + errorSuffix);
981 0 : return result;
982 : }
983 : }
984 8731 : stop.edge = edge->getID();
985 8731 : if (myActivePlan != nullptr) {
986 108 : ROPerson::addStop(*myActivePlan, stop, edge);
987 108 : result = myActivePlan->back()->getStopParameters();
988 8623 : } else if (myVehicleParameter != nullptr) {
989 7558 : myVehicleParameter->stops.push_back(stop);
990 7558 : result = &myVehicleParameter->stops.back();
991 : } else {
992 1065 : myActiveRouteStops.push_back(stop);
993 : result = &myActiveRouteStops.back();
994 : }
995 8731 : if (myInsertStopEdgesAt >= 0) {
996 3130 : myActiveRoute.insert(myActiveRoute.begin() + myInsertStopEdgesAt, edge);
997 3130 : myInsertStopEdgesAt++;
998 : }
999 : return result;
1000 8731 : }
1001 :
1002 :
1003 : void
1004 64 : RORouteHandler::addRide(const SUMOSAXAttributes& attrs) {
1005 64 : bool ok = true;
1006 64 : std::vector<ROPerson::PlanItem*>& plan = *myActivePlan;
1007 64 : const std::string pid = myVehicleParameter->id;
1008 :
1009 : const ROEdge* from = nullptr;
1010 : const ROEdge* to = nullptr;
1011 64 : parseFromViaTo(SUMO_TAG_PERSON, attrs, ok);
1012 72 : if (attrs.hasAttribute(SUMO_ATTR_FROM) || attrs.hasAttribute(SUMO_ATTR_FROM_JUNCTION) || attrs.hasAttribute(SUMO_ATTR_FROM_TAZ)
1013 72 : || attrs.hasAttribute(SUMO_ATTR_FROMLONLAT) || attrs.hasAttribute(SUMO_ATTR_FROMXY)) {
1014 60 : if (ok) {
1015 60 : from = myActiveRoute.front();
1016 : }
1017 4 : } else if (plan.empty()) {
1018 0 : myErrorOutput->inform("The start edge for person '" + pid + "' is not known.");
1019 0 : return;
1020 : }
1021 : std::string stoppingPlaceID;
1022 128 : const SUMOVehicleParameter::Stop* stop = retrieveStoppingPlace(attrs, " for ride of person '" + myVehicleParameter->id + "'", stoppingPlaceID);
1023 64 : if (stop != nullptr) {
1024 84 : to = myNet.getEdge(SUMOXMLDefinitions::getEdgeIDFromLane(stop->lane));
1025 : } else {
1026 40 : if (attrs.hasAttribute(SUMO_ATTR_TO) || attrs.hasAttribute(SUMO_ATTR_TO_JUNCTION) || attrs.hasAttribute(SUMO_ATTR_TO_TAZ)
1027 40 : || attrs.hasAttribute(SUMO_ATTR_TOLONLAT) || attrs.hasAttribute(SUMO_ATTR_TOXY)) {
1028 36 : to = myActiveRoute.back();
1029 : } else {
1030 0 : myErrorOutput->inform("The to edge is missing within a ride of '" + myVehicleParameter->id + "'.");
1031 0 : return;
1032 : }
1033 : }
1034 64 : double arrivalPos = attrs.getOpt<double>(SUMO_ATTR_ARRIVALPOS, myVehicleParameter->id.c_str(), ok,
1035 : stop == nullptr ? std::numeric_limits<double>::infinity() : stop->endPos);
1036 64 : const std::string desc = attrs.get<std::string>(SUMO_ATTR_LINES, pid.c_str(), ok);
1037 128 : const std::string group = attrs.getOpt<std::string>(SUMO_ATTR_GROUP, pid.c_str(), ok, "");
1038 :
1039 64 : if (plan.empty() && myVehicleParameter->departProcedure == DepartDefinition::TRIGGERED) {
1040 20 : StringTokenizer st(desc);
1041 20 : if (st.size() != 1) {
1042 0 : myErrorOutput->inform("Triggered departure for person '" + pid + "' requires a unique lines value.");
1043 0 : return;
1044 : }
1045 20 : const std::string vehID = st.front();
1046 20 : if (!myNet.knowsVehicle(vehID)) {
1047 16 : myErrorOutput->inform("Unknown vehicle '" + vehID + "' in triggered departure for person '" + pid + "'.");
1048 8 : return;
1049 : }
1050 12 : SUMOTime vehDepart = myNet.getDeparture(vehID);
1051 12 : if (vehDepart == -1) {
1052 0 : myErrorOutput->inform("Cannot use triggered vehicle '" + vehID + "' in triggered departure for person '" + pid + "'.");
1053 0 : return;
1054 : }
1055 12 : myVehicleParameter->depart = vehDepart + 1; // write person after vehicle
1056 20 : }
1057 56 : ROPerson::addRide(plan, from, to, desc, arrivalPos, stoppingPlaceID, group);
1058 : }
1059 :
1060 :
1061 : void
1062 32 : RORouteHandler::addTransport(const SUMOSAXAttributes& attrs) {
1063 32 : if (myActiveContainerPlan != nullptr && myActiveContainerPlanSize == 0 && myVehicleParameter->departProcedure == DepartDefinition::TRIGGERED) {
1064 12 : bool ok = true;
1065 : const std::string pid = myVehicleParameter->id;
1066 12 : const std::string desc = attrs.get<std::string>(SUMO_ATTR_LINES, pid.c_str(), ok);
1067 16 : StringTokenizer st(desc);
1068 12 : if (st.size() != 1) {
1069 0 : throw ProcessError(TLF("Triggered departure for container '%' requires a unique lines value.", pid));
1070 : }
1071 12 : const std::string vehID = st.front();
1072 12 : if (!myNet.knowsVehicle(vehID)) {
1073 8 : throw ProcessError("Unknown vehicle '" + vehID + "' in triggered departure for container '" + pid + "'.");
1074 : }
1075 8 : SUMOTime vehDepart = myNet.getDeparture(vehID);
1076 8 : if (vehDepart == -1) {
1077 0 : throw ProcessError("Cannot use triggered vehicle '" + vehID + "' in triggered departure for container '" + pid + "'.");
1078 : }
1079 8 : myVehicleParameter->depart = vehDepart + 1; // write container after vehicle
1080 12 : }
1081 28 : }
1082 :
1083 :
1084 : void
1085 32 : RORouteHandler::addTranship(const SUMOSAXAttributes& /*attrs*/) {
1086 32 : }
1087 :
1088 :
1089 : void
1090 341724 : RORouteHandler::parseEdges(const std::string& desc, ConstROEdgeVector& into,
1091 : const std::string& rid, bool& ok) {
1092 2061432 : for (StringTokenizer st(desc); st.hasNext();) {
1093 1377984 : const std::string id = st.next();
1094 1377984 : const ROEdge* edge = myNet.getEdge(id);
1095 1377984 : if (edge == nullptr) {
1096 42 : myErrorOutput->inform("The edge '" + id + "' within the route " + rid + " is not known.");
1097 21 : ok = false;
1098 : } else {
1099 1377963 : into.push_back(edge);
1100 : }
1101 341724 : }
1102 341724 : }
1103 :
1104 :
1105 : void
1106 1231 : RORouteHandler::parseWalkPositions(const SUMOSAXAttributes& attrs, const std::string& personID,
1107 : const ROEdge* /*fromEdge*/, const ROEdge*& toEdge,
1108 : double& departPos, double& arrivalPos, std::string& busStopID,
1109 : const ROPerson::PlanItem* const lastStage, bool& ok) {
1110 1231 : const std::string description = "walk or personTrip of '" + personID + "'.";
1111 1231 : if (attrs.hasAttribute(SUMO_ATTR_DEPARTPOS)) {
1112 0 : WRITE_WARNING(TL("The attribute departPos is no longer supported for walks, please use the person attribute, the arrivalPos of the previous step or explicit stops."));
1113 : }
1114 1231 : departPos = myVehicleParameter->departPos;
1115 1231 : if (lastStage != nullptr) {
1116 76 : departPos = lastStage->getDestinationPos();
1117 : }
1118 :
1119 1231 : busStopID = attrs.getOpt<std::string>(SUMO_ATTR_BUS_STOP, nullptr, ok, "");
1120 :
1121 1231 : const SUMOVehicleParameter::Stop* bs = retrieveStoppingPlace(attrs, description, busStopID);
1122 1231 : if (bs != nullptr) {
1123 336 : toEdge = myNet.getEdge(SUMOXMLDefinitions::getEdgeIDFromLane(bs->lane));
1124 112 : arrivalPos = (bs->startPos + bs->endPos) / 2;
1125 : }
1126 1231 : if (toEdge != nullptr) {
1127 1231 : if (attrs.hasAttribute(SUMO_ATTR_ARRIVALPOS)) {
1128 36 : arrivalPos = SUMOVehicleParserHelper::parseWalkPos(SUMO_ATTR_ARRIVALPOS,
1129 36 : myHardFail, description, toEdge->getLength(),
1130 72 : attrs.get<std::string>(SUMO_ATTR_ARRIVALPOS, description.c_str(), ok));
1131 : }
1132 : } else {
1133 0 : throw ProcessError(TLF("No destination edge for %.", description));
1134 : }
1135 1231 : }
1136 :
1137 :
1138 : void
1139 1231 : RORouteHandler::addPersonTrip(const SUMOSAXAttributes& attrs) {
1140 1231 : bool ok = true;
1141 1231 : const char* const id = myVehicleParameter->id.c_str();
1142 : assert(!attrs.hasAttribute(SUMO_ATTR_EDGES));
1143 : const ROEdge* from = nullptr;
1144 1231 : const ROEdge* to = nullptr;
1145 1231 : parseFromViaTo(SUMO_TAG_PERSON, attrs, ok);
1146 1231 : myInsertStopEdgesAt = -1;
1147 1475 : if (attrs.hasAttribute(SUMO_ATTR_FROM) || attrs.hasAttribute(SUMO_ATTR_FROM_JUNCTION) || attrs.hasAttribute(SUMO_ATTR_FROM_TAZ)
1148 1319 : || attrs.hasAttribute(SUMO_ATTR_FROMLONLAT) || attrs.hasAttribute(SUMO_ATTR_FROMXY)) {
1149 1159 : if (ok) {
1150 1147 : from = myActiveRoute.front();
1151 : }
1152 72 : } else if (myActivePlan->empty()) {
1153 0 : throw ProcessError(TLF("Start edge not defined for person '%'.", myVehicleParameter->id));
1154 : } else {
1155 72 : from = myActivePlan->back()->getDestination();
1156 : }
1157 1511 : if (attrs.hasAttribute(SUMO_ATTR_TO) || attrs.hasAttribute(SUMO_ATTR_TO_JUNCTION) || attrs.hasAttribute(SUMO_ATTR_TO_TAZ)
1158 1359 : || attrs.hasAttribute(SUMO_ATTR_TOLONLAT) || attrs.hasAttribute(SUMO_ATTR_TOXY)) {
1159 1119 : to = myActiveRoute.back();
1160 : } // else, to may also be derived from stopping place
1161 :
1162 1231 : const SUMOTime duration = attrs.getOptSUMOTimeReporting(SUMO_ATTR_DURATION, id, ok, -1);
1163 1231 : if (attrs.hasAttribute(SUMO_ATTR_DURATION) && duration <= 0) {
1164 0 : throw ProcessError(TLF("Non-positive walking duration for '%'.", myVehicleParameter->id));
1165 : }
1166 :
1167 1231 : double departPos = 0;
1168 1231 : double arrivalPos = std::numeric_limits<double>::infinity();
1169 : std::string busStopID;
1170 1231 : const ROPerson::PlanItem* const lastStage = myActivePlan->empty() ? nullptr : myActivePlan->back();
1171 1231 : parseWalkPositions(attrs, myVehicleParameter->id, from, to, departPos, arrivalPos, busStopID, lastStage, ok);
1172 :
1173 1231 : const std::string modes = attrs.getOpt<std::string>(SUMO_ATTR_MODES, id, ok, "");
1174 2462 : const std::string group = attrs.getOpt<std::string>(SUMO_ATTR_GROUP, id, ok, "");
1175 : SVCPermissions modeSet = 0;
1176 3609 : for (StringTokenizer st(modes); st.hasNext();) {
1177 1147 : const std::string mode = st.next();
1178 1147 : if (mode == "car") {
1179 370 : modeSet |= SVC_PASSENGER;
1180 777 : } else if (mode == "taxi") {
1181 48 : modeSet |= SVC_TAXI;
1182 729 : } else if (mode == "bicycle") {
1183 12 : modeSet |= SVC_BICYCLE;
1184 717 : } else if (mode == "public") {
1185 717 : modeSet |= SVC_BUS;
1186 : } else {
1187 0 : throw InvalidArgument("Unknown person mode '" + mode + "'.");
1188 : }
1189 1231 : }
1190 1231 : const std::string types = attrs.getOpt<std::string>(SUMO_ATTR_VTYPES, id, ok, "");
1191 1231 : double walkFactor = attrs.getOpt<double>(SUMO_ATTR_WALKFACTOR, id, ok, OptionsCont::getOptions().getFloat("persontrip.walkfactor"));
1192 1231 : if (ok) {
1193 1219 : const std::string originStopID = myActivePlan->empty() ? "" : myActivePlan->back()->getStopDest();
1194 1219 : ROPerson::addTrip(*myActivePlan, myVehicleParameter->id, from, to, modeSet, types,
1195 : departPos, originStopID, arrivalPos, busStopID, walkFactor, group);
1196 1219 : myParamStack.push_back(myActivePlan->back());
1197 : }
1198 1231 : }
1199 :
1200 :
1201 : void
1202 228 : RORouteHandler::addWalk(const SUMOSAXAttributes& attrs) {
1203 : // parse walks from->to as person trips
1204 228 : if (attrs.hasAttribute(SUMO_ATTR_EDGES) || attrs.hasAttribute(SUMO_ATTR_ROUTE)) {
1205 : // XXX allow --repair?
1206 72 : bool ok = true;
1207 72 : if (attrs.hasAttribute(SUMO_ATTR_ROUTE)) {
1208 12 : const std::string routeID = attrs.get<std::string>(SUMO_ATTR_ROUTE, myVehicleParameter->id.c_str(), ok);
1209 12 : RORouteDef* routeDef = myNet.getRouteDef(routeID);
1210 12 : const RORoute* route = routeDef != nullptr ? routeDef->getFirstRoute() : nullptr;
1211 12 : if (route == nullptr) {
1212 0 : throw ProcessError("The route '" + routeID + "' for walk of person '" + myVehicleParameter->id + "' is not known.");
1213 : }
1214 12 : myActiveRoute = route->getEdgeVector();
1215 : } else {
1216 : myActiveRoute.clear();
1217 240 : parseEdges(attrs.get<std::string>(SUMO_ATTR_EDGES, myVehicleParameter->id.c_str(), ok), myActiveRoute, " walk for person '" + myVehicleParameter->id + "'", ok);
1218 : }
1219 72 : const char* const objId = myVehicleParameter->id.c_str();
1220 72 : const double duration = attrs.getOpt<double>(SUMO_ATTR_DURATION, objId, ok, -1);
1221 72 : if (attrs.hasAttribute(SUMO_ATTR_DURATION) && duration <= 0) {
1222 0 : throw ProcessError(TLF("Non-positive walking duration for '%'.", myVehicleParameter->id));
1223 : }
1224 72 : const double speed = attrs.getOpt<double>(SUMO_ATTR_SPEED, objId, ok, -1.);
1225 72 : if (attrs.hasAttribute(SUMO_ATTR_SPEED) && speed <= 0) {
1226 0 : throw ProcessError(TLF("Non-positive walking speed for '%'.", myVehicleParameter->id));
1227 : }
1228 : double departPos = 0.;
1229 : double arrivalPos = std::numeric_limits<double>::infinity();
1230 72 : if (attrs.hasAttribute(SUMO_ATTR_DEPARTPOS)) {
1231 0 : WRITE_WARNING(TL("The attribute departPos is no longer supported for walks, please use the person attribute, the arrivalPos of the previous step or explicit stops."));
1232 : }
1233 72 : if (attrs.hasAttribute(SUMO_ATTR_ARRIVALPOS)) {
1234 32 : arrivalPos = SUMOVehicleParserHelper::parseWalkPos(SUMO_ATTR_ARRIVALPOS, myHardFail, objId, myActiveRoute.back()->getLength(), attrs.get<std::string>(SUMO_ATTR_ARRIVALPOS, objId, ok));
1235 : }
1236 : std::string stoppingPlaceID;
1237 72 : const std::string errorSuffix = " for walk of person '" + myVehicleParameter->id + "'";
1238 72 : retrieveStoppingPlace(attrs, errorSuffix, stoppingPlaceID);
1239 72 : if (ok) {
1240 72 : ROPerson::addWalk(*myActivePlan, myActiveRoute, duration, speed, departPos, arrivalPos, stoppingPlaceID);
1241 72 : myParamStack.push_back(myActivePlan->back());
1242 : }
1243 : } else {
1244 156 : addPersonTrip(attrs);
1245 : }
1246 228 : }
1247 :
1248 :
1249 : void
1250 69 : RORouteHandler::initLaneTree(NamedRTree* tree) {
1251 5414 : for (const auto& edgeItem : myNet.getEdgeMap()) {
1252 9955 : for (ROLane* lane : edgeItem.second->getLanes()) {
1253 4610 : Boundary b = lane->getShape().getBoxBoundary();
1254 4610 : const float cmin[2] = {(float) b.xmin(), (float) b.ymin()};
1255 4610 : const float cmax[2] = {(float) b.xmax(), (float) b.ymax()};
1256 4610 : tree->Insert(cmin, cmax, lane);
1257 4610 : }
1258 : }
1259 69 : }
1260 :
1261 :
1262 : ROEdge*
1263 512 : RORouteHandler::retrieveEdge(const std::string& id) {
1264 512 : return myNet.getEdge(id);
1265 : }
1266 :
1267 : bool
1268 134957 : RORouteHandler::checkLastDepart() {
1269 134957 : if (!myUnsortedInput) {
1270 134136 : return SUMORouteHandler::checkLastDepart();
1271 : }
1272 : return true;
1273 : }
1274 :
1275 : /****************************************************************************/
|