[server]: Plot some basic train data

This commit is contained in:
2025-03-01 22:37:11 +00:00
parent a09d08435e
commit 0788940899
6 changed files with 206 additions and 88 deletions

View File

@ -3,3 +3,4 @@ Bus Station: https://www.flaticon.com/authors/boris-farias
Train Station: https://www.flaticon.com/authors/nawicon
Bus: https://www.flaticon.com/authors/hilmy-abiyyu-a
Train: https://www.flaticon.com/authors/google
DART: https://www.freepik.com/

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<title>Iompar</title>
</head>
<body>
<div id="root"></div>

View File

@ -11,11 +11,13 @@
"@tailwindcss/vite": "^4.0.9",
"autoprefixer": "^10.4.20",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"postcss": "^8.5.3",
"react": "^19.0.0-rc.1",
"react-dom": "^19.0.0-rc.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-helmet": "^6.1.0",
"react-leaflet": "^5.0.0-rc.2",
"react-leaflet-markercluster": "^5.0.0-rc.0",
"tailwindcss": "^4.0.9"
},
"devDependencies": {
@ -38,6 +40,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -54,6 +57,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -70,6 +74,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -86,6 +91,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -102,6 +108,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -118,6 +125,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -134,6 +142,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -150,6 +159,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -166,6 +176,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -182,6 +193,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -198,6 +210,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -214,6 +227,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -230,6 +244,7 @@
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -246,6 +261,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -262,6 +278,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -278,6 +295,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -294,6 +312,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -310,6 +329,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -326,6 +346,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -342,6 +363,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -358,6 +380,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -374,6 +397,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -390,6 +414,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -406,6 +431,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -422,6 +448,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -638,6 +665,17 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@react-leaflet/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
"license": "Hippocratic-2.1",
"peerDependencies": {
"leaflet": "^1.9.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.8",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz",
@ -645,6 +683,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -658,6 +697,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -671,6 +711,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -684,6 +725,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -697,6 +739,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -710,6 +753,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -723,6 +767,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -736,6 +781,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -749,6 +795,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -762,6 +809,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -775,6 +823,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -788,6 +837,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -801,6 +851,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -814,6 +865,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -827,6 +879,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -840,6 +893,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -853,6 +907,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -866,6 +921,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -879,6 +935,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1339,6 +1396,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
@ -2191,6 +2249,7 @@
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@ -2564,6 +2623,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -3361,6 +3421,15 @@
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
"license": "MIT",
"peerDependencies": {
"leaflet": "^1.3.1"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -4061,12 +4130,12 @@
"license": "MIT"
},
"node_modules/react-leaflet": {
"version": "5.0.0-rc.2",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0-rc.2.tgz",
"integrity": "sha512-1xQGYG9mEIW+nfkQhqgHImwUuB1UDlnzYFSzv6PrBFDBeYrFmv0BbpwpNAFdJg/UQ2yz5UZSL7ZwlUxjwb8MZw==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
"integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
"license": "Hippocratic-2.1",
"dependencies": {
"@react-leaflet/core": "^3.0.0-rc.2"
"@react-leaflet/core": "^3.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
@ -4074,15 +4143,22 @@
"react-dom": "^19.0.0"
}
},
"node_modules/react-leaflet/node_modules/@react-leaflet/core": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
"license": "Hippocratic-2.1",
"node_modules/react-leaflet-markercluster": {
"version": "5.0.0-rc.0",
"resolved": "https://registry.npmjs.org/react-leaflet-markercluster/-/react-leaflet-markercluster-5.0.0-rc.0.tgz",
"integrity": "sha512-jWa4bPD5LfLV3Lid1RWgl+yKUuQtnqeYtJzzLb/fiRjvX+rtwzY8pMoUFuygqyxNrWxMTQlWKBHxkpI7Sxvu4Q==",
"license": "MIT",
"dependencies": {
"@react-leaflet/core": "^3.0.0",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"react-leaflet": "^5.0.0"
},
"peerDependencies": {
"leaflet": "^1.9.0",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-leaflet": "^5.0.0"
}
},
"node_modules/reflect.getprototypeof": {
@ -4161,6 +4237,7 @@
"version": "4.34.8",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz",
"integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.6"
@ -4729,6 +4806,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz",
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",

View File

@ -13,11 +13,13 @@
"@tailwindcss/vite": "^4.0.9",
"autoprefixer": "^10.4.20",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"postcss": "^8.5.3",
"react": "^19.0.0-rc.1",
"react-dom": "^19.0.0-rc.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-helmet": "^6.1.0",
"react-leaflet": "^5.0.0-rc.2",
"react-leaflet-markercluster": "^5.0.0-rc.0",
"tailwindcss": "^4.0.9"
},
"devDependencies": {

View File

@ -1,79 +1,130 @@
// App.jsx
import React from "react";
import React, { useState } from "react";
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import L from "leaflet";
import { Icon } from "leaflet";
import L, { Icon } from "leaflet";
// icons
import busStationIconURL from "../src/assets/icons/bus-station.png";
import busIconURL from "../src/assets/icons/bus.png";
import trainStationIconURL from "../src/assets/icons/train-station.png";
import trainIconURL from "../src/assets/icons/train.png";
import trainLateIconURL from "../src/assets/icons/train_late.png";
import trainNotRunningIconURL from "../src/assets/icons/train_notrunning.png";
import trainOntimeIconURL from "../src/assets/icons/train_ontime.png";
import tramStationIconURL from "../src/assets/icons/tram-station.png";
import trainStationURL from "../src/assets/icons/train-station.png"
import TabTitle from "./components/TabTitle.jsx";
// Fix marker icon issue with Leaflet
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconUrl:
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
iconRetinaUrl:
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
shadowUrl:
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
iconUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png",
iconRetinaUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png",
shadowUrl: "https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
});
let mapCenter = [53.4494762, -7.5029786];
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
(position) => {
mapCenter = [position.coords.latitude, position.coords.longitude];
},
(error) => {
}
);
}
const icons = new Map();
icons.set(
"train-station",
"IrishRailStation",
new Icon({
iconUrl: trainStationURL,
iconSize: [38,38]
iconUrl: trainStationIconURL,
iconSize: [38, 38],
})
)
);
const markers = [
{
coords: [53.4494762, -7.5029786],
popup: "Popup lol",
icon: "train-station"
}
]
icons.set(
"IrishRailTrain",
new Icon({
iconUrl: trainIconURL,
iconSize: [38, 38],
})
);
const TRAINSIENT_DATA_API = "https://281bc6mcm5.execute-api.us-east-1.amazonaws.com/transient_data"
const dataSources = [
{ id: "IrishRailTrains", name: "Irish Rail Trains", url: TRAINSIENT_DATA_API + "?objectType=IrishRailTrain" },
{ id: "source2", name: "Data Source 2", url: "https://api.example.com/source2" },
{ id: "source3", name: "Data Source 3", url: "https://api.example.com/source3" },
];
function App() {
const [selectedSources, setSelectedSources] = useState([]);
const [markers, setMarkers] = useState([]);
const [loading, setLoading] = useState(false);
const handleCheckboxChange = (id) => {
setSelectedSources((prev) =>
prev.includes(id) ? prev.filter((source) => source !== id) : [...prev, id]
);
};
const fetchData = async () => {
setLoading(true);
const newMarkers = [];
for (const source of dataSources) {
if (selectedSources.includes(source.id)) {
try {
const response = await fetch(source.url);
const data = await response.json();
data.forEach((item) => {
newMarkers.push({
coords: [item.latitude, item.longitude],
popup: item.objectType,
icon: item.objectType,
});
});
} catch (error) {
console.error(`Error fetching data from ${source.name}:`, error);
}
}
}
setMarkers(newMarkers);
setLoading(false);
};
return (
<>
<TabTitle />
<div style={{height: "100vh", width: "100vw"}}>
<MapContainer center={mapCenter} zoom={7} style={{height: "100%", width: "100%"}}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
{markers.map(marker => (
<Marker position={marker.coords} icon={icons.get(marker.icon)}>
<Popup>
<h2> Train 2</h2>
<h2> Train 2</h2>
<h2> Train 2</h2>
<h2> Train 2</h2>
<h2> Train 2</h2>
<h2> Train 2</h2>
</Popup>
</Marker>
<div style={{ height: "100vh", width: "100vw", display: "flex", position: "relative" }}>
{loading && (
<div style={{
position: "absolute", top: 0, left: 0, width: "100%", height: "100%",
background: "rgba(0, 0, 0, 0.5)", display: "flex",
alignItems: "center", justifyContent: "center",
color: "white", fontSize: "20px", fontWeight: "bold",
zIndex: 1000
}}>
Loading data...
</div>
)}
<div style={{ width: "250px", padding: "10px", background: "#000000" }}>
<h3>Select Data Sources</h3>
{dataSources.map((source) => (
<div key={source.id}>
<input
type="checkbox"
id={source.id}
checked={selectedSources.includes(source.id)}
onChange={() => handleCheckboxChange(source.id)}
/>
<label htmlFor={source.id}>{source.name}</label>
</div>
))}
</MapContainer>
<button onClick={fetchData} style={{ marginTop: "10px" }}>Submit</button>
</div>
<div style={{ flex: 1 }}>
<MapContainer center={[53.4494762, -7.5029786]} zoom={7} minZoom={4} style={{ height: "100%", width: "100%" }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
/>
{markers.map((marker, index) => (
<Marker key={index} position={marker.coords} icon={icons.get(marker.icon)}>
<Popup>{marker.popup}</Popup>
</Marker>
))}
</MapContainer>
</div>
</div>
</>
);

View File

@ -1,14 +0,0 @@
import React from "react";
import { Helmet } from "react-helmet";
import favicon from "../../src/assets/icons/train-station.png"
const TabTitle = () => {
return (
<Helmet>
<title>Iompar</title>
<link rel="icon" href={favicon} />
</Helmet>
);
};
export default TabTitle;