org.transitime.avl.NextBusAvlModule.java Source code

Java tutorial

Introduction

Here is the source code for org.transitime.avl.NextBusAvlModule.java

Source

/* 
 * This file is part of Transitime.org
 * 
 * Transitime.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * Transitime.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Transitime.org .  If not, see <http://www.gnu.org/licenses/>.
 */
package org.transitime.avl;

import java.util.List;

import org.jdom2.Document;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.transitime.config.BooleanConfigValue;
import org.transitime.config.StringConfigValue;
import org.transitime.db.structs.AvlReport;
import org.transitime.db.structs.AvlReport.AssignmentType;
import org.transitime.modules.Module;
import org.transitime.utils.Geo;
import org.transitime.utils.MathUtils;
import org.transitime.utils.Time;

/**
 * Reads AVL data from a NextBus AVL feed and processes each AVL report.
 * 
 * @author SkiBu Smith
 *
 */
public class NextBusAvlModule extends XmlPollingAvlModule {

    // Parameter that specifies URL of the NextBus feed.
    // Note that uses the old NextBus feed since that provides 
    // non-predictable vehicles, block assignment, driver ID, and other info.
    // If the old NextBus feed goes away then can use the private one
    // /service/xmlFeed .

    private static StringConfigValue nextBusFeedUrl = new StringConfigValue("transitime.avl.nextbus.url",
            "http://webservices.nextbus.com/s/xmlFeed", "The URL of the NextBus feed to use.");

    private static String getNextBusFeedUrl() {
        return nextBusFeedUrl.getValue();
    }

    // Config param that specifies the agency name to use as part
    // of the NextBus feed URL.
    private static StringConfigValue agencyNameForFeed = new StringConfigValue(
            "transitime.avl.nextbus.agencyNameForFeed", "If set then specifies the agency name to use for the "
                    + "feed. If not set then the transitime.core.agencyId " + "is used.");

    private static String getAgencyNameForFeed() {
        return agencyNameForFeed.getValue();
    }

    private static BooleanConfigValue useTripShortNameForAssignment = new BooleanConfigValue(
            "transitime.avl.nextbus.useTripShortNameForAssignment", false,
            "For some agencies the block info in the feed doesn't "
                    + "match the GTFS data. For these can sometimes use the " + "trip short name instead.");

    // So can just get data since last query. Initialize so when first called
    // get data for last 10 minutes. Definitely don't want to use t=0 because
    // then can end up with some really old reports.
    private long previousTime = System.currentTimeMillis() - 10 * Time.MS_PER_MIN;

    private static final Logger logger = LoggerFactory.getLogger(NextBusAvlModule.class);

    /********************** Member Functions **************************/

    /**
     * Constructor
     * @param agencyId
     */
    public NextBusAvlModule(String agencyId) {
        super(agencyId);
    }

    /**
     * Feed specific URL to use when accessing data.
     */
    @Override
    protected String getUrl() {
        // Determine the URL to use.
        String url = getNextBusFeedUrl();
        String agencyStr = getAgencyNameForFeed() != null ? getAgencyNameForFeed() : getAgencyId();
        String queryStr = "?command=vehicleLocations" + "&a=" + agencyStr + "&details=true" + // So get the more detailed info
                "&t=" + previousTime;
        return url + queryStr;
    }

    /**
     * Extracts the AVL data from the XML document.
     * Uses JDOM to parse the XML because it makes the Java code much simpler.
     * @param doc
     * @throws NumberFormatException
     */
    @Override
    protected void extractAvlData(Document doc) throws NumberFormatException {
        logger.info("Extracting data from xml file");

        // Get root of doc
        Element rootNode = doc.getRootElement();

        // Handle any error message
        Element error = rootNode.getChild("Error");
        if (error != null) {
            String errorStr = error.getTextNormalize();
            logger.error("While processing AVL data in NextBusAvlModule: " + errorStr);
            return;
        }

        // Handle getting last time. This is the system time of the server.
        // This means it can be used to along with secsSinceReport to determine
        // the epoch time when the GPS report was generated.
        Element lastTime = rootNode.getChild("lastTime");
        if (lastTime != null) {
            String lastTimeStr = lastTime.getAttributeValue("time");
            // Store previous time so that it can be used in the URL
            // the next time the feed is polled.
            previousTime = Long.parseLong(lastTimeStr);
            logger.debug("PreviousTime={}", Time.dateTimeStr(previousTime));
        }

        // Handle getting vehicle location data
        List<Element> vehicles = rootNode.getChildren("vehicle");
        for (Element vehicle : vehicles) {
            String vehicleId = vehicle.getAttributeValue("id");
            float lat = Float.parseFloat(vehicle.getAttributeValue("lat"));
            float lon = Float.parseFloat(vehicle.getAttributeValue("lon"));

            // Determine GPS time. Use the previousTime read from the feed
            // because it indicates what the secsSinceReport is relative to
            int secsSinceReport = Integer.parseInt(vehicle.getAttributeValue("secsSinceReport"));
            long gpsEpochTime = previousTime - secsSinceReport * 1000;

            // Handle the speed
            float speed = Float.NaN;
            String speedStr = vehicle.getAttributeValue("speedKmHr");
            if (speedStr != null)
                speed = Geo.converKmPerHrToMetersPerSecond(Float.parseFloat(speedStr));

            // Handle heading
            float heading = Float.NaN;
            String headingStr = vehicle.getAttributeValue("heading");
            if (headingStr != null) {
                heading = Float.parseFloat(headingStr);
                // Heading less than 0 means it is invalid
                if (heading < 0)
                    heading = Float.NaN;
            }

            // Get block ID. Since for some feeds the block ID from the feed
            // doesn't match the GTFS data need to process the block ID.
            String blockId = processBlockId(vehicle.getAttributeValue("block"));

            String tripId = vehicle.getAttributeValue("tripTag");

            // Determine if part of consist
            String leadingVehicleId = vehicle.getAttributeValue("leadingVehicleId");

            // Get driver ID. Be consistent about using null if not set 
            // instead of empty string
            String driverId = vehicle.getAttributeValue("driverId");
            if (driverId != null && driverId.length() == 0)
                driverId = null;

            // Get passenger count
            Integer passengerCount = null;
            String passengerCountStr = vehicle.getAttributeValue("passengerCount");
            if (passengerCountStr != null) {
                passengerCount = Integer.parseInt(passengerCountStr);
                if (passengerCount < 0)
                    passengerCount = 0;
            }

            // Log raw info for debugging
            logger.debug(
                    "vehicleId={} time={} lat={} lon={} spd={} head={} " + "blk={} leadVeh={} drvr={} psngCnt={}",
                    vehicleId, Time.timeStrMsec(gpsEpochTime), lat, lon, speed, heading, blockId, leadingVehicleId,
                    driverId, passengerCount);

            // Create the AVL object and send it to the JMS topic.
            // The NextBus feed provides silly amount of precision so 
            // round to just 5 decimal places.
            AvlReport avlReport = new AvlReport(vehicleId, gpsEpochTime, MathUtils.round(lat, 5),
                    MathUtils.round(lon, 5), speed, heading, "NextBus", leadingVehicleId, driverId, null, // license plate
                    passengerCount, Float.NaN); // passengerFullness

            // Record the assignment for the vehicle if it is available
            if (blockId != null && !useTripShortNameForAssignment.getValue())
                avlReport.setAssignment(blockId, AssignmentType.BLOCK_ID);
            else if (tripId != null && useTripShortNameForAssignment.getValue())
                avlReport.setAssignment(tripId, AssignmentType.TRIP_SHORT_NAME);
            else
                avlReport.setAssignment(null, AssignmentType.UNSET);

            processAvlReport(avlReport);
        }
    }

    /**
     * Sometimes the raw block ID from the feed does not match the GTFS data.
     * For such cases need to override this method.
     * 
     * @param originalBlockIdFromFeed
     * @return The processed block ID
     */
    protected String processBlockId(String originalBlockIdFromFeed) {
        // This default method simply returns the original block ID.
        // Subclasses can process the block ID as needed.
        return originalBlockIdFromFeed;
    }

    /**
     * Just for debugging
     */
    public static void main(String[] args) {
        // Create a NextBusAvlModue for testing
        Module.start("org.transitime.avl.NextBusAvlModule");
    }

}