updaters.FeedStatsCalculator.java Source code

Java tutorial

Introduction

Here is the source code for updaters.FeedStatsCalculator.java

Source

/* 
  This program is free software: you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License
  as published by the Free Software Foundation, either version 3 of
  the License, or (props, at your option) any later version.
    
  This program 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 Lesser General Public License
  along with this program. If not, see <http://www.gnu.org/licenses/>. 
*/

package updaters;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.onebusaway.gtfs.impl.GtfsDaoImpl;
import org.onebusaway.gtfs.model.Agency;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.FeedInfo;
import org.onebusaway.gtfs.model.ServiceCalendar;
import org.onebusaway.gtfs.model.ServiceCalendarDate;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.calendar.ServiceDate;
import org.onebusaway.gtfs.serialization.GtfsReader;

import models.GtfsFeed;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.algorithm.ConvexHull;

import play.Logger;
import utils.GeometryUtils;

import au.com.bytecode.opencsv.CSVReader;

/**
 * Calculate feed stats for a zipped GTFS.
 * @author mattwigway
 *
 */
public class FeedStatsCalculator {
    private File rawGtfs;
    private GtfsDaoImpl store;
    private int stops;

    // These dates are stored in agency local time in this timezone
    private TimeZone timezone;
    private Date startDate;
    private Date endDate;
    private MultiPolygon the_geom;

    public Date getStartDate() {
        return startDate;
    }

    public Date getEndDate() {
        return endDate;
    }

    public Geometry getGeometry() {
        return the_geom;
    }

    public FeedStatsCalculator(File gtfs) throws Exception {
        this.rawGtfs = gtfs;
        GtfsReader reader = new GtfsReader();
        reader.setInputLocation(rawGtfs);

        this.store = new GtfsDaoImpl();
        reader.setEntityStore(this.store);
        reader.run();
        reader.close();

        this.startDate = null;
        this.endDate = null;

        findTimeZone();
        calculateStartAndEnd();
        calculateGeometry();
        calculateNumStops();
    }

    private void findTimeZone() {
        for (Agency agency : store.getAllAgencies()) {
            // TODO: Multiple agencies with different time zones
            this.timezone = TimeZone.getTimeZone(agency.getTimezone());
            break;
        }
    }

    private void calculateNumStops() {
        stops = store.getAllStops().size();
    }

    /**
    * Apply the calculated feed stats to the appropriate fields of the given GtfsFeed
    */
    public void apply(GtfsFeed feed) {
        feed.startDate = this.startDate;
        feed.expirationDate = this.endDate;
        feed.the_geom = this.the_geom;
        feed.stops = this.stops;
        feed.timezone = this.timezone;
    }

    /**
     * Apply not just the feed stats, but also the 
     */

    private void calculateStartAndEnd() throws Exception {
        // First, read feed_info.txt
        // TODO is 1 ever not the correct value?
        FeedInfo feedInfo = store.getFeedInfoForId(1);
        if (feedInfo != null) {
            ServiceDate d;
            d = feedInfo.getStartDate();
            if (d != null) {
                Calendar c = d.getAsCalendar(timezone);
                // move to GTFS noon, which will always be during the day. This accounts for both
                // multitimezone feeds and for daylight savings time 
                c.add(Calendar.HOUR_OF_DAY, 12);
                startDate = c.getTime();
            }

            d = feedInfo.getEndDate();
            if (d != null) {
                Calendar c = d.getAsCalendar(timezone);
                c.add(Calendar.HOUR_OF_DAY, 12);
                endDate = c.getTime();
            }
        }

        // we have an authoritative answer
        if (startDate != null && endDate != null)
            return;

        // let OBA deal with the complexities of interactions between
        // calendar.txt and
        // calendar_dates.txt
        // This code is lifted and slightly modified from
        // https://github.com/demory/otp_gtfs/blob/master/java/gtfsmetrics/src/main/java/org/openplans/gtfsmetrics/CalendarStatus.java
        Map<AgencyAndId, Set<ServiceDate>> addExceptions = new HashMap<AgencyAndId, Set<ServiceDate>>();
        Map<AgencyAndId, Set<String>> removeExceptions = new HashMap<AgencyAndId, Set<String>>();
        for (ServiceCalendarDate date : store.getAllCalendarDates()) {
            if (date.getExceptionType() == ServiceCalendarDate.EXCEPTION_TYPE_ADD) {
                Set<ServiceDate> dateSet = addExceptions.get(date.getServiceId());
                if (dateSet == null) {
                    dateSet = new HashSet<ServiceDate>();
                    addExceptions.put(date.getServiceId(), dateSet);
                }
                dateSet.add(date.getDate());
            } else if (date.getExceptionType() == ServiceCalendarDate.EXCEPTION_TYPE_REMOVE) {
                Set<String> dateSet = removeExceptions.get(date.getServiceId());
                if (dateSet == null) {
                    dateSet = new HashSet<String>();
                    removeExceptions.put(date.getServiceId(), dateSet);
                }
                dateSet.add(constructMDYString(date.getDate()));
            }
        }

        DateTime latestEnd = new DateTime(0);
        DateTime earliestStart = null;

        for (ServiceCalendar svcCal : store.getAllCalendars()) {

            Calendar c;
            c = svcCal.getStartDate().getAsCalendar(timezone);
            c.add(Calendar.HOUR_OF_DAY, 12);
            DateTime start = new DateTime(c.getTime());

            c = svcCal.getEndDate().getAsCalendar(timezone);
            c.add(Calendar.HOUR_OF_DAY, 12);
            DateTime end = new DateTime(c.getTime());

            int totalDays = Days.daysBetween(start, end).getDays();
            for (int d = 0; d < totalDays; d++) {
                int gd = getDay(svcCal, end.dayOfWeek().get());// dateCal.get(Calendar.DAY_OF_WEEK));
                boolean removeException = false;
                Set<String> dateSet = removeExceptions.get(svcCal.getServiceId());
                if (dateSet != null) {
                    removeException = dateSet.contains(constructMDYString(end));
                }
                if (gd == 1 && !removeException)
                    break;
                end = end.minusDays(1);
            }
            if (end.isAfter(latestEnd))
                latestEnd = end;

            totalDays = Days.daysBetween(start, end).getDays();
            for (int d = 0; d < totalDays; d++) {
                int gd = getDay(svcCal, start.dayOfWeek().get());// dateCal.get(Calendar.DAY_OF_WEEK));
                boolean removeException = false;
                Set<String> dateSet = removeExceptions.get(svcCal.getServiceId());
                if (dateSet != null) {
                    removeException = dateSet.contains(constructMDYString(start));
                }
                if (gd == 1 && !removeException)
                    break;
                start = start.plusDays(1);
            }
            if (earliestStart == null || start.isBefore(earliestStart))
                earliestStart = start;

        }

        // now, expand based on calendar_dates.txt
        for (Set<ServiceDate> dateSet : addExceptions.values()) {
            for (ServiceDate sd : dateSet) {
                DateTime dt = new DateTime(sd.getAsDate(timezone).getTime());
                if (dt.isAfter(latestEnd))
                    latestEnd = dt;
                if (dt.isBefore(earliestStart))
                    earliestStart = dt;
            }
        }

        this.startDate = earliestStart.toDate();
        this.endDate = latestEnd.toDate();
    }

    private static int getDay(ServiceCalendar cal, int dow) {
        switch (dow) {
        case 1:
            return cal.getMonday();
        case 2:
            return cal.getTuesday();
        case 3:
            return cal.getWednesday();
        case 4:
            return cal.getThursday();
        case 5:
            return cal.getFriday();
        case 6:
            return cal.getSaturday();
        case 7:
            return cal.getSunday();
        }
        return 0;
    }

    private static String constructMDYString(ServiceDate date) {
        return date.getMonth() + "-" + date.getDay() + "-" + date.getYear();
    }

    private static String constructMDYString(DateTime dt) {
        return dt.getMonthOfYear() + "-" + dt.getDayOfMonth() + "-" + dt.getYear();
    }

    private void calculateGeometry() {
        List<Coordinate> stopsGeom = new ArrayList<Coordinate>();
        // Make a coordinate for each stop, and add it to the MultiPoint
        double lon, lat;
        for (Stop stop : store.getAllStops()) {
            lon = stop.getLon();
            lat = stop.getLat();

            // ignore stops near 0,0 island
            if (Math.abs(lon) < 1 && Math.abs(lat) < 1)
                continue;

            stopsGeom.add(new Coordinate(lon, lat));
        }

        GeometryFactory gf = GeometryUtils.getGeometryFactoryForSrid(4326);
        Coordinate[] coords = new Coordinate[stopsGeom.size()];
        stopsGeom.toArray(coords);
        Geometry geom = new ConvexHull(coords, gf).getConvexHull();

        if (geom instanceof Polygon) {
            Polygon[] poly = new Polygon[] { (Polygon) geom };
            geom = gf.createMultiPolygon(poly);
        }

        this.the_geom = (MultiPolygon) geom;
    }

    /**
     * Apply not just the stats, but other GTFS-derived info.
     * @param feed
     */
    public void applyExtended(GtfsFeed feed) {
        this.apply(feed);
        for (Agency agency : store.getAllAgencies()) {
            feed.agencyName = agency.getName();
            feed.agencyUrl = agency.getUrl();
            break;
        }
    }
}