diff --git a/transitclock/src/main/java/org/transitclock/applications/Core.java b/transitclock/src/main/java/org/transitclock/applications/Core.java index 75a0c173f..b621e93f7 100755 --- a/transitclock/src/main/java/org/transitclock/applications/Core.java +++ b/transitclock/src/main/java/org/transitclock/applications/Core.java @@ -44,7 +44,7 @@ import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; import org.transitclock.core.dataCache.VehicleDataCache; import org.transitclock.core.dataCache.ehcache.StopArrivalDepartureCache; -import org.transitclock.core.dataCache.ehcache.TripDataHistoryCache; +import org.transitclock.core.dataCache.ehcache.scheduled.TripDataHistoryCache; import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; import org.transitclock.core.dataCache.scheduled.ScheduleBasedHistoricalAverageCache; import org.transitclock.db.hibernate.DataDbLogger; @@ -409,6 +409,77 @@ public static void startRmiServers(String agencyId) { PredictionAnalysisServer.start(agencyId); HoldingTimeServer.start(agencyId); } + + static private void populateCaches() throws Exception + { + Session session = HibernateUtils.getSession(); + + Date endDate=Calendar.getInstance().getTime(); + + /* populate one day at a time to avoid memory issue */ + for(int i=0;i0&&cacheReloadEndTimeStr.getValue().length()>0) + { + if(TripDataHistoryCacheFactory.getInstance()!=null) + { + logger.debug("Populating TripDataHistoryCache cache for period {} to {}",cacheReloadStartTimeStr.getValue(),cacheReloadEndTimeStr.getValue()); + TripDataHistoryCacheFactory.getInstance().populateCacheFromDb(session, new Date(Time.parse(cacheReloadStartTimeStr.getValue()).getTime()), new Date(Time.parse(cacheReloadEndTimeStr.getValue()).getTime())); + } + + if(FrequencyBasedHistoricalAverageCache.getInstance()!=null) + { + logger.debug("Populating FrequencyBasedHistoricalAverageCache cache for period {} to {}",cacheReloadStartTimeStr.getValue(),cacheReloadEndTimeStr.getValue()); + FrequencyBasedHistoricalAverageCache.getInstance().populateCacheFromDb(session, new Date(Time.parse(cacheReloadStartTimeStr.getValue()).getTime()), new Date(Time.parse(cacheReloadEndTimeStr.getValue()).getTime())); + } + }else + { + for(int i=0;i0&&cacheReloadEndTimeStr.getValue().length()>0) - { - logger.debug("Populating TripDataHistoryCache cache for period {} to {}",cacheReloadStartTimeStr.getValue(),cacheReloadEndTimeStr.getValue()); - TripDataHistoryCacheFactory.getInstance().populateCacheFromDb(session, new Date(Time.parse(cacheReloadStartTimeStr.getValue()).getTime()), new Date(Time.parse(cacheReloadEndTimeStr.getValue()).getTime())); - - logger.debug("Populating FrequencyBasedHistoricalAverageCache cache for period {} to {}",cacheReloadStartTimeStr.getValue(),cacheReloadEndTimeStr.getValue()); - FrequencyBasedHistoricalAverageCache.getInstance().populateCacheFromDb(session, new Date(Time.parse(cacheReloadStartTimeStr.getValue()).getTime()), new Date(Time.parse(cacheReloadEndTimeStr.getValue()).getTime())); - }else - { - for(int i=0;i lastDaysTimes(TripDataHistoryCacheInterface ca if (arrival != null && departure != null) { TravelTimeDetails travelTimeDetails=new TravelTimeDetails(departure, arrival); - - times.add(travelTimeDetails); - - num_found++; + + if(travelTimeDetails.getTravelTime()!=-1) + { + times.add(travelTimeDetails); + num_found++; + } } } } diff --git a/transitclock/src/main/java/org/transitclock/core/TravelTimeDetails.java b/transitclock/src/main/java/org/transitclock/core/TravelTimeDetails.java index 6cd03be97..feffcb6e6 100644 --- a/transitclock/src/main/java/org/transitclock/core/TravelTimeDetails.java +++ b/transitclock/src/main/java/org/transitclock/core/TravelTimeDetails.java @@ -13,7 +13,7 @@ public class TravelTimeDetails { private static final IntegerConfigValue maxTravelTime = new IntegerConfigValue( "transitclock.core.maxTravelTime", - 10 * Time.MS_PER_MIN, + 30 * Time.MS_PER_MIN, "This is a maximum allowed for travel between two stops. Used as a sanity check for cache and predictions."); private static final Logger logger = LoggerFactory @@ -53,7 +53,7 @@ public boolean sanityCheck() { long travelTime=this.arrival.getTime()-this.getDeparture().getTime(); - if(travelTime<0||travelTime>maxTravelTime.getValue()) + if(travelTime<=0||travelTime>maxTravelTime.getValue()) { return false; }else diff --git a/transitclock/src/main/java/org/transitclock/core/VehicleState.java b/transitclock/src/main/java/org/transitclock/core/VehicleState.java index 0352d71b4..f2dafca38 100644 --- a/transitclock/src/main/java/org/transitclock/core/VehicleState.java +++ b/transitclock/src/main/java/org/transitclock/core/VehicleState.java @@ -1,6 +1,6 @@ -/* +/* * 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 @@ -46,7 +46,7 @@ /** * Keeps track of vehicle state including its block assignment, where it * last matched to its assignment, and AVL reports. - * + * * @author SkiBu Smith * */ @@ -58,50 +58,50 @@ public class VehicleState { // Will be set to block ID (even if used trip to determine assignment) or route ID private String assignmentId; private Date assignmentTime; - + private boolean predictable; // First is most recent - private LinkedList temporalMatchHistory = + private LinkedList temporalMatchHistory = new LinkedList(); // First is most recent private LinkedList avlReportHistory = new LinkedList(); private List predictions; private TemporalDifference realTimeSchedAdh; - + // create a hashamp to store the trip start times. TODO change to LinkedList doesn't grow HashMap tripStartTimesMap=new HashMap (); - + // For keeping track of how many bad matches have been encountered. // This way can ignore bad matches if only get a couple private int numberOfBadMatches = 0; - + // So can make sure that departure time is after the arrival time private Arrival arrivalToStoreToDb; private long lastArrivalTime = 0; - + // So can keep track of whether assigning vehicle to same block that // just got unassigned for. The unassignedTime member is the time when the // vehicle was unassigned. private Block previousBlockBeforeUnassigned = null; private Date unassignedTime = null; - - // For determining if should use a previous assignment if the current + + // For determining if should use a previous assignment if the current // assignment is not valid. private int badAssignmentsInARow = 0; - + // For keeping track if vehicle delayed private boolean isDelayed = false; - + private Integer tripCounter = 0; - + private Headway headway=null; - + private HoldingTime holdingTime=null; //Used for schedPred AVL. Identify if trip is canceled. private boolean isCanceled; - - + + public Headway getHeadway() { return headway; } @@ -121,10 +121,10 @@ public Integer getTripCounter() { return tripCounter; } public void incrementTripCounter() { - tripCounter=tripCounter+1; + tripCounter=tripCounter+1; } - - private static final Logger logger = + + private static final Logger logger = LoggerFactory.getLogger(VehicleState.class); /********************** Member Functions **************************/ @@ -132,11 +132,11 @@ public void incrementTripCounter() { public VehicleState(String vehicleId) { this.vehicleId = vehicleId; } - + /** * Sets the block assignment for vehicle. Also, this is how it is specified * whether a vehicle is predictable or not. - * + * * @param newBlock * The current block assignment for the vehicle. Set to null if * vehicle not assigned. @@ -149,7 +149,7 @@ public VehicleState(String vehicleId) { * @param predictable * Whether vehicle is predictable */ - public void setBlock(Block newBlock, BlockAssignmentMethod assignmentMethod, + public void setBlock(Block newBlock, BlockAssignmentMethod assignmentMethod, String assignmentId, boolean predictable) { // When vehicle is made unpredictable remember the previous assignment // so can tell if getting assigned to same block again (which could @@ -163,24 +163,24 @@ public void setBlock(Block newBlock, BlockAssignmentMethod assignmentMethod, this.assignmentMethod = assignmentMethod; this.assignmentId = assignmentId; this.predictable = predictable; - this.assignmentTime = getAvlReport().getDate(); + this.assignmentTime = getAvlReport().getDate(); } - + /** * Sets the block for this VehicleState to null. Also sets assignmentId * to null and predictable to false. - * + * * @param assignmentMethod * How vehicle was assigned (AVL feed, auto assigner, etc). Set * to null if vehicle not assigned. */ public void unsetBlock(BlockAssignmentMethod assignmentMethod) { setBlock(null, // newBlock - assignmentMethod, + assignmentMethod, null, // assignmentId false); // predictable } - + /** * Determines if vehicle is currently getting assigned and it is getting assigned * back to the same block it was assigned to just a while ago. In other @@ -188,34 +188,34 @@ public void unsetBlock(BlockAssignmentMethod assignmentMethod) { * getting reassigned again. In this kind of situation don't want to for * example determine arrivals/departures back to the beginning of the block * because probably already did so when vehicle was previously assigned. - * + * * @return True if vehicle is being reassigned to the same block as before */ public boolean vehicleNewlyAssignedToSameBlock() { - // If previously wasn't assigned but it is now then it is + // If previously wasn't assigned but it is now then it is // newly assigned... if (getPreviousMatch() == null && getMatch() != null) { // If being assigned to same block it had previously... if (previousBlockBeforeUnassigned == getBlock()) { // If didn't get unassigned that long ago - if (getAvlReport().getTime() < + if (getAvlReport().getTime() < this.unassignedTime.getTime() + 20 * Time.MS_PER_MIN) { - // It is being newly assigned to the same block it was + // It is being newly assigned to the same block it was // recently unassigned from return true; } } } - + // Vehicle not newly assigned return false; } - + /** * Sets the match for the vehicle into the history. If set to null then * VehicleState.predictable is set to false. Also resets numberOfBadMatches * to 0. - * + * * @param match */ public void setMatch(TemporalMatch match) { @@ -225,27 +225,27 @@ public void setMatch(TemporalMatch match) { this.isCanceled=false; // Add match to history temporalMatchHistory.addFirst(match); - + // Set predictability if (match == null) { predictable = false; - + // Make sure that the arrival time buffer is cleared so that // when get a new assignment won't try to use it since something // peculiar might have happened. setArrivalToStoreToDb(null); } - + // Reset numberOfBadMatches numberOfBadMatches = 0; - + // Truncate list if it has gotten too long - while (temporalMatchHistory.size() > + while (temporalMatchHistory.size() > CoreConfig.getMatchHistoryMaxSize()) { temporalMatchHistory.removeLast(); } } - + /** * Returns the last temporal match. Returns null if there isn't one. * @return @@ -262,7 +262,7 @@ public TemporalMatch getMatch() { * Goes through the match history for the vehicle and returns match that is * at least minimumAge old. If there isn't a match that old then returns * null. - * + * * @param minimumAgeMsec * The minimum age in msec of the match to be returned * @return TemporalMatch for vehicle that is at least minimumAge old, or @@ -273,21 +273,21 @@ public TemporalMatch getPreviousMatch(int minimumAgeMsec) { AvlReport currentAvlReport = getAvlReport(); if (currentAvlReport == null) return null; - + // Go through math history to find one that is old enough for (TemporalMatch match : temporalMatchHistory) { // If the previous match was null then don't keep on // looking because vehicle was not predictable at some // point. Simply return null. - if (match == null) + if (match == null) return null; - + // If found match in history that is old enough then use it - if (match.getAvlTime() < + if (match.getAvlTime() < currentAvlReport.getTime() - minimumAgeMsec) return match; } - + // Went through all matches in history and didn't find one old enough. // If the history wasn't full then simply don't have enough matches yet. // But if history was full then it shows that the GPS reporting is so @@ -312,7 +312,7 @@ public TemporalMatch getPreviousMatch(int minimumAgeMsec) { // Didn't have an old enough matches in history return null; } - + /** * To be called when predictable vehicle has no valid spatial/temporal * match. Only allowed so many of these before vehicle is made @@ -321,54 +321,54 @@ public TemporalMatch getPreviousMatch(int minimumAgeMsec) { public void incrementNumberOfBadMatches() { ++numberOfBadMatches; } - + /** * Returns if have exceeded the number of allowed bad matches. If so * then vehicle should be made unpredictable. - * + * * @return */ public boolean overLimitOfBadMatches() { return numberOfBadMatches > CoreConfig.getAllowableNumberOfBadMatches(); } - + /** * Returns the number of sequential bad spatial/temporal matches that * occurred while vehicle was predictable. - * + * * @return current number of bad matches */ public int numberOfBadMatches() { return numberOfBadMatches; } - + /** * Returns true if the last AVL report was successfully matched to the * assignment indicating that can generate predictions and arrival/departure * times etc. - * + * * @return True if last match is valid */ public boolean lastMatchIsValid() { return numberOfBadMatches == 0; } - + /** * Stores the specified avlReport into the history for the vehicle. * Makes sure that the AVL history doesn't exceed maximum size. - * + * * @param avlReport */ public void setAvlReport(AvlReport avlReport) { // Add AVL report to history avlReportHistory.addFirst(avlReport); - + // Truncate list if it is too long or data in it is too old while (avlReportHistory.size() > CoreConfig.getAvlHistoryMaxSize()) { avlReportHistory.removeLast(); } } - + public void putTripStartTime(Integer tripCounter, Long date) { /* only add time once, as it is used as part of the GTFS trip descriptor for frequency based trips as is required to stay the same. */ @@ -379,55 +379,60 @@ public void putTripStartTime(Integer tripCounter, Long date) } } public Long getTripStartTime(Integer tripCounter) - { - return this.tripStartTimesMap.get(tripCounter); + { + return this.tripStartTimesMap.get(tripCounter); } /** * Increment trip counter is event it is arrival first stop on trip. Used for frequency based services. * @param event */ public void incrementTripCounter(ArrivalDeparture event) - { - VehicleState vehicleState = VehicleStateManager.getInstance().getVehicleState(event.getVehicleId()); - + { + VehicleState vehicleState = VehicleStateManager.getInstance().getVehicleState(event.getVehicleId()); + if(event.getStopPathIndex()==0) - { + { if(event.isArrival()) { vehicleState.incrementTripCounter(); - logger.debug("Setting vehicle counter to : {} because of event : {}",vehicleState.getTripCounter(), event); + logger.debug("Setting vehicle counter to : {} because of event : {}",vehicleState.getTripCounter(), event); }else { logger.debug("Not arrival so not incrementing trip counter : {}", event); } - } + }else + if(this.getPreviousMatch().getStopPathIndex()>event.getStopPathIndex()) + { + vehicleState.incrementTripCounter(); + logger.debug("Setting vehicle counter to : {} because of event : {}",vehicleState.getTripCounter(), event); + } } /** * Returns the current Trip for the vehicle. Returns null if there is not * current trip. - * + * * @return Trip or null. */ public Trip getTrip() { TemporalMatch lastMatch = getMatch(); return lastMatch!=null ? lastMatch.getTrip() : null; } - + /** * Returns the current route ID for the vehicle. Returns null if not * currently associated with a trip. - * + * * @return Route ID or null. */ public String getRouteId() { Trip trip = getTrip(); return trip!=null ? trip.getRouteId() : null; } - + /** * Returns the current GTFS route_short_name for the vehicle. Returns null * if not currently associated with a trip. - * + * * @return route short name or null */ public String getRouteShortName() { @@ -439,19 +444,19 @@ public String getRouteShortName() { * Returns the current name of the route that the vehicle is on. The route * name is a combination of the GTFS route_short_name and the GTFS * route_long_name so get a full name of something like "38 - Geary". - * + * * @return route name or null */ public String getRouteName() { Trip trip = getTrip(); return trip != null ? trip.getRouteName() : null; } - + /** * Returns true if last temporal match for vehicle indicates that it is at a * layover. A layover stop is a wait stop where a vehicle can leave route path before - * departing this stop at the scheduled time since the driver is taking a break. - * + * departing this stop at the scheduled time since the driver is taking a break. + * * @return true if at a layover */ public boolean isLayover() { @@ -461,14 +466,14 @@ public boolean isLayover() { else return temporalMatch.isLayover(); } - + /** * Returns true if last temporal match for vehicle indicates that it is at a * wait stop. A wait stop is when a vehicle is supposed to depart at a * scheduled time. A layover is always a wait stop but you can have a wait * stop that is not a layover due to the vehicle not being allowed to go - * away from the stop. - * + * away from the stop. + * * @return true if at a wait stop */ public boolean isWaitStop() { @@ -478,13 +483,13 @@ public boolean isWaitStop() { else return temporalMatch.isWaitStop(); } - + /** * Returns the next to last temporal match. Returns null if there isn't * one. Useful for when need to compare the previous to last match with * the last one, such as for determining if vehicle has crossed any * stops. - * + * * @return */ public TemporalMatch getPreviousMatch() { @@ -505,14 +510,14 @@ public AvlReport getAvlReport() { return null; } } - - - + + + /** * Looks in the AvlReport history for the most recent AvlReport that is at * least minDistanceFromCurrentReport from the current AvlReport. Also makes * sure that previous report isn't too old (more than 20 minutes old). - * + * * @param minDistanceFromCurrentReport * Distance in meters * @return The previous AvlReport, or null if there isn't one that far away @@ -527,7 +532,7 @@ public AvlReport getPreviousAvlReport(double minDistanceFromCurrentReport) { // If the previous report is too old then return null if (currentTime - previousAvlReport.getTime() > 20 * Time.MS_PER_MIN) return null; - + // If previous location far enough away from current location // then return the previous AVL report. Location previousLoc = previousAvlReport.getLocation(); @@ -535,7 +540,7 @@ public AvlReport getPreviousAvlReport(double minDistanceFromCurrentReport) { return previousAvlReport; } } - + // Didn't find a previous AvlReport in history that was far enough away // so return null return null; @@ -545,7 +550,7 @@ public AvlReport getPreviousAvlReport(double minDistanceFromCurrentReport) { * Goes through the AVL history for the vehicle and returns AVL report that * is at least minimumAge old. If there isn't a match that old then returns * null. - * + * * @param minimumAgeMsec * The minimum age in msec of the AVL report to be returned * @return AvlReport for vehicle that is at least minimumAge old, or null if @@ -556,7 +561,7 @@ public AvlReport getPreviousAvlReport(int minimumAgeMsec) { if (avlReport.getTime() < getAvlReport().getTime() - minimumAgeMsec) return avlReport; } - + // Went through all AVL reports in history and didn't find one old enough. // If the history wasn't full then simply don't have enough matches yet. // But if history was full then it shows that the GPS reporting is so @@ -575,7 +580,7 @@ public AvlReport getPreviousAvlReport(int minimumAgeMsec) { // Didn't have an old enough AVL reports in history return null; } - + /** * Returns the next to last AvlReport where successfully matched the * vehicle. This isn't necessarily simply the previous AvlReport since that @@ -583,12 +588,12 @@ public AvlReport getPreviousAvlReport(int minimumAgeMsec) { * the proper AvlReport when matching a vehicle or such because otherwise * the elapsed time between the last successful match and the current match * would be wrong. - * + * * @return The last successfully matched AvlReport, or null if no such * report is available */ public AvlReport getPreviousAvlReportFromSuccessfulMatch() { - if (avlReportHistory.size() >= 2+numberOfBadMatches) + if (avlReportHistory.size() >= 2+numberOfBadMatches) return avlReportHistory.get(1+numberOfBadMatches); else return null; @@ -603,7 +608,7 @@ public AvlReport getPreviousAvlReportFromSuccessfulMatch() { * converted to a block assignment for doing the comparison. When using a * route assignment then makes sure the route ID has not changed. For when * reassigning a vehicle via the AVL feed. - * + * * @param avlReport * For determining possibly new assignment * @return True if new assignment such that vehicle needs to be matched to @@ -615,8 +620,8 @@ public boolean hasNewAssignment(AvlReport avlReport) { if (avlReport.getAssignmentType() == AssignmentType.UNSET) return false; - // If block assignment then simply check assignment ID. This is much - // more straight forward than determining the new AVL block and + // If block assignment then simply check assignment ID. This is much + // more straight forward than determining the new AVL block and // comparing the new AVL block to the old block since determining // the new block is problematic given that there can be multiple // service IDs active at any given time. @@ -633,7 +638,7 @@ public boolean hasNewAssignment(AvlReport avlReport) { BlockAssigner.getInstance().getBlockAssignment(avlReport); return block != avlBlock; } - + // Not block or trip assignment so try route assignment if (avlReport.isRouteIdAssignmentType()) { String routeId = @@ -645,14 +650,14 @@ public boolean hasNewAssignment(AvlReport avlReport) { return !Objects.equals(assignmentId, newAssignment); } } - + // Can't determine new assignment so return false. This shouldn't // actually happen - logger.error("Could not determine if vehicle has new assignment. {}", + logger.error("Could not determine if vehicle has new assignment. {}", avlReport); return false; } - + /** * Returns true if previously the vehicle had the same assignment but that * assignment was recently removed due to a problem where the vehicle @@ -662,7 +667,7 @@ public boolean hasNewAssignment(AvlReport avlReport) { * continue to get that assignment via the AVL feed don't want to reassign * it to the problem assignment again. *

- * BUT WHAT ABOUT A VEHICLE SIMPLY BECOMING UNPREDICTABLE BECAUSE IT + * BUT WHAT ABOUT A VEHICLE SIMPLY BECOMING UNPREDICTABLE BECAUSE IT * TEMPORARILY WENT OFF ROUTE FOR 3 AVL REPORTS?? IN THAT CASE WANT VEHICLE * TO MATCH ASSIGNMENT AGAIN. OR WHAT IF BLOCK SIMPLY ENDED?? * SEEMS THAT NEED TO REMEMBER IF VEHICLE WAS UNASSIGNED IN SUCH A WAY @@ -670,7 +675,7 @@ public boolean hasNewAssignment(AvlReport avlReport) { *

* An old assignment is considered recent if the unassignment happened * within the last 2 hours. - * + * * @param avlReport * @return True if vehicle already had the assignment but it was problematic */ @@ -680,35 +685,35 @@ public boolean previousAssignmentProblematic(AvlReport avlReport) { if (assignmentMethod != BlockAssignmentMethod.ASSIGNMENT_GRABBED && assignmentMethod != BlockAssignmentMethod.ASSIGNMENT_TERMINATED) return false; - + // If the AVL report indicates a new assignment then don't have to // worry about the old one being problematic if (hasNewAssignment(avlReport)) return false; - + // Got same assignment from AVL feed that was previously problematic. // If the old problem assignment was somewhat recent then return true. - return avlReport.getTime() - unassignedTime.getTime() < - 2 * Time.MS_PER_HOUR; + return avlReport.getTime() - unassignedTime.getTime() < + 2 * Time.MS_PER_HOUR; } - + /********************** Getter methods ************************/ - + /** * Returns an unmodifiable list of the match history. The most recent * one is first. The size of the list will be not greater than * MATCH_HISTORY_SIZE. - * + * * @return the match history */ public List getMatches() { return Collections.unmodifiableList(temporalMatchHistory); } - + /** * The current block assignment. But will be null if vehicle not currently * assigned. - * + * * @return */ public Block getBlock() { @@ -718,17 +723,17 @@ public Block getBlock() { /** * Can be the blockId, tripId, or tripShortName depending on the type of * assignment received from the AVL feed. - * + * * @return blockId, tripId, or tripShortName or null if not assigned */ public String getAssignmentId() { return assignmentId; } - + /** * Indicates how the vehicle was assigned (via block assignment, route * assignment, auto assignment, etc). - * + * * @return */ public BlockAssignmentMethod getAssignmentMethod() { @@ -742,48 +747,48 @@ public Date getAssignmentTime() { public String getVehicleId() { return vehicleId; } - + public boolean isPredictable() { return predictable; } - + /** * Returns true if not a real vehicle but instead was created to produce * schedule based predictions. - * + * * @return true if for schedule based predictions */ public boolean isForSchedBasedPreds() { AvlReport avlReport = getAvlReport(); return avlReport != null && avlReport.isForSchedBasedPreds(); } - + /** * Records the specified arrival as one that still needs to be stored to the * db. This is important because can generate arrivals into the future but * need to make sure that the arrival is before the subsequent departure and * can't do so until get additional AVL reports. - * + * * @param arrival */ public void setArrivalToStoreToDb(Arrival arrival) { this.arrivalToStoreToDb = arrival; } - + public Arrival getArrivalToStoreToDb() { return arrivalToStoreToDb; } - + /** * Sets the current list of predictions for the vehicle to the * predictions parameter. - * + * * @param predictions */ public void setPredictions(List predictions) { this.predictions = predictions; } - + /** * Gets the current list of predictions for the vehicle. Can be null. * @return @@ -791,21 +796,21 @@ public void setPredictions(List predictions) { public List getPredictions() { return predictions; } - + /** * Stores the real-time schedule adherence for the vehicle. - * + * * @param realTimeSchedAdh */ public void setRealTimeSchedAdh(TemporalDifference realTimeSchedAdh) { this.realTimeSchedAdh = realTimeSchedAdh; } - + /** * Returns the current real-time schedule adherence for the vehicle, or null * if schedule adherence not currently valid (vehicle is not predictable or * running a non-schedule based assignment). - * + * * @return The TemporalDifference representing schedule adherence, or null * if vehicle not currently predictable or the assignment doesn't * have a schedule @@ -816,12 +821,12 @@ public TemporalDifference getRealTimeSchedAdh() { else return null; } - + /** * Determines the heading of the vector that defines the stop path segment * that the vehicle is currently on. The heading will be between 0.0 and * 360.0 degrees. - * + * * @return Heading of vehicle according to path segment. NaN if not * currently matched or there is no heading for that segment. */ @@ -830,15 +835,15 @@ public float getPathHeading() { SpatialMatch match = getMatch(); if (match == null) return Float.NaN; - + // If layover stop then the heading of the path isn't really valid // since the vehicle might be deadheading to the stop. StopPath stopPath = getTrip().getStopPath(match.getStopPathIndex()); if (stopPath.isLayoverStop()) return Float.NaN; - + // Vehicle on non-layover path so return heading of that path. - VectorWithHeading vector = + VectorWithHeading vector = stopPath.getSegmentVector(match.getSegmentIndex()); return vector.getHeading(); } @@ -853,7 +858,7 @@ public float getPathHeading() { * if vehicle is actually within 200m of the layover. Otherwise the * vehicle is deadheading and don't want to use the next path heading * if vehicle is actually reasonably far away from the layover. - * + * * @return */ private float getNextPathHeadingIfAtLayover() { @@ -865,15 +870,15 @@ private float getNextPathHeadingIfAtLayover() { // This is only for layovers so if not at layover return NaN if (!match.isLayover()) return Float.NaN; - + // Determine if actually at the layover instead of deadheading. If more - // than 200m away then don't use the next path heading. Instead + // than 200m away then don't use the next path heading. Instead // return NaN. double distanceToLayoverStop = match.getStopPath() .getEndOfPathLocation().distance(getAvlReport().getLocation()); - if (distanceToLayoverStop > 200.0) + if (distanceToLayoverStop > 200.0) return Float.NaN; - + // Vehicle is reasonably close to the layover stop so determine the // heading of the segment just after the layover. // If already at end of trip can't go on to next stop path @@ -883,10 +888,10 @@ private float getNextPathHeadingIfAtLayover() { // Determine the next first segment vector of the next path StopPath stopPath = getTrip().getStopPath(match.getStopPathIndex()+1); VectorWithHeading vector = stopPath.getSegmentVector(0); - + return vector.getHeading(); } - + /** * Looks in avlReportHistory and returns last valid heading. Will still * return null in certain situations such as there not being a history, the @@ -897,50 +902,50 @@ private float getNextPathHeadingIfAtLayover() { * if the heading is valid. At layovers it is likely that a vehicle turned a * corner a bit before the layover point and don't want to show the old * heading before the turn. - * + * * @return */ private float recentValidHeading() { long maxAge = System.currentTimeMillis() - 2 * Time.MS_PER_MIN; - + for (AvlReport avlReport : avlReportHistory) { // If report is too old then don't use it if (avlReport.getTime() < maxAge) return Float.NaN; - + // If AVL has valid heading then use it if (!Float.isNaN(avlReport.getHeading())) { return avlReport.getHeading(); } } - + // No reports have a valid heading so return NaN return Float.NaN; } - + /** * Normally uses the heading from getPathHeading(). But if that returns NaN * then uses recent valid heading from last AVL report, though that might be * NaN as well. This can be better then always using heading from AVL report * since that often won't line up with the path and can make vehicles be * oriented in noticeably peculiar ways when drawn on an map. - * + * * @return The best heading for a vehicle */ public float getHeading() { - // Try using path heading so that direction of vehicle will really + // Try using path heading so that direction of vehicle will really // follow path. This make the maps look good. float heading = getPathHeading(); if (!Float.isNaN(heading)) return heading; - + // If vehicle not matched to path or if matched to layover then the // heading from getPathHeading() is NaN. For this situation use the // most recent valid GPS heading. heading = recentValidHeading(); if (!Float.isNaN(heading)) return heading; - + // Most recent heading wasn't valid either so as last shot try using // path segment of next segment past layover. heading = getNextPathHeadingIfAtLayover(); @@ -949,13 +954,13 @@ public float getHeading() { @Override public String toString() { - return "VehicleState [" - + "vehicleId=" + vehicleId + return "VehicleState [" + + "vehicleId=" + vehicleId + ", blockId=" + (block==null? null : block.getId()) + ", assignmentId=" + assignmentId + ", assignmentMethod=" + assignmentMethod - + ", assignmentTime=" + assignmentTime - + ", predictable=" + predictable + + ", assignmentTime=" + assignmentTime + + ", predictable=" + predictable + ", realTimeSchedAdh=" + realTimeSchedAdh + (isDelayed() ? ", isDelayed=true" : "") + ", pathHeading=" + StringUtils.twoDigitFormat(getHeading()) @@ -963,27 +968,27 @@ public String toString() { + ", getAvlReport()=" + getAvlReport() + (arrivalToStoreToDb != null ? "\n arrivalToStoreToDb=" + arrivalToStoreToDb : "") //+ ",\n block=" + block // Block info too verbose so commented out - //+ ",\n temporalMatchHistory=" + temporalMatchHistory - //+ ",\n avlReportHistory=" + avlReportHistory + //+ ",\n temporalMatchHistory=" + temporalMatchHistory + //+ ",\n avlReportHistory=" + avlReportHistory + "]"; } public String toStringVerbose() { - return "VehicleState [" - + "vehicleId=" + vehicleId + return "VehicleState [" + + "vehicleId=" + vehicleId + ", blockId=" + (block==null? null : block.getId()) + ", assignmentId=" + assignmentId + ", assignmentMethod=" + assignmentMethod - + ", assignmentTime=" + assignmentTime - + ", predictable=" + predictable + + ", assignmentTime=" + assignmentTime + + ", predictable=" + predictable + ", realTimeSchedAdh=" + realTimeSchedAdh + (isDelayed() ? ", isDelayed=true" : "") + ", pathHeading=" + StringUtils.twoDigitFormat(getHeading()) + ", getMatch()=" + getMatch() + ", getAvlReport()=" + getAvlReport() //+ ", \nblock=" + block // Block info too verbose so commented out - + ",\n temporalMatchHistory=" + temporalMatchHistory - + ",\n avlReportHistory=" + avlReportHistory + + ",\n temporalMatchHistory=" + temporalMatchHistory + + ",\n avlReportHistory=" + avlReportHistory + (arrivalToStoreToDb != null ? "\n arrivalToStoreToDb=" + arrivalToStoreToDb : "") + "]"; } @@ -1000,7 +1005,7 @@ public void setLastArrivalTime(long arrivalTime) { /** * Returns the last stored arrival time so can make sure that departure * times are after the arrival times. - * + * * @return */ public long getLastArrivalTime() { @@ -1014,21 +1019,21 @@ public int getBadAssignmentsInARow() { public void setBadAssignmentsInARow(int badAssignmentsInARow) { this.badAssignmentsInARow = badAssignmentsInARow; } - + public void setIsDelayed(boolean isDelayed) { this.isDelayed = isDelayed; } - + public boolean isDelayed() { return isDelayed; } public void setCanceled(boolean isCanceled) { this.isCanceled=isCanceled; - + } public boolean isCanceled() { return isCanceled; } - - + + } diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeCacheKey.java b/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeCacheKey.java deleted file mode 100644 index 5ad238b29..000000000 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeCacheKey.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.transitclock.core.dataCache; - -import java.io.Serializable; - -import org.transitclock.core.Indices; - -public class DwellTimeCacheKey implements Serializable { - /** - * - */ - private static final long serialVersionUID = 4967988031829738238L; - private String tripId; - private Integer stopPathIndex; - public DwellTimeCacheKey(String tripId, Integer stopPathIndex) { - this.tripId = tripId; - this.stopPathIndex = stopPathIndex; - } - - public DwellTimeCacheKey(Indices indices) { - - this.tripId = indices.getTrip().getId(); - this.stopPathIndex = indices.getStopPathIndex(); - } - public String getTripId() { - return tripId; - } - public void setTripId(String tripId) { - this.tripId = tripId; - } - public Integer getStopPathIndex() { - return stopPathIndex; - } - public void setStopPathIndex(Integer stopPathIndex) { - this.stopPathIndex = stopPathIndex; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((stopPathIndex == null) ? 0 : stopPathIndex.hashCode()); - result = prime * result + ((tripId == null) ? 0 : tripId.hashCode()); - return result; - } - - @Override - public String toString() { - return "DwellTimeCacheKey [tripId=" + tripId + ", stopPathIndex=" + stopPathIndex + "]"; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - DwellTimeCacheKey other = (DwellTimeCacheKey) obj; - if (stopPathIndex == null) { - if (other.stopPathIndex != null) - return false; - } else if (!stopPathIndex.equals(other.stopPathIndex)) - return false; - if (tripId == null) { - if (other.tripId != null) - return false; - } else if (!tripId.equals(other.tripId)) - return false; - return true; - } - - -} diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheFactory.java b/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheFactory.java index 19293d8ed..66940933e 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheFactory.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheFactory.java @@ -10,7 +10,7 @@ public class DwellTimeModelCacheFactory { private static StringConfigValue className = new StringConfigValue("transitclock.core.cache.dwellTimeModelCache", - "org.transitclock.core.dataCache.jcs.DwellTimeModelCache", + "org.transitclock.core.dataCache.jcs.scheduled.DwellTimeModelCache", "Specifies the class used to cache RLS data for a stop."); private static DwellTimeModelCacheInterface singleton = null; diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheInterface.java b/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheInterface.java index 9ce6a04d2..d169ebeeb 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheInterface.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/DwellTimeModelCacheInterface.java @@ -1,14 +1,13 @@ package org.transitclock.core.dataCache; -import org.transitclock.core.Indices; import org.transitclock.db.structs.ArrivalDeparture; import org.transitclock.db.structs.Headway; public interface DwellTimeModelCacheInterface { - void addSample(Indices indices, Headway headway, long dwellTime); + void addSample(ArrivalDeparture event, Headway headway, long dwellTime); void addSample(ArrivalDeparture departure); - Long predictDwellTime(Indices indices, Headway headway); + Long predictDwellTime(StopPathCacheKey cacheKey, Headway headway); } diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/ErrorCacheFactory.java b/transitclock/src/main/java/org/transitclock/core/dataCache/ErrorCacheFactory.java index 6939fb24f..4d1e4b033 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/ErrorCacheFactory.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/ErrorCacheFactory.java @@ -10,7 +10,7 @@ public class ErrorCacheFactory { private static StringConfigValue className = new StringConfigValue("transitclock.core.cache.errorCacheClass", - "org.transitclock.core.dataCache.jcs.KalmanErrorCache", + "org.transitclock.core.dataCache.ehcache.KalmanErrorCache", "Specifies the class used to cache the Kalamn error values."); private static ErrorCache singleton = null; diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/StopPathCacheKey.java b/transitclock/src/main/java/org/transitclock/core/dataCache/StopPathCacheKey.java index aac34cb57..2e42b06b0 100755 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/StopPathCacheKey.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/StopPathCacheKey.java @@ -33,6 +33,7 @@ public StopPathCacheKey(String tripId, Integer stopPathIndex) this.tripId = tripId; this.stopPathIndex = stopPathIndex; this.travelTime=true; + this.startTime=null; } public StopPathCacheKey(String tripId, Integer stopPathIndex, boolean travelTime) { diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/TripDataHistoryCacheFactory.java b/transitclock/src/main/java/org/transitclock/core/dataCache/TripDataHistoryCacheFactory.java index 2f1d6fa37..00548236f 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/TripDataHistoryCacheFactory.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/TripDataHistoryCacheFactory.java @@ -10,7 +10,7 @@ public class TripDataHistoryCacheFactory { private static StringConfigValue className = new StringConfigValue("transitclock.core.cache.tripDataHistoryCache", - "org.transitclock.core.dataCache.jcs.TripDataHistoryCache", + "org.transitclock.core.dataCache.ehcache.frequency.TripDataHistoryCache", "Specifies the class used to cache the arrival and departures for a trip."); private static TripDataHistoryCacheInterface singleton = null; diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/TripKey.java b/transitclock/src/main/java/org/transitclock/core/dataCache/TripKey.java index 1b821b26b..cfa1ff89b 100755 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/TripKey.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/TripKey.java @@ -86,6 +86,12 @@ public String toString() { return "TripKey [tripId=" + tripId + ", tripStartDate=" + tripStartDate + ", startTime=" + startTime + "]"; } + public void setStartTime(Integer time) { + // TODO Auto-generated method stub + this.startTime=time; + + } + diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/KalmanErrorCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/KalmanErrorCache.java index 8038e1cd0..03141d8cd 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/KalmanErrorCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/KalmanErrorCache.java @@ -30,7 +30,7 @@ public class KalmanErrorCache implements ErrorCache { * @return */ - KalmanErrorCache() { + public KalmanErrorCache() { CacheManager cm = CacheManager.getInstance(); if (cm.getCache(cacheName) == null) { diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/frequency/TripDataHistoryCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/frequency/TripDataHistoryCache.java new file mode 100755 index 000000000..4f20356e5 --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/frequency/TripDataHistoryCache.java @@ -0,0 +1,364 @@ +/** + * + */ +package org.transitclock.core.dataCache.ehcache.frequency; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.LinkedList; +import java.util.List; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.Element; +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.store.Policy; + +import org.apache.commons.beanutils.BeanComparator; +import org.apache.commons.lang3.time.DateUtils; +import org.hibernate.Criteria; +import org.hibernate.criterion.Restrictions; +import org.hibernate.Session; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.applications.Core; +import org.transitclock.config.IntegerConfigValue; +import org.transitclock.core.dataCache.ArrivalDepartureComparator; +import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; +import org.transitclock.core.dataCache.TripDataHistoryCacheInterface; +import org.transitclock.core.dataCache.TripKey; +import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; +import org.transitclock.db.structs.ArrivalDeparture; +import org.transitclock.db.structs.Block; +import org.transitclock.db.structs.Trip; +import org.transitclock.gtfs.DbConfig; +import org.transitclock.gtfs.GtfsData; +import org.transitclock.utils.Time; + +/** + * @author Sean Og Crudden + * This is a Cache to hold historical arrival departure data for trips. It + * is intended to look up a trips historical data when a trip starts and + * place in cache for use in generating predictions based on a Kalman + * filter. Uses Ehcache for caching rather than just using a concurrent + * hashmap. This approach to holding data in memory for transitime needs + * to be proven. + * + * TODO this could do with an interface, factory class, and alternative implementations, perhaps using Infinispan. + */ +public class TripDataHistoryCache implements TripDataHistoryCacheInterface{ + private static TripDataHistoryCacheInterface singleton = new TripDataHistoryCache(); + + private static boolean debug = false; + + final private static String cacheByTrip = "arrivalDeparturesByTrip"; + + + private static final Logger logger = LoggerFactory + .getLogger(TripDataHistoryCache.class); + + private Cache cache = null; + + /** + * Default is 4 as we need 3 days worth for Kalman Filter implementation + */ + private static final IntegerConfigValue tripDataCacheMaxAgeSec = new IntegerConfigValue( + "transitclock.tripdatacache.tripDataCacheMaxAgeSec", + 15 * Time.SEC_PER_DAY, + "How old an arrivaldeparture has to be before it is removed from the cache "); + + /** + * Gets the singleton instance of this class. + * + * @return + */ + public static TripDataHistoryCacheInterface getInstance() { + return singleton; + } + + public TripDataHistoryCache() { + CacheManager cm = CacheManager.getInstance(); + EvictionAgePolicy evictionPolicy = null; + if(tripDataCacheMaxAgeSec!=null) + { + evictionPolicy = new EvictionAgePolicy( + tripDataCacheMaxAgeSec.getValue() * Time.MS_PER_SEC); + }else + { + evictionPolicy = new EvictionAgePolicy( + 15 * Time.SEC_PER_DAY *Time.MS_PER_SEC); + } + + if (cm.getCache(cacheByTrip) == null) { + cm.addCache(cacheByTrip); + } + cache = cm.getCache(cacheByTrip); + + //CacheConfiguration config = cache.getCacheConfiguration(); + /*TODO We need to refine the eviction policy. */ + cache.setMemoryStoreEvictionPolicy(evictionPolicy); + } + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#getKeys() + */ + @Override + public List getKeys() + { + return cache.getKeys(); + } + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#logCache(org.slf4j.Logger) + */ + @Override + public void logCache(Logger logger) + { + logger.debug("Cache content log."); + @SuppressWarnings("unchecked") + List keys = cache.getKeys(); + + for(TripKey key : keys) + { + Element result=cache.get(key); + if(result!=null) + { + logger.info("Key: "+key.toString()); + @SuppressWarnings("unchecked") + + List ads=(List) result.getObjectValue(); + + for(ArrivalDeparture ad : ads) + { + logger.info(ad.toString()); + } + } + } + + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#getTripHistory(org.transitclock.core.dataCache.TripKey) + */ + @Override + @SuppressWarnings("unchecked") + public List getTripHistory(TripKey tripKey) { + + //logger.debug(cache.toString()); + logger.debug("Looking for TripDataHistoryCache cache element using key {}.", tripKey); + + Element result = cache.get(tripKey); + + if(result!=null) + { + logger.debug("Found TripDataHistoryCache cache element using key {}.", tripKey); + return (List) result.getObjectValue(); + } + else + { + return null; + } + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#putArrivalDeparture(org.transitclock.db.structs.ArrivalDeparture) + */ + @Override + @SuppressWarnings("unchecked") + synchronized public TripKey putArrivalDeparture(ArrivalDeparture arrivalDeparture) { + + Block block=null; + if(arrivalDeparture.getBlock()==null) + { + DbConfig dbConfig = Core.getInstance().getDbConfig(); + block=dbConfig.getBlock(arrivalDeparture.getServiceId(), arrivalDeparture.getBlockId()); + }else + { + block=arrivalDeparture.getBlock(); + } + + /* just put todays time in for last three days to aid development. This means it will kick in in 1 days rather than 3. Perhaps be a good way to start rather than using default transiTime method but I doubt it. */ + int days_back=1; + if(debug) + days_back=3; + TripKey tripKey=null; + + for(int i=0;i < days_back;i++) + { + Date nearestDay = DateUtils.truncate(new Date(arrivalDeparture.getTime()), Calendar.DAY_OF_MONTH); + + nearestDay=DateUtils.addDays(nearestDay, i*-1); + + DbConfig dbConfig = Core.getInstance().getDbConfig(); + + Trip trip=dbConfig.getTrip(arrivalDeparture.getTripId()); + + // TODO need to set start time based on start of bucket + if(arrivalDeparture.getFreqStartTime()!=null) + { + Integer time=FrequencyBasedHistoricalAverageCache.secondsFromMidnight(arrivalDeparture.getFreqStartTime(),2); + + time=FrequencyBasedHistoricalAverageCache.round(time, FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + if(trip!=null) + { + + tripKey = new TripKey(arrivalDeparture.getTripId(), + nearestDay, + time); + + logger.debug("Putting :{} in TripDataHistoryCache cache using key {}.", arrivalDeparture, tripKey); + + List list = null; + + Element result = cache.get(tripKey); + + if (result != null && result.getObjectValue() != null) { + list = (List) result.getObjectValue(); + cache.remove(tripKey); + } else { + list = new ArrayList(); + } + + list.add(arrivalDeparture); + + Element arrivalDepartures = new Element(tripKey, Collections.synchronizedList(list)); + + cache.put(arrivalDepartures); + } + } + else + { + logger.error("Cannot add event to TripDataHistoryCache as it has no freqStartTime set. {}", arrivalDeparture); + } + } + return tripKey; + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#populateCacheFromDb(org.hibernate.Session, java.util.Date, java.util.Date) + */ + + @Override + public void populateCacheFromDb(Session session, Date startDate, Date endDate) + { + Criteria criteria =session.createCriteria(ArrivalDeparture.class); + + @SuppressWarnings("unchecked") + List results=criteria.add(Restrictions.between("time", startDate, endDate)).list(); + + for(ArrivalDeparture result : results) + { + // TODO this might be better done in the database. + if(GtfsData.routeNotFiltered(result.getRouteId())) + { + TripDataHistoryCacheFactory.getInstance().putArrivalDeparture(result); + } + } + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.ehcache.test#findPreviousArrivalEvent(java.util.List, org.transitclock.db.structs.ArrivalDeparture) + */ + @Override + public ArrivalDeparture findPreviousArrivalEvent(List arrivalDepartures,ArrivalDeparture current) + { + Collections.sort(arrivalDepartures, new ArrivalDepartureComparator()); + for (ArrivalDeparture tocheck : emptyIfNull(arrivalDepartures)) + { + if(tocheck.getStopId().equals(current.getStopId()) && (current.isDeparture() && tocheck.isArrival())) + { + return tocheck; + } + } + return null; + } + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.ehcache.test#findPreviousDepartureEvent(java.util.List, org.transitclock.db.structs.ArrivalDeparture) + */ + @Override + public ArrivalDeparture findPreviousDepartureEvent(List arrivalDepartures,ArrivalDeparture current) + { + Collections.sort(arrivalDepartures, new ArrivalDepartureComparator()); + for (ArrivalDeparture tocheck : emptyIfNull(arrivalDepartures)) + { + try { + + if(tocheck.getStopPathIndex()==(current.getStopPathIndex()-1) + && (current.isArrival() && tocheck.isDeparture()) + && current.getFreqStartTime().equals(tocheck.getFreqStartTime())) + { + return tocheck; + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return null; + } + + private static Iterable emptyIfNull(Iterable iterable) { + return iterable == null ? Collections. emptyList() : iterable; + } + /** + * This policy evicts arrival departures from the cache + * when they are X (age) number of milliseconds old + * + */ + private class EvictionAgePolicy implements Policy { + private String name = "AGE"; + + private long age = 0L; + + public EvictionAgePolicy(long age) { + super(); + this.age = age; + } + + @Override + public boolean compare(Element arg0, Element arg1) { + if (arg0.getObjectKey() instanceof TripKey + && arg1.getObjectKey() instanceof TripKey) { + if (((TripKey) arg0.getObjectKey()).getTripStartDate().after( + ((TripKey) arg1.getObjectKey()).getTripStartDate())) { + return true; + } + if (((TripKey) arg0.getObjectKey()).getTripStartDate() + .compareTo( + ((TripKey) arg1.getObjectKey()) + .getTripStartDate()) == 0) { + if (((TripKey) arg0.getObjectKey()).getStartTime() > ((TripKey) arg1 + .getObjectKey()).getStartTime()) { + return true; + } + } + } + return false; + } + + @Override + public String getName() { + return name; + } + + @Override + public Element selectedBasedOnPolicy(Element[] arg0, Element arg1) { + + for (int i = 0; i < arg0.length; i++) { + + if (arg0[i].getObjectKey() instanceof TripKey) { + TripKey key = (TripKey) arg0[i].getObjectKey(); + + if (Calendar.getInstance().getTimeInMillis() + - key.getTripStartDate().getTime() + + (key.getStartTime().intValue() * 1000) > age) { + return arg0[i]; + } + } + } + return null; + } + } +} diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/TripDataHistoryCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/scheduled/TripDataHistoryCache.java similarity index 93% rename from transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/TripDataHistoryCache.java rename to transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/scheduled/TripDataHistoryCache.java index 0246f6e17..e771429d5 100755 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/TripDataHistoryCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/ehcache/scheduled/TripDataHistoryCache.java @@ -1,7 +1,7 @@ /** * */ -package org.transitclock.core.dataCache.ehcache; +package org.transitclock.core.dataCache.ehcache.scheduled; import java.util.Collections; import java.util.Comparator; @@ -39,14 +39,11 @@ /** * @author Sean Og Crudden - * This is a Cache to hold historical arrival departure data for trips. It + * This is a Cache to hold historical arrival departure data for frequency based trips. It * is intended to look up a trips historical data when a trip starts and * place in cache for use in generating predictions based on a Kalman - * filter. Uses Ehcache for caching rather than just using a concurrent - * hashmap. This approach to holding data in memory for transitime needs - * to be proven. + * filter. * - * TODO this could do with an interface, factory class, and alternative implementations, perhaps using Infinispan. */ public class TripDataHistoryCache implements TripDataHistoryCacheInterface{ private static TripDataHistoryCacheInterface singleton = new TripDataHistoryCache(); diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/frequency/FrequencyBasedHistoricalAverageCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/frequency/FrequencyBasedHistoricalAverageCache.java index f8433143d..a6cebcf4b 100755 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/frequency/FrequencyBasedHistoricalAverageCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/frequency/FrequencyBasedHistoricalAverageCache.java @@ -27,7 +27,7 @@ import org.transitclock.core.Indices; import org.transitclock.core.VehicleState; import org.transitclock.core.dataCache.*; -import org.transitclock.core.dataCache.ehcache.TripDataHistoryCache; +import org.transitclock.core.dataCache.ehcache.scheduled.TripDataHistoryCache; import org.transitclock.db.structs.ArrivalDeparture; import org.transitclock.db.structs.Trip; import org.transitclock.gtfs.DbConfig; diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/DwellTimeModelCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/DwellTimeModelCache.java new file mode 100644 index 000000000..d3d1012d6 --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/DwellTimeModelCache.java @@ -0,0 +1,226 @@ +package org.transitclock.core.dataCache.jcs.frequency; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.jcs.JCS; +import org.apache.commons.jcs.access.CacheAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.applications.Core; +import org.transitclock.config.DoubleConfigValue; +import org.transitclock.config.IntegerConfigValue; +import org.transitclock.config.LongConfigValue; +import org.transitclock.core.Indices; +import org.transitclock.core.dataCache.StopArrivalDepartureCacheFactory; +import org.transitclock.core.dataCache.StopArrivalDepartureCacheKey; +import org.transitclock.core.dataCache.StopPathCacheKey; +import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; +import org.transitclock.core.predictiongenerator.scheduled.dwell.rls.TransitClockRLS; +import org.transitclock.db.structs.ArrivalDeparture; +import org.transitclock.db.structs.Block; +import org.transitclock.db.structs.Headway; +import org.transitclock.gtfs.DbConfig; +import org.transitclock.utils.Time; + +public class DwellTimeModelCache implements org.transitclock.core.dataCache.DwellTimeModelCacheInterface { + + final private static String cacheName = "DwellTimeModelCache"; + + private static IntegerConfigValue maxDwellTimeAllowedInModel = new IntegerConfigValue("org.transitclock.core.dataCache.jcs.maxDwellTimeAllowedInModel", 2 * Time.MS_PER_MIN, "Max dwell time to be considered in dwell RLS algotithm."); + private static LongConfigValue maxHeadwayAllowedInModel = new LongConfigValue("org.transitclock.core.dataCache.jcs.maxHeadwayAllowedInModel", 1*Time.MS_PER_HOUR, "Max headway to be considered in dwell RLS algotithm."); + + private static DoubleConfigValue lambda = new DoubleConfigValue("org.transitclock.core.dataCache.jcs.lambda", 0.75, "This sets the rate at which the RLS algorithm forgets old values. Value are between 0 and 1. With 0 being the most forgetful."); + + private CacheAccess cache = null; + + private static final Logger logger = LoggerFactory.getLogger(DwellTimeModelCache.class); + + public DwellTimeModelCache() { + cache = JCS.getInstance(cacheName); + } + @Override + synchronized public void addSample(ArrivalDeparture event, Headway headway, long dwellTime) { + + Integer time=FrequencyBasedHistoricalAverageCache.secondsFromMidnight(event.getFreqStartTime(),2); + + time=FrequencyBasedHistoricalAverageCache.round(time, FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + StopPathCacheKey key=new StopPathCacheKey(event.getTripId(), event.getStopPathIndex(), false, new Long(time)); + + TransitClockRLS rls = null; + if(cache.get(key)!=null) + { + rls=cache.get(key); + + double[] x = new double[1]; + x[0]=headway.getHeadway(); + + double y = Math.log10(dwellTime); + + + double[] arg0 = new double[1]; + arg0[0]=headway.getHeadway(); + if(rls.getRls()!=null) + { + double prediction = Math.pow(10,rls.getRls().predict(arg0)); + + logger.debug("Predicted dwell: "+prediction + " for: "+key + " based on headway: "+TimeUnit.MILLISECONDS.toMinutes((long) headway.getHeadway())+" mins"); + + logger.debug("Actual dwell: "+ dwellTime + " for: "+key + " based on headway: "+TimeUnit.MILLISECONDS.toMinutes((long) headway.getHeadway())+" mins"); + } + + rls.addSample(headway.getHeadway(), Math.log10(dwellTime)); + if(rls.getRls()!=null) + { + double prediction = Math.pow(10,rls.getRls().predict(arg0)); + + logger.debug("Predicted dwell after: "+ prediction + " for: "+key+ " with samples: "+rls.numSamples()); + } + }else + { + + rls=new TransitClockRLS(lambda.getValue()); + rls.addSample(headway.getHeadway(), Math.log10(dwellTime)); + } + cache.put(key,rls); + } + + @Override + public void addSample(ArrivalDeparture departure) { + if(departure!=null && !departure.isArrival()) + { + Block block=null; + if(departure.getBlock()==null) + { + DbConfig dbConfig = Core.getInstance().getDbConfig(); + block=dbConfig.getBlock(departure.getServiceId(), departure.getBlockId()); + }else + { + block=departure.getBlock(); + } + + Indices indices = new Indices(departure); + StopArrivalDepartureCacheKey key= new StopArrivalDepartureCacheKey(departure.getStopId(), departure.getDate()); + List stopData = StopArrivalDepartureCacheFactory.getInstance().getStopHistory(key); + + if(stopData!=null && stopData.size()>1) + { + ArrivalDeparture arrival=findArrival(stopData, departure); + if(arrival!=null) + { + ArrivalDeparture previousArrival=findPreviousArrival(stopData, arrival); + if(arrival!=null&&previousArrival!=null) + { + Headway headway=new Headway(); + headway.setHeadway(arrival.getTime()-previousArrival.getTime()); + long dwelltime=departure.getTime()-arrival.getTime(); + headway.setTripId(arrival.getTripId()); + + // Negative dwell times are errors in data so do not include. + // TODO not sure if it should ignore zero values. + if(dwelltime>=0) + { + /* Leave out silly values as they are most likely errors or unusual circumstance. */ + if(dwelltime stopData, ArrivalDeparture arrival) { + for(ArrivalDeparture event:stopData) + { + if(event.isArrival()) + { + if(!event.getVehicleId().equals(arrival.getVehicleId())) + { + if(!event.getTripId().equals(arrival.getTripId())) + { + if(event.getStopId().equals(arrival.getStopId())) + { + if(event.getTime() stopData, ArrivalDeparture departure) { + + for(ArrivalDeparture event:stopData) + { + if(event.isArrival()) + { + if(event.getStopId().equals(departure.getStopId())) + { + if(event.getVehicleId().equals(departure.getVehicleId())) + { + if(event.getTripId().equals(departure.getTripId())) + { + return event; + } + } + } + } + } + return null; + } + @Override + public Long predictDwellTime(StopPathCacheKey cacheKey, Headway headway) { + + TransitClockRLS rls=cache.get(cacheKey); + if(rls!=null&&rls.getRls()!=null) + { + double[] arg0 = new double[1]; + arg0[0]=headway.getHeadway(); + rls.getRls().predict(arg0); + long prediction = (long) Math.pow(10, rls.getRls().predict(arg0)); + + // If silly values returned then need to reset model and allow it use the super prediction. + if(prediction>maxDwellTimeAllowedInModel.getValue()) + { + cache.remove(cacheKey); + return null; + } + return prediction; + }else + { + return null; + } + } + public static void main(String[] args) + { + double startvalue=1000; + double result1 = Math.log10(startvalue); + double result2 = Math.pow(10, result1); + if(startvalue==result2) + System.out.println("As expected they are the same."); + } + +} diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/TripDataHistoryCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/TripDataHistoryCache.java new file mode 100644 index 000000000..5d42c2465 --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/frequency/TripDataHistoryCache.java @@ -0,0 +1,183 @@ +package org.transitclock.core.dataCache.jcs.frequency; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.apache.commons.jcs.JCS; +import org.apache.commons.jcs.access.CacheAccess; +import org.apache.commons.lang3.time.DateUtils; +import org.hibernate.Criteria; +import org.hibernate.Session; +import org.hibernate.criterion.Restrictions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.applications.Core; +import org.transitclock.core.dataCache.ArrivalDepartureComparator; +import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; +import org.transitclock.core.dataCache.TripDataHistoryCacheInterface; +import org.transitclock.core.dataCache.TripKey; +import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; +import org.transitclock.db.structs.ArrivalDeparture; +import org.transitclock.db.structs.Trip; +import org.transitclock.gtfs.DbConfig; +import org.transitclock.gtfs.GtfsData; + +import net.sf.ehcache.Element; + +public class TripDataHistoryCache implements TripDataHistoryCacheInterface { + final private static String cacheName = "FrequencyTripDataHistoryCache"; + + private static final Logger logger = LoggerFactory + .getLogger(TripDataHistoryCache.class); + + private CacheAccess> cache = null; + + @Override + public List getKeys() { + ArrayList fulllist=new ArrayList(); + Set names = JCS.getGroupCacheInstance(cacheName).getGroupNames(); + + for(String name:names) + { + Set keys = JCS.getGroupCacheInstance(cacheName).getGroupKeys(name); + + for(Object key:keys) + { + fulllist.add((TripKey)key); + } + } + return fulllist; + } + + public TripDataHistoryCache() { + cache = JCS.getInstance(cacheName); + } + + @Override + public void logCache(Logger logger) { + + logger.debug("Cache content log. Not implemented."); + } + + @Override + public List getTripHistory(TripKey tripKey) { + + /* this is what gets the trip from the buckets */ + int time = FrequencyBasedHistoricalAverageCache.round(tripKey.getStartTime(), FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + tripKey.setStartTime(time); + + return cache.get(tripKey); + } + + @Override + synchronized public TripKey putArrivalDeparture(ArrivalDeparture arrivalDeparture) { + logger.debug("Putting :"+arrivalDeparture.toString() + " in TripDataHistoryCache cache."); + /* just put todays time in for last three days to aid development. This means it will kick in in 1 days rather than 3. Perhaps be a good way to start rather than using default transiTime method but I doubt it. */ + int days_back=1; + + TripKey tripKey=null; + + for(int i=0;i < days_back;i++) + { + Date nearestDay = DateUtils.truncate(new Date(arrivalDeparture.getTime()), Calendar.DAY_OF_MONTH); + + nearestDay=DateUtils.addDays(nearestDay, i*-1); + + DbConfig dbConfig = Core.getInstance().getDbConfig(); + + Trip trip=dbConfig.getTrip(arrivalDeparture.getTripId()); + + if(trip!=null) + { + Integer time=FrequencyBasedHistoricalAverageCache.secondsFromMidnight(arrivalDeparture.getDate(),2); + + /* this is what gets the trip from the buckets */ + time=FrequencyBasedHistoricalAverageCache.round(time, FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + tripKey = new TripKey(arrivalDeparture.getTripId(), + nearestDay, + time); + + List list = cache.get(tripKey); + + if(list==null) + list = new ArrayList(); + + list.add(arrivalDeparture); + cache.put(tripKey, Collections.synchronizedList(list)); + } + } + return tripKey; + + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.TripDataHistoryCacheInterface#populateCacheFromDb(org.hibernate.Session, java.util.Date, java.util.Date) + */ + + @Override + public void populateCacheFromDb(Session session, Date startDate, Date endDate) + { + Criteria criteria =session.createCriteria(ArrivalDeparture.class); + + @SuppressWarnings("unchecked") + List results=criteria.add(Restrictions.between("time", startDate, endDate)).list(); + + for(ArrivalDeparture result : results) + { + // TODO this might be better done in the database. + if(GtfsData.routeNotFiltered(result.getRouteId())) + { + TripDataHistoryCacheFactory.getInstance().putArrivalDeparture(result); + } + } + } + + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.ehcache.test#findPreviousArrivalEvent(java.util.List, org.transitclock.db.structs.ArrivalDeparture) + */ + @Override + public ArrivalDeparture findPreviousArrivalEvent(List arrivalDepartures,ArrivalDeparture current) + { + Collections.sort(arrivalDepartures, new ArrivalDepartureComparator()); + for (ArrivalDeparture tocheck : emptyIfNull(arrivalDepartures)) + { + if(tocheck.getStopId().equals(current.getStopId()) && (current.isDeparture() && tocheck.isArrival())) + { + return tocheck; + } + } + return null; + } + /* (non-Javadoc) + * @see org.transitclock.core.dataCache.ehcache.test#findPreviousDepartureEvent(java.util.List, org.transitclock.db.structs.ArrivalDeparture) + */ + @Override + public ArrivalDeparture findPreviousDepartureEvent(List arrivalDepartures,ArrivalDeparture current) + { + Collections.sort(arrivalDepartures, new ArrivalDepartureComparator()); + for (ArrivalDeparture tocheck : emptyIfNull(arrivalDepartures)) + { + try { + if(tocheck.getStopPathIndex()==(current.getStopPathIndex()-1) + && (current.isArrival() && tocheck.isDeparture()) + && current.getFreqStartTime().equals(tocheck.getFreqStartTime())) + { + return tocheck; + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return null; + } + private static Iterable emptyIfNull(Iterable iterable) { + return iterable == null ? Collections. emptyList() : iterable; + } +} diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/DwellTimeModelCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/DwellTimeModelCache.java similarity index 79% rename from transitclock/src/main/java/org/transitclock/core/dataCache/jcs/DwellTimeModelCache.java rename to transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/DwellTimeModelCache.java index 470cb65c9..71f65ae1d 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/DwellTimeModelCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/DwellTimeModelCache.java @@ -1,4 +1,4 @@ -package org.transitclock.core.dataCache.jcs; +package org.transitclock.core.dataCache.jcs.scheduled; import java.util.Calendar; import java.util.Date; @@ -13,23 +13,17 @@ import org.transitclock.config.DoubleConfigValue; import org.transitclock.config.IntegerConfigValue; import org.transitclock.config.LongConfigValue; -import org.transitclock.config.StringConfigValue; -import org.transitclock.core.HeadwayDetails; import org.transitclock.core.Indices; -import org.transitclock.core.TravelTimes; -import org.transitclock.core.dataCache.DwellTimeCacheKey; import org.transitclock.core.dataCache.StopArrivalDepartureCacheFactory; import org.transitclock.core.dataCache.StopArrivalDepartureCacheKey; -import org.transitclock.core.predictiongenerator.rls.dwell.DwellTimePredictionGeneratorImpl; -import org.transitclock.core.predictiongenerator.rls.dwell.TransitClockRLS; +import org.transitclock.core.dataCache.StopPathCacheKey; +import org.transitclock.core.predictiongenerator.scheduled.dwell.rls.TransitClockRLS; import org.transitclock.db.structs.ArrivalDeparture; import org.transitclock.db.structs.Block; import org.transitclock.db.structs.Headway; import org.transitclock.gtfs.DbConfig; import org.transitclock.utils.Time; -import smile.regression.RLS; - public class DwellTimeModelCache implements org.transitclock.core.dataCache.DwellTimeModelCacheInterface { final private static String cacheName = "DwellTimeModelCache"; @@ -40,54 +34,54 @@ public class DwellTimeModelCache implements org.transitclock.core.dataCache.Dwel private static LongConfigValue maxHeadwayAllowedInModel = new LongConfigValue("transitclock.prediction.rls.maxHeadwayAllowedInModel", 1*Time.MS_PER_HOUR, "Max headway to be considered in dwell RLS algotithm."); private static LongConfigValue minHeadwayAllowedInModel = new LongConfigValue("transitclock.prediction.rls.minHeadwayAllowedInModel", (long) 1000, "Min headway to be considered in dwell RLS algotithm."); - - private static DoubleConfigValue lambda = new DoubleConfigValue("transitclock.prediction.rls.lambda", 0.5, "This sets the rate at which the RLS algorithm forgets old values. Value are between 0 and 1. With 0 being the most forgetful."); - - private CacheAccess cache = null; - + + private static DoubleConfigValue lambda = new DoubleConfigValue("transitclock.prediction.rls.lambda", 0.75, "This sets the rate at which the RLS algorithm forgets old values. Value are between 0 and 1. With 0 being the most forgetful."); + + private CacheAccess cache = null; + private static final Logger logger = LoggerFactory.getLogger(DwellTimeModelCache.class); - + public DwellTimeModelCache() { - cache = JCS.getInstance(cacheName); + cache = JCS.getInstance(cacheName); } @Override - synchronized public void addSample(Indices indices, Headway headway, long dwellTime) { - - DwellTimeCacheKey key=new DwellTimeCacheKey(headway.getTripId(), indices.getStopPathIndex()); - - + synchronized public void addSample(ArrivalDeparture event, Headway headway, long dwellTime) { + + StopPathCacheKey key=new StopPathCacheKey(headway.getTripId(), event.getStopPathIndex()); + + TransitClockRLS rls = null; if(cache.get(key)!=null) - { + { rls=cache.get(key); - + double[] x = new double[1]; x[0]=headway.getHeadway(); - + double y = Math.log10(dwellTime); - - + + double[] arg0 = new double[1]; arg0[0]=headway.getHeadway(); if(rls.getRls()!=null) { double prediction = Math.pow(10,rls.getRls().predict(arg0)); - + logger.debug("Predicted dwell: "+prediction + " for: "+key + " based on headway: "+TimeUnit.MILLISECONDS.toMinutes((long) headway.getHeadway())+" mins"); - + logger.debug("Actual dwell: "+ dwellTime + " for: "+key + " based on headway: "+TimeUnit.MILLISECONDS.toMinutes((long) headway.getHeadway())+" mins"); } - - rls.addSample(headway.getHeadway(), Math.log10(dwellTime)); + + rls.addSample(headway.getHeadway(), Math.log10(dwellTime)); if(rls.getRls()!=null) { double prediction = Math.pow(10,rls.getRls().predict(arg0)); - + logger.debug("Predicted dwell after: "+ prediction + " for: "+key+ " with samples: "+rls.numSamples()); } }else - { - + { + rls=new TransitClockRLS(lambda.getValue()); rls.addSample(headway.getHeadway(), Math.log10(dwellTime)); } @@ -102,16 +96,16 @@ public void addSample(ArrivalDeparture departure) { if(departure.getBlock()==null) { DbConfig dbConfig = Core.getInstance().getDbConfig(); - block=dbConfig.getBlock(departure.getServiceId(), departure.getBlockId()); + block=dbConfig.getBlock(departure.getServiceId(), departure.getBlockId()); }else { block=departure.getBlock(); } - + Indices indices = new Indices(departure); StopArrivalDepartureCacheKey key= new StopArrivalDepartureCacheKey(departure.getStopId(), departure.getDate()); List stopData = StopArrivalDepartureCacheFactory.getInstance().getStopHistory(key); - + if(stopData!=null && stopData.size()>1) { ArrivalDeparture arrival=findArrival(stopData, departure); @@ -119,31 +113,30 @@ public void addSample(ArrivalDeparture departure) { { ArrivalDeparture previousArrival=findPreviousArrival(stopData, arrival); if(arrival!=null&&previousArrival!=null) - { + { Headway headway=new Headway(); headway.setHeadway(arrival.getTime()-previousArrival.getTime()); long dwelltime=departure.getTime()-arrival.getTime(); headway.setTripId(arrival.getTripId()); - /* Leave out silly values as they are most likely errors or unusual circumstance. */ /* TODO Should abstract this behind an anomaly detention interface/Factory */ - if(dwelltime minDwellTimeAllowedInModel.getValue() && - headway.getHeadway() < maxHeadwayAllowedInModel.getValue() + if(dwelltime minDwellTimeAllowedInModel.getValue() && + headway.getHeadway() < maxHeadwayAllowedInModel.getValue() && headway.getHeadway() > minHeadwayAllowedInModel.getValue()) { - addSample(indices, headway,dwelltime); + addSample(departure,headway,dwelltime); } - + } } } - } + } } - private ArrivalDeparture findPreviousArrival(List stopData, ArrivalDeparture arrival) { + private ArrivalDeparture findPreviousArrival(List stopData, ArrivalDeparture arrival) { for(ArrivalDeparture event:stopData) { if(event.isArrival()) @@ -153,7 +146,7 @@ private ArrivalDeparture findPreviousArrival(List stopData, Ar if(!event.getTripId().equals(arrival.getTripId())) { if(event.getStopId().equals(arrival.getStopId())) - { + { if(event.getTime() stopData, ArrivalDeparture departure) { @@ -196,10 +189,9 @@ private ArrivalDeparture findArrival(List stopData, ArrivalDep return null; } @Override - public Long predictDwellTime(Indices indices, Headway headway) { - - DwellTimeCacheKey key=new DwellTimeCacheKey(indices); - TransitClockRLS rls=cache.get(key); + public Long predictDwellTime(StopPathCacheKey cacheKey, Headway headway) { + + TransitClockRLS rls=cache.get(cacheKey); if(rls!=null&&rls.getRls()!=null) { double[] arg0 = new double[1]; @@ -211,13 +203,13 @@ public Long predictDwellTime(Indices indices, Headway headway) { return null; } } - public static void main(String[] args) + public static void main(String[] args) { double startvalue=1000; double result1 = Math.log10(startvalue); double result2 = Math.pow(10, result1); if(startvalue==result2) - System.out.println("As expected they are the same."); + System.out.println("As expected they are the same."); } } diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/TripDataHistoryCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/TripDataHistoryCache.java similarity index 99% rename from transitclock/src/main/java/org/transitclock/core/dataCache/jcs/TripDataHistoryCache.java rename to transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/TripDataHistoryCache.java index c37eb222a..6ddb03a08 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/TripDataHistoryCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/jcs/scheduled/TripDataHistoryCache.java @@ -1,4 +1,4 @@ -package org.transitclock.core.dataCache.jcs; +package org.transitclock.core.dataCache.jcs.scheduled; import java.util.ArrayList; import java.util.Calendar; diff --git a/transitclock/src/main/java/org/transitclock/core/dataCache/scheduled/ScheduleBasedHistoricalAverageCache.java b/transitclock/src/main/java/org/transitclock/core/dataCache/scheduled/ScheduleBasedHistoricalAverageCache.java index 4860ed664..9eb90d5d5 100644 --- a/transitclock/src/main/java/org/transitclock/core/dataCache/scheduled/ScheduleBasedHistoricalAverageCache.java +++ b/transitclock/src/main/java/org/transitclock/core/dataCache/scheduled/ScheduleBasedHistoricalAverageCache.java @@ -23,7 +23,7 @@ import org.transitclock.core.dataCache.StopPathCacheKey; import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; import org.transitclock.core.dataCache.TripKey; -import org.transitclock.core.dataCache.ehcache.TripDataHistoryCache; +import org.transitclock.core.dataCache.ehcache.scheduled.TripDataHistoryCache; import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; import org.transitclock.db.structs.ArrivalDeparture; import org.transitclock.db.structs.Trip; diff --git a/transitclock/src/main/java/org/transitclock/core/holdingmethod/HoldingTimeGeneratorDefaultImpl.java b/transitclock/src/main/java/org/transitclock/core/holdingmethod/HoldingTimeGeneratorDefaultImpl.java index 217f4e822..1df6c66f9 100755 --- a/transitclock/src/main/java/org/transitclock/core/holdingmethod/HoldingTimeGeneratorDefaultImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/holdingmethod/HoldingTimeGeneratorDefaultImpl.java @@ -225,6 +225,58 @@ public HoldingTime generateHoldingTime(VehicleState vehicleState, ArrivalDepartu // Return null so has no effect. return null; } + public static List getOrderedListOfVehicles(String routeId) + { + int count=0; + boolean canorder=true; + List unordered=new ArrayList(); + List ordered=null; + for(VehicleState currentVehicleState:VehicleStateManager.getInstance().getVehiclesState()) + { + if(currentVehicleState.getTrip()!=null&¤tVehicleState.getTrip().getRoute().getId().equals(routeId)&¤tVehicleState.isPredictable()) + { + count++; + unordered.add(currentVehicleState); + if(currentVehicleState.getHeadway()==null) + { + canorder=false; + + } + } + } + if(canorder) + { + ordered=new ArrayList(); + + while(((count+1) > 0)&&unordered.size() > 0) + { + if(ordered.size()==0) + { + String first=unordered.get(0).getVehicleId(); + String second=unordered.get(0).getHeadway().getOtherVehicleId(); + + ordered.add(first); + count--; + ordered.add(second); + count--; + }else + { + ordered.add(VehicleStateManager.getInstance().getVehicleState(ordered.get(ordered.size()-1)).getHeadway().getOtherVehicleId()); + count--; + } + + } + // check first vehicle equals last vehicle + if(ordered.size()>1) + { + if(!ordered.get(ordered.size()-1).equals(ordered.get(0))) + { + return null; + } + } + } + return ordered; + } protected ArrayList getCurrentHoldingTimesForStop(String stopId) { ArrayList currentHoldingTimes=new ArrayList(); diff --git a/transitclock/src/main/java/org/transitclock/core/predAccuracy/PredictionAccuracyModule.java b/transitclock/src/main/java/org/transitclock/core/predAccuracy/PredictionAccuracyModule.java index b86b29a99..40654cd20 100755 --- a/transitclock/src/main/java/org/transitclock/core/predAccuracy/PredictionAccuracyModule.java +++ b/transitclock/src/main/java/org/transitclock/core/predAccuracy/PredictionAccuracyModule.java @@ -412,7 +412,7 @@ protected synchronized void getAndProcessData(List routesAndStops predictionsReadTime, pred.isArrival(), pred.isAffectedByWaitStop(), - "Transitime",null,null); + "TransitClock",null,null); storePrediction(accuracyPred); predictionsFound = true; } diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/dwell/rls/DwellTimePredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/dwell/rls/DwellTimePredictionGeneratorImpl.java new file mode 100644 index 000000000..5c829b3af --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/dwell/rls/DwellTimePredictionGeneratorImpl.java @@ -0,0 +1,92 @@ +package org.transitclock.core.predictiongenerator.frequency.dwell.rls; + +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.core.Indices; +import org.transitclock.core.VehicleState; +import org.transitclock.core.dataCache.DwellTimeModelCacheFactory; +import org.transitclock.core.dataCache.StopPathCacheKey; +import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; +import org.transitclock.core.predictiongenerator.frequency.traveltime.kalman.KalmanPredictionGeneratorImpl; +import org.transitclock.db.structs.AvlReport; +import org.transitclock.db.structs.Headway; + +/** + * @author Sean Og Crudden + * + * This is an experiment to see if headway can be used to better predict dwell time. Most of what + * I have read tells me it can but in conjunction with APC data and estimation of demand at stops. + * + * This is for frequency based services. + * + * + * + */ +public class DwellTimePredictionGeneratorImpl extends KalmanPredictionGeneratorImpl { + + private static final Logger logger = LoggerFactory.getLogger(DwellTimePredictionGeneratorImpl.class); + + @Override + public long getStopTimeForPath(Indices indices, AvlReport avlReport, VehicleState vehicleState) { + Long result=null; + try { + Headway headway = vehicleState.getHeadway(); + + if(headway!=null) + { + logger.debug("Headway at {} based on avl {} is {}.",indices, avlReport, headway); + + /* Change approach to use a RLS model. + */ + if(super.getStopTimeForPath(indices, avlReport, vehicleState)>0) + { + // TODO Would be more correct to use the start time of the trip. + Integer time=FrequencyBasedHistoricalAverageCache.secondsFromMidnight(new Date(avlReport.getTime()),2); + + time=FrequencyBasedHistoricalAverageCache.round(time, FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + StopPathCacheKey cacheKey=new StopPathCacheKey(indices.getTrip().getId(), indices.getStopPathIndex(), false, new Long(time)); + + result = DwellTimeModelCacheFactory.getInstance().predictDwellTime(cacheKey, headway); + + if(result==null) + { + logger.debug("Using scheduled value for dwell time as no RLS data available for {}.", indices); + result = super.getStopTimeForPath(indices, avlReport, vehicleState); + } + + + /* should never have a negative dwell time */ + if(result<0) + { + logger.debug("Predicted negative dwell time {} for {}.", result, indices); + result=0L; + } + + }else + { + logger.debug("Scheduled dwell time is less than 0 for {}.", indices); + result = super.getStopTimeForPath(indices, avlReport, vehicleState); + } + + + } + else + { + result = super.getStopTimeForPath(indices, avlReport, vehicleState); + logger.debug("Using dwell time {} for {} instead of {}. No headway.",result,indices, super.getStopTimeForPath(indices ,avlReport, vehicleState)); + } + + } catch (Exception e) { + + logger.error(e.getMessage(),e); + e.printStackTrace(); + + } + + return result; + } + +} diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/frequency/HistoricalAveragePredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/average/HistoricalAveragePredictionGeneratorImpl.java similarity index 98% rename from transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/frequency/HistoricalAveragePredictionGeneratorImpl.java rename to transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/average/HistoricalAveragePredictionGeneratorImpl.java index fdfe58956..4a5ac82c1 100755 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/frequency/HistoricalAveragePredictionGeneratorImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/average/HistoricalAveragePredictionGeneratorImpl.java @@ -1,4 +1,4 @@ -package org.transitclock.core.predictiongenerator.average.frequency; +package org.transitclock.core.predictiongenerator.frequency.traveltime.average; import java.util.Calendar; import java.util.Date; diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/kalman/KalmanPredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/kalman/KalmanPredictionGeneratorImpl.java new file mode 100755 index 000000000..131f1dc36 --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/frequency/traveltime/kalman/KalmanPredictionGeneratorImpl.java @@ -0,0 +1,240 @@ +package org.transitclock.core.predictiongenerator.frequency.traveltime.kalman; + +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang3.time.DateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.applications.Core; +import org.transitclock.config.BooleanConfigValue; +import org.transitclock.config.DoubleConfigValue; +import org.transitclock.config.IntegerConfigValue; +import org.transitclock.core.Indices; +import org.transitclock.core.SpatialMatch; +import org.transitclock.core.TravelTimeDetails; +import org.transitclock.core.VehicleState; +import org.transitclock.core.dataCache.ErrorCache; +import org.transitclock.core.dataCache.ErrorCacheFactory; +import org.transitclock.core.dataCache.KalmanErrorCacheKey; +import org.transitclock.core.dataCache.StopPathPredictionCache; +import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; +import org.transitclock.core.dataCache.TripDataHistoryCacheInterface; +import org.transitclock.core.dataCache.VehicleStateManager; +import org.transitclock.core.dataCache.frequency.FrequencyBasedHistoricalAverageCache; +import org.transitclock.core.predictiongenerator.PredictionComponentElementsGenerator; +import org.transitclock.core.predictiongenerator.frequency.traveltime.average.HistoricalAveragePredictionGeneratorImpl; +import org.transitclock.core.predictiongenerator.kalman.KalmanPrediction; +import org.transitclock.core.predictiongenerator.kalman.KalmanPredictionResult; +import org.transitclock.core.predictiongenerator.kalman.TripSegment; +import org.transitclock.core.predictiongenerator.kalman.Vehicle; +import org.transitclock.core.predictiongenerator.kalman.VehicleStopDetail; +import org.transitclock.db.structs.AvlReport; +import org.transitclock.db.structs.PredictionForStopPath; + +/** + * @author Sean Óg Crudden This is a prediction generator that uses a Kalman + * filter to provide predictions for a frequency based service. + */ +public class KalmanPredictionGeneratorImpl extends HistoricalAveragePredictionGeneratorImpl + implements PredictionComponentElementsGenerator { + + private String alternative="LastVehiclePredictionGeneratorImpl"; + + /* + * TODO I think this needs to be a minimum of three and if just two will use + * historical value. + */ + private static final IntegerConfigValue minKalmanDays = new IntegerConfigValue( + "transitclock.prediction.data.kalman.mindays", new Integer(3), + "Min number of days trip data that needs to be available before Kalman prediciton is used instead of default transiTime prediction."); + + private static final IntegerConfigValue maxKalmanDays = new IntegerConfigValue( + "transitclock.prediction.data.kalman.maxdays", new Integer(3), + "Max number of historical days trips to include in Kalman prediction calculation."); + + private static final IntegerConfigValue maxKalmanDaysToSearch = new IntegerConfigValue( + "transitclock.prediction.data.kalman.maxdaystoseach", new Integer(30), + "Max number of days to look back for data. This will also be effected by how old the data in the cache is."); + + private static final DoubleConfigValue initialErrorValue = new DoubleConfigValue( + "transitclock.prediction.data.kalman.initialerrorvalue", new Double(100), + "Initial Kalman error value to use to start filter."); + + /* May be better to use the default implementation as it splits things down into segments. */ + private static final BooleanConfigValue useKalmanForPartialStopPaths = new BooleanConfigValue ( + "transitclock.prediction.data.kalman.usekalmanforpartialstoppaths", new Boolean(true), + "Will use Kalman prediction to get to first stop of prediction." + ); + + + private static final Logger logger = LoggerFactory.getLogger(KalmanPredictionGeneratorImpl.class); + + /* + * (non-Javadoc) + * + * @see + * org.transitclock.core.PredictionGeneratorDefaultImpl#getTravelTimeForPath + * (org.transitclock.core.Indices, org.transitclock.db.structs.AvlReport) + */ + @Override + public long getTravelTimeForPath(Indices indices, AvlReport avlReport, VehicleState vehicleState) { + + logger.debug("Calling frequency based Kalman prediction algorithm for : "+indices.toString()); + + + Integer time=FrequencyBasedHistoricalAverageCache.secondsFromMidnight(avlReport.getDate(),2); + + time=FrequencyBasedHistoricalAverageCache.round(time, FrequencyBasedHistoricalAverageCache.getCacheIncrementsForFrequencyService()); + + TripDataHistoryCacheInterface tripCache = TripDataHistoryCacheFactory.getInstance(); + + ErrorCache kalmanErrorCache = ErrorCacheFactory.getInstance(); + + VehicleStateManager vehicleStateManager = VehicleStateManager.getInstance(); + + VehicleState currentVehicleState = vehicleStateManager.getVehicleState(avlReport.getVehicleId()); + + TravelTimeDetails travelTimeDetails = this.getLastVehicleTravelTime(currentVehicleState, indices); + + + /* + * The first vehicle of the day should use schedule or historic data to + * make prediction. Cannot use Kalman as yesterdays vehicle will have + * little to say about todays. + */ + if (travelTimeDetails!=null) { + + logger.debug("Kalman has last vehicle info for : " +indices.toString()+ " : "+travelTimeDetails); + + Date nearestDay = DateUtils.truncate(avlReport.getDate(), Calendar.DAY_OF_MONTH); + + List lastDaysTimes = lastDaysTimes(tripCache, currentVehicleState.getTrip().getId(),currentVehicleState.getTrip().getDirectionId(), + indices.getStopPathIndex(), nearestDay, time, + maxKalmanDaysToSearch.getValue(), maxKalmanDays.getValue()); + + if(lastDaysTimes!=null&&lastDaysTimes.size()>0) + { + logger.debug("Kalman has " +lastDaysTimes.size()+ " historical values for : " +indices.toString()); + } + /* + * if we have enough data start using Kalman filter otherwise revert + * to extended class for prediction. + */ + if (lastDaysTimes != null && lastDaysTimes.size() >= minKalmanDays.getValue().intValue()) { + + logger.debug("Generating Kalman prediction for : "+indices.toString()); + + try { + + KalmanPrediction kalmanPrediction = new KalmanPrediction(); + + KalmanPredictionResult kalmanPredictionResult; + + Vehicle vehicle = new Vehicle(avlReport.getVehicleId()); + + VehicleStopDetail originDetail = new VehicleStopDetail(null, 0, vehicle); + TripSegment[] historical_segments_k = new TripSegment[lastDaysTimes.size()]; + for (int i = 0; i < lastDaysTimes.size() && i < maxKalmanDays.getValue(); i++) { + + logger.debug("Kalman is using historical value : "+lastDaysTimes.get(i) +" for : " + indices.toString()); + + VehicleStopDetail destinationDetail = new VehicleStopDetail(null, lastDaysTimes.get(i).getTravelTime(), + vehicle); + historical_segments_k[i] = new TripSegment(originDetail, destinationDetail); + } + + VehicleStopDetail destinationDetail_0_k_1 = new VehicleStopDetail(null, travelTimeDetails.getTravelTime(), vehicle); + + TripSegment ts_day_0_k_1 = new TripSegment(originDetail, destinationDetail_0_k_1); + + TripSegment last_vehicle_segment = ts_day_0_k_1; + + Indices previousVehicleIndices = new Indices(travelTimeDetails.getArrival()); + + Double last_prediction_error = lastVehiclePredictionError(kalmanErrorCache, previousVehicleIndices); + + logger.debug("Using error value: " + last_prediction_error +" found with vehicle id "+travelTimeDetails.getArrival().getVehicleId()+ " from: "+new KalmanErrorCacheKey(previousVehicleIndices).toString()); + + //TODO this should also display the detail of which vehicle it choose as the last one. + logger.debug("Using last vehicle value: " + travelTimeDetails + " for : "+ indices.toString()); + + kalmanPredictionResult = kalmanPrediction.predict(last_vehicle_segment, historical_segments_k, + last_prediction_error); + + long predictionTime = (long) kalmanPredictionResult.getResult(); + + logger.debug("Setting Kalman error value: " + kalmanPredictionResult.getFilterError() + " for : "+ new KalmanErrorCacheKey(indices).toString()); + + kalmanErrorCache.putErrorValue(indices, kalmanPredictionResult.getFilterError()); + + logger.debug("Using Kalman prediction: " + predictionTime + " instead of "+alternative+" prediction: " + + super.getTravelTimeForPath(indices, avlReport, vehicleState) +" for : " + indices.toString()); + + if(storeTravelTimeStopPathPredictions.getValue()) + { + PredictionForStopPath predictionForStopPath=new PredictionForStopPath(vehicleState.getVehicleId(), new Date(Core.getInstance().getSystemTime()), new Double(new Long(predictionTime).intValue()), indices.getTrip().getId(), indices.getStopPathIndex(), "KALMAN", true, null); + Core.getInstance().getDbLogger().add(predictionForStopPath); + StopPathPredictionCache.getInstance().putPrediction(predictionForStopPath); + } + return predictionTime; + + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + return super.getTravelTimeForPath(indices, avlReport, vehicleState); + } + + @Override + public long expectedTravelTimeFromMatchToEndOfStopPath(AvlReport avlReport, SpatialMatch match) { + + if(useKalmanForPartialStopPaths.getValue().booleanValue()) + { + VehicleStateManager vehicleStateManager = VehicleStateManager.getInstance(); + + VehicleState currentVehicleState = vehicleStateManager.getVehicleState(avlReport.getVehicleId()); + + long fulltime = this.getTravelTimeForPath(match.getIndices(), avlReport, currentVehicleState); + + double distanceAlongStopPath = match.getDistanceAlongStopPath(); + + double stopPathLength = + match.getStopPath().getLength(); + + long remainingtime = (long) (fulltime * ((stopPathLength-distanceAlongStopPath)/stopPathLength)); + + logger.debug("Using Kalman for first stop path {} with value {} instead of {}.", match.getIndices(), remainingtime, super.expectedTravelTimeFromMatchToEndOfStopPath(avlReport, match)); + + return remainingtime; + }else + { + return super.expectedTravelTimeFromMatchToEndOfStopPath(avlReport, match); + } + } + + private Double lastVehiclePredictionError(ErrorCache cache, Indices indices) { + + Double result = cache.getErrorValue(indices); + if(result!=null&&!result.isNaN()) + { + logger.debug("Kalman Error value : "+result +" for key: "+new KalmanErrorCacheKey(indices).toString()); + } + else + { + logger.debug("Kalman Error value set to default: "+initialErrorValue.getValue() +" for key: "+new KalmanErrorCacheKey(indices).toString()); + return initialErrorValue.getValue(); + } + return result; + } + + @Override + public long getStopTimeForPath(Indices indices, AvlReport avlReport, VehicleState vehicleState) { + long result=super.getStopTimeForPath(indices, avlReport, vehicleState); + + return result; + + } +} diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPrediction.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPrediction.java index 9ecdcd92c..a19712690 100755 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPrediction.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPrediction.java @@ -1,6 +1,5 @@ package org.transitclock.core.predictiongenerator.kalman; - /** * @author Sean Óg Crudden * diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/Prediction.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/Prediction.java deleted file mode 100755 index 81166d797..000000000 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/Prediction.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.transitclock.core.predictiongenerator.kalman; - -public interface Prediction { - -} diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/lastvehicle/LastVehiclePredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/lastvehicle/LastVehiclePredictionGeneratorImpl.java index 78f274265..5392ae467 100755 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/lastvehicle/LastVehiclePredictionGeneratorImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/lastvehicle/LastVehiclePredictionGeneratorImpl.java @@ -18,7 +18,7 @@ import org.transitclock.core.dataCache.StopPathPredictionCache; import org.transitclock.core.dataCache.VehicleDataCache; import org.transitclock.core.dataCache.VehicleStateManager; -import org.transitclock.core.dataCache.ehcache.TripDataHistoryCache; +import org.transitclock.core.dataCache.ehcache.scheduled.TripDataHistoryCache; import org.transitclock.core.dataCache.scheduled.ScheduleBasedHistoricalAverageCache; import org.transitclock.core.predictiongenerator.PredictionComponentElementsGenerator; import org.transitclock.db.structs.AvlReport; diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/scheduled/HistoricalAveragePredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/average/HistoricalAveragePredictionGeneratorImpl.java similarity index 96% rename from transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/scheduled/HistoricalAveragePredictionGeneratorImpl.java rename to transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/average/HistoricalAveragePredictionGeneratorImpl.java index 16b2459e3..ab2224504 100755 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/average/scheduled/HistoricalAveragePredictionGeneratorImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/average/HistoricalAveragePredictionGeneratorImpl.java @@ -1,4 +1,4 @@ -package org.transitclock.core.predictiongenerator.average.scheduled; +package org.transitclock.core.predictiongenerator.scheduled.average; import java.util.Calendar; import java.util.Date; diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/DwellTimePredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/DwellTimePredictionGeneratorImpl.java similarity index 87% rename from transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/DwellTimePredictionGeneratorImpl.java rename to transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/DwellTimePredictionGeneratorImpl.java index c111278e6..269410de1 100644 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/DwellTimePredictionGeneratorImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/DwellTimePredictionGeneratorImpl.java @@ -1,4 +1,4 @@ -package org.transitclock.core.predictiongenerator.rls.dwell; +package org.transitclock.core.predictiongenerator.scheduled.dwell.rls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,8 +8,9 @@ import org.transitclock.core.TemporalDifference; import org.transitclock.core.VehicleState; import org.transitclock.core.dataCache.DwellTimeModelCacheFactory; +import org.transitclock.core.dataCache.StopPathCacheKey; import org.transitclock.core.dataCache.VehicleStateManager; -import org.transitclock.core.predictiongenerator.kalman.KalmanPredictionGeneratorImpl; +import org.transitclock.core.predictiongenerator.scheduled.traveltime.kalman.KalmanPredictionGeneratorImpl; import org.transitclock.db.structs.AvlReport; import org.transitclock.db.structs.Headway; import org.transitclock.ipc.data.IpcPrediction; @@ -41,7 +42,11 @@ public long getStopTimeForPath(Indices indices, AvlReport avlReport, VehicleSta */ if(super.getStopTimeForPath(indices, avlReport, vehicleState)>0) { - result = DwellTimeModelCacheFactory.getInstance().predictDwellTime(indices, headway); + + StopPathCacheKey cacheKey=new StopPathCacheKey(indices.getTrip().getId(), indices.getStopPathIndex(), false); + + + result = DwellTimeModelCacheFactory.getInstance().predictDwellTime(cacheKey, headway); if(result==null) { diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/TransitClockRLS.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/TransitClockRLS.java similarity index 94% rename from transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/TransitClockRLS.java rename to transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/TransitClockRLS.java index b7aafc017..1d7da2cf5 100644 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/rls/dwell/TransitClockRLS.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/dwell/rls/TransitClockRLS.java @@ -1,4 +1,4 @@ -package org.transitclock.core.predictiongenerator.rls.dwell; +package org.transitclock.core.predictiongenerator.scheduled.dwell.rls; import java.io.Serializable; diff --git a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPredictionGeneratorImpl.java b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/traveltime/kalman/KalmanPredictionGeneratorImpl.java similarity index 91% rename from transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPredictionGeneratorImpl.java rename to transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/traveltime/kalman/KalmanPredictionGeneratorImpl.java index 25ae221b0..7cc7d89d6 100755 --- a/transitclock/src/main/java/org/transitclock/core/predictiongenerator/kalman/KalmanPredictionGeneratorImpl.java +++ b/transitclock/src/main/java/org/transitclock/core/predictiongenerator/scheduled/traveltime/kalman/KalmanPredictionGeneratorImpl.java @@ -1,4 +1,4 @@ -package org.transitclock.core.predictiongenerator.kalman; +package org.transitclock.core.predictiongenerator.scheduled.traveltime.kalman; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -26,10 +26,15 @@ import org.transitclock.core.dataCache.TripDataHistoryCacheFactory; import org.transitclock.core.dataCache.TripDataHistoryCacheInterface; import org.transitclock.core.dataCache.VehicleStateManager; -import org.transitclock.core.dataCache.ehcache.TripDataHistoryCache; +import org.transitclock.core.dataCache.ehcache.scheduled.TripDataHistoryCache; import org.transitclock.core.dataCache.jcs.KalmanErrorCache; import org.transitclock.core.predictiongenerator.PredictionComponentElementsGenerator; -import org.transitclock.core.predictiongenerator.average.scheduled.HistoricalAveragePredictionGeneratorImpl; +import org.transitclock.core.predictiongenerator.kalman.KalmanPrediction; +import org.transitclock.core.predictiongenerator.kalman.KalmanPredictionResult; +import org.transitclock.core.predictiongenerator.kalman.TripSegment; +import org.transitclock.core.predictiongenerator.kalman.Vehicle; +import org.transitclock.core.predictiongenerator.kalman.VehicleStopDetail; +import org.transitclock.core.predictiongenerator.scheduled.average.HistoricalAveragePredictionGeneratorImpl; import org.transitclock.db.structs.AvlReport; import org.transitclock.db.structs.PredictionForStopPath; import org.transitclock.ipc.data.IpcPrediction; @@ -214,7 +219,7 @@ public long expectedTravelTimeFromMatchToEndOfStopPath(AvlReport avlReport, Spat private Double lastVehiclePredictionError(ErrorCache cache, Indices indices) { Double result = cache.getErrorValue(indices); - if(result!=null) + if(result!=null&&!result.isNaN()) { logger.debug("Kalman Error value : "+result +" for key: "+new KalmanErrorCacheKey(indices).toString()); } diff --git a/transitclock/src/main/java/org/transitclock/custom/bullrunner/BullrunnerPlaybackModule.java b/transitclock/src/main/java/org/transitclock/custom/bullrunner/BullrunnerPlaybackModule.java new file mode 100644 index 000000000..49f4d3dff --- /dev/null +++ b/transitclock/src/main/java/org/transitclock/custom/bullrunner/BullrunnerPlaybackModule.java @@ -0,0 +1,191 @@ +package org.transitclock.custom.bullrunner; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.transitclock.applications.Core; +import org.transitclock.avl.PollUrlAvlModule; +import org.transitclock.config.StringConfigValue; +import org.transitclock.db.structs.AvlReport; +import org.transitclock.db.structs.AvlReport.AssignmentType; +import org.transitclock.modules.Module; +import org.transitclock.utils.Time; + +/** + * @author scrudden + * Throw away module. Just used to read in archived Bullrunner data to work on Kalman predictions for frequency based services. + * + */ +public class BullrunnerPlaybackModule extends PollUrlAvlModule { + + private static StringConfigValue bullrunnerbasePlaybackUrl = + new StringConfigValue("transitclock.avl.bullrunnerbasePlaybackUrl", "http://transit.jadorno.com/bullrunner/gtfsrt/vehicle_positions", + "The URL of the historical json feed to use."); + + private static String getPlaybackStartTimeStr() { + return playbackStartTimeStr.getValue(); + } + + private static StringConfigValue playbackStartTimeStr = + new StringConfigValue("transitclock.avl.bullrunner.playbackStartTime", + "08-06-2018 12:00:00", + "Date and time of when to start the playback."); + + private static StringConfigValue playbackEndTimeStr = + new StringConfigValue("transitclock.avl.bullrunner.playbackEndTime", + "08-09-2018 23:00:00", + "Date and time of when to end the playback."); + + + + private static StringConfigValue runnerTestRoute = + new StringConfigValue("transitclock.avl.testroute", null, + "Route to test against."); + + private static final Logger logger = LoggerFactory + .getLogger(BullrunnerPlaybackModule.class); + + public BullrunnerPlaybackModule(String agencyId) { + super(agencyId); + + if(latesttime==null) + latesttime=parsePlaybackStartTime(playbackStartTimeStr.getValue()); + + } + Long latesttime=null; + + // If debugging feed and want to not actually process + // AVL reports to generate predictions and such then + // set shouldProcessAvl to false; + private static boolean shouldProcessAvl = true; + @Override + protected String getUrl() { + + return bullrunnerbasePlaybackUrl.getValue()+"?timestamp="+latesttime; + } + + public static void main(String[] args) { + // TODO Auto-generated method stub + Module.start("org.transitclock.custom.bullrunner.BullrunnerPlaybackModule"); + } + + @Override + protected Collection processData(InputStream in) throws Exception { + + String jsonStr = getJsonString(in); + Collection avlReportsReadIn = new ArrayList(); + try { + // Convert JSON string to a JSON object + JSONObject jsonObj = new JSONObject(jsonStr); + + JSONArray entities=(JSONArray) jsonObj.get("entity"); + JSONObject header=(JSONObject) jsonObj.get("header"); + + //JSONArray vehicles = entityObj.getJSONArray("vehicle"); + + for(int i=0;i(); + + } + + } + + + private static long parsePlaybackStartTime(String playbackStartTimeStr) { + try { + long playbackStartTime = Time.parse(playbackStartTimeStr).getTime(); + + // If specified time is in the future then reject. + if (playbackStartTime > System.currentTimeMillis()) { + logger.error("Playback start time \"{}\" specified by " + + "transitclock.avl.bullrunner.playbackStartTime parameter is in " + + "the future and therefore invalid!", + playbackStartTimeStr); + System.exit(-1); + } + + return playbackStartTime; + } catch (java.text.ParseException e) { + logger.error("Paramater -t \"{}\" specified by " + + "transitclock.avl.bullrunner.playbackStartTime parameter could not " + + "be parsed. Format must be \"MM-dd-yyyy HH:mm:ss\"", + playbackStartTimeStr); + System.exit(-1); + + // Will never be reached because the above state exits program but + // needed so compiler doesn't complain. + return -1; + } + } + private static long parsePlaybackEndTime(String playbackEndTimeStr) { + try { + long playbackEndTime = Time.parse(playbackEndTimeStr).getTime(); + + // If specified time is in the future then reject. + if (playbackEndTime > System.currentTimeMillis()) { + logger.error("Playback end time \"{}\" specified by " + + "transitclock.avl.playbackEndTime parameter is in " + + "the future and therefore invalid!", + playbackEndTimeStr); + System.exit(-1); + } + + return playbackEndTime; + } catch (java.text.ParseException e) { + logger.error("Paramater -t \"{}\" specified by " + + "transitclock.avl.playbackEndTime parameter could not " + + "be parsed. Format must be \"MM-dd-yyyy HH:mm:ss\"", + playbackEndTimeStr); + System.exit(-1); + + // Will never be reached because the above state exits program but + // needed so compiler doesn't complain. + return -1; + } + } +} diff --git a/transitclock/src/main/java/org/transitclock/db/structs/Agency.java b/transitclock/src/main/java/org/transitclock/db/structs/Agency.java index aab61664c..121f41b50 100644 --- a/transitclock/src/main/java/org/transitclock/db/structs/Agency.java +++ b/transitclock/src/main/java/org/transitclock/db/structs/Agency.java @@ -55,7 +55,8 @@ public class Agency implements Serializable { // Note: this is the GTFS agency_id, not the usual // Transitime agencyId. - @Column(length=HibernateUtils.DEFAULT_ID_SIZE) + @Column(length=HibernateUtils.DEFAULT_ID_SIZE) + @Id private final String agencyId; @Column diff --git a/transitclock/src/main/resources/ddl_mysql_org_transitime_db_structs.sql b/transitclock/src/main/resources/ddl_mysql_org_transitclock_db_structs.sql similarity index 99% rename from transitclock/src/main/resources/ddl_mysql_org_transitime_db_structs.sql rename to transitclock/src/main/resources/ddl_mysql_org_transitclock_db_structs.sql index 819223bd1..2476abca0 100644 --- a/transitclock/src/main/resources/ddl_mysql_org_transitime_db_structs.sql +++ b/transitclock/src/main/resources/ddl_mysql_org_transitclock_db_structs.sql @@ -9,8 +9,8 @@ create table Agencies ( configRev integer not null, agencyName varchar(60) not null, + agencyId varchar(60) not null, agencyFareUrl varchar(255), - agencyId varchar(60), agencyLang varchar(15), agencyPhone varchar(15), agencyTimezone varchar(40), @@ -19,7 +19,7 @@ maxLon double precision, minLat double precision, minLon double precision, - primary key (configRev, agencyName) + primary key (configRev, agencyName, agencyId) ); create table ArrivalsDepartures ( diff --git a/transitclock/src/main/resources/ddl_mysql_org_transitime_db_webstructs.sql b/transitclock/src/main/resources/ddl_mysql_org_transitclock_db_webstructs.sql similarity index 100% rename from transitclock/src/main/resources/ddl_mysql_org_transitime_db_webstructs.sql rename to transitclock/src/main/resources/ddl_mysql_org_transitclock_db_webstructs.sql diff --git a/transitclock/src/main/resources/ddl_oracle_org_transitime_db_structs.sql b/transitclock/src/main/resources/ddl_oracle_org_transitclock_db_structs.sql similarity index 99% rename from transitclock/src/main/resources/ddl_oracle_org_transitime_db_structs.sql rename to transitclock/src/main/resources/ddl_oracle_org_transitclock_db_structs.sql index 6f0c49cc4..fb7af706d 100644 --- a/transitclock/src/main/resources/ddl_oracle_org_transitime_db_structs.sql +++ b/transitclock/src/main/resources/ddl_oracle_org_transitclock_db_structs.sql @@ -9,8 +9,8 @@ create table Agencies ( configRev number(10,0) not null, agencyName varchar2(60 char) not null, + agencyId varchar2(60 char) not null, agencyFareUrl varchar2(255 char), - agencyId varchar2(60 char), agencyLang varchar2(15 char), agencyPhone varchar2(15 char), agencyTimezone varchar2(40 char), @@ -19,7 +19,7 @@ maxLon double precision, minLat double precision, minLon double precision, - primary key (configRev, agencyName) + primary key (configRev, agencyName, agencyId) ); create table ArrivalsDepartures ( diff --git a/transitclock/src/main/resources/ddl_oracle_org_transitime_db_webstructs.sql b/transitclock/src/main/resources/ddl_oracle_org_transitclock_db_webstructs.sql similarity index 100% rename from transitclock/src/main/resources/ddl_oracle_org_transitime_db_webstructs.sql rename to transitclock/src/main/resources/ddl_oracle_org_transitclock_db_webstructs.sql diff --git a/transitclock/src/main/resources/ddl_postgres_org_transitime_db_structs.sql b/transitclock/src/main/resources/ddl_postgres_org_transitclock_db_structs.sql similarity index 99% rename from transitclock/src/main/resources/ddl_postgres_org_transitime_db_structs.sql rename to transitclock/src/main/resources/ddl_postgres_org_transitclock_db_structs.sql index 33cce163c..838c52147 100644 --- a/transitclock/src/main/resources/ddl_postgres_org_transitime_db_structs.sql +++ b/transitclock/src/main/resources/ddl_postgres_org_transitclock_db_structs.sql @@ -9,8 +9,8 @@ create table Agencies ( configRev int4 not null, agencyName varchar(60) not null, + agencyId varchar(60) not null, agencyFareUrl varchar(255), - agencyId varchar(60), agencyLang varchar(15), agencyPhone varchar(15), agencyTimezone varchar(40), @@ -19,7 +19,7 @@ maxLon float8, minLat float8, minLon float8, - primary key (configRev, agencyName) + primary key (configRev, agencyName, agencyId) ); create table ArrivalsDepartures ( diff --git a/transitclock/src/main/resources/ddl_postgres_org_transitime_db_webstructs.sql b/transitclock/src/main/resources/ddl_postgres_org_transitclock_db_webstructs.sql similarity index 100% rename from transitclock/src/main/resources/ddl_postgres_org_transitime_db_webstructs.sql rename to transitclock/src/main/resources/ddl_postgres_org_transitclock_db_webstructs.sql diff --git a/transitclock/src/main/resources/logback.xml b/transitclock/src/main/resources/logback.xml index b5542989f..7b8764c75 100755 --- a/transitclock/src/main/resources/logback.xml +++ b/transitclock/src/main/resources/logback.xml @@ -399,33 +399,11 @@ - - - - - - - - - - - - - - - + @@ -513,9 +491,9 @@ level="warn" additivity="false"> - - + (); for (Location loc : shape.getLocations()) { this.locations.add(new ApiLocation(loc.getLat(), loc.getLon())); } + int size=shape.getLocations().size(); + if(size>0 && Geo.distance(shape.getLocations().get(0), shape.getLocations().get(size-1)) routeIdsOrShortNames, + @QueryParam(value = "r") List routeIdsOrShortNames, @Parameter(description="If set then only the shape for specified direction is marked as being for the UI." ,required=false) @QueryParam(value = "d") String directionId, @Parameter(description="If set then only this stop and the remaining ones on " diff --git a/transitclockWebapp/src/main/webapp/synoptic/index.jsp b/transitclockWebapp/src/main/webapp/synoptic/index.jsp index bfae658a5..be0b214ee 100644 --- a/transitclockWebapp/src/main/webapp/synoptic/index.jsp +++ b/transitclockWebapp/src/main/webapp/synoptic/index.jsp @@ -52,8 +52,11 @@ +
+ style="width: 100%; height: 480px; left: 0px; top: 50px; position: absolute;' ">