diff --git a/client/src/App.js b/client/src/App.js
index 0dcad028..a316c69c 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -1,13 +1,15 @@
import React from "react";
import "./App.css"
import MapComponent from './components/RealtimeMap';
-import HeaderBanner from "./components/HeaderBanner";
+import Banner from "./components/Banner";
+import InputBox from "./components/inputBox";
function App() {
return (
-
+
+
);
diff --git a/client/src/components/ApiCall.js b/client/src/components/ApiCall.js
new file mode 100644
index 00000000..d0ca8cc1
--- /dev/null
+++ b/client/src/components/ApiCall.js
@@ -0,0 +1,28 @@
+export function fetchCurrentData(map, route) {
+ const bounds = map.getBounds();
+ const upperLeft = bounds.getNorthWest();
+ const lowerRight = bounds.getSouthEast();
+ const hostname = window.location.hostname;
+ const protocol = window.location.protocol;
+ const port = hostname === "localhost" ? "3001" : "443";
+
+ return fetch(`${protocol}//${hostname}:${port}/api/${route}/`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ "bounds": {
+ "upper-left": {
+ "lat": upperLeft.lat,
+ "lon": upperLeft.lng
+ },
+ "lower-right": {
+ "lat": lowerRight.lat,
+ "lon": lowerRight.lng
+ }
+ }
+ })
+ })
+ .then(response => response.json());
+}
diff --git a/client/src/components/Banner.js b/client/src/components/Banner.js
index e69de29b..1b9b550a 100644
--- a/client/src/components/Banner.js
+++ b/client/src/components/Banner.js
@@ -0,0 +1,21 @@
+import React, { useEffect, useRef } from "react";
+import "./css/Banner.css";
+
+function Banner() {
+ return (
+
+
Wischen & Mischen
+
{/* This will take up the unused space */}
+
+
+
+
+
+ {/* Add more nav items as needed */}
+
+
+
+ );
+}
+
+export default Banner;
\ No newline at end of file
diff --git a/client/src/components/RealtimeMap.js b/client/src/components/RealtimeMap.js
index 4de7f944..7c31184d 100644
--- a/client/src/components/RealtimeMap.js
+++ b/client/src/components/RealtimeMap.js
@@ -2,97 +2,48 @@ import React, { useState, useEffect } from "react";
import L from 'leaflet';
import './../../node_modules/leaflet/dist/leaflet.css'
import './css/RealtimeMap.css';
+import './css/Vehicle.css';
import 'leaflet-realtime';
-import Vehicle from './Vehicle.js';
-import { Marker } from 'react-leaflet';
+import { Marker, Circle } from 'react-leaflet';
import { Popup } from 'react-leaflet';
import { MapContainer, TileLayer, useMap } from 'react-leaflet';
-import busIconUrl from '../resources/bus_icon.png';
-import trainIconUrl from '../resources/train_icon.png';
-import tramIconUrl from '../resources/tram_icon.png';
-import subwayIconUrl from '../resources/subway_icon.png';
-import ferryIconUrl from '../resources/ferry_icon.png';
-import expressIconUrl from '../resources/express_icon.png';
-import suburbanIconUrl from '../resources/suburban_icon.png';
+import VehicleIcons from './VehicleIcons';
+import { fetchCurrentData } from './ApiCall';
+import { getIcon, getColor } from './VehicleIcons';
-const busIcon = L.icon({
- iconUrl: busIconUrl,
- iconSize: [40, 40],
-});
-
-const trainIcon = L.icon({
- iconUrl: trainIconUrl,
- iconSize: [25, 41],
-});
-
-const tramIcon = L.icon({
- iconUrl: tramIconUrl,
- iconSize: [25, 41],
-});
-
-const subwayIcon = L.icon({
- iconUrl: subwayIconUrl,
- iconSize: [25, 41],
-});
-
-const ferryIcon = L.icon({
- iconUrl: suburbanIconUrl,
- iconSize: [25, 41],
-});
-
-const expressIcon = L.icon({
- iconUrl: expressIconUrl,
- iconSize: [25, 41],
-});
-
-const suburbanIcon = L.icon({
- iconUrl: ferryIconUrl,
- iconSize: [25, 41],
-});
+function capitalizeFirstLetter(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
function RealtimeMap() {
const map = useMap();
const [data, setData] = useState([]);
+ const [stations, setStations] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
- const bounds = map.getBounds();
- const upperLeft = bounds.getNorthWest();
- const lowerRight = bounds.getSouthEast();
- const hostname = window.location.hostname;
- const protocol = window.location.protocol;
- const port = hostname === "localhost" ? "3001" : "443";
- fetch(`${protocol}//${hostname}:${port}/api/trips/`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- "bounds": {
- "upper-left": {
- "lat": upperLeft.lat,
- "lon": upperLeft.lng
- },
- "lower-right": {
- "lat": lowerRight.lat,
- "lon": lowerRight.lng
- }
- }
- })
- })
- .then(response => response.json())
+ fetchCurrentData(map, 'trips')
.then(data => {
setData(data);
});
}, 1000);
- return () => clearInterval(interval);
-}, []);
+ return () => clearInterval(interval);
+ }, []);
- return Object.values(data).map(item => {
+ useEffect(() => {
+ fetchCurrentData(map, 'stations')
+ .then(data => {
+ setStations(data);
+ });
+ }, []);
+
+ const tripMarkers = Object.values(data).map(item => {
let icon;
let colorClass;
- if (item.utilization.rel < 0.3) {
+ if (item.utilization.rel == null) {
+ colorClass = 'gray-icon';
+ } else if (item.utilization.rel < 0.3) {
colorClass = 'green-icon';
} else if (item.utilization.rel >= 0.3 && item.utilization.rel <= 0.7) {
colorClass = 'yellow-icon';
@@ -100,39 +51,17 @@ function RealtimeMap() {
colorClass = 'red-icon';
}
- switch (item.subtype) {
- case "suburban":
- icon = suburbanIcon;
- break;
- case "subway":
- icon = subwayIcon;
- break;
- case "tram":
- icon = tramIcon;
- break;
- case "bus":
- icon = busIcon;
- break;
- case "ferry":
- icon = ferryIcon;
- break;
- case "express":
- icon = expressIcon;
- break;
- case "regional":
- icon = trainIcon;
- break;
- default:
- icon = busIcon;
- break;
- }
+ icon = getIcon(item.subType);
- icon.options.className = colorClass;
+ icon.options.className = `${colorClass} vehicle-icon`;
return (
+ Type: {capitalizeFirstLetter(item.type)}
+ Line: {item.line} | Direction: {item.direction}
+
Absolute Utilization: {item.utilization.abs}
Relative Utilization: {item.utilization.rel}
@@ -140,11 +69,28 @@ function RealtimeMap() {
);
});
+
+ const stationMarkers = Object.values(stations).map(item => {
+ let color = getColor(item.products);
+ return (
+
+
+ {item.name}
+
+ Absolute Utilization: {item.utilization.abs}
+ Relative Utilization: {item.utilization.rel}
+
+
+
+ );
+ });
+
+ return [...tripMarkers, ...stationMarkers];
}
function MapComponent() {
return (
-
+
diff --git a/client/src/components/Vehicle.js b/client/src/components/Vehicle.js
deleted file mode 100644
index 7dcd0e48..00000000
--- a/client/src/components/Vehicle.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React, { useState, useEffect } from "react";
-import './css/Vehicle.css';
-
-function Train({ data }) {
- return (
-
-
{data.type}
-
ID: {data.id}
-
Latitude: {data.position.lat}
-
Longitude: {data.position.lon}
-
- );
-}
-
-export default Train;
diff --git a/client/src/components/VehicleIcons.js b/client/src/components/VehicleIcons.js
new file mode 100644
index 00000000..e9d2070e
--- /dev/null
+++ b/client/src/components/VehicleIcons.js
@@ -0,0 +1,81 @@
+import L from 'leaflet';
+import busIconUrl from '../resources/bus_icon.png';
+import trainIconUrl from '../resources/train_icon.png';
+import tramIconUrl from '../resources/tram_icon.png';
+import subwayIconUrl from '../resources/subway_icon.png';
+import ferryIconUrl from '../resources/ferry_icon.png';
+import expressIconUrl from '../resources/express_icon.png';
+import suburbanIconUrl from '../resources/suburban_icon.png';
+
+const busIcon = L.icon({
+ iconUrl: busIconUrl,
+ iconSize: [40, 40],
+});
+
+const trainIcon = L.icon({
+ iconUrl: trainIconUrl,
+ iconSize: [25, 25],
+});
+
+const tramIcon = L.icon({
+ iconUrl: tramIconUrl,
+ iconSize: [40, 40],
+});
+
+const subwayIcon = L.icon({
+ iconUrl: subwayIconUrl,
+ iconSize: [40, 40],
+});
+
+const ferryIcon = L.icon({
+ iconUrl: ferryIconUrl,
+ iconSize: [40, 40],
+});
+
+const expressIcon = L.icon({
+ iconUrl: expressIconUrl,
+ iconSize: [40, 40],
+});
+
+const suburbanIcon = L.icon({
+ iconUrl: suburbanIconUrl,
+ iconSize: [32, 32],
+});
+
+
+export function getIcon(subtype) {
+ switch (subtype) {
+ case "suburban":
+ return suburbanIcon;
+ case "subway":
+ return subwayIcon;
+ case "tram":
+ return tramIcon;
+ case "bus":
+ return busIcon;
+ case "ferry":
+ return ferryIcon;
+ case "express":
+ return expressIcon;
+ case "regional":
+ return trainIcon;
+ default:
+ return busIcon;
+ }
+}
+
+export function getColor(products) {
+ if (products.suburban) {
+ return "green";
+ } else if (products.subway) {
+ return "red";
+ } else if (products.tram) {
+ return "blue";
+ } else if (products.bus) {
+ return "yellow";
+ } else if (products.ferry) {
+ return "purple";
+ } else {
+ return "black";
+ }
+}
diff --git a/client/src/components/css/Banner.css b/client/src/components/css/Banner.css
new file mode 100644
index 00000000..b932afb6
--- /dev/null
+++ b/client/src/components/css/Banner.css
@@ -0,0 +1,70 @@
+/* In Navbar.css */
+.navbar {
+ display: flex;
+ flex-direction: row; /* Default direction */
+ align-items: center;
+ justify-content: space-between; /* Align items with space between */
+ background-color: #333;
+ color: white;
+ padding: 5px 20px 5px 0px; /* Added padding to the right and left */
+ width: 100%; /* Full width */
+ position: fixed;
+ z-index: 1000; /* Ensures it's above the map */
+}
+
+.brand {
+ padding: 10px;
+ font-size: large;
+ font-weight: bold;
+ /* margin-left: 50px; */ /* Ensures some space between the brand and the nav items */
+}
+
+.nav-items {
+ display: flex; /* Display nav items in a row */
+}
+
+.nav-item {
+ margin: 0 10px;
+ padding: 5px 10px; /* Padding inside each nav item for better touch targets */
+ cursor: pointer;
+ background-color: #3c3c3c; /* Slightly darker background for clickable items */
+ border-radius: 5px; /* Rounded borders for a softer look */
+ transition: background-color 0.3s; /* Smooth transition for hover effect */
+}
+
+.nav-item:hover {
+ background-color: #555; /* Slightly lighter on hover for feedback */
+ text-decoration: none; /* Overrides the underline from previous styles if needed */
+}
+
+.nav-item a {
+ color: inherit; /* Makes the link color the same as the parent element */
+ text-decoration: none; /* Removes the underline from links */
+ cursor: default; /* Changes the cursor to the default arrow, not the pointer typically used for links */
+}
+
+/* Optional: Style for hover effect to make it look like normal text */
+.nav-item a:hover {
+ text-decoration: none;
+}
+
+/* Media query for screens smaller than 600px */
+@media (max-width: 600px) {
+ .navbar {
+ flex-direction: column; /* Stack items vertically */
+ align-items: center; /* Center items */
+ }
+
+ .nav-items {
+ flex-direction: column; /* Stack nav items vertically */
+ }
+
+ .nav-item {
+ margin: 5px 0; /* Adjust margin for vertical layout */
+ }
+
+ .spacer {
+ display: none; /* Hide spacer in mobile view */
+ }
+}
+
diff --git a/client/src/components/css/InputBox.css b/client/src/components/css/InputBox.css
new file mode 100644
index 00000000..d0319a26
--- /dev/null
+++ b/client/src/components/css/InputBox.css
@@ -0,0 +1,18 @@
+.translucent-box {
+ background-color: rgba(255, 255, 255, 0.7); /* Translucent white background */
+ border: 1px solid #ccc;
+ border-radius: 5px;
+ padding: 20px;
+ width: 300px;
+ margin: 0 auto;
+ z-index: 999;
+ }
+
+ .input-field {
+ width: 100%;
+ padding: 10px;
+ margin-bottom: 10px;
+ z-index: 9999;
+ }
+
+
\ No newline at end of file
diff --git a/client/src/components/css/RealtimeMap.css b/client/src/components/css/RealtimeMap.css
index d1e68db8..858ebfa3 100644
--- a/client/src/components/css/RealtimeMap.css
+++ b/client/src/components/css/RealtimeMap.css
@@ -1,22 +1,8 @@
.map {
- height: 600px;
- width: 95%;
+ height: 100vh;
+ width: 100%;
display: block;
margin-left: auto;
margin-right: auto;
- margin-top: 100px;
- border-radius: 0.5rem;
- box-shadow: 0 0 1rem rgba(0, 0, 0, 0.4);
}
-.green-icon {
- filter: hue-rotate(90deg) brightness(150%);
-}
-
-.yellow-icon {
- filter: hue-rotate(45deg) brightness(150%);
-}
-
-.red-icon {
- filter: hue-rotate(0deg) brightness(150%);
-}
diff --git a/client/src/components/css/Vehicle.css b/client/src/components/css/Vehicle.css
index f6c095d8..e178799f 100644
--- a/client/src/components/css/Vehicle.css
+++ b/client/src/components/css/Vehicle.css
@@ -1,3 +1,31 @@
-.bus{
+.vehicle-icon {
+ padding: 10px !important;
+ background-color: white; /* Icon background color - blue */
+ border-radius: 50%; /* Circular shape */
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2); /* Subtle shadow for depth */
+ color: white; /* Icon text color */
+ font-size: 12px; /* Icon text size */
+ font-family: Arial, sans-serif; /* Font for any text inside the icon */
+ border-width: 4px;
+ border-style: solid;
+}
+
+.gray-icon {
+ border-color: gray;
+}
+
+.green-icon {
+ border-color: green;
+}
+
+.yellow-icon {
+ border-color: orange;
+}
+.red-icon {
+ border-color: red;
}
+
\ No newline at end of file
diff --git a/client/src/components/inputBox.js b/client/src/components/inputBox.js
new file mode 100644
index 00000000..e8e7ec8a
--- /dev/null
+++ b/client/src/components/inputBox.js
@@ -0,0 +1,114 @@
+import React, { useState } from 'react';
+
+function App() {
+ const [input1, setInput1] = useState('');
+ const [input2, setInput2] = useState('');
+ const [suggestedStations1, setSuggestedStations1] = useState([]);
+ const [suggestedStations2, setSuggestedStations2] = useState([]);
+
+ const handleInputChange1 = async (event) => {
+ const query = event.target.value;
+ setInput1(query);
+ if (query.trim() === '') {
+ setSuggestedStations1([]); // Clear suggestions if input is empty
+ return;
+ }
+ try {
+ const response = await fetch(`https://v6.vbb.transport.rest/stations?query=${query}`, {
+ headers: {
+ 'Accept': 'application/x-ndjson'
+ }
+ });
+ const reader = response.body.getReader();
+ let decoder = new TextDecoder();
+ let partialChunk = '';
+ const stations = [];
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ partialChunk += decoder.decode(value, { stream: true });
+ const lines = partialChunk.split('\n');
+ lines.forEach(line => {
+ if (line.trim() !== '') {
+ const station = JSON.parse(line);
+ stations.push(station.name);
+ }
+ });
+ partialChunk = lines.pop() || '';
+ }
+ setSuggestedStations1([...new Set(stations.slice(0, 3))]); // Update suggested stations for input1, filtering duplicates
+ } catch (error) {
+ console.error('Error fetching stations:', error);
+ }
+ };
+
+ const handleInputChange2 = async (event) => {
+ const query = event.target.value;
+ setInput2(query);
+ if (query.trim() === '') {
+ setSuggestedStations2([]); // Clear suggestions if input is empty
+ return;
+ }
+ try {
+ const response = await fetch(`https://v6.vbb.transport.rest/stations?query=${query}`, {
+ headers: {
+ 'Accept': 'application/x-ndjson'
+ }
+ });
+ const reader = response.body.getReader();
+ let decoder = new TextDecoder();
+ let partialChunk = '';
+ const stations = [];
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ partialChunk += decoder.decode(value, { stream: true });
+ const lines = partialChunk.split('\n');
+ lines.forEach(line => {
+ if (line.trim() !== '') {
+ const station = JSON.parse(line);
+ stations.push(station.name);
+ }
+ });
+ partialChunk = lines.pop() || '';
+ }
+ setSuggestedStations2([...new Set(stations.slice(0, 3))]); // Update suggested stations for input2, filtering duplicates
+ } catch (error) {
+ console.error('Error fetching stations:', error);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/client/src/resources/logo.png b/client/src/resources/logo.png
new file mode 100644
index 00000000..9edd8dbd
Binary files /dev/null and b/client/src/resources/logo.png differ
diff --git a/client/src/resources/tram_icon.png b/client/src/resources/tram_icon.png
index 316f453c..1936032f 100644
Binary files a/client/src/resources/tram_icon.png and b/client/src/resources/tram_icon.png differ
diff --git a/randomutil.py b/dev/randomutil.py
similarity index 100%
rename from randomutil.py
rename to dev/randomutil.py
diff --git a/output.txt b/output.txt
new file mode 100644
index 00000000..687be7c1
--- /dev/null
+++ b/output.txt
@@ -0,0 +1,60 @@
+[2024-04-06 05:06:54]
+[2024-04-06 05:06:55]
+[2024-04-06 05:06:56]
+[2024-04-06 05:06:57]
+[2024-04-06 05:06:57]
+[2024-04-06 05:06:58]
+[2024-04-06 05:06:59]
+[2024-04-06 05:07:00]
+[2024-04-06 05:07:00]
+[2024-04-06 05:07:01]
+[2024-04-06 05:07:02]
+[2024-04-06 05:07:03]
+[2024-04-06 05:07:03]
+[2024-04-06 05:07:04]
+[2024-04-06 05:07:05]
+[2024-04-06 05:07:05]
+[2024-04-06 05:07:06]
+[2024-04-06 05:07:07]
+[2024-04-06 05:07:08]
+[2024-04-06 05:07:08]
+[2024-04-06 05:07:09]
+[2024-04-06 05:07:10]
+[2024-04-06 05:07:11]
+[2024-04-06 05:07:12]
+[2024-04-06 05:07:12]
+[2024-04-06 05:07:13]
+[2024-04-06 05:07:14]
+[2024-04-06 05:07:15]
+[2024-04-06 05:07:16]
+[2024-04-06 05:07:16]
+[2024-04-06 05:07:17]
+[2024-04-06 05:07:18]
+[2024-04-06 05:07:19]
+[2024-04-06 05:07:19]
+[2024-04-06 05:07:20]
+[2024-04-06 05:07:21]
+[2024-04-06 05:07:21]
+[2024-04-06 05:07:23]
+[2024-04-06 05:07:23]
+[2024-04-06 05:07:24]
+[2024-04-06 05:07:26]
+[2024-04-06 05:07:26]
+[2024-04-06 05:07:27]
+[2024-04-06 05:07:28]
+[2024-04-06 05:07:29]
+[2024-04-06 05:07:29]
+[2024-04-06 05:07:30]
+[2024-04-06 05:07:31]
+[2024-04-06 05:07:32]
+[2024-04-06 05:07:32]
+[2024-04-06 05:07:33]
+[2024-04-06 05:07:34]
+[2024-04-06 05:07:34]
+[2024-04-06 05:07:35]
+[2024-04-06 05:07:36]
+[2024-04-06 05:07:37]
+[2024-04-06 05:07:37]
+[2024-04-06 05:07:38]
+[2024-04-06 05:07:39]
+[2024-04-06 05:07:40]
diff --git a/server/fetch_stations.py b/server/fetch_stations.py
index 5d7255a2..8aa836a0 100644
--- a/server/fetch_stations.py
+++ b/server/fetch_stations.py
@@ -38,7 +38,7 @@ async def fetch_station_data_periodically(period_time: int = 1800):
# Map the external API response to your StationInfo model
# This is a basic mapping, adjust according to the actual response structure and your model
- database.stationDataDict[stationId] = database.station.StationDataItem(
+ newStationDataDict[stationId] = database.station.StationDataItem(
id=stationId,
name=item["name"],
position=core.Position(
diff --git a/server/routers/radar.py b/server/routers/radar.py
deleted file mode 100644
index f002777f..00000000
--- a/server/routers/radar.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# import httpx
-# from fastapi import APIRouter, HTTPException
-
-# router = APIRouter()
-
-# async def fetch_radar_data(north: float, west: float, south: float, east: float, results: int = 256, duration: int = 30, frames: int = 3, polylines: bool = True, language: str = "en"):
-# url = "https://v6.vbb.transport.rest/radar"
-# params = {
-# "north": north,
-# "west": west,
-# "south": south,
-# "east": east,
-# "results": results,
-# "duration": duration,
-# "frames": frames,
-# "polylines": polylines,
-# "language": language
-# }
-
-# async with httpx.AsyncClient() as client:
-# response = await client.get(url, params=params)
-
-# if response.status_code == 200:
-# return response.json()
-# else:
-# raise HTTPException(status_code=response.status_code, detail="Failed to fetch radar data from VBB API")
-
-# @router.get("/api/radar")
-# async def api_vbb_radar(north: float, west: float, south: float, east: float, results: int = 256, duration: int = 30, frames: int = 3, polylines: bool = True, language: str = "en"):
-# return await fetch_radar_data(north, west, south, east, results, duration, frames, polylines, language)
\ No newline at end of file
diff --git a/start_all_videos.py b/stream-processing/start_all_videos.py
similarity index 100%
rename from start_all_videos.py
rename to stream-processing/start_all_videos.py