org.onebusaway.api.actions.siri.impl.SiriSupportV2.java Source code

Java tutorial

Introduction

Here is the source code for org.onebusaway.api.actions.siri.impl.SiriSupportV2.java

Source

/**
 * Copyright (C) 2016 Cambridge Systematics, Inc.
 *
 * 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.api.actions.siri.impl;

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

import org.apache.commons.lang.StringUtils;
import org.onebusaway.api.actions.siri.model.DetailLevel;
import org.onebusaway.api.actions.siri.model.RouteDirection;
import org.onebusaway.api.actions.siri.model.RouteForDirection;
import org.onebusaway.api.actions.siri.model.RouteResult;
import org.onebusaway.api.actions.siri.model.StopOnRoute;
import org.onebusaway.api.actions.siri.model.StopRouteDirection;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.presentation.impl.AgencySupportLibrary;
import org.onebusaway.presentation.impl.DateUtil;
import org.onebusaway.presentation.services.realtime.PresentationService;
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 org.onebusaway.transit_data.services.TransitDataService;
import org.onebusaway.transit_data_federation.siri.SiriPolyLinesExtension;
import org.onebusaway.transit_data_federation.siri.SiriUpcomingServiceExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import uk.org.siri.siri_2.AnnotatedDestinationStructure;
import uk.org.siri.siri_2.AnnotatedLineStructure;
import uk.org.siri.siri_2.AnnotatedLineStructure.Destinations;
import uk.org.siri.siri_2.AnnotatedLineStructure.Directions;
import uk.org.siri.siri_2.AnnotatedStopPointStructure;
import uk.org.siri.siri_2.AnnotatedStopPointStructure.Lines;
import uk.org.siri.siri_2.BlockRefStructure;
import uk.org.siri.siri_2.DataFrameRefStructure;
import uk.org.siri.siri_2.DestinationRefStructure;
import uk.org.siri.siri_2.DirectionRefStructure;
import uk.org.siri.siri_2.ExtensionsStructure;
import uk.org.siri.siri_2.FramedVehicleJourneyRefStructure;
import uk.org.siri.siri_2.JourneyPatternRefStructure;
import uk.org.siri.siri_2.JourneyPlaceRefStructure;
import uk.org.siri.siri_2.LineDirectionStructure;
import uk.org.siri.siri_2.LineRefStructure;
import uk.org.siri.siri_2.LocationStructure;
import uk.org.siri.siri_2.MonitoredCallStructure;
import uk.org.siri.siri_2.MonitoredVehicleJourneyStructure;
import uk.org.siri.siri_2.NaturalLanguageStringStructure;
import uk.org.siri.siri_2.OnwardCallStructure;
import uk.org.siri.siri_2.OnwardCallsStructure;
import uk.org.siri.siri_2.OperatorRefStructure;
import uk.org.siri.siri_2.ProgressRateEnumeration;
import uk.org.siri.siri_2.RouteDirectionStructure;
import uk.org.siri.siri_2.RouteDirectionStructure.JourneyPatterns;
import uk.org.siri.siri_2.RouteDirectionStructure.JourneyPatterns.JourneyPattern;
import uk.org.siri.siri_2.RouteDirectionStructure.JourneyPatterns.JourneyPattern.StopsInPattern;
import uk.org.siri.siri_2.SituationRefStructure;
import uk.org.siri.siri_2.SituationSimpleRefStructure;
import uk.org.siri.siri_2.StopPointInPatternStructure;
import uk.org.siri.siri_2.StopPointRefStructure;
import uk.org.siri.siri_2.VehicleModesEnumeration;
import uk.org.siri.siri_2.VehicleRefStructure;

import static org.apache.commons.lang.StringUtils.isBlank;

public final class SiriSupportV2 {

    private static Logger _log = LoggerFactory.getLogger(SiriSupportV2.class);

    public enum OnwardCallsMode {
        VEHICLE_MONITORING, STOP_MONITORING
    }

    public enum Filters {
        DIRECTION_REF, OPERATOR_REF, LINE_REF, USE_LINE_REF, UPCOMING_SCHEDULED_SERVICE, DETAIL_LEVEL, MAX_STOP_VISITS, MIN_STOP_VISITS, INCLUDE_POLYLINES
    }

    /**
     * NOTE: The tripDetails bean here may not be for the trip the vehicle is
     * currently on in the case of A-D for stop!
     * @param filters 
     */

    public static void fillMonitoredVehicleJourney(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            TripBean framedJourneyTripBean, TripStatusBean currentVehicleTripStatus, StopBean monitoredCallStopBean,
            OnwardCallsMode onwardCallsMode, PresentationService presentationService,
            TransitDataService transitDataService, int maximumOnwardCalls,
            List<TimepointPredictionRecord> stopLevelPredictions, boolean hasRealtimeData, DetailLevel detailLevel,
            long responseTimestamp, Map<Filters, String> filters) {
        BlockInstanceBean blockInstance = transitDataService.getBlockInstance(
                currentVehicleTripStatus.getActiveTrip().getBlockId(), currentVehicleTripStatus.getServiceDate());

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

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

        /**********************************************/

        //Route ID
        LineRefStructure lineRef = new LineRefStructure();
        lineRef.setValue(framedJourneyTripBean.getRoute().getId());

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

        //Route Short Name
        NaturalLanguageStringStructure routeShortName = new NaturalLanguageStringStructure();
        String shortName = framedJourneyTripBean.getRoute().getShortName();
        if (!isBlank(currentVehicleTripStatus.getActiveTrip().getRouteShortName())) {
            // look for an override like an express desginator
            shortName = currentVehicleTripStatus.getActiveTrip().getRouteShortName();
        }
        if (shortName == null) {
            shortName = framedJourneyTripBean.getRoute().getId().split("_")[1];
        }
        routeShortName.setValue(shortName);

        //Agency Id
        OperatorRefStructure operatorRef = new OperatorRefStructure();
        operatorRef.setValue(AgencySupportLibrary.getAgencyForId(framedJourneyTripBean.getRoute().getId()));

        //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());

        //Shape Id
        JourneyPatternRefStructure journeyPattern = new JourneyPatternRefStructure();
        journeyPattern.setValue(framedJourneyTripBean.getShapeId());

        //Destination
        NaturalLanguageStringStructure headsign = new NaturalLanguageStringStructure();
        headsign.setValue(framedJourneyTripBean.getTripHeadsign());

        // Vehicle Id
        VehicleRefStructure vehicleRef = new VehicleRefStructure();
        if (currentVehicleTripStatus.getVehicleId() == null) {
            String tripId = framedJourneyTripBean.getId();
            String blockId = framedJourneyTripBean.getBlockId();
            String directionId = framedJourneyTripBean.getDirectionId();
            String vehicleIdHash = Integer.toString((tripId + blockId + directionId).hashCode());
            String agencyName = tripId.split("_")[0];
            String vehicleId = agencyName + "_" + vehicleIdHash;

            vehicleRef.setValue(vehicleId);
        } else {
            vehicleRef.setValue(currentVehicleTripStatus.getVehicleId());
        }

        // Set Origin and Destination stops from Block trips. 
        StopBean lastStop = new StopBean();
        JourneyPlaceRefStructure origin = new JourneyPlaceRefStructure();

        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();

                origin.setValue(stops.get(0).getStopTime().getStop().getId());

                lastStop = stops.get(stops.size() - 1).getStopTime().getStop();
                break;
            }
        }

        // 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())));
        }

        // 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.getProgressStatus().add(progressStatus);
        }

        // 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) {
                long departureTime = currentVehicleTripStatus.getServiceDate()
                        + (originDepartureStopTime.getStopTime().getDepartureTime() * 1000);
                monitoredVehicleJourney.setOriginAimedDepartureTime(DateUtil.toXmlGregorianCalendar(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, transitDataService, stopIdToPredictionRecordMap,
                    hasRealtimeData, detailLevel, responseTimestamp);

        // detail level - minimal
        if (detailLevel.equals(DetailLevel.MINIMUM) || detailLevel.equals(DetailLevel.BASIC)
                || detailLevel.equals(DetailLevel.NORMAL) || detailLevel.equals(DetailLevel.CALLS)) {

            monitoredVehicleJourney.getPublishedLineName().add(routeShortName);
            monitoredVehicleJourney.getDestinationName().add(headsign);
            monitoredVehicleJourney.setMonitored(currentVehicleTripStatus.isPredicted());
            monitoredVehicleJourney.getVehicleMode().add(toVehicleMode(currentVehicleTripStatus.getVehicleType()));
            monitoredVehicleJourney.setVehicleRef(vehicleRef);
            monitoredVehicleJourney.setBearing((float) currentVehicleTripStatus.getOrientation());
            monitoredVehicleJourney.setVehicleLocation(location);
        }

        // detail level - basic
        if (detailLevel.equals(DetailLevel.BASIC) || detailLevel.equals(DetailLevel.NORMAL)
                || detailLevel.equals(DetailLevel.CALLS)) {
            monitoredVehicleJourney.setFramedVehicleJourneyRef(framedJourney);
            monitoredVehicleJourney.setDirectionRef(directionRef);

            // since LineRef is fully qualified with operatorref, moving OperatorRef to normal detail
            //monitoredVehicleJourney.setOperatorRef(operatorRef);

            DestinationRefStructure dest = new DestinationRefStructure();
            dest.setValue(lastStop.getId());
            monitoredVehicleJourney.setDestinationRef(dest);

            monitoredVehicleJourney.setLineRef(lineRef);
            monitoredVehicleJourney.setProgressRate(getProgressRateForPhaseAndStatus(
                    currentVehicleTripStatus.getStatus(), currentVehicleTripStatus.getPhase()));
        }

        // detail level - normal
        if (detailLevel.equals(DetailLevel.NORMAL) || detailLevel.equals(DetailLevel.CALLS)) {
            monitoredVehicleJourney.setOperatorRef(operatorRef);
            // block ref
            if (presentationService.isBlockLevelInference(currentVehicleTripStatus)) {
                BlockRefStructure blockRef = new BlockRefStructure();
                blockRef.setValue(framedJourneyTripBean.getBlockId());
                monitoredVehicleJourney.setBlockRef(blockRef);
            }

            monitoredVehicleJourney.setOriginRef(origin);
            monitoredVehicleJourney.setJourneyPatternRef(journeyPattern);
        }

        // onward calls
        if (detailLevel.equals(DetailLevel.CALLS)) {
            if (!presentationService.isOnDetour(currentVehicleTripStatus))
                fillOnwardCalls(monitoredVehicleJourney, blockInstance, framedJourneyTripBean,
                        currentVehicleTripStatus, onwardCallsMode, presentationService, transitDataService,
                        stopIdToPredictionRecordMap, maximumOnwardCalls, responseTimestamp);
        }

        // situations
        fillSituations(monitoredVehicleJourney, currentVehicleTripStatus);

        return;
    }

    public static VehicleModesEnumeration toVehicleMode(String typeString) {
        VehicleModesEnumeration mode;
        if (typeString == null) {
            mode = VehicleModesEnumeration.BUS;
            return mode;
        }

        switch (typeString) {
        case "bus":
            mode = VehicleModesEnumeration.BUS;
            break;
        case "light_rail":
            mode = VehicleModesEnumeration.TRAM;
            break;
        case "rail":
            mode = VehicleModesEnumeration.RAIL;
            break;
        case "ferry":
            mode = VehicleModesEnumeration.FERRY;
            break;
        default:
            _log.error("Unknown vehicleMode " + typeString + ", defaulting to BUS");
            mode = VehicleModesEnumeration.BUS;
        }
        return mode;
    }

    public static boolean fillAnnotatedStopPointStructure(AnnotatedStopPointStructure annotatedStopPoint,
            StopRouteDirection stopRouteDirection, Map<Filters, String> filters, DetailLevel detailLevel,
            long currentTime) {

        StopBean stopBean = stopRouteDirection.getStop();
        List<RouteForDirection> routeDirections = stopRouteDirection.getRouteDirections();

        // Set Stop Name
        NaturalLanguageStringStructure stopName = new NaturalLanguageStringStructure();
        stopName.setValue(stopBean.getName());

        // Set Route and Direction
        Lines lines = new Lines();

        for (RouteForDirection routeDirection : routeDirections) {

            String directionId = routeDirection.getDirectionId();
            String routeId = routeDirection.getRouteId();

            LineRefStructure line = new LineRefStructure();
            line.setValue(routeId);

            DirectionRefStructure direction = new DirectionRefStructure();
            direction.setValue(directionId);

            LineDirectionStructure lineDirection = new LineDirectionStructure();
            lineDirection.setDirectionRef(direction);
            lineDirection.setLineRef(line);

            lines.getLineRefOrLineDirection().add(lineDirection);

        }

        // Set Lat and Lon
        BigDecimal stopLat = new BigDecimal(stopBean.getLat());
        BigDecimal stopLon = new BigDecimal(stopBean.getLon());

        LocationStructure location = new LocationStructure();
        location.setLongitude(stopLon.setScale(6, BigDecimal.ROUND_HALF_DOWN));
        location.setLatitude(stopLat.setScale(6, BigDecimal.ROUND_HALF_DOWN));

        // Set StopId
        StopPointRefStructure stopPointRef = new StopPointRefStructure();
        stopPointRef.setValue(stopBean.getId());

        // Details -- minimum
        annotatedStopPoint.getStopName().add(stopName);

        // Details -- normal
        if (detailLevel.equals(DetailLevel.NORMAL) || detailLevel.equals(DetailLevel.FULL)) {
            annotatedStopPoint.setLocation(location);
            annotatedStopPoint.setLines(lines);
            annotatedStopPoint.setMonitored(true);
        }

        annotatedStopPoint.setStopPointRef(stopPointRef);

        return true;
    }

    public static boolean fillAnnotatedLineStructure(AnnotatedLineStructure annotatedLineStructure,
            RouteResult routeResult, Map<Filters, String> filters, DetailLevel detailLevel, long currentTime) {

        Directions directions = new Directions();

        // Set Line Value
        LineRefStructure line = new LineRefStructure();
        line.setValue(routeResult.getId());

        NaturalLanguageStringStructure lineName = new NaturalLanguageStringStructure();
        lineName.setValue(routeResult.getShortName());

        // DETAIL - minimum: Return only the name and identifier of stops
        //ideally, this would return only stops with scheduled service
        annotatedLineStructure.setLineRef(line);
        annotatedLineStructure.getLineName().add(lineName);
        annotatedLineStructure.setDirections(directions);
        annotatedLineStructure.setMonitored(true);

        // Loop through Direction Ids
        for (RouteDirection direction : routeResult.getDirections()) {

            // Check for existing stops in direction
            if (direction == null | direction.getStops().size() == 0)
                continue;

            String directionId = direction.getDirectionId();

            // Journey patterns - holds stop points for direction
            JourneyPattern pattern = new JourneyPattern();
            JourneyPatterns patterns = new JourneyPatterns();

            // Directions
            DirectionRefStructure dirRefStructure = new DirectionRefStructure();
            dirRefStructure.setValue(directionId);

            RouteDirectionStructure routeDirectionStructure = new RouteDirectionStructure();
            NaturalLanguageStringStructure directionName = new NaturalLanguageStringStructure();

            directionName.setValue(direction.getDestination());
            routeDirectionStructure.getDirectionName().add(directionName);
            directions.getDirection().add(routeDirectionStructure);

            // Destination
            Destinations destinations = new Destinations();
            AnnotatedDestinationStructure annotatedDest = new AnnotatedDestinationStructure();
            DestinationRefStructure destRef = new DestinationRefStructure();
            destRef.setValue(direction.getDestination());
            annotatedDest.setDestinationRef(destRef);
            destinations.getDestination().add(annotatedDest);

            // Stops
            StopsInPattern stopsInPattern = new StopsInPattern();
            List<StopOnRoute> scheduledStops = new ArrayList<StopOnRoute>();
            List<StopOnRoute> allStops = new ArrayList<StopOnRoute>();

            // Loop through StopOnRoute for particular Direction Id   
            // Categorize by Scheduled and Unscheduled Stops
            for (StopOnRoute stop : direction.getStops()) {
                if (stop.getHasUpcomingScheduledStop() != null && stop.getHasUpcomingScheduledStop())
                    scheduledStops.add(stop);

                allStops.add(stop);
            }

            // DETAIL -- normal: Return name, identifier and coordinates of the stop.??
            // my interpretation is that normal returns the list of stops with coordinates and their polylines
            //ideally, this would return only stops with scheduled service

            if (detailLevel.equals(DetailLevel.NORMAL)) {

                for (int i = 0; i < scheduledStops.size(); i++) {

                    StopOnRoute stop = direction.getStops().get(i);

                    BigDecimal stopLat = new BigDecimal(stop.getLatitude());
                    BigDecimal stopLon = new BigDecimal(stop.getLongitude());

                    LocationStructure location = new LocationStructure();
                    location.setLongitude(stopLon.setScale(6, BigDecimal.ROUND_HALF_DOWN));
                    location.setLatitude(stopLat.setScale(6, BigDecimal.ROUND_HALF_DOWN));

                    StopPointInPatternStructure pointInPattern = new StopPointInPatternStructure();
                    pointInPattern.setLocation(location);
                    pointInPattern.setOrder(BigInteger.valueOf(i));
                    NaturalLanguageStringStructure stopName = new NaturalLanguageStringStructure();
                    stopName.setValue(stop.getName());
                    pointInPattern.getStopName().add(stopName);

                    StopPointRefStructure spr = new StopPointRefStructure();
                    spr.setValue(stop.getId());

                    stopsInPattern.getStopPointInPattern().add(pointInPattern);
                }

            }

            // DETAIL -- stops: Return name, identifier and coordinates of the stop.??
            // my interpretation is that normal returns the list of stops with coordinates and their polylines
            //ideally, this would return both stops with scheduled and unscheduled service

            if (detailLevel.equals(DetailLevel.STOPS) || detailLevel.equals(DetailLevel.FULL)) {
                for (int i = 0; i < allStops.size(); i++) {

                    StopOnRoute stop = direction.getStops().get(i);
                    Boolean hasUpcomingScheduledService = stop.getHasUpcomingScheduledStop();

                    BigDecimal stopLat = new BigDecimal(stop.getLatitude());
                    BigDecimal stopLon = new BigDecimal(stop.getLongitude());

                    LocationStructure location = new LocationStructure();
                    location.setLongitude(stopLon.setScale(6, BigDecimal.ROUND_HALF_DOWN));
                    location.setLatitude(stopLat.setScale(6, BigDecimal.ROUND_HALF_DOWN));

                    StopPointRefStructure spr = new StopPointRefStructure();
                    spr.setValue(stop.getId());

                    StopPointInPatternStructure pointInPattern = new StopPointInPatternStructure();
                    pointInPattern.setLocation(location);
                    pointInPattern.setOrder(BigInteger.valueOf(i));
                    NaturalLanguageStringStructure stopName = new NaturalLanguageStringStructure();
                    stopName.setValue(stop.getName());
                    pointInPattern.getStopName().add(stopName);
                    pointInPattern.setStopPointRef(spr);

                    stopsInPattern.getStopPointInPattern().add(pointInPattern);

                    // HasUpcomingService Extension
                    SiriUpcomingServiceExtension upcomingService = new SiriUpcomingServiceExtension();
                    upcomingService.setUpcomingScheduledService(hasUpcomingScheduledService);

                    ExtensionsStructure upcomingServiceExtensions = new ExtensionsStructure();
                    upcomingServiceExtensions.setAny(upcomingService);
                    pointInPattern.setExtensions(upcomingServiceExtensions);
                }
            }

            String includePolylineFilter = filters.get(Filters.INCLUDE_POLYLINES);
            if (includePolylineFilter != null && passFilter("true", includePolylineFilter)) {
                // Polyline Extension
                SiriPolyLinesExtension polylines = new SiriPolyLinesExtension();
                for (String polyline : direction.getPolylines()) {
                    polylines.getPolylines().add(polyline);
                }

                ExtensionsStructure PolylineExtension = new ExtensionsStructure();
                PolylineExtension.setAny(polylines);
                routeDirectionStructure.setExtensions(PolylineExtension);
            }

            routeDirectionStructure.setJourneyPatterns(patterns);
            pattern.setStopsInPattern(stopsInPattern);
            patterns.getJourneyPattern().add(pattern);
            routeDirectionStructure.setDirectionRef(dirRefStructure);

        }

        return true;
    }

    /***
     * PRIVATE STATIC METHODS
     */

    private static void fillOnwardCalls(MonitoredVehicleJourneyStructure monitoredVehicleJourney,
            BlockInstanceBean blockInstance, TripBean framedJourneyTripBean,
            TripStatusBean currentVehicleTripStatus, OnwardCallsMode onwardCallsMode,
            PresentationService presentationService, TransitDataService transitDataService,
            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, TransitDataService transitDataService,
            Map<String, TimepointPredictionRecord> stopLevelPredictions, boolean hasRealtimeData,
            DetailLevel detailLevel, 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())) {
                    double distanceAlongTrip = tripStatus.getDistanceAlongTrip();
                    if (!hasRealtimeData) {
                        distanceAlongTrip = tripStatus.getScheduledDistanceAlongTrip();
                    }
                    distanceOfVehicleAlongBlock += distanceAlongTrip;

                    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()),
                                        detailLevel, 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.getStopPointName().add(stopPoint);

        if (prediction != null) {
            if (prediction.getTimepointPredictedArrivalTime() < responseTimestamp) {
                // TODO - LCARABALLO - should this be setExpectedArrivalTime?
                onwardCallStructure.setExpectedArrivalTime(DateUtil.toXmlGregorianCalendar(responseTimestamp));
                onwardCallStructure.setExpectedDepartureTime(DateUtil.toXmlGregorianCalendar(responseTimestamp));
            } else {
                onwardCallStructure.setExpectedArrivalTime(
                        DateUtil.toXmlGregorianCalendar(prediction.getTimepointPredictedArrivalTime()));
                onwardCallStructure.setExpectedDepartureTime(
                        DateUtil.toXmlGregorianCalendar(prediction.getTimepointPredictedArrivalTime()));
            }
        }

        // Distances
        NaturalLanguageStringStructure presentableDistance = new NaturalLanguageStringStructure();
        presentableDistance.setValue(presentationService.getPresentableDistance(distanceOfVehicleFromCall, index));

        onwardCallStructure.setNumberOfStopsAway(BigInteger.valueOf(index));
        onwardCallStructure.setDistanceFromStop(new BigDecimal(distanceOfVehicleFromCall).toBigInteger());
        onwardCallStructure.setArrivalProximityText(presentableDistance);

        // siri extensions
        // TODO - LCARABALLO - Distance Along Route Might Still need Extension
        /*SiriExtensionWrapper wrapper = new SiriExtensionWrapper();
        ExtensionsStructure distancesExtensions = new ExtensionsStructure();
        SiriDistanceExtension distances = new SiriDistanceExtension();
            
        DecimalFormat df = new DecimalFormat();
        df.setMaximumFractionDigits(2);
        df.setGroupingUsed(false);
            
        distances.setCallDistanceAlongRoute(Double.valueOf(df
            .format(distanceOfCallAlongTrip)));
            
        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,
            DetailLevel detailLevel, long responseTimestamp) {

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

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

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

        if (prediction != null) {
            // do not allow predicted times to be less than ResponseTimestamp
            if (prediction.getTimepointPredictedArrivalTime() < 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(DateUtil.toXmlGregorianCalendar(responseTimestamp + 1000));
                monitoredCallStructure
                        .setExpectedDepartureTime(DateUtil.toXmlGregorianCalendar(responseTimestamp + 1000));
            } else {
                monitoredCallStructure.setExpectedArrivalTime(
                        DateUtil.toXmlGregorianCalendar(prediction.getTimepointPredictedArrivalTime()));
                monitoredCallStructure.setExpectedDepartureTime(
                        DateUtil.toXmlGregorianCalendar(prediction.getTimepointPredictedArrivalTime()));
            }

        }

        // siri extensions
        // TODO - LCARABALLO - Distance Along Route Might Still need Extension
        /*SiriExtensionWrapper wrapper = new SiriExtensionWrapper();
        ExtensionsStructure distancesExtensions = new ExtensionsStructure();
        SiriDistanceExtension distances = new SiriDistanceExtension();
            
        DecimalFormat df = new DecimalFormat();
        df.setMaximumFractionDigits(2);
        df.setGroupingUsed(false);
            
        distances.setCallDistanceAlongRoute(Double.valueOf(df
            .format(distanceOfCallAlongTrip)));
            
        wrapper.setDistances(distances);
        distancesExtensions.setAny(wrapper);
        monitoredCallStructure.setExtensions(distancesExtensions);*/

        // distances
        NaturalLanguageStringStructure presentableDistance = new NaturalLanguageStringStructure();
        presentableDistance.setValue(presentationService.getPresentableDistance(distanceOfVehicleFromCall, index));

        monitoredCallStructure.setNumberOfStopsAway(BigInteger.valueOf(index));
        monitoredCallStructure.setDistanceFromStop(new BigDecimal(distanceOfVehicleFromCall).toBigInteger());
        monitoredCallStructure.setArrivalProximityText(presentableDistance);

        // basic 
        if (detailLevel.equals(DetailLevel.BASIC) || detailLevel.equals(DetailLevel.NORMAL)
                || detailLevel.equals(DetailLevel.CALLS)) {
            monitoredCallStructure.getStopPointName().add(stopPoint);
        }

        // normal
        if (detailLevel.equals(DetailLevel.NORMAL) || detailLevel.equals(DetailLevel.CALLS)) {
            monitoredCallStructure.setStopPointRef(stopPointRef);
        }

        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;
    }

    public static boolean passFilter(String value, String filterValue) {
        if (StringUtils.isNotBlank(filterValue) && !value.equalsIgnoreCase(filterValue.trim()))
            return false;

        return true;
    }

    public static Integer convertToNumeric(String param, Integer defaultValue) {
        Integer numericValue = defaultValue;
        try {
            numericValue = Integer.parseInt(param);
        } catch (NumberFormatException e) {
            numericValue = defaultValue;
        }
        return numericValue;
    }
}