Files
traintracker/src/components/Map.vue
2023-03-02 12:54:50 +00:00

366 lines
10 KiB
Vue

<template>
<h2>Insights:</h2>
<p>Total number of trains: {{ this.numTrains }}</p>
<p>Number of actively running trains: {{ this.numRunningTrains }}</p>
<p>Percentage late: {{ this.percentageLate }}%</p>
<p>Percentage early or ontime: {{ this.percentageEarly }}%</p>
<p v-if="this.latestTrain['TrainCode']">Latest train: {{ this.latestTrain["TrainCode"][0] }}, {{ this.latestTrain["Direction"][0] }}, {{ this.latestTime }} mins late</p>
<p v-if="this.earliestTrain['TrainCode']">Earliest train: {{ this.earliestTrain["TrainCode"][0] }}, {{ this.earliestTrain["Direction"][0] }}, {{ this.earliestTime * -1 }} mins early</p>
<button @click="getLiveTrainData">Fetch Data</button>
<button @click="postLiveTrainData">Populate Database</button>
<!--Sidebar, fades out on click of X button-->
<transition id="sidebar" name="slideLeft">
<div v-if="this.display" id= "sidebarDiv">
<div id = "sidebarHeader">
<img id = "headerImage" src="../assets/train-solid.svg" alt="Train Icon">
<div v-on:click="this.display = false" id="xButton">X</div>
</div>
<div id= "sidebarDiv">
<h2>Train Code: {{ selectedDataMap["TrainCode"] }}</h2>
<p>Date: {{ selectedDataMap["TrainDate"] }}</p>
<p>Status: {{ selectedDataMap["TrainStatus"] }}</p>
<p>Longitude: {{ selectedDataMap["TrainLongitude"] }}</p>
<p>Latitude: {{ selectedDataMap["TrainLatitude"] }}</p>
<p>Direction: {{ selectedDataMap["Direction"] }}</p>
<p>Public Message: {{ selectedDataMap["PublicMessage"] }}</p>
</div>
</div>
</transition>
<ol-map :loadTilesWhileAnimating="true" :loadTilesWhileInteracting="true" style="height: 100vh; width: 100vw">
<ol-view ref="view" :center="center" :rotation="rotation" :zoom="zoom" :projection="projection" />
<ol-tile-layer>
<ol-source-osm />
</ol-tile-layer>
<template v-for="coordinate, i in coordinates" :position="inline-block">
<!-- overlay offset is the size of the image so that it is centered-->
<ol-overlay :position="coordinate" :positioning="center-center" :offset="[-16,-16]">
<div class="overlay-content" @click="getSelectedTrain(i)">
<img src="../assets/train-solid.svg" class="trainMapIcon" alt="Train Icon">
</div>
</ol-overlay>
</template>
</ol-map>
</template>
<script>
import { ref } from 'vue';
import {fromLonLat, toLonLat} from 'ol/proj.js';
import app from '../api/firebase';
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
export default {
name: "MapsOverlay",
data() {
const center = ref(fromLonLat([-7.5029786, 53.4494762]))
const projection = ref('EPSG:3857')
const zoom = ref(7)
const rotation = ref(0)
const radius = ref(10)
const strokeWidth = ref(1)
const strokeColor = ref('black')
const fillColor = ref('red')
return {
center,
projection,
zoom,
rotation,
radius,
strokeWidth,
strokeColor,
fillColor,
coordinates: [],
dbLiveTrainData: [],
allDataMap: {},
selectedDataMap: {},
display: false,
numTrains: 0,
numRunningTrains: 0,
numLateRunningTrains: 0,
latestTrain: {},
earliestTrain: {},
percentageEarly: 0,
percentageLate: 0,
latestTime: 0,
earliestTime: 0,
}
},
created() {
if (window.location.hostname === '127.0.0.1' || window.location.hostname === 'localhost') {
this.postLiveTrainData()
}
else {
this.getLiveTrainData()
}
// request new data every 60 seconds
// window.setInterval(this.getLiveTrainData, 60000);
},
methods: {
// fetch live train data from the Firestore database
getLiveTrainData() {
const functions = getFunctions(app);
let host = window.location.hostname
if (host === '127.0.0.1' || host === 'localhost') {
connectFunctionsEmulator(functions, host, 5001);
}
const getData = httpsCallable(functions, 'getLiveTrainData');
let loader = this.$loading.show({
loader: 'dots',
container: this.$refs.container,
canCancel: false
});
getData().then((response) => {
try {
this.dbLiveTrainData = response.data;
console.log(this.dbLiveTrainData)
this.numRunningTrains = 0;
this.numTrains = 0;
this.numLateRunningTrains = 0;
var latest = null
var currLatestTime = 0
var earliest = null
var currEarliestTime = 0
// create an array of coordinates and hashmap with the key-values {index: JSON obj}
for(var i=0; i<this.dbLiveTrainData.length; i++) {
this.coordinates[i] = ref(fromLonLat([this.dbLiveTrainData[i]["TrainLongitude"][0], this.dbLiveTrainData[i]["TrainLatitude"][0]]))
this.allDataMap[i] = this.dbLiveTrainData[i];
// check if the train is running
if (this.dbLiveTrainData[i]["TrainStatus"][0] == "R") {
this.numRunningTrains += 1;
let publicMessage = this.dbLiveTrainData[i]["PublicMessage"][0];
let startTimeStr = publicMessage.indexOf("(")
// late
if (publicMessage[startTimeStr+1] != "-" && publicMessage[startTimeStr+1] != "0") {
this.numLateRunningTrains += 1;
if (!latest) {
latest = this.dbLiveTrainData[i];
}
let timeEnd = publicMessage.indexOf(" ", startTimeStr+1);
let num = parseInt(publicMessage.substring(startTimeStr+1, timeEnd))
// new latest train
if (num > currLatestTime) {
latest = this.dbLiveTrainData[i]
currLatestTime = num
}
}
// early or ontime
else {
if (!earliest) {
earliest = this.dbLiveTrainData[i];
}
let timeEnd = publicMessage.indexOf(" ", startTimeStr+1);
let num = parseInt(publicMessage.substring(startTimeStr+1, timeEnd))
// new earliest train, negative as early trains defined as negative x mins late
if (num < currEarliestTime) {
earliest = this.dbLiveTrainData[i]
currEarliestTime = num
}
}
}
}
this.percentageLate = ((this.numLateRunningTrains / this.numRunningTrains) * 100).toFixed(2);
this.percentageEarly = 100 - this.percentageLate;
this.numTrains = Object.keys(this.allDataMap).length;
this.latestTrain = latest;
this.earliestTrain = earliest;
this.latestTime = currLatestTime;
this.earliestTime = currEarliestTime;
loader.hide();
}
catch (error) {
loader.hide();
}
})
},
// assign a single train's data to the selected hashmap
getSelectedTrain(i) {
this.display = true;
this.selectedDataMap["TrainCode"] = this.allDataMap[i]["TrainCode"][0];
this.selectedDataMap["TrainDate"] = this.allDataMap[i]["TrainDate"][0];
this.selectedDataMap["TrainStatus"] = this.allDataMap[i]["TrainStatus"][0];
this.selectedDataMap["TrainLongitude"] = this.allDataMap[i]["TrainLongitude"][0];
this.selectedDataMap["TrainLatitude"] = this.allDataMap[i]["TrainLatitude"][0];
this.selectedDataMap["Direction"] = this.allDataMap[i]["Direction"][0];
this.selectedDataMap["PublicMessage"] = this.allDataMap[i]["PublicMessage"][0];
},
// ---------------- TESTING ----------------
postLiveTrainData() {
const functions = getFunctions(app);
let host = window.location.hostname
if (host === '127.0.0.1' || host === 'localhost') {
connectFunctionsEmulator(functions, host, 5001);
}
const postData = httpsCallable(functions, 'postLiveTrainData');
postData().then((response) => {
this.getLiveTrainData()
})
}
// ---------------- TESTING ----------------
}
}
</script>
<style scoped>
.overlay-content {
width: 1%;
}
.trainMapIcon {
height: 32px;
width: 32px;
}
.slideLeft-enter-active, .slideLeft-leave-active {
transition: opacity .5s;
transition: all 0.8s;
}
.slideLeft-enter-from, .slideLeft-leave-to{
opacity: 0;
transform: translateX(-100px);
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
#sidebar{
position: absolute;
height: 85%;
width: 20%;
left: 2%;
top: 12%;
z-index: 2;
text-align: center;
animation: gradient 15s ease infinite;
background: linear-gradient(45deg, #000000, #111111, #222222, #333333, #444444, #555555);
background-size: 400%, 400%;
box-shadow: 0 0 4px 2px #333333;
}
#sidebarDiv{
position: relative;
height: 100%;
width: 100%;
color: white;
}
#sidebarHeader{
position: relative;
top:0%;
height: 10%;
width: 100%;
overflow: hidden;
}
#headerImage{
height: 100%;
width: auto;
overflow: hidden;
}
#xButton{
font-size: 80%;
font-family: Georgia;
color: white;
position: absolute;
top:10px;
right:10px;
}
#xButton:hover{
color:red;
}
#sidebarFooter{
position: relative;
bottom:0%;
height:10%;
text-align: center;
color: azure;
}
#sidebarMain{
position: relative;
height:80%;
width:100%;
overflow: hidden;
}
#sidebarContent{
position: relative;
size: 6px;
color: white;
overflow-wrap: break-word;
font-family: Georgia, 'Times New Roman', Times, serif ;
}
#mapDiv{
background-color: black;
position: absolute;
float: right;
right: 0%;
top: 0%;
width:100%;
overflow: hidden;
height: 100%;
}
#mapIFrame{
position: relative;
height: 100%;
width: 100%;
top: 0%;
z-index: 0;
}
#buttonDiv{
position: absolute;
float: right;
right: 10%;
top: 0%;
width:10%;
height:10px;
}
#buttonElement{
position: relative;
top: 50%;
left: 50%;
z-index: 0;
}
</style>