Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2012-2026 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file MSQueueExport.cpp
15 : /// @author Daniel Krajzewicz
16 : /// @author Jakob Erdmann
17 : /// @author Mario Krumnow
18 : /// @author Michael Behrisch
19 : /// @date 2012-04-26
20 : ///
21 : // Export the queueing length in front of a junction (very experimental!)
22 : /****************************************************************************/
23 : #include <config.h>
24 :
25 : #include <algorithm>
26 : #include <cmath>
27 : #include <limits>
28 : #include <microsim/MSEdgeControl.h>
29 : #include <microsim/MSEdge.h>
30 : #include <microsim/MSLane.h>
31 : #include <microsim/MSGlobals.h>
32 : #include <mesosim/MELoop.h>
33 : #include <mesosim/MESegment.h>
34 : #include <mesosim/MEVehicle.h>
35 : #include <microsim/MSVehicleType.h>
36 : #include <microsim/cfmodels/MSCFModel.h>
37 : #include <utils/options/OptionsCont.h>
38 : #include <utils/iodevices/OutputDevice.h>
39 : #include "MSQueueExport.h"
40 : #include <microsim/MSNet.h>
41 : #include <microsim/MSVehicle.h>
42 :
43 :
44 : // ===========================================================================
45 : // static member definitions
46 : // ===========================================================================
47 : SUMOTime MSQueueExport::myIntervalStart = SUMOTime_MIN;
48 : std::map<const MSEdge*, std::vector<std::pair<double, double> > > MSQueueExport::myEdgeSamples;
49 :
50 :
51 : // ===========================================================================
52 : // method definitions
53 : // ===========================================================================
54 : void
55 165267 : MSQueueExport::write(OutputDevice& of, SUMOTime timestep) {
56 165267 : const OptionsCont& oc = OptionsCont::getOptions();
57 165267 : const SUMOTime begin = string2time(oc.getString("begin"));
58 165267 : const SUMOTime period = string2time(oc.getString("queue-output.period"));
59 165267 : if (period > 0 && (timestep - begin) % period != 0) {
60 3540 : return;
61 : }
62 162999 : const double threshold = oc.getFloat("queue-output.speed-threshold");
63 162999 : const SUMOTime aggregation = string2time(oc.getString("queue-output.aggregation"));
64 162999 : if (aggregation > 0) {
65 1272 : const SUMOTime intervalStart = begin + ((timestep - begin) / aggregation) * aggregation;
66 1272 : if (myIntervalStart != SUMOTime_MIN && intervalStart != myIntervalStart) {
67 42 : writeInterval(of, myIntervalStart, myIntervalStart + aggregation);
68 : }
69 1272 : myIntervalStart = intervalStart;
70 1272 : writeEdge(nullptr, threshold, [] {});
71 1272 : return;
72 : }
73 : // The timestep block is opened lazily so that with --queue-output.skip-empty
74 : // (and, in particular, for columnar formats like Parquet or CSV) no row is
75 : // emitted for time steps without any queue.
76 161727 : bool wrapperOpen = false;
77 525487 : auto ensureOpen = [&]() {
78 525487 : if (!wrapperOpen) {
79 323454 : of.openTag("data").writeAttr("timestep", time2string(timestep));
80 161727 : of.openTag("lanes");
81 161727 : wrapperOpen = true;
82 : }
83 525487 : };
84 323454 : if (!oc.getBool("queue-output.skip-empty")) {
85 161727 : ensureOpen();
86 : }
87 161727 : writeEdge(&of, threshold, ensureOpen);
88 161727 : if (wrapperOpen) {
89 161727 : of.closeTag(); // lanes
90 323454 : of.closeTag(); // data
91 : }
92 : }
93 :
94 :
95 : void
96 179 : MSQueueExport::finish(OutputDevice& of, SUMOTime timestep) {
97 179 : const SUMOTime aggregation = string2time(OptionsCont::getOptions().getString("queue-output.aggregation"));
98 179 : if (aggregation > 0 && myIntervalStart != SUMOTime_MIN) {
99 24 : writeInterval(of, myIntervalStart, MIN2(myIntervalStart + aggregation, timestep));
100 : }
101 179 : myIntervalStart = SUMOTime_MIN;
102 : myEdgeSamples.clear();
103 179 : }
104 :
105 :
106 : void
107 162999 : MSQueueExport::writeEdge(OutputDevice* of, double threshold, const std::function<void()>& ensureOpen) {
108 162999 : MSEdgeControl& ec = MSNet::getInstance()->getEdgeControl();
109 3515427 : for (const MSEdge* const edge : ec.getEdges()) {
110 3352428 : if (MSGlobals::gUseMesoSim) {
111 : double segmentOffset = 0.;
112 196880 : for (const MESegment* segment = MSGlobals::gMesoNet->getSegmentForEdge(*edge); segment != nullptr; segment = segment->getNextSegment()) {
113 301568 : for (int qIdx = 0; qIdx < segment->numQueues(); ++qIdx) {
114 152384 : writeMesoQueue(of, *edge, *segment, qIdx, segmentOffset, threshold, ensureOpen);
115 : }
116 149184 : segmentOffset += segment->getLength();
117 : }
118 : } else {
119 6631864 : for (const MSLane* const lane : edge->getLanes()) {
120 3327132 : writeLane(of, *lane, threshold, ensureOpen);
121 : }
122 : }
123 : }
124 162999 : }
125 :
126 :
127 : void
128 3327132 : MSQueueExport::writeLane(OutputDevice* of, const MSLane& lane, double threshold, const std::function<void()>& ensureOpen) {
129 : // maximum of all vehicle waiting times
130 3327132 : double queueing_time = 0.0;
131 : // back of last stopped vehicle (XXX does not check for continuous queue)
132 3327132 : double queueing_length = 0.0;
133 : // back of last slow vehicle (XXX does not check for continuous queue)
134 3327132 : double queueing_length2 = 0.0;
135 : // number of slow vehicles
136 : int queueing_count = 0;
137 :
138 3327132 : if (!lane.empty()) {
139 32352937 : for (MSLane::VehCont::const_iterator it_veh = lane.myVehicles.begin(); it_veh != lane.myVehicles.end(); ++it_veh) {
140 31154290 : const MSVehicle& veh = **it_veh;
141 31154290 : if (!veh.isOnRoad()) {
142 0 : continue;
143 : }
144 :
145 31154290 : if (veh.getWaitingSeconds() > 0) {
146 30614164 : queueing_time = MAX2(veh.getWaitingSeconds(), queueing_time);
147 15307082 : const double veh_back_to_lane_end = (lane.getLength() - veh.getPositionOnLane()) + veh.getVehicleType().getLength();
148 30250363 : queueing_length = MAX2(veh_back_to_lane_end, queueing_length);
149 : }
150 :
151 : //Experimental
152 31154290 : if (veh.getSpeed() < (threshold) && (veh.getPositionOnLane() > (veh.getLane()->getLength()) * 0.25)) {
153 13172148 : const double veh_back_to_lane_end = (lane.getLength() - veh.getPositionOnLane()) + veh.getVehicleType().getLength();
154 13172148 : queueing_length2 = MAX2(veh_back_to_lane_end, queueing_length2);
155 13172148 : queueing_count++;
156 : }
157 : }
158 : }
159 :
160 : //Output
161 3327132 : if (of != nullptr) {
162 3289820 : if (queueing_length > 1 || queueing_length2 > 1) {
163 : ensureOpen();
164 363690 : of->openTag("lane").writeAttr("id", lane.getID()).writeAttr("queueing_time", queueing_time).writeAttr("queueing_length", queueing_length);
165 727380 : of->writeAttr("queueing_length_experimental", queueing_length2).closeTag();
166 : }
167 37312 : } else if (queueing_count > 0) {
168 468 : myEdgeSamples[&lane.getEdge()].push_back(std::make_pair((double)queueing_count, queueing_length2));
169 : }
170 3327132 : }
171 :
172 :
173 : void
174 152384 : MSQueueExport::writeMesoQueue(OutputDevice* of, const MSEdge& edge, const MESegment& segment, int qIdx, double segmentOffset, double threshold, const std::function<void()>& ensureOpen) {
175 : const std::vector<MEVehicle*>& queue = segment.getQueue(qIdx);
176 152384 : const int queueSize = (int)queue.size();
177 : // maximum of all vehicle waiting times
178 152384 : double queueing_time = 0.0;
179 : // position of the last slow vehicle counted from the head of the queue
180 152384 : int queueing_count = 0;
181 : // distance from the segment end to the back of the last slow vehicle
182 152384 : double queueing_length = 0.0;
183 : double occupancy = 0.0;
184 : const double segLength = segment.getLength();
185 152384 : const double segmentEnd = segmentOffset + segLength;
186 152384 : const double lanesCovered = segment.numQueues() == 1 ? std::round(segment.getCapacity() / segLength) : 1.;
187 : // positions and earliest exit times are interpolated as in MSEdge::getMesoPositions
188 : SUMOTime earliestExitTime = segment.getQueueBlockTime(qIdx);
189 : // conservative exit times use the jam-dependent headway as in MESegment::getMeanSpeed
190 : SUMOTime conservativeExitTime = earliestExitTime;
191 : double prevPos = std::numeric_limits<double>::max();
192 : bool prevQueued = false;
193 152384 : const double now = SIMTIME;
194 163418 : for (int i = 0; i < queueSize; ++i) {
195 : // vehicles are stored in reverse entry order (the queue head is at the back)
196 11034 : const MEVehicle* const veh = queue[queueSize - 1 - i];
197 11034 : const double lengthWithGap = veh->getVehicleType().getLengthWithGap();
198 11034 : occupancy += lengthWithGap;
199 : earliestExitTime = MAX2(earliestExitTime, veh->getEventTime());
200 : conservativeExitTime = MAX2(conservativeExitTime, veh->getEventTime());
201 : double maxPos = segmentEnd;
202 11034 : if (i > 0) {
203 5746 : earliestExitTime += segment.getMinTauWithVehLength(lengthWithGap, veh->getVehicleType().getCarFollowModel().getHeadwayTime());
204 5746 : conservativeExitTime += segment.getTauWithVehLength(qIdx, lengthWithGap, veh->getVehicleType().getCarFollowModel().getHeadwayTime());
205 5746 : maxPos = MIN2(maxPos, prevPos - lengthWithGap / lanesCovered);
206 : }
207 : const double entry = veh->getLastEntryTimeSeconds();
208 11034 : const double travelTime = MAX2(STEPS2TIME(earliestExitTime) - entry, TS);
209 11034 : const double linearPos = MIN2(segmentEnd, segmentOffset + segLength * (now - entry) / travelTime);
210 : // conservative speed of the interpolated position trajectory. Unlike
211 : // getSpeed() this accounts for blocked vehicles further ahead in the queue
212 11034 : const double interpolatedSpeed = segLength / MAX2(STEPS2TIME(conservativeExitTime) - entry, TS);
213 : // a vehicle is queued if it is slow by itself or if it has caught up
214 : // with the clamped position behind a queued vehicle ahead
215 22068 : const bool queued = MIN2(veh->getSpeed(), interpolatedSpeed) < threshold || (prevQueued && linearPos >= maxPos - POSITION_EPS);
216 : if (queued) {
217 396 : queueing_count = i + 1;
218 396 : queueing_time = MAX2(veh->getWaitingSeconds(), queueing_time);
219 396 : if (MSGlobals::gMesoInterpolatePos) {
220 0 : queueing_length = MAX2(0., segmentEnd - (veh->getPositionOnLane() - veh->getVehicleType().getLength()));
221 : } else {
222 396 : queueing_length = occupancy / lanesCovered;
223 : }
224 : }
225 : prevQueued = queued;
226 : prevPos = MIN2(linearPos, maxPos);
227 : }
228 152384 : if (queueing_count == 0) {
229 152196 : return;
230 : }
231 188 : if (of != nullptr) {
232 : ensureOpen();
233 70 : of->openTag("lane").writeAttr("id", edge.getLanes()[qIdx]->getID()).writeAttr("segment", segment.getIndex());
234 140 : of->writeAttr("queueing_time", queueing_time).writeAttr("queueing_length", queueing_length).writeAttr("queueing_count", queueing_count).closeTag();
235 : } else {
236 118 : myEdgeSamples[&edge].push_back(std::make_pair((double)queueing_count, queueing_length));
237 : }
238 : }
239 :
240 :
241 : void
242 66 : MSQueueExport::writeInterval(OutputDevice& of, SUMOTime begin, SUMOTime end) {
243 : // The interval element is opened lazily so that with --queue-output.skip-empty
244 : // (and, in particular, for columnar formats) no row is emitted for intervals
245 : // without any queue.
246 66 : bool intervalOpen = false;
247 104 : auto ensureOpen = [&]() {
248 104 : if (!intervalOpen) {
249 132 : of.openTag("interval").writeAttr("begin", time2string(begin)).writeAttr("end", time2string(end));
250 66 : intervalOpen = true;
251 : }
252 104 : };
253 66 : const OptionsCont& oc = OptionsCont::getOptions();
254 132 : if (!oc.getBool("queue-output.skip-empty")) {
255 66 : ensureOpen();
256 : }
257 66 : const double p = oc.getFloat("queue-output.percentile") / 100.;
258 : // iterate in network edge order for deterministic output
259 2970 : for (const MSEdge* const edge : MSNet::getInstance()->getEdgeControl().getEdges()) {
260 : const auto it = myEdgeSamples.find(edge);
261 2904 : if (it == myEdgeSamples.end()) {
262 2866 : continue;
263 : }
264 : std::vector<double> counts;
265 : std::vector<double> lengths;
266 624 : for (const auto& sample : it->second) {
267 586 : counts.push_back(sample.first);
268 586 : lengths.push_back(sample.second);
269 : }
270 38 : std::sort(counts.begin(), counts.end());
271 38 : std::sort(lengths.begin(), lengths.end());
272 38 : ensureOpen();
273 38 : of.openTag("edge").writeAttr("id", edge->getID()).writeAttr("samples", (int)counts.size());
274 38 : of.writeAttr("maxQueueLengthInVehicles", counts.back());
275 38 : of.writeAttr("medianQueueLengthInVehicles", percentile(counts, 0.5));
276 38 : of.writeAttr("percentileQueueLengthInVehicles", percentile(counts, p));
277 38 : of.writeAttr("maxQueueLengthInMeters", lengths.back());
278 38 : of.writeAttr("medianQueueLengthInMeters", percentile(lengths, 0.5));
279 38 : of.writeAttr("percentileQueueLengthInMeters", percentile(lengths, p));
280 38 : of.closeTag();
281 38 : }
282 66 : if (intervalOpen) {
283 132 : of.closeTag();
284 : }
285 : myEdgeSamples.clear();
286 66 : }
287 :
288 :
289 : double
290 152 : MSQueueExport::percentile(const std::vector<double>& sorted, double p) {
291 : assert(!sorted.empty());
292 152 : const double rank = p * (double)(sorted.size() - 1);
293 152 : const int lower = (int)rank;
294 152 : if (lower + 1 >= (int)sorted.size()) {
295 24 : return sorted.back();
296 : }
297 128 : const double frac = rank - (double)lower;
298 128 : return sorted[lower] * (1. - frac) + sorted[lower + 1] * frac;
299 : }
300 :
301 :
302 : /****************************************************************************/
|