diff --git a/.gitignore b/.gitignore
index ff23e873..3a65a175 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,10 @@
#####################
# Python
#####################
+# testing
+output.json
+outputlines.json
+
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
diff --git a/client/package-lock.json b/client/package-lock.json
index c727769e..416b5408 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -12,8 +12,11 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
+ "leaflet": "^1.9.4",
+ "leaflet-realtime": "^2.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-leaflet": "^4.2.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
@@ -3353,6 +3356,16 @@
}
}
},
+ "node_modules/@react-leaflet/core": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
+ "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
+ "peerDependencies": {
+ "leaflet": "^1.9.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -12401,6 +12414,16 @@
"shell-quote": "^1.8.1"
}
},
+ "node_modules/leaflet": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+ "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
+ },
+ "node_modules/leaflet-realtime": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/leaflet-realtime/-/leaflet-realtime-2.2.0.tgz",
+ "integrity": "sha512-Z7xbUNdhvwngJUf3plG9Zw3SaGHzcOzi59tVS3jD0GcS6cOWbRNNfP2biwmtHbufM5VYTIWLATrQ4vrlGI23WA=="
+ },
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -15123,6 +15146,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
+ "node_modules/react-leaflet": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
+ "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
+ "dependencies": {
+ "@react-leaflet/core": "^2.1.0"
+ },
+ "peerDependencies": {
+ "leaflet": "^1.9.0",
+ "react": "^18.0.0",
+ "react-dom": "^18.0.0"
+ }
+ },
"node_modules/react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
diff --git a/client/package.json b/client/package.json
index 47232b24..91225e47 100644
--- a/client/package.json
+++ b/client/package.json
@@ -7,8 +7,11 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
+ "leaflet": "^1.9.4",
+ "leaflet-realtime": "^2.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-leaflet": "^4.2.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
diff --git a/client/src/App.js b/client/src/App.js
index 18a0736b..05e3639a 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -1,5 +1,7 @@
import React, { useState, useEffect } from "react";
import Axios from "axios";
+import MapComponent from './components/RealtimeMap';
+import Title from './components/Title';
function App() {
const [products, setProducts] = useState([]);
@@ -15,16 +17,14 @@ function App() {
const products = data;
setProducts(products);
};
-
useEffect(() => {
fetchProducts();
}, []);
return (
-
- {products.map((product) => (
-
{product.name}
- ))}
+
+
+
);
}
diff --git a/client/src/components/Bus.js b/client/src/components/Bus.js
new file mode 100644
index 00000000..9379e7c4
--- /dev/null
+++ b/client/src/components/Bus.js
@@ -0,0 +1,15 @@
+import React, { useState, useEffect } from "react";
+import './css/Vehicle.css';
+
+function Bus({ data }) {
+ return (
+
+
{data.type}
+
ID: {data.id}
+
Latitude: {data.position.lat}
+
Longitude: {data.position.lon}
+
+ );
+}
+
+export default Bus;
diff --git a/client/src/components/RealtimeMap.js b/client/src/components/RealtimeMap.js
new file mode 100644
index 00000000..4663f844
--- /dev/null
+++ b/client/src/components/RealtimeMap.js
@@ -0,0 +1,63 @@
+import React, { useState, useEffect } from "react";
+import L from 'leaflet';
+import './../../node_modules/leaflet/dist/leaflet.css'
+import './css/RealtimeMap.css';
+import 'leaflet-realtime';
+import Bus from './Bus';
+import Tram from './Tram';
+import Train from './Train';
+import { Marker } from 'react-leaflet';
+import { MapContainer, TileLayer, useMap } from 'react-leaflet';
+
+function RealtimeMap() {
+ const map = useMap();
+ const [data, setData] = useState([]);
+
+ useEffect(() => {
+ const bounds = map.getBounds();
+ const upperLeft = bounds.getNorthWest();
+ const lowerRight = bounds.getSouthEast();
+ fetch('https://hackhpi24.ivo-zilkenat.de/api/trafficData/', {
+ 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())
+ .then(data => {
+ setData(data);
+ });
+ }, []);
+ data["2"] = {"id": "2", "type": "bus", "position": {"lat": 52.3906, "lon": 13.0645}};
+
+ return Object.values(data).map(item => (
+
+ {item.type === "bus" && }
+ {item.type === "tram" && }
+ {item.type === "train" && }
+
+ ));
+}
+
+function MapComponent() {
+ return (
+
+
+
+
+ );
+}
+
+export default MapComponent;
diff --git a/client/src/components/Title.js b/client/src/components/Title.js
new file mode 100644
index 00000000..6afa704f
--- /dev/null
+++ b/client/src/components/Title.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import './css/Title.css';
+
+function Title() {
+ return (
+
+
SFPOPOS
+
San Franciscos Privately Owned Public Spaces
+
+ )
+}
+
+export default Title
\ No newline at end of file
diff --git a/client/src/components/Train.js b/client/src/components/Train.js
new file mode 100644
index 00000000..7dcd0e48
--- /dev/null
+++ b/client/src/components/Train.js
@@ -0,0 +1,15 @@
+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/Tram.js b/client/src/components/Tram.js
new file mode 100644
index 00000000..a4ab9078
--- /dev/null
+++ b/client/src/components/Tram.js
@@ -0,0 +1,15 @@
+import React, { useState, useEffect } from "react";
+import './css/Vehicle.css';
+
+function Tram({ data }) {
+ return (
+
+
{data.type}
+
ID: {data.id}
+
Latitude: {data.position.lat}
+
Longitude: {data.position.lon}
+
+ );
+}
+
+export default Tram;
diff --git a/client/src/components/Vehicle.js b/client/src/components/Vehicle.js
new file mode 100644
index 00000000..4f748087
--- /dev/null
+++ b/client/src/components/Vehicle.js
@@ -0,0 +1,15 @@
+import React, { useState, useEffect } from "react";
+import './css/Vehicle.css';
+
+function Vehicle({ data }) {
+ return (
+
+
{data.type}
+
ID: {data.id}
+
Latitude: {data.position.lat}
+
Longitude: {data.position.lon}
+
+ );
+}
+
+export default Vehicle;
diff --git a/client/src/components/css/RealtimeMap.css b/client/src/components/css/RealtimeMap.css
new file mode 100644
index 00000000..b53bce1c
--- /dev/null
+++ b/client/src/components/css/RealtimeMap.css
@@ -0,0 +1,9 @@
+.map {
+ height: 400px;
+ width: 800px;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ border-radius: 0.5rem;
+ box-shadow: 0 0 1rem rgba(0, 0, 0, 0.4);
+}
diff --git a/client/src/components/css/Title.css b/client/src/components/css/Title.css
new file mode 100644
index 00000000..b2261088
--- /dev/null
+++ b/client/src/components/css/Title.css
@@ -0,0 +1,11 @@
+.Title {
+ box-sizing: border-box;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: baseline;
+ padding: 1em;
+ margin-bottom: 2em;
+ background-color: rgb(192, 45, 26);
+ color: #fff;
+ }
\ No newline at end of file
diff --git a/client/src/components/css/Vehicle.css b/client/src/components/css/Vehicle.css
new file mode 100644
index 00000000..f6c095d8
--- /dev/null
+++ b/client/src/components/css/Vehicle.css
@@ -0,0 +1,3 @@
+.bus{
+
+}
diff --git a/client/src/index.css b/client/src/index.css
index ec2585e8..7c036384 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -5,6 +5,8 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
+ background-color: white;
+ color: #000000;
}
code {
diff --git a/install.py b/install.py
index fc3de0c6..983ef50e 100644
--- a/install.py
+++ b/install.py
@@ -29,8 +29,6 @@ def install_backend_dependencies():
# Linux and macOS use this command
run_command('. venv/bin/activate && pip install -r requirements.txt')
- print("Dependencies installed successfully.")
-
os.chdir("..")
def main():
diff --git a/server/database.py b/server/database.py
new file mode 100644
index 00000000..92d65537
--- /dev/null
+++ b/server/database.py
@@ -0,0 +1,18 @@
+from typing import Dict, List
+from models import trafficDataItem
+
+trafficDataDict: Dict[str, trafficDataItem.TrafficDataItem] = dict()
+
+# Example traffic data (you would fetch or compute this data in a real application)
+trafficDataDict = {
+ "1": trafficDataItem.TrafficDataItem(
+ id="1",
+ type="Bus",
+ subType="City Bus",
+ position=trafficDataItem.Position(lat=40.7128, lon=-74.0060),
+ line="Line 1",
+ direction="North",
+ utilization=trafficDataItem.Utilization(abs=30, rel=0.6)
+ ),
+ # Add more items as needed...
+}
\ No newline at end of file
diff --git a/server/fetch_radar.py b/server/fetch_radar.py
new file mode 100644
index 00000000..15251a4a
--- /dev/null
+++ b/server/fetch_radar.py
@@ -0,0 +1,64 @@
+import httpx
+from fastapi import HTTPException
+from models import trafficDataItem
+import asyncio
+import database
+
+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")
+
+async def fetch_radar_data_periodically(period_time: int = 1):
+ while True:
+ await asyncio.sleep(period_time) # Wait for n seconds
+ # print("Fetching radar data...")
+ radar_data = await fetch_radar_data(
+ north=52.4288,
+ west=12.96249,
+ south=52.35401,
+ east=13.16608,
+ results=1024,
+ duration=period_time * 2,
+ frames=2,
+ polylines=True,
+ language="en"
+ )
+
+ database.trafficDataDict = dict()
+ for movement in radar_data["movements"]:
+
+ database.trafficDataDict[movement["tripId"]] = trafficDataItem.TrafficDataItem(
+ id=movement["tripId"],
+ type=movement["line"]["mode"],
+ subType=movement["line"]["product"],
+ position=trafficDataItem.Position(
+ lon=movement["location"]["longitude"],
+ lat=movement["location"]["latitude"]
+ ),
+ line=movement["line"]["name"],
+ direction=movement["direction"],
+ utilization=trafficDataItem.Utilization(
+ abs=None,
+ rel=None
+ )
+ )
+
+
\ No newline at end of file
diff --git a/server/main.py b/server/main.py
index 46e8b20e..ccf40965 100644
--- a/server/main.py
+++ b/server/main.py
@@ -1,6 +1,8 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
-from routers import items, users
+from routers import trafficData, updateUtilization
+from fetch_radar import fetch_radar_data_periodically
+import asyncio
app = FastAPI()
@@ -13,5 +15,12 @@
allow_headers=["*"], # Allows all headers
)
-app.include_router(items.router)
-app.include_router(users.router)
+app.include_router(trafficData.router, prefix="/api")
+app.include_router(updateUtilization.router, prefix="/api")
+# app.include_router(radar.router, prefix="/api")
+
+@app.on_event("startup")
+async def app_startup():
+ task = asyncio.create_task(
+ fetch_radar_data_periodically()
+ )
\ No newline at end of file
diff --git a/server/models/item.py b/server/models/item.py
deleted file mode 100644
index cb2cd91e..00000000
--- a/server/models/item.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# /models/item.py
-from pydantic import BaseModel
-
-class Item(BaseModel):
- name: str
- description: str = None
- price: float
- tax: float = None
diff --git a/server/models/trafficDataItem.py b/server/models/trafficDataItem.py
new file mode 100644
index 00000000..b0044361
--- /dev/null
+++ b/server/models/trafficDataItem.py
@@ -0,0 +1,27 @@
+from pydantic import BaseModel, Field
+
+# Model Definitions
+class Position(BaseModel):
+ lat: float = Field(..., example=40.7128)
+ lon: float = Field(..., example=-74.0060)
+
+class Utilization(BaseModel):
+ abs: int | None = Field(..., example=50)
+ rel: float | None = Field(..., example=0.75)
+
+class TrafficDataItem(BaseModel):
+ id: str = Field(..., example="1")
+ type: str = Field(..., example="Bus")
+ subType: str = Field(..., example="City Bus")
+ position: Position
+ line: str = Field(..., example="Line 1")
+ direction: str = Field(..., example="North")
+ utilization: Utilization
+
+# Request Model
+class Bounds(BaseModel):
+ upper_left: Position = Field(..., alias="upper-left")
+ lower_right: Position = Field(..., alias="lower-right")
+
+class TrafficDataRequest(BaseModel):
+ bounds: Bounds
\ No newline at end of file
diff --git a/server/models/user.py b/server/models/user.py
deleted file mode 100644
index 59545c75..00000000
--- a/server/models/user.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# /models/user.py
-from pydantic import BaseModel
-
-class User(BaseModel):
- username: str
- full_name: str = None
diff --git a/server/requirements.txt b/server/requirements.txt
index 9b84904b..af39a68a 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -1,8 +1,13 @@
annotated-types==0.6.0
anyio==4.3.0
+certifi==2024.2.2
click==8.1.7
+colorama==0.4.6
+exceptiongroup==1.2.0
fastapi==0.110.1
h11==0.14.0
+httpcore==1.0.5
+httpx==0.27.0
idna==3.6
pydantic==2.6.4
pydantic_core==2.16.3
diff --git a/server/routers/items.py b/server/routers/items.py
deleted file mode 100644
index 6a10f21f..00000000
--- a/server/routers/items.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# /routers/items.py
-from fastapi import APIRouter
-
-router = APIRouter()
-
-@router.get("/api/items/")
-async def read_items():
- return [{"name": "Item Foo"}, {"name": "Item Bar"}]
diff --git a/server/routers/radar.py b/server/routers/radar.py
new file mode 100644
index 00000000..f002777f
--- /dev/null
+++ b/server/routers/radar.py
@@ -0,0 +1,30 @@
+# 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/server/routers/trafficData.py b/server/routers/trafficData.py
new file mode 100644
index 00000000..3b4fc9ac
--- /dev/null
+++ b/server/routers/trafficData.py
@@ -0,0 +1,11 @@
+from fastapi import APIRouter
+from models import trafficDataItem
+from typing import Dict
+
+import database
+
+router = APIRouter()
+
+@router.post("/trafficData/", response_model=Dict[str, trafficDataItem.TrafficDataItem])
+async def read_trafficData(request: trafficDataItem.TrafficDataRequest):
+ return database.trafficDataDict
\ No newline at end of file
diff --git a/server/routers/updateUtilization.py b/server/routers/updateUtilization.py
new file mode 100644
index 00000000..b60290fb
--- /dev/null
+++ b/server/routers/updateUtilization.py
@@ -0,0 +1,13 @@
+from fastapi import APIRouter
+from models import trafficDataItem
+
+import database
+
+router = APIRouter()
+
+@router.post("/{tripId}/updateUtilization")
+async def update_bus_utilization(tripId: str, utilization: trafficDataItem.Utilization):
+ # Here you can update the utilization data for the specific bus
+ # For simplicity, this example just stores the data in the `buses_utilization` dictionary
+ database.trafficDataDict[tripId].utilization = utilization
+ return {"message": f"Updated utilization for bus {tripId}"}
\ No newline at end of file
diff --git a/server/routers/users.py b/server/routers/users.py
deleted file mode 100644
index 37fdccf4..00000000
--- a/server/routers/users.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# /routers/users.py
-from fastapi import APIRouter
-
-router = APIRouter()
-
-@router.get("/api/users/")
-async def read_users():
- return [{"username": "johndoe"}, {"username": "janedoe"}]