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