org.onebusaway.nyc.presentation.impl.realtime.SiriSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.nyc.presentation.impl.realtime.SiriSupport.java

Source

/*
 * Copyright 2010, OpenPlans 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 org.onebusaway.nyc.presentation.impl.realtime;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.nyc.presentation.impl.AgencySupportLibrary;
import org.onebusaway.nyc.presentation.service.realtime.PresentationService;
import org.onebusaway.nyc.transit_data.services.NycTransitDataService;
import org.onebusaway.nyc.transit_data_federation.siri.SiriDistanceExtension;
import org.onebusaway.nyc.transit_data_federation.siri.SiriExtensionWrapper;
import org.onebusaway.realtime.api.TimepointPredictionRecord;
import org.onebusaway.transit_data.model.StopBean;
import org.onebusaway.transit_data.model.blocks.BlockInstanceBean;
import org.onebusaway.transit_data.model.blocks.BlockStopTimeBean;
import org.onebusaway.transit_data.model.blocks.BlockTripBean;
import org.onebusaway.transit_data.model.service_alerts.ServiceAlertBean;
import org.onebusaway.transit_data.model.trips.TripBean;
import org.onebusaway.transit_data.model.trips.TripStatusBean;

import uk.org.siri.siri.BlockRefStructure;
import uk.org.siri.siri.DataFrameRefStructure;
import uk.org.siri.siri.DestinationRefStructure;
import uk.org.siri.siri.DirectionRefStructure;
import uk.org.siri.siri.ExtensionsStructure;
import uk.org.siri.siri.FramedVehicleJourneyRefStructure;
import uk.org.siri.siri.JourneyPatternRefStructure;
import uk.org.siri.siri.JourneyPlaceRefStructure;
import uk.org.siri.siri.LineRefStructure;
import uk.org.siri.siri.LocationStructure;
import uk.org.siri.siri.MonitoredCallStructure;
import uk.org.siri.siri.MonitoredVehicleJourneyStructure;
import uk.org.siri.siri.NaturalLanguageStringStructure;
import uk.org.siri.siri.OnwardCallStructure;
import uk.org.siri.siri.OnwardCallsStructure;
import uk.org.siri.siri.OperatorRefStructure;
import uk.org.siri.siri.ProgressRateEnumeration;
import uk.org.siri.siri.SituationRefStructure;
import uk.org.siri.siri.SituationSimpleRefStructure;
import uk.org.siri.siri.StopPointRefStructure;
import uk.org.siri.siri.VehicleRefStructure;

public final class SiriSupport {

    public enum OnwardCallsMode {
        VEHICLE_MONITORING, STOP_MONITORING
    }

    /**
     * NOTE: The tripDetails bean here may not be for the trip the vehicle is currently on 
     * in the case of A-D for stop!
     */
    public static void fillMonitoredVehicleJourney(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            TripBean framedJourneyTripBean, TripStatusBean currentVehicleTripStatus, StopBean monitoredCallStopBean,
            OnwardCallsMode onwardCallsMode, PresentationService presentationService,
            NycTransitDataService nycTransitDataService, int maximumOnwardCalls,
            List<TimepointPredictionRecord> stopLevelPredictions, long responseTimestamp) {
        BlockInstanceBean blockInstance = nycTransitDataService.getBlockInstance(
                currentVehicleTripStatus.getActiveTrip().getBlockId(), currentVehicleTripStatus.getServiceDate());

        List<BlockTripBean> blockTrips = blockInstance.getBlockConfiguration().getTrips();

        if (monitoredCallStopBean == null) {
            monitoredCallStopBean = currentVehicleTripStatus.getNextStop();
        }

        /////////////

        LineRefStructure lineRef = new LineRefStructure();
        lineRef.setValue(framedJourneyTripBean.getRoute().getId());
        monitoredVehicleJourney.setLineRef(lineRef);

        OperatorRefStructure operatorRef = new OperatorRefStructure();
        operatorRef.setValue(AgencySupportLibrary.getAgencyForId(framedJourneyTripBean.getRoute().getId()));
        monitoredVehicleJourney.setOperatorRef(operatorRef);

        DirectionRefStructure directionRef = new DirectionRefStructure();
        directionRef.setValue(framedJourneyTripBean.getDirectionId());
        monitoredVehicleJourney.setDirectionRef(directionRef);

        NaturalLanguageStringStructure routeShortName = new NaturalLanguageStringStructure();
        routeShortName.setValue(framedJourneyTripBean.getRoute().getShortName());
        monitoredVehicleJourney.setPublishedLineName(routeShortName);

        JourneyPatternRefStructure journeyPattern = new JourneyPatternRefStructure();
        journeyPattern.setValue(framedJourneyTripBean.getShapeId());
        monitoredVehicleJourney.setJourneyPatternRef(journeyPattern);

        NaturalLanguageStringStructure headsign = new NaturalLanguageStringStructure();
        headsign.setValue(framedJourneyTripBean.getTripHeadsign());
        monitoredVehicleJourney.setDestinationName(headsign);

        VehicleRefStructure vehicleRef = new VehicleRefStructure();
        vehicleRef.setValue(currentVehicleTripStatus.getVehicleId());
        monitoredVehicleJourney.setVehicleRef(vehicleRef);

        monitoredVehicleJourney.setMonitored(currentVehicleTripStatus.isPredicted());

        monitoredVehicleJourney.setBearing((float) currentVehicleTripStatus.getOrientation());

        monitoredVehicleJourney.setProgressRate(getProgressRateForPhaseAndStatus(
                currentVehicleTripStatus.getStatus(), currentVehicleTripStatus.getPhase()));

        // origin-destination
        for (int i = 0; i < blockTrips.size(); i++) {
            BlockTripBean blockTrip = blockTrips.get(i);

            if (blockTrip.getTrip().getId().equals(framedJourneyTripBean.getId())) {
                List<BlockStopTimeBean> stops = blockTrip.getBlockStopTimes();

                JourneyPlaceRefStructure origin = new JourneyPlaceRefStructure();
                origin.setValue(stops.get(0).getStopTime().getStop().getId());
                monitoredVehicleJourney.setOriginRef(origin);

                StopBean lastStop = stops.get(stops.size() - 1).getStopTime().getStop();
                DestinationRefStructure dest = new DestinationRefStructure();
                dest.setValue(lastStop.getId());
                monitoredVehicleJourney.setDestinationRef(dest);

                break;
            }
        }

        // framed journey 
        FramedVehicleJourneyRefStructure framedJourney = new FramedVehicleJourneyRefStructure();
        DataFrameRefStructure dataFrame = new DataFrameRefStructure();
        dataFrame.setValue(String.format("%1$tY-%1$tm-%1$td", currentVehicleTripStatus.getServiceDate()));
        framedJourney.setDataFrameRef(dataFrame);
        framedJourney.setDatedVehicleJourneyRef(framedJourneyTripBean.getId());
        monitoredVehicleJourney.setFramedVehicleJourneyRef(framedJourney);

        // location
        // if vehicle is detected to be on detour, use actual lat/lon, not snapped location.
        LocationStructure location = new LocationStructure();

        DecimalFormat df = new DecimalFormat();
        df.setMaximumFractionDigits(6);

        if (presentationService.isOnDetour(currentVehicleTripStatus)) {
            location.setLatitude(
                    new BigDecimal(df.format(currentVehicleTripStatus.getLastKnownLocation().getLat())));
            location.setLongitude(
                    new BigDecimal(df.format(currentVehicleTripStatus.getLastKnownLocation().getLon())));
        } else {
            location.setLatitude(new BigDecimal(df.format(currentVehicleTripStatus.getLocation().getLat())));
            location.setLongitude(new BigDecimal(df.format(currentVehicleTripStatus.getLocation().getLon())));
        }

        monitoredVehicleJourney.setVehicleLocation(location);

        // progress status
        List<String> progressStatuses = new ArrayList<String>();

        if (presentationService.isInLayover(currentVehicleTripStatus)) {
            progressStatuses.add("layover");
        }

        // "prevTrip" really means not on the framedvehiclejourney trip
        if (!framedJourneyTripBean.getId().equals(currentVehicleTripStatus.getActiveTrip().getId())) {
            progressStatuses.add("prevTrip");
        }

        if (!progressStatuses.isEmpty()) {
            NaturalLanguageStringStructure progressStatus = new NaturalLanguageStringStructure();
            progressStatus.setValue(StringUtils.join(progressStatuses, ","));
            monitoredVehicleJourney.setProgressStatus(progressStatus);
        }

        // block ref
        if (presentationService.isBlockLevelInference(currentVehicleTripStatus)) {
            BlockRefStructure blockRef = new BlockRefStructure();
            blockRef.setValue(framedJourneyTripBean.getBlockId());
            monitoredVehicleJourney.setBlockRef(blockRef);
        }

        // scheduled depature time
        if (presentationService.isBlockLevelInference(currentVehicleTripStatus) && (presentationService
                .isInLayover(currentVehicleTripStatus)
                || !framedJourneyTripBean.getId().equals(currentVehicleTripStatus.getActiveTrip().getId()))) {
            BlockStopTimeBean originDepartureStopTime = null;

            for (int t = 0; t < blockTrips.size(); t++) {
                BlockTripBean thisTrip = blockTrips.get(t);
                BlockTripBean nextTrip = null;
                if (t + 1 < blockTrips.size()) {
                    nextTrip = blockTrips.get(t + 1);
                }

                if (thisTrip.getTrip().getId().equals(currentVehicleTripStatus.getActiveTrip().getId())) {
                    // just started new trip
                    if (currentVehicleTripStatus.getDistanceAlongTrip() < (0.5
                            * currentVehicleTripStatus.getTotalDistanceAlongTrip())) {
                        originDepartureStopTime = thisTrip.getBlockStopTimes().get(0);

                        // at end of previous trip
                    } else {
                        if (nextTrip != null) {
                            originDepartureStopTime = nextTrip.getBlockStopTimes().get(0);
                        }
                    }

                    break;
                }
            }

            if (originDepartureStopTime != null) {
                Date departureTime = new Date(currentVehicleTripStatus.getServiceDate()
                        + (originDepartureStopTime.getStopTime().getDepartureTime() * 1000));
                monitoredVehicleJourney.setOriginAimedDepartureTime(departureTime);
            }
        }

        Map<String, TimepointPredictionRecord> stopIdToPredictionRecordMap = new HashMap<String, TimepointPredictionRecord>();

        // (build map of stop IDs to TPRs)
        if (stopLevelPredictions != null) {
            for (TimepointPredictionRecord tpr : stopLevelPredictions) {
                stopIdToPredictionRecordMap.put(AgencyAndId.convertToString(tpr.getTimepointId()), tpr);
            }
        }

        // monitored call
        if (!presentationService.isOnDetour(currentVehicleTripStatus))
            fillMonitoredCall(monitoredVehicleJourney, blockInstance, currentVehicleTripStatus,
                    monitoredCallStopBean, presentationService, nycTransitDataService, stopIdToPredictionRecordMap,
                    responseTimestamp);

        // onward calls
        if (!presentationService.isOnDetour(currentVehicleTripStatus))
            fillOnwardCalls(monitoredVehicleJourney, blockInstance, framedJourneyTripBean, currentVehicleTripStatus,
                    onwardCallsMode, presentationService, nycTransitDataService, stopIdToPredictionRecordMap,
                    maximumOnwardCalls, responseTimestamp);

        // situations
        fillSituations(monitoredVehicleJourney, currentVehicleTripStatus);

        return;
    }

    /***
     * PRIVATE STATIC METHODS
     */
    private static void fillOnwardCalls(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            BlockInstanceBean blockInstance, TripBean framedJourneyTripBean,
            TripStatusBean currentVehicleTripStatus, OnwardCallsMode onwardCallsMode,
            PresentationService presentationService, NycTransitDataService nycTransitDataService,
            Map<String, TimepointPredictionRecord> stopLevelPredictions, int maximumOnwardCalls,
            long responseTimestamp) {

        String tripIdOfMonitoredCall = framedJourneyTripBean.getId();

        monitoredVehicleJourney.setOnwardCalls(new OnwardCallsStructure());

        //////////

        // no need to go further if this is the case!
        if (maximumOnwardCalls == 0) {
            return;
        }

        List<BlockTripBean> blockTrips = blockInstance.getBlockConfiguration().getTrips();

        double distanceOfVehicleAlongBlock = 0;
        int blockTripStopsAfterTheVehicle = 0;
        int onwardCallsAdded = 0;

        boolean foundActiveTrip = false;
        for (int i = 0; i < blockTrips.size(); i++) {
            BlockTripBean blockTrip = blockTrips.get(i);

            if (!foundActiveTrip) {
                if (currentVehicleTripStatus.getActiveTrip().getId().equals(blockTrip.getTrip().getId())) {
                    distanceOfVehicleAlongBlock += currentVehicleTripStatus.getDistanceAlongTrip();

                    foundActiveTrip = true;
                } else {
                    // a block trip's distance along block is the *beginning* of that block trip along the block
                    // so to get the size of this one, we have to look at the next.
                    if (i + 1 < blockTrips.size()) {
                        distanceOfVehicleAlongBlock = blockTrips.get(i + 1).getDistanceAlongBlock();
                    }

                    // bus has already served this trip, so no need to go further
                    continue;
                }
            }

            if (onwardCallsMode == OnwardCallsMode.STOP_MONITORING) {
                // always include onward calls for the trip the monitored call is on ONLY.
                if (!blockTrip.getTrip().getId().equals(tripIdOfMonitoredCall)) {
                    continue;
                }
            }

            HashMap<String, Integer> visitNumberForStopMap = new HashMap<String, Integer>();
            for (BlockStopTimeBean stopTime : blockTrip.getBlockStopTimes()) {
                int visitNumber = getVisitNumber(visitNumberForStopMap, stopTime.getStopTime().getStop());

                // block trip stops away--on this trip, only after we've passed the stop, 
                // on future trips, count always.
                if (currentVehicleTripStatus.getActiveTrip().getId().equals(blockTrip.getTrip().getId())) {
                    if (stopTime.getDistanceAlongBlock() >= distanceOfVehicleAlongBlock) {
                        blockTripStopsAfterTheVehicle++;
                    } else {
                        // stop is behind the bus--no need to go further
                        continue;
                    }

                    // future trip--bus hasn't reached this trip yet, so count all stops
                } else {
                    blockTripStopsAfterTheVehicle++;
                }

                monitoredVehicleJourney.getOnwardCalls().getOnwardCall()
                        .add(getOnwardCallStructure(stopTime.getStopTime().getStop(), presentationService,
                                stopTime.getDistanceAlongBlock() - blockTrip.getDistanceAlongBlock(),
                                stopTime.getDistanceAlongBlock() - distanceOfVehicleAlongBlock, visitNumber,
                                blockTripStopsAfterTheVehicle - 1,
                                stopLevelPredictions.get(stopTime.getStopTime().getStop().getId()),
                                responseTimestamp));

                onwardCallsAdded++;

                if (onwardCallsAdded >= maximumOnwardCalls) {
                    return;
                }
            }

            // if we get here, we added our stops
            return;
        }

        return;
    }

    private static void fillMonitoredCall(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            BlockInstanceBean blockInstance, TripStatusBean tripStatus, StopBean monitoredCallStopBean,
            PresentationService presentationService, NycTransitDataService nycTransitDataService,
            Map<String, TimepointPredictionRecord> stopLevelPredictions, long responseTimestamp) {

        List<BlockTripBean> blockTrips = blockInstance.getBlockConfiguration().getTrips();

        double distanceOfVehicleAlongBlock = 0;
        int blockTripStopsAfterTheVehicle = 0;

        boolean foundActiveTrip = false;
        for (int i = 0; i < blockTrips.size(); i++) {
            BlockTripBean blockTrip = blockTrips.get(i);

            if (!foundActiveTrip) {
                if (tripStatus.getActiveTrip().getId().equals(blockTrip.getTrip().getId())) {
                    distanceOfVehicleAlongBlock += tripStatus.getDistanceAlongTrip();

                    foundActiveTrip = true;
                } else {
                    // a block trip's distance along block is the *beginning* of that block trip along the block
                    // so to get the size of this one, we have to look at the next.
                    if (i + 1 < blockTrips.size()) {
                        distanceOfVehicleAlongBlock = blockTrips.get(i + 1).getDistanceAlongBlock();
                    }

                    // bus has already served this trip, so no need to go further
                    continue;
                }
            }

            HashMap<String, Integer> visitNumberForStopMap = new HashMap<String, Integer>();

            for (BlockStopTimeBean stopTime : blockTrip.getBlockStopTimes()) {
                int visitNumber = getVisitNumber(visitNumberForStopMap, stopTime.getStopTime().getStop());

                // block trip stops away--on this trip, only after we've passed the stop, 
                // on future trips, count always.
                if (tripStatus.getActiveTrip().getId().equals(blockTrip.getTrip().getId())) {
                    if (stopTime.getDistanceAlongBlock() >= distanceOfVehicleAlongBlock) {
                        blockTripStopsAfterTheVehicle++;
                    } else {
                        // bus has passed this stop already--no need to go further
                        continue;
                    }

                    // future trip--bus hasn't reached this trip yet, so count all stops
                } else {
                    blockTripStopsAfterTheVehicle++;
                }

                // monitored call
                if (stopTime.getStopTime().getStop().getId().equals(monitoredCallStopBean.getId())) {
                    if (!presentationService.isOnDetour(tripStatus)) {
                        monitoredVehicleJourney.setMonitoredCall(
                                getMonitoredCallStructure(stopTime.getStopTime().getStop(), presentationService,
                                        stopTime.getDistanceAlongBlock() - blockTrip.getDistanceAlongBlock(),
                                        stopTime.getDistanceAlongBlock() - distanceOfVehicleAlongBlock, visitNumber,
                                        blockTripStopsAfterTheVehicle - 1,
                                        stopLevelPredictions.get(stopTime.getStopTime().getStop().getId()),
                                        responseTimestamp));
                    }

                    // we found our monitored call--stop
                    return;
                }
            }
        }
    }

    private static void fillSituations(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            TripStatusBean tripStatus) {
        if (tripStatus == null || tripStatus.getSituations() == null || tripStatus.getSituations().isEmpty()) {
            return;
        }

        List<SituationRefStructure> situationRef = monitoredVehicleJourney.getSituationRef();

        for (ServiceAlertBean situation : tripStatus.getSituations()) {
            SituationRefStructure sitRef = new SituationRefStructure();
            SituationSimpleRefStructure sitSimpleRef = new SituationSimpleRefStructure();
            sitSimpleRef.setValue(situation.getId());
            sitRef.setSituationSimpleRef(sitSimpleRef);
            situationRef.add(sitRef);
        }
    }

    private static OnwardCallStructure getOnwardCallStructure(StopBean stopBean,
            PresentationService presentationService, double distanceOfCallAlongTrip,
            double distanceOfVehicleFromCall, int visitNumber, int index, TimepointPredictionRecord prediction,
            long responseTimestamp) {

        OnwardCallStructure onwardCallStructure = new OnwardCallStructure();
        onwardCallStructure.setVisitNumber(BigInteger.valueOf(visitNumber));

        StopPointRefStructure stopPointRef = new StopPointRefStructure();
        stopPointRef.setValue(stopBean.getId());
        onwardCallStructure.setStopPointRef(stopPointRef);

        NaturalLanguageStringStructure stopPoint = new NaturalLanguageStringStructure();
        stopPoint.setValue(stopBean.getName());
        onwardCallStructure.setStopPointName(stopPoint);

        if (prediction != null) {
            if (prediction.getTimepointPredictedTime() < responseTimestamp) {
                onwardCallStructure.setExpectedArrivalTime(new Date(responseTimestamp));
                onwardCallStructure.setExpectedDepartureTime(new Date(responseTimestamp));
            } else {
                onwardCallStructure.setExpectedArrivalTime(new Date(prediction.getTimepointPredictedTime()));
                onwardCallStructure.setExpectedDepartureTime(new Date(prediction.getTimepointPredictedTime()));
            }
        }

        // siri extensions
        SiriExtensionWrapper wrapper = new SiriExtensionWrapper();
        ExtensionsStructure distancesExtensions = new ExtensionsStructure();
        SiriDistanceExtension distances = new SiriDistanceExtension();

        DecimalFormat df = new DecimalFormat();
        df.setMaximumFractionDigits(2);
        df.setGroupingUsed(false);

        distances.setStopsFromCall(index);
        distances.setCallDistanceAlongRoute(Double.valueOf(df.format(distanceOfCallAlongTrip)));
        distances.setDistanceFromCall(Double.valueOf(df.format(distanceOfVehicleFromCall)));
        distances.setPresentableDistance(presentationService.getPresentableDistance(distances));

        wrapper.setDistances(distances);
        distancesExtensions.setAny(wrapper);
        onwardCallStructure.setExtensions(distancesExtensions);

        return onwardCallStructure;
    }

    private static MonitoredCallStructure getMonitoredCallStructure(StopBean stopBean,
            PresentationService presentationService, double distanceOfCallAlongTrip,
            double distanceOfVehicleFromCall, int visitNumber, int index, TimepointPredictionRecord prediction,
            long responseTimestamp) {

        MonitoredCallStructure monitoredCallStructure = new MonitoredCallStructure();
        monitoredCallStructure.setVisitNumber(BigInteger.valueOf(visitNumber));

        StopPointRefStructure stopPointRef = new StopPointRefStructure();
        stopPointRef.setValue(stopBean.getId());
        monitoredCallStructure.setStopPointRef(stopPointRef);

        NaturalLanguageStringStructure stopPoint = new NaturalLanguageStringStructure();
        stopPoint.setValue(stopBean.getName());
        monitoredCallStructure.setStopPointName(stopPoint);

        if (prediction != null) {
            // do not allow predicted times to be less than ResponseTimestamp
            if (prediction.getTimepointPredictedTime() < responseTimestamp) {
                /*
                 * monitoredCall has less precision than onwardCall (date vs. timestamp)
                 * which results in a small amount of error when converting back to timestamp.
                 * Add a second here to prevent negative values from showing up in the UI 
                 * (actual precision of the value is 1 minute, so a second has little influence)
                 */
                monitoredCallStructure.setExpectedArrivalTime(new Date(responseTimestamp + 1000));
                monitoredCallStructure.setExpectedDepartureTime(new Date(responseTimestamp + 1000));
            } else {
                monitoredCallStructure.setExpectedArrivalTime(new Date(prediction.getTimepointPredictedTime()));
                monitoredCallStructure.setExpectedDepartureTime(new Date(prediction.getTimepointPredictedTime()));
            }

        }

        // siri extensions
        SiriExtensionWrapper wrapper = new SiriExtensionWrapper();
        ExtensionsStructure distancesExtensions = new ExtensionsStructure();
        SiriDistanceExtension distances = new SiriDistanceExtension();

        DecimalFormat df = new DecimalFormat();
        df.setMaximumFractionDigits(2);
        df.setGroupingUsed(false);

        distances.setStopsFromCall(index);
        distances.setCallDistanceAlongRoute(Double.valueOf(df.format(distanceOfCallAlongTrip)));
        distances.setDistanceFromCall(Double.valueOf(df.format(distanceOfVehicleFromCall)));
        distances.setPresentableDistance(presentationService.getPresentableDistance(distances));

        wrapper.setDistances(distances);
        distancesExtensions.setAny(wrapper);
        monitoredCallStructure.setExtensions(distancesExtensions);

        return monitoredCallStructure;
    }

    private static int getVisitNumber(HashMap<String, Integer> visitNumberForStop, StopBean stop) {
        int visitNumber;

        if (visitNumberForStop.containsKey(stop.getId())) {
            visitNumber = visitNumberForStop.get(stop.getId()) + 1;
        } else {
            visitNumber = 1;
        }

        visitNumberForStop.put(stop.getId(), visitNumber);

        return visitNumber;
    }

    private static ProgressRateEnumeration getProgressRateForPhaseAndStatus(String status, String phase) {
        if (phase == null) {
            return ProgressRateEnumeration.UNKNOWN;
        }

        if (phase.toLowerCase().startsWith("layover") || phase.toLowerCase().startsWith("deadhead")
                || phase.toLowerCase().equals("at_base")) {
            return ProgressRateEnumeration.NO_PROGRESS;
        }

        if (status != null && status.toLowerCase().equals("stalled")) {
            return ProgressRateEnumeration.NO_PROGRESS;
        }

        if (phase.toLowerCase().equals("in_progress")) {
            return ProgressRateEnumeration.NORMAL_PROGRESS;
        }

        return ProgressRateEnumeration.UNKNOWN;
    }

}