LCOV - code coverage report
Current view: top level - src/microsim/output - MSQueueExport.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 98.6 % 139 137
Test Date: 2026-07-26 16:30:20 Functions: 100.0 % 9 9

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

Generated by: LCOV version 2.0-1