//tracking.js //Olivia Alberts //Last updated: May 12th, 2020 //This file is the bulk of the project - it is the screen shown to the user after login //and is where everything happens! import React, { Component } from 'react'; import {StyleSheet, ActivityIndicator, View, Text, KeyboardAvoidingView, Image, TextInput, TouchableOpacity} from 'react-native' import UserApp from '../App.js' /************************************************************************************************ References: Youtube: The Net Ninja - React Native Tutorial for Beginners - some really great tutorials for basic React Native stuff, a playlist of all his React Native tutorial videos, his navigation videos were especially helpful Alyssa Khor & her 2019 Capstone Positive Notifications - Uplift **************************************************************************************************/ console.disableYellowBox = true; //disables warning messages from appearing at bottom of screen export default class Tracking extends React.Component{ constructor(props){ super(props); this.routeID = 2; this.userETA; this.locationArray = []; //the array of location data from the bus, so holds the lat, long, and time of bus //index 0 of locationArray is most recent location of bus, index 1 is 2nd most recent, and so on... this.routeArray = []; //the array of lat and long of all stops, along with the time the bus is supposed to be there //index 0 of routeArray is first stop being made on the route, index 1 is second stop, and so on... this.arrayETA = []; this.upcomingStopNumber = 0; //the number of the upcoming stop this.userStopNumber = 0; this.routeDone = false; this.state = { myText: "ETA: ", isLoading: true, //used for showing a loading symbol while loading }; } //this is ran on start up - all it does is get the route data right away //route data is only grabbed once and it holds the data for each stop on the route componentDidMount() { this.getRouteData(); } //this happens upon closure of the app - it clears the interval componentWillUnmount() { clearInterval(this.interval); } //stop the location interval stopLocationInterval() { clearInterval(this.interval); } /*************************************************************************** Function: driverFunction() Description: This function drives the program. It is the function that is called every interval. If the route is done, it will stop the interval. Otherwise, it will grab the current data from the bus and determine if the bus is at the upcoming stop. ***************************************************************************/ driverFunction() { if(this.routeDone) { //if our route is done, we want to stop getting all this info this.stopLocationInterval(); console.log("No more stops!"); } else { //if our route is still going this.getCurrentData(); //grab the current bus data var routeIndex = this.busAtNextStop(); //is the bus at the next stop? //console.log("Index stopped at: " + routeIndex); //testing console.log(" "); console.log(" "); } } /*************************************************************************** Function: getRouteData() Description: grabs the route data that was uploaded to the server by admin. It does so by using fetch(). Works almost identical to getCurrentData(), but this one grabs from a different file, stores in a different array, and is only gotten once from the server because the route data does not change. It does initialize the ETA array, but ETA is calculated in calculateETA(), not part of route data. ***************************************************************************/ getRouteData() { return fetch('http://compsci02.snc.edu/cs460/2020/albeoa/routeData.json') .then(response => response.json()) .then(responseJSON => { //console.log(responseJSON.locations); this.setState({ isLoading: false, //dataSource: responseJSON }); this.routeArray = responseJSON; //copy the array with all this bus data from the server into our own copy console.log("Route Array: " + this.routeArray); //testing if(this.routeArray.length == 0) this.routeDone = true; else { this.initializeETAarray(); this.routeDone = false; this.setState({ myText: "ETA: " + this.routeArray[this.routeID].time.toString() }) //show the stop's ETA right away this.interval = setInterval(() => this.driverFunction(), 5000); //getCurrentData(), 10000); } }) .catch(error => { console.error(error); }); } /*************************************************************************** Function: getCurrentData() Description: grabs the current data from the server that is being updated constantly from the bus. It does so by using fetch(). Works identical to getRouteData(), but this one grabs from a different file, stores in a different array, and gets called every interval. ***************************************************************************/ getCurrentData() { return fetch('http://compsci02.snc.edu/cs460/2020/albeoa/locationData.json') // dataTest.json') // ..../albeoa/locationData.json updates from bus .then(response => response.json()) .then(responseJSON => { this.setState({ isLoading: false, //dataSource: responseJSON }); this.locationArray = responseJSON; //copy the array with all this bus data from the server into our own copy //console.log(this.locationArray); //testing //this.calculateETA(); }) .catch(error => { console.error(error); }); } /*************************************************************************** Function: busNotMoving() Description: uses array with bus location data from server to decide if the bus is stopped. It does so by checking if the bus' latitude and long have not changed but its time has. If the time has changed and it's still in the same spot, it returns the index of which location item in location array the bus is stopped at. If the array is empty, undefined, or there is no stop, then it returns -1. NOTE: just because the bus is stopped, does not mean it is AT a stop. See busAtStop() function for that. ***************************************************************************/ busNotMoving() { //if our array is undefined or empty we don't want to try to do anything with it if(!(this.arrayEmptyOrUndefined(this.locationArray))) { for(let i = 0; i < this.locationArray.length - 1; i++) { if( (this.locationArray[i].lat == this.locationArray[i + 1].lat) //if latitudes are the same && (this.locationArray[i].long == this.locationArray[i + 1].long) //and longitudes are the same && (this.locationArray[i].time != this.locationArray[i + 1].time)) //but the times are different { return i; } } } return -1; } /*************************************************************************** Function: busAtNextStop Description: Returns true or false based on whether or not we are at the upcoming stop. It does so by first asking if we still have stops left in our route. It then asks if the bus itself is stopped somewhere and if it is within range of the upcoming stop. If all of that is true, that means we are at the next stop, so the function will get rid of that stop since it's been visited and it will move on to the next one and update its ETA. If there are no more stops, it will update the routeDone variable to be true, indicating that the last stop has been reached and the route is over. ***************************************************************************/ busAtNextStop() { if(!(this.arrayEmptyOrUndefined(this.routeArray))) { //if we still have stops, if routeArray has stuff in it const stopIndex = this.busNotMoving(); if(stopIndex != -1) { //and if bus is stopped somewhere //for(let i = 0; i < this.routeArray.length; i++) { if(this.withinRange(this.upcomingStopNumber, stopIndex)) { //and if our bus is within range of the upcoming stop console.log("At a stop!"); //then we're at the current stop! this.routeArray.shift(); //get rid of that stop, we already visited it //we want to update the next stop's ETA, but if we're at the end, we don't want to because there is no next stop if(this.upcomingStopNumber < this.routeArray.length) { //if we still have more stops this.updateETA(this.upcomingStopNumber, this.upcomingStopNumber+1); this.upcomingStopNumber++; //move on to look at the next upcoming stop } else this.routeDone = true; //otherwise we're done so we set done global to be done return true; //return true, it's at the stop! } } } //if any of the conditions aren't met, we return a -1 return false; } /*************************************************************************** Function: updateETA() Description: this function updates the ETA of a stop by using the previous ETA, the new ETA, and the current time. This gets called anytime the bus is at a stop. It takes the amount of time the bus is late or early and updates the ETA accordingly. So, if the bus was 5 minutes late at the stop it was just at, the ETA's of the following stops will be updated to be 5 minutes later. ***************************************************************************/ updateETA (previousETA, newETA) { var hour = new Date().getHours(); //current hours var min = new Date().getMinutes(); //current minutes var sec = new Date().getSeconds(); //current seconds console.log("Current time: " + (hour + ':' + min + ':' + sec).toString()); console.log("Previous ETA: " + this.arrayETA[previousETA]); console.log("Upcoming ETA: " + this.arrayETA[newETA]); //split the time string on the colon so we can get each individual part const prevTimeSplit = this.arrayETA[previousETA].split(':'); //console.log(timeSplit); //testing const newTimeSplit = this.arrayETA[newETA].split(':'); //change strings into ints so we can convert them const prevHour = parseInt(prevTimeSplit[0], 10); //10 is the base, so we are doing decimal here const prevMin = parseInt(prevTimeSplit[1], 10); const prevSec = parseInt(prevTimeSplit[2], 10); const newHour = parseInt(newTimeSplit[0], 10); const newMin = parseInt(newTimeSplit[1], 10); const newSec = parseInt(newTimeSplit[2], 10); //convert the current time into seconds const totalCurrentSeconds = sec + (min*60) + (hour * 3600); //console.log(totalCurrentSeconds); //convert the previous stop's ETA into seconds const totalPrevSeconds = prevSec + (prevMin*60) + (prevHour * 3600); //console.log(totalPrevSeconds); //convert the upcoming stop's ETA into seconds const totalNewSeconds = newSec + (newMin*60) + (newHour * 3600); //console.log(totalNewSeconds); //calculate the distance in time between the previous stop's ETA and the time it was actually picked up const timeDifference = Math.abs(totalPrevSeconds - totalCurrentSeconds); var totalUpdatedSeconds; if(totalPrevSeconds >= totalCurrentSeconds) //if our ETA is later than our current, also if equal time difference will be zero so no change totalUpdatedSeconds = totalNewSeconds - timeDifference; //we are ahead of schedule, so we subtract from the upcoming ETA else if (totalPrevSeconds < totalCurrentSeconds) //if our ETA is earlier than our current time totalUpdatedSeconds = totalNewSeconds + timeDifference; //we are behind schedule, so we add to the upcoming ETA //convert our new time from seconds into HH:MM:SS const newETATime = this.secondsToHHMMSS(totalUpdatedSeconds); //update the upcoming ETA in the array this.arrayETA[newETA] = newETATime; this.setState({ myText: "ETA: " + newETATime }) //console.log("New ETA in array: " + this.arrayETA[newETA]); //console.log("New ETA: " + newETATime); } //this function converts seconds into HH:MM:SS format secondsToHHMMSS(totalSeconds) { const hours = Math.floor(totalSeconds / 3600); totalSeconds %= 3600; const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; const newTime = (hours + ":" + minutes + ":" + seconds).toString(); console.log("New Time: " + newTime); return newTime; } //gives us a little wiggle room for driveways and a bit of inaccuracy when dealing with stops withinRange(rIndex, sIndex) { if((Math.abs(this.locationArray[sIndex].lat) <= Math.abs(this.routeArray[rIndex].lat) + .005) && (Math.abs(this.locationArray[sIndex].lat) >= Math.abs(this.routeArray[rIndex].lat) - .005)) { //if bus is within .005 of the latitude of the stop at rIndex in route array if((Math.abs(this.locationArray[sIndex].long) <= Math.abs(this.routeArray[rIndex].long) + .005) && (Math.abs(this.locationArray[sIndex].long) >= Math.abs(this.routeArray[rIndex].long) - .005)) { //if bus is within .005 of the longitude of the stop at rIndex in route array return true; //we are within the range! } } return false; } //fills ETA array with the routeTimes to start initializeETAarray() { for(let i = 0; i < this.routeArray.length; i++) this.arrayETA.push(this.routeArray[i].time); console.log("ETA array: " + this.arrayETA); } /*************************************************************************** Function: arrayEmptyOrUndefined Description: checks to see if array is undefined or empty. It checks undefined by using typeof and checks empty by seeing if length is zero. Returns false if there's stuff in it, otherwise there's something wrong so it returns true. ***************************************************************************/ arrayEmptyOrUndefined(arrayChecked) { if((typeof arrayChecked != 'undefined') && (arrayChecked.length != 0)) return false; else return true; } render() { if(this.state.isLoading) { return( ) } return ( Here is your stop information: Stop number: 4 {this.state.myText} Press the button in the top left corner to return to the homescreen. ); } }; const styles = StyleSheet.create({ container: { flex: 1, // paddingTop:0, backgroundColor: '#343e7a', justifyContent: 'flex-start' }, title: { fontSize: 25, marginLeft: 10, marginRight: 10, color: '#fbe99e', fontStyle: 'italic', textAlign: 'center', }, regularText: { fontSize: 40, marginLeft: 10, marginRight: 10, color: '#fbe99e', textAlign: 'center', }, submitButton: { backgroundColor: '#fbe99e', padding: 10, width:130, alignSelf: 'center', margin: 15, height: 50, }, submitButtonText:{ color: '#343e7a', fontSize: 20, fontWeight: '700', textAlign: 'center', }, input: { margin: 15, height: 50, fontSize: 15, borderColor: '#fbe99e', borderWidth: 1, alignContent: 'center' } });