/* * #! * % * Copyright (C) 2014 - 2016 Humboldt-Universität zu Berlin * % * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #_ */ package de.hub.cs.dbis.lrb.operators; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backtype.storm.task.OutputCollector; import backtype.storm.task.TopologyContext; import backtype.storm.topology.OutputFieldsDeclarer; import backtype.storm.topology.base.BaseRichBolt; import backtype.storm.tuple.Fields; import backtype.storm.tuple.Tuple; import backtype.storm.tuple.Values; import de.hub.cs.dbis.aeolus.utils.TimestampMerger; import de.hub.cs.dbis.lrb.types.PositionReport; import de.hub.cs.dbis.lrb.types.internal.AvgVehicleSpeedTuple; import de.hub.cs.dbis.lrb.types.util.SegmentIdentifier; import de.hub.cs.dbis.lrb.util.AvgValue; import de.hub.cs.dbis.lrb.util.Time; /** * {@link AverageVehicleSpeedBolt} computes the average speed of a vehicle within an express way-segment (single * direction) every minute. The input is expected to be of type {@link PositionReport}, to be ordered by timestamp, and * must be grouped by vehicle. A new average speed computation is trigger each time a vehicle changes the express way, * segment or direction as well as each 60 seconds (ie, changing 'minute number' [see {@link Time#getMinute(short)}]).<br /> * <br /> * <strong>Input schema:</strong> {@link PositionReport}<br /> * <strong>Output schema:</strong> {@link AvgVehicleSpeedTuple} * * @author msoyka * @author mjsax */ public class AverageVehicleSpeedBolt extends BaseRichBolt { private final static long serialVersionUID = 5537727428628598519L; private static final Logger LOGGER = LoggerFactory.getLogger(AverageVehicleSpeedBolt.class); /** The storm provided output collector. */ private OutputCollector collector; /** Internally (re)used object to access individual attributes. */ private final PositionReport inputPositionReport = new PositionReport(); /** Internally (re)used object. */ private final SegmentIdentifier segment = new SegmentIdentifier(); /** * Maps each vehicle to its average speed value that corresponds to the current 'minute number' and specified * segment. */ private final Map<Integer, Pair<AvgValue, SegmentIdentifier>> avgSpeedsMap = new HashMap<Integer, Pair<AvgValue, SegmentIdentifier>>(); /** The currently processed 'minute number'. */ private short currentMinute = 1; @Override public void prepare(@SuppressWarnings("rawtypes") Map stormConf, TopologyContext context, OutputCollector collector) { this.collector = collector; } @Override public void execute(Tuple input) { if(input.getSourceStreamId().equals(TimestampMerger.FLUSH_STREAM_ID)) { if(input.getValue(0) == null) { this.flushBuffer(); this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values((Object)null)); } this.collector.ack(input); return; } this.inputPositionReport.clear(); this.inputPositionReport.addAll(input.getValues()); LOGGER.trace(this.inputPositionReport.toString()); Integer vid = this.inputPositionReport.getVid(); short minute = this.inputPositionReport.getMinuteNumber(); int speed = this.inputPositionReport.getSpeed().intValue(); this.segment.set(this.inputPositionReport); assert (minute >= this.currentMinute); if(minute > this.currentMinute) { // emit all values for last minute // (because input tuples are ordered by ts, we can close the last minute safely) this.flushBuffer(); this.collector.emit(TimestampMerger.FLUSH_STREAM_ID, new Values(new Short(minute))); this.avgSpeedsMap.clear(); this.currentMinute = minute; } Pair<AvgValue, SegmentIdentifier> vehicleEntry = this.avgSpeedsMap.get(vid); if(vehicleEntry != null && !vehicleEntry.getRight().equals(this.segment)) { SegmentIdentifier segId = vehicleEntry.getRight(); // VID, Minute-Number, X-Way, Segment, Direction, Avg(speed) this.collector.emit(new AvgVehicleSpeedTuple(vid, new Short(this.currentMinute), segId.getXWay(), segId .getSegment(), segId.getDirection(), vehicleEntry.getLeft().getAverage())); // set to null to get new vehicle entry below vehicleEntry = null; } if(vehicleEntry == null) { vehicleEntry = new MutablePair<AvgValue, SegmentIdentifier>(new AvgValue(speed), this.segment.copy()); this.avgSpeedsMap.put(vid, vehicleEntry); } else { vehicleEntry.getLeft().updateAverage(speed); } this.collector.ack(input); } private void flushBuffer() { for(Entry<Integer, Pair<AvgValue, SegmentIdentifier>> entry : this.avgSpeedsMap.entrySet()) { Pair<AvgValue, SegmentIdentifier> value = entry.getValue(); SegmentIdentifier segId = value.getRight(); // VID, Minute-Number, X-Way, Segment, Direction, Avg(speed) this.collector.emit(new AvgVehicleSpeedTuple(entry.getKey(), new Short(this.currentMinute), segId.getXWay(), segId.getSegment(), segId.getDirection(), value.getLeft().getAverage())); } } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(AvgVehicleSpeedTuple.getSchema()); declarer.declareStream(TimestampMerger.FLUSH_STREAM_ID, new Fields("ts")); } }