Compare commits
13 Commits
b9a63aa6d7
...
backend
| Author | SHA1 | Date | |
|---|---|---|---|
| c5abf60bff | |||
| 75581a9ddc | |||
| 2771b5ea74 | |||
| eeb63fa510 | |||
|
|
06f3129fb9 | ||
| bfe47fb443 | |||
| a5fceda42f | |||
|
|
eaf76f94a1 | ||
| 7cb29179a2 | |||
| 8a5db40d1d | |||
| 9c51163505 | |||
| ba32d9d482 | |||
| a7aff8e169 |
41
.gitea/workflows/deploy.yaml
Normal file
41
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Deploy Express API
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- backend
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: linux_amd64
|
||||
|
||||
steps:
|
||||
# Checkout code
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Pull latest code
|
||||
- name: Pull latest code
|
||||
run: |
|
||||
cd /var/www/myapp/Project
|
||||
git reset --hard
|
||||
git pull origin main
|
||||
|
||||
# Install dependencies
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd /var/www/myapp/Project
|
||||
npm install
|
||||
|
||||
# Kill existing process (optional)
|
||||
- name: Stop previous instance
|
||||
run: |
|
||||
pkill -f "node .*app.js" || true
|
||||
pkill -f "node .*start.js" || true
|
||||
|
||||
# Start app in background
|
||||
- name: Start app
|
||||
run: |
|
||||
cd /var/www/myapp/Project
|
||||
nohup npm start > app.log 2>&1 &
|
||||
|
||||
4059
alerts.json
Normal file
4059
alerts.json
Normal file
File diff suppressed because it is too large
Load Diff
208
public/app.js
Normal file
208
public/app.js
Normal file
@@ -0,0 +1,208 @@
|
||||
|
||||
var streets = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
});
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}).addTo(map);
|
||||
|
||||
const API_URL = "http://localhost:7653/api/v0/";
|
||||
const ROUTE_STYLES = {
|
||||
"701": { color: "#0074D9", train: "🔵", stop: "🔹" },
|
||||
"704": { color: "#2ECC40", train: "🟢", stop: "🟩" },
|
||||
"703": { color: "#FF4136", train: "🔴", stop: "🔺" },
|
||||
"750": { color: "#B10DC9", train: "🟣", stop: "🔮" },
|
||||
"720": { color: "#000000", train: "⚪", stop: "🔷" }
|
||||
};
|
||||
|
||||
|
||||
function buildIcon(emoji, bearing) {
|
||||
const svg = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
|
||||
<text x="50%" y="50%" text-anchor="middle"
|
||||
dominant-baseline="central" font-size="26"
|
||||
font-family="Apple Color Emoji, Segoe UI Emoji, NotoColorEmoji, sans-serif"
|
||||
>
|
||||
${emoji}
|
||||
</text>
|
||||
</svg>`;
|
||||
|
||||
return L.divIcon({
|
||||
html: svg,
|
||||
className: "emoji-icon",
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 16],
|
||||
popupAnchor: [0, -16]
|
||||
});
|
||||
}
|
||||
|
||||
function addMarker(lat, lon, content, icon) {
|
||||
if (!isNaN(lat) && !isNaN(lon)) {
|
||||
const marker = L.marker([lat, lon], { icon: icon }).addTo(map);
|
||||
if (content) marker.bindPopup(content);
|
||||
} else {
|
||||
console.warn("Invalid coordinates:", latitude, longitude);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function drawPolyLine(polylinePoints, color) {
|
||||
L.polyline(polylinePoints, { color, weight: 4, opacity: 0.8 }).addTo(map);
|
||||
}
|
||||
|
||||
function drawLine(route) {
|
||||
fetch(API_URL + "routepaths/" + route)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const trains = data.data;
|
||||
const filtered = route ? trains.filter(t => t.routeId == route) : trains;
|
||||
filtered.forEach(t => {
|
||||
addMarker(t.location.latitude, t.location.longitude, t.routeName + ": Vehicle " + t.vehicleId, trainEmojiIcon);
|
||||
});
|
||||
})
|
||||
.catch(err => console.error("Error fetching trains:", err));
|
||||
}
|
||||
|
||||
function drawLines() {
|
||||
MJR_LINES.forEach(drawLine);
|
||||
}
|
||||
|
||||
|
||||
let stopMarkers = {};
|
||||
function getStopsByRoute(route) {
|
||||
fetch(API_URL + 'stops/' + route)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const stops = data.data;
|
||||
stops.forEach(s => {
|
||||
const lat = parseFloat(s.stop_lat);
|
||||
const lon = parseFloat(s.stop_lon);
|
||||
addMarker(lat,lon, s.stop_name + " - " + s.stop_desc, stopsEmojiIcon);
|
||||
});
|
||||
})
|
||||
.catch(err => console.error("Error fetching stops:", err));
|
||||
}
|
||||
|
||||
let trainMarkers = {};
|
||||
let filterType = "all";
|
||||
|
||||
const filterSelect = document.getElementById("filterSelect");
|
||||
if (filterSelect) {
|
||||
filterSelect.addEventListener("change", (e) => {
|
||||
filterType = e.target.value;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshVehicles() {
|
||||
fetch(API_URL + "vehicles")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
let vehicles = data.data;
|
||||
|
||||
if (filterType === "lrt") {
|
||||
vehicles = vehicles.filter(v => LRT_LINES.includes(v.routeNum) || FRONTRUNNER.includes(v.routeNum));
|
||||
}
|
||||
|
||||
vehicles = vehicles.filter(v => MJR_LINES.includes(v.routeNum));
|
||||
|
||||
Object.keys(trainMarkers).forEach(id => {
|
||||
if (!vehicles.find(v => v.vehicleId == id)) {
|
||||
map.removeLayer(trainMarkers[id]);
|
||||
delete trainMarkers[id];
|
||||
}
|
||||
});
|
||||
|
||||
vehicles.forEach(v => {
|
||||
const { vehicleId, routeNum, routeName, speed, bearing } = v;
|
||||
const lat = v.location.latitude;
|
||||
const lon = v.location.longitude;
|
||||
const icon = buildIcon(ROUTE_STYLES[routeNum].train, bearing);
|
||||
|
||||
if (!trainMarkers[vehicleId]) {
|
||||
const marker = L.marker([lat, lon], { icon })
|
||||
.bindPopup(`${routeName}<br>Vehicle ${vehicleId}<br>Speed: ${(speed*2.23694).toFixed(1)} mph`)
|
||||
.addTo(map);
|
||||
marker.vehicleData = v;
|
||||
trainMarkers[vehicleId] = marker;
|
||||
} else {
|
||||
if (trainMarkers[vehicleId].slideTo) {
|
||||
trainMarkers[vehicleId].slideTo([lat, lon], { duration: 1000 });
|
||||
} else {
|
||||
trainMarkers[vehicleId].setLatLng([lat, lon]);
|
||||
}
|
||||
trainMarkers[vehicleId].setIcon(icon);
|
||||
trainMarkers[vehicleId].vehicleData = v;
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => console.error("Error refreshing vehicles:", err));
|
||||
}
|
||||
|
||||
|
||||
let userMarker, accuracyCircle;
|
||||
const ON_BOARD_RADIUS = 50; // meters
|
||||
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
const lat = position.coords.latitude;
|
||||
const lon = position.coords.longitude;
|
||||
|
||||
userMarker = L.circleMarker([lat, lon], {
|
||||
radius: 8,
|
||||
fillColor: "#007AFF",
|
||||
color: "#fff",
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.9
|
||||
}).addTo(map);
|
||||
userMarker.bindPopup("You are here").openPopup();
|
||||
|
||||
accuracyCircle = L.circle([lat, lon], {
|
||||
radius: position.coords.accuracy,
|
||||
color: "#007AFF",
|
||||
fillColor: "#007AFF",
|
||||
fillOpacity: 0.2
|
||||
}).addTo(map);
|
||||
|
||||
map.setView([lat, lon], 13);
|
||||
},
|
||||
(err) => console.warn("Geolocation error:", err.message),
|
||||
{ enableHighAccuracy: true, timeout: 500, maximumAge: 0 }
|
||||
);
|
||||
|
||||
navigator.geolocation.watchPosition(
|
||||
(position) => {
|
||||
const userLat = position.coords.latitude;
|
||||
const userLon = position.coords.longitude;
|
||||
|
||||
if (userMarker) userMarker.setLatLng([userLat, userLon]);
|
||||
if (accuracyCircle) accuracyCircle.setLatLng([userLat, userLon]).setRadius(position.coords.accuracy);
|
||||
|
||||
const vehicles = Object.values(trainMarkers).map(m => m.vehicleData);
|
||||
let closest = null, minDistance = Infinity;
|
||||
|
||||
vehicles.forEach(v => {
|
||||
const distance = map.distance([userLat, userLon], [v.location.latitude, v.location.longitude]);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closest = v;
|
||||
}
|
||||
});
|
||||
|
||||
if (closest && minDistance <= ON_BOARD_RADIUS) {
|
||||
console.log(`User is on vehicle ${closest.vehicleId} (${closest.routeNum})`);
|
||||
}
|
||||
},
|
||||
(err) => console.warn("Geolocation watch error:", err.message),
|
||||
{ enableHighAccuracy: true, maximumAge: 0 }
|
||||
);
|
||||
} else {
|
||||
console.warn("Geolocation not supported by this browser.");
|
||||
}
|
||||
|
||||
drawLines();
|
||||
MJR_LINES.forEach(r => getStopsByRoute(r));
|
||||
refreshVehicles();
|
||||
setInterval(refreshVehicles, 1000);
|
||||
24
public/index.html
Normal file
24
public/index.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Traxer</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
crossorigin=""
|
||||
/>
|
||||
<script
|
||||
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
crossorigin=""
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" class="w-full h-screen"></div>
|
||||
|
||||
</body>
|
||||
<script src="app.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -6020,7 +6020,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"701":
|
||||
"703":
|
||||
[
|
||||
{
|
||||
"shape_id": "238255",
|
||||
|
||||
159
src/dal/postgresDAL.js
Normal file
159
src/dal/postgresDAL.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import pg from "pg";
|
||||
|
||||
const pool = new pg.Pool({
|
||||
connectionString: "postgresql://nate@localhost:5432/gtfs"
|
||||
});
|
||||
|
||||
async function dbQuery(sql, params = []) {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
const res = await client.query(sql, params);
|
||||
return res.rows;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
function toNumber(v) {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
|
||||
export async function getVehicles() {
|
||||
const rows = await dbQuery(
|
||||
`SELECT vehicle_id, route_num, route_name, destination,
|
||||
bearing, speed, latitude, longitude
|
||||
FROM vehicles`
|
||||
);
|
||||
|
||||
return rows.map((v) => ({
|
||||
vehicleId: v.vehicle_id,
|
||||
routeNum: v.route_num,
|
||||
routeName: v.route_name,
|
||||
destination: v.destination,
|
||||
bearing: v.bearing == null ? undefined : toNumber(v.bearing),
|
||||
speed: v.speed == null ? undefined : toNumber(v.speed),
|
||||
location: {
|
||||
latitude: toNumber(v.latitude),
|
||||
longitude: toNumber(v.longitude),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getVehicleById(id) {
|
||||
if (id == null) return null;
|
||||
|
||||
const rows = await dbQuery(
|
||||
`SELECT vehicle_id, route_num, route_name, destination,
|
||||
bearing, speed, latitude, longitude
|
||||
FROM vehicles
|
||||
WHERE vehicle_id = $1`,
|
||||
[String(id)]
|
||||
);
|
||||
|
||||
if (!rows.length) return null;
|
||||
const v = rows[0];
|
||||
|
||||
return {
|
||||
vehicleId: v.vehicle_id,
|
||||
routeNum: v.route_num,
|
||||
routeName: v.route_name,
|
||||
destination: v.destination,
|
||||
bearing: v.bearing == null ? undefined : toNumber(v.bearing),
|
||||
speed: v.speed == null ? undefined : toNumber(v.speed),
|
||||
location: {
|
||||
latitude: toNumber(v.latitude),
|
||||
longitude: toNumber(v.longitude),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export async function getRoutes() {
|
||||
const rows = await dbQuery(`SELECT data FROM routes ORDER BY route_id`);
|
||||
return rows.map((r) => r.data);
|
||||
}
|
||||
|
||||
export async function getRouteById(routeId) {
|
||||
if (routeId == null) return null;
|
||||
|
||||
const rows = await dbQuery(
|
||||
`SELECT data FROM routes WHERE route_id = $1`,
|
||||
[String(routeId)]
|
||||
);
|
||||
|
||||
return rows[0]?.data || null;
|
||||
}
|
||||
|
||||
|
||||
export async function getRoutePathsMap() {
|
||||
const rows = await dbQuery(`SELECT route_id, path FROM route_paths`);
|
||||
const map = {};
|
||||
for (const row of rows) {
|
||||
map[String(row.route_id)] = row.path;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function getRoutePath(routeId) {
|
||||
if (routeId == null) return null;
|
||||
|
||||
const rows = await dbQuery(
|
||||
`SELECT path FROM route_paths WHERE route_id = $1`,
|
||||
[String(routeId)]
|
||||
);
|
||||
|
||||
return rows[0]?.path || null;
|
||||
}
|
||||
|
||||
|
||||
function transformationStationRow(row) {
|
||||
return {
|
||||
stop_id: row.stop_id,
|
||||
stop_code: row.stop_code,
|
||||
stop_name: row.stop_name,
|
||||
stop_desc: row.stop_desc,
|
||||
location: {
|
||||
latitude: toNumber(row.latitude),
|
||||
longitude: toNumber(row.longitude),
|
||||
},
|
||||
stop_url: row.stop_url,
|
||||
location_type: row.location_type,
|
||||
parent_station: row.parent_station,
|
||||
lines: row.lines, // text[]
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStationsRaw() {
|
||||
const rows = await dbQuery(`SELECT * FROM stations`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function getStations() {
|
||||
const rows = await dbQuery(`SELECT * FROM stations`);
|
||||
return rows.map(transformationStationRow);
|
||||
}
|
||||
|
||||
export async function getStopsByRoute(routeId) {
|
||||
if (routeId == null) return [];
|
||||
const rows = await dbQuery(
|
||||
`SELECT * FROM stations WHERE lines @> ARRAY[$1]::text[]`,
|
||||
[String(routeId)]
|
||||
);
|
||||
return rows.map(transformationStationRow);
|
||||
}
|
||||
|
||||
export async function getStationById(id) {
|
||||
if (id == null) return null;
|
||||
|
||||
const rows = await dbQuery(
|
||||
`SELECT * FROM stations WHERE stop_id = $1`,
|
||||
[String(id)]
|
||||
);
|
||||
|
||||
if (!rows.length) return null;
|
||||
return transformationStationRow(rows[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,41 @@ const dataDir = path.join(process.cwd(), "src", "dal", "data");
|
||||
function readJSON(name) {
|
||||
try {
|
||||
const p = path.join(dataDir, name);
|
||||
|
||||
if (!fs.existsSync(p)) return null;
|
||||
const raw = fs.readFileSync(p, "utf8");
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
return JSON.parse(fs.readFileSync(p, "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function toNumber(v) {
|
||||
const n = Number(v);
|
||||
return Number.isFinite(n) ? n : undefined;
|
||||
}
|
||||
|
||||
export function getVehicles() {
|
||||
return readJSON("vehicles.json") || [];
|
||||
const raw = readJSON("vehicles.json");
|
||||
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw.map(v => {
|
||||
const loc = v.location || {};
|
||||
|
||||
return {
|
||||
vehicleId: v.vehicleId,
|
||||
routeNum: v.routeNum,
|
||||
routeName: v.routeName,
|
||||
destination: v.destination,
|
||||
bearing: v.bearing == null ? undefined : toNumber(v.bearing),
|
||||
speed: v.speed == null ? undefined : toNumber(v.speed),
|
||||
location: {
|
||||
latitude: toNumber(loc.latitude),
|
||||
longitude: toNumber(loc.longitude)
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export function getVehicleById(id) {
|
||||
@@ -26,81 +51,142 @@ export function getVehicleById(id) {
|
||||
}
|
||||
|
||||
export function getRoutes() {
|
||||
const explicit = readJSON("routes.json");
|
||||
if (Array.isArray(explicit) && explicit.length) return explicit;
|
||||
const raw = readJSON("routes.json");
|
||||
|
||||
const vehicles = getVehicles();
|
||||
const map = new Map();
|
||||
vehicles.forEach(v => {
|
||||
const key = String(v.routeNum ?? "");
|
||||
if (!map.has(key)) {
|
||||
map.set(key, {
|
||||
routeId: key,
|
||||
routeName: v.routeName ?? null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
trains: []
|
||||
});
|
||||
}
|
||||
|
||||
map.get(key).trains.push(v.vehicleId);
|
||||
|
||||
});
|
||||
|
||||
return Array.from(map.values());
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function getRouteById(routeId) {
|
||||
if (routeId == null) return null;
|
||||
const routes = getRoutes();
|
||||
|
||||
return routes.find(r => String(r.routeId) === String(routeId)) || null;
|
||||
return routes.find(r => String(r.route_id) === String(routeId)) || null;
|
||||
}
|
||||
|
||||
function readRoutepathsRaw() {
|
||||
const raw = readJSON("routePath.json");
|
||||
|
||||
if (!Array.isArray(raw)) return [];
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function getRoutePathsMap() {
|
||||
const raw = readJSON("routepaths.json");
|
||||
const raw = readRoutepathsRaw();
|
||||
const map = {};
|
||||
|
||||
return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
||||
for (const item of raw) {
|
||||
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
||||
const keys = Object.keys(item);
|
||||
|
||||
if (keys.length !== 1) continue;
|
||||
const k = String(keys[0]);
|
||||
|
||||
if (Array.isArray(item[k])) map[k] = item[k];
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getRoutePath(routeId) {
|
||||
if (routeId == null) return null;
|
||||
const map = getRoutePathsMap();
|
||||
const keys = Object.keys(map || {});
|
||||
const foundKey = keys.find(k => String(k) === String(routeId));
|
||||
const key = String(routeId);
|
||||
|
||||
return foundKey ? map[foundKey] : null;
|
||||
return Object.prototype.hasOwnProperty.call(map, key) ? map[key] : null;
|
||||
}
|
||||
|
||||
export function getStationsRaw() {
|
||||
return readJSON("stations.json") || null;
|
||||
}
|
||||
|
||||
export function getStations() {
|
||||
return readJSON("stations.json") || [];
|
||||
const raw = getStationsRaw();
|
||||
|
||||
if (raw == null) return [];
|
||||
if (Array.isArray(raw)) {
|
||||
const isArrayOfMaps = raw.every(item => typeof item === "object" && !Array.isArray(item) && Object.values(item).every(v => Array.isArray(v)));
|
||||
if (isArrayOfMaps) return raw.flatMap(item => Object.values(item).flat());
|
||||
return raw;
|
||||
}
|
||||
if (typeof raw === "object") {
|
||||
return Object.values(raw).flat();
|
||||
}
|
||||
return [];
|
||||
|
||||
}
|
||||
|
||||
function transformationStation(s) {
|
||||
return {
|
||||
stop_id: s.stop_id,
|
||||
stop_code: s.stop_code,
|
||||
stop_name: s.stop_name,
|
||||
stop_desc: s.stop_desc,
|
||||
location: { latitude: toNumber(s.stop_lat), longitude: toNumber(s.stop_lon) },
|
||||
stop_url: s.stop_url,
|
||||
location_type: s.location_type,
|
||||
parent_station: s.parent_station,
|
||||
lines: s.lines
|
||||
};
|
||||
}
|
||||
|
||||
export function getStopsByRoute(routeId) {
|
||||
if (routeId == null) return [];
|
||||
const stations = getStations();
|
||||
|
||||
if (!Array.isArray(stations)) return [];
|
||||
return stations.filter(s => {
|
||||
const lines = s.lines ?? s.lines_arr ?? s.line_ids ?? null;
|
||||
if (!Array.isArray(lines)) return false;
|
||||
|
||||
return lines.map(String).includes(String(routeId));
|
||||
});
|
||||
const raw = getStationsRaw();
|
||||
|
||||
if (raw == null) return [];
|
||||
if (typeof raw === "object" && !Array.isArray(raw)) {
|
||||
const arr = raw[routeId];
|
||||
if (!Array.isArray(arr)) return [];
|
||||
return arr.map(transformationStation);
|
||||
}
|
||||
|
||||
export function getStationById(stationId) {
|
||||
if (stationId == null) return null;
|
||||
const stations = getStations();
|
||||
|
||||
if (!Array.isArray(stations)) return null;
|
||||
return stations.find(s => {
|
||||
if (s.stop_id && String(s.stop_id) === String(stationId)) return true;
|
||||
if (s.stationId && String(s.stationId) === String(stationId)) return true;
|
||||
if (s.id && String(s.id) === String(stationId)) return true;
|
||||
|
||||
return false;
|
||||
}) || null;
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
const matchesFromArrayMaps = [];
|
||||
for (const item of raw) {
|
||||
if (typeof item === "object" && !Array.isArray(item) && Object.prototype.hasOwnProperty.call(item, routeId)) {
|
||||
const arr = item[routeId];
|
||||
if (Array.isArray(arr)) matchesFromArrayMaps.push(...arr.map(transformationStation));
|
||||
}
|
||||
}
|
||||
if (matchesFromArrayMaps.length) return matchesFromArrayMaps;
|
||||
|
||||
return raw.filter(s => Array.isArray(s.lines) && s.lines.map(String).includes(String(routeId))).map(transformationStation);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getStationById(id) {
|
||||
if (id == null) return null;
|
||||
const raw = getStationsRaw();
|
||||
if (raw == null) return null;
|
||||
|
||||
if (typeof raw === "object" && !Array.isArray(raw)) {
|
||||
for (const key of Object.keys(raw)) {
|
||||
const arr = raw[key];
|
||||
if (!Array.isArray(arr)) continue;
|
||||
const found = arr.find(s => String(s.stop_id) === String(id) || String(s.stationId) === String(id));
|
||||
if (found) return transformationStation(found);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(raw)) {
|
||||
const isArrayOfMaps = raw.every(item => typeof item === "object" && !Array.isArray(item) && Object.values(item).every(v => Array.isArray(v)));
|
||||
if (isArrayOfMaps) {
|
||||
for (const item of raw) {
|
||||
for (const key of Object.keys(item)) {
|
||||
const arr = item[key];
|
||||
const found = arr.find(s => String(s.stop_id) === String(id));
|
||||
if (found) return transformationStation(found);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const found = raw.find(s => String(s.stop_id) === String(id) || String(s.stationId) === String(id));
|
||||
return found ? transformationStation(found) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -11,32 +11,49 @@ router.get("/routes", (req, res) => {
|
||||
|
||||
router.get("/routes/:routeId", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
let route = dal.getRouteById(routeId) ?? null;
|
||||
const route = dal.getRouteById(routeId);
|
||||
|
||||
if (route === null) {
|
||||
const all = dal.getRoutes();
|
||||
route = all.find(r => {
|
||||
const rid = r.route_id ?? r.routeId ?? r.route;
|
||||
return rid != null && String(rid) === String(routeId);
|
||||
}) ?? null;
|
||||
}
|
||||
if (!route) return res.status(404).json({error: "Route Was Not Found"});
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
const routePath = dal.getRoutePath(routeId) ?? null;
|
||||
let stations = dal.getStopsByRoute(routeId) ?? [];
|
||||
res.json({data: {route, stations}});
|
||||
});
|
||||
|
||||
res.json({data: {route, routePath, stations}});
|
||||
router.get("/route/:routeId", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
const route = dal.getRouteById(routeId);
|
||||
|
||||
if (!route) return res.status(404).json({error: "Route Was Not Found"});
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
res.json({data: {route, stations}});
|
||||
});
|
||||
|
||||
router.get("/routepaths/:routeId", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
const rp = dal.getRoutePath(routeId);
|
||||
|
||||
if (!rp) return res.status(404).json({error: "RoutePath Was Not Found"});
|
||||
res.json({meta: {routeId: String(routeId), returned: Array.isArray(rp) ? rp.length : 0}, data: rp});
|
||||
});
|
||||
|
||||
router.get("/routes/:routeId/stations", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
const stations = dal.getStopsByRoute(routeId) ?? [];
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
res.json({meta: {routeId: String(routeId), returned: stations.length}, data: stations});
|
||||
});
|
||||
|
||||
router.get("/stops/:routeId", (req, res) => {
|
||||
const routeId = req.params.routeId;
|
||||
const stations = dal.getStopsByRoute(routeId);
|
||||
|
||||
res.json({meta: {routeId: String(routeId), returned: stations.length}, data: stations});
|
||||
});
|
||||
|
||||
router.get("/stations", (req, res) => {
|
||||
const route = req.query.route;
|
||||
const stations = route ? (dal.getStopsByRoute(route) ?? []) : (dal.getStations() ?? []);
|
||||
const stations = route ? dal.getStopsByRoute(route) : dal.getStations();
|
||||
|
||||
res.json({meta: {returned: stations.length}, data: stations});
|
||||
});
|
||||
@@ -44,8 +61,8 @@ router.get("/stations", (req, res) => {
|
||||
router.get("/station/:stationId", (req, res) => {
|
||||
const stationId = req.params.stationId;
|
||||
const station = dal.getStationById(stationId);
|
||||
if (!station) return res.status(404).json({error: "Station Was Not Found"});
|
||||
|
||||
if (!station) return res.status(404).json({error: "Station Was Not Found"});
|
||||
res.json({data: station});
|
||||
});
|
||||
|
||||
|
||||
11
start.js
11
start.js
@@ -1,7 +1,12 @@
|
||||
//Starts
|
||||
import express from "express";
|
||||
import app from "./app.js";
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const PORT = process.env.PORT || 7653;
|
||||
|
||||
// Serve static files from "public" folder
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Transit API http://localhost:${PORT}`);
|
||||
console.log(`Transit API running at http://localhost:${PORT}`);
|
||||
});
|
||||
69024
tripUpdates.json
Normal file
69024
tripUpdates.json
Normal file
File diff suppressed because it is too large
Load Diff
6689
vehicles.json
Normal file
6689
vehicles.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user