Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@

**RouteIQ** is an enterprise-grade, full-stack transit management, real-time satellite vehicle tracking, and intelligent seat reservation system. Designed as a SaaS-ready multi-tenant platform, it features low-latency WebSocket coordinate streaming, AI-driven traffic arrival estimation, mobile driver location broadcasting, Stripe card checkout, and PWA offline ticket vault storage.

### 🇱🇰 Sri Lanka — Colombo Focus

This build is customized for **Sri Lanka's Western Province** with Colombo as the central transit hub:

- **Pre-seeded Colombo routes**: Colombo Fort ↔ Kandy, Galle, Negombo, Matara, Nuwara Eliya, Jaffna, Gampaha & local Kadawatha corridor
- **Real Colombo bus stops** with accurate GPS coordinates (Fort, Pettah, Kadawatha, Katunayake Airport, Wellawatte, Panadura, Kalutara, Galle, Matara, etc.)
- **Multi-corridor map overlays**: A1 Highway, E01 Southern Expressway, A3 Negombo Road & Southern coastal route — each in a distinct color
- **Sri Lankan Rupee (LKR) pricing** across booking and seat selection
- **Sri Lankan phone numbers** (+94) and local operator contact details
- **Trilingual i18n**: English, සිංහල (Sinhala), தமிழ் (Tamil)
- **Sri Lankan bus registration format** (e.g. `WP-CA-1001`, `SP-GA-1501`)
- **Realistic SLTB & private operator route names** with distance/duration tuned for local traffic conditions

---

## 🌟 Key Platform Features
Expand Down Expand Up @@ -121,7 +134,14 @@ STRIPE_SECRET_KEY=sk_test_mock_stripe_key
VITE_API_BASE_URL=http://localhost:5000
```

### 3. Run Development Servers
### 3. Seed Colombo Routes & Buses
```bash
cd backend
npm run seed:colombo
```
This inserts **8 real Colombo transit routes** (Fort → Kandy, Galle, Negombo, Matara, Nuwara Eliya, Jaffna, Gampaha, Kadawatha local) with accurate GPS waypoints, and **12 sample buses** with Sri Lankan registration numbers.

### 4. Run Development Servers
```bash
# Terminal 1: Backend API & WebSocket Server
cd backend
Expand Down
3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "node --test"
"test": "node --test",
"seed:colombo": "node seeds/colomboRoutes.js"
},
"dependencies": {
"bcryptjs": "^2.4.3",
Expand Down
194 changes: 194 additions & 0 deletions backend/seeds/colomboRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* Seed script: Colombo Metropolitan Bus Routes
* Run: node seeds/colomboRoutes.js
* Requires: MONGO_URI in .env
*/
require("dotenv").config();
const mongoose = require("mongoose");
const Route = require("../models/Route");
const Bus = require("../models/Bus");
const connectDb = require("../config/db");

const COLOMBO_ROUTES = [
{
routeName: "Colombo Fort → Kandy",
origin: "Colombo Fort",
destination: "Kandy",
distanceKm: 116,
estimatedDurationMinutes: 150,
farePrice: 520,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Pettah Bus Stand", latitude: 6.9487, longitude: 79.8593, sequenceOrder: 2 },
{ name: "Kadawatha Interchange", latitude: 7.0840, longitude: 79.9926, sequenceOrder: 3 },
{ name: "Nittambuwa Station", latitude: 7.2000, longitude: 79.9800, sequenceOrder: 4 },
{ name: "Mawanella Central", latitude: 7.2483, longitude: 80.3458, sequenceOrder: 5 },
{ name: "Kandy Goods Shed", latitude: 7.2906, longitude: 80.6337, sequenceOrder: 6 },
],
},
{
routeName: "Colombo Fort → Galle",
origin: "Colombo Fort",
destination: "Galle",
distanceKm: 119,
estimatedDurationMinutes: 135,
farePrice: 480,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Wellawatte Junction", latitude: 6.8890, longitude: 79.8531, sequenceOrder: 2 },
{ name: "Panadura Bus Stand", latitude: 6.7905, longitude: 79.9057, sequenceOrder: 3 },
{ name: "Kalutara South", latitude: 6.6833, longitude: 79.9000, sequenceOrder: 4 },
{ name: "Beruwala", latitude: 6.5833, longitude: 80.1500, sequenceOrder: 5 },
{ name: "Galle Main Stand", latitude: 6.0535, longitude: 80.2208, sequenceOrder: 6 },
],
},
{
routeName: "Colombo Fort → Negombo",
origin: "Colombo Fort",
destination: "Negombo",
distanceKm: 37,
estimatedDurationMinutes: 55,
farePrice: 180,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Ja-Ela Junction", latitude: 7.0033, longitude: 79.8833, sequenceOrder: 2 },
{ name: "Seeduwa", latitude: 7.0833, longitude: 79.8833, sequenceOrder: 3 },
{ name: "Katunayake Airport", latitude: 7.1500, longitude: 79.8500, sequenceOrder: 4 },
{ name: "Negombo Bus Stand", latitude: 7.2100, longitude: 79.8300, sequenceOrder: 5 },
],
},
{
routeName: "Colombo Fort → Matara",
origin: "Colombo Fort",
destination: "Matara",
distanceKm: 160,
estimatedDurationMinutes: 180,
farePrice: 650,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Kalutara South", latitude: 6.6833, longitude: 79.9000, sequenceOrder: 2 },
{ name: "Ambalangoda", latitude: 6.4500, longitude: 80.0500, sequenceOrder: 3 },
{ name: "Hikkaduwa", latitude: 6.2500, longitude: 80.1000, sequenceOrder: 4 },
{ name: "Galle Main Stand", latitude: 6.0535, longitude: 80.2208, sequenceOrder: 5 },
{ name: "Matara Bus Complex", latitude: 5.9485, longitude: 80.5353, sequenceOrder: 6 },
],
},
{
routeName: "Colombo Fort → Nuwara Eliya",
origin: "Colombo Fort",
destination: "Nuwara Eliya",
distanceKm: 180,
estimatedDurationMinutes: 240,
farePrice: 780,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Kadawatha Interchange", latitude: 7.0840, longitude: 79.9926, sequenceOrder: 2 },
{ name: "Kandy Goods Shed", latitude: 7.2906, longitude: 80.6337, sequenceOrder: 3 },
{ name: "Peradeniya Junction", latitude: 7.2733, longitude: 80.5833, sequenceOrder: 4 },
{ name: "Hatton", latitude: 6.8889, longitude: 80.5833, sequenceOrder: 5 },
{ name: "Nuwara Eliya Bus Stand", latitude: 6.9493, longitude: 80.7888, sequenceOrder: 6 },
],
},
{
routeName: "Colombo Fort → Jaffna",
origin: "Colombo Fort",
destination: "Jaffna",
distanceKm: 397,
estimatedDurationMinutes: 480,
farePrice: 1250,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Kadawatha Interchange", latitude: 7.0840, longitude: 79.9926, sequenceOrder: 2 },
{ name: "Kurunegala", latitude: 7.4863, longitude: 80.3647, sequenceOrder: 3 },
{ name: "Anuradhapura", latitude: 8.3114, longitude: 80.4037, sequenceOrder: 4 },
{ name: "Vavuniya", latitude: 8.7380, longitude: 80.4770, sequenceOrder: 5 },
{ name: "Jaffna Bus Stand", latitude: 9.6615, longitude: 80.0255, sequenceOrder: 6 },
],
},
{
routeName: "Colombo Pettah → Kadawatha (Local)",
origin: "Colombo Pettah",
destination: "Kadawatha",
distanceKm: 16,
estimatedDurationMinutes: 35,
farePrice: 80,
active: true,
stops: [
{ name: "Colombo Pettah Stand", latitude: 6.9487, longitude: 79.8593, sequenceOrder: 1 },
{ name: "Maradana", latitude: 6.9328, longitude: 79.8742, sequenceOrder: 2 },
{ name: "Kelaniya Temple", latitude: 6.9567, longitude: 79.9267, sequenceOrder: 3 },
{ name: "Kiribathgoda", latitude: 6.9700, longitude: 79.9300, sequenceOrder: 4 },
{ name: "Kadawatha Interchange", latitude: 7.0840, longitude: 79.9926, sequenceOrder: 5 },
],
},
{
routeName: "Colombo Fort → Gampaha",
origin: "Colombo Fort",
destination: "Gampaha",
distanceKm: 33,
estimatedDurationMinutes: 55,
farePrice: 150,
active: true,
stops: [
{ name: "Colombo Fort Bus Station", latitude: 6.9271, longitude: 79.8612, sequenceOrder: 1 },
{ name: "Kelaniya Temple", latitude: 6.9567, longitude: 79.9267, sequenceOrder: 2 },
{ name: "Kiribathgoda", latitude: 6.9700, longitude: 79.9300, sequenceOrder: 3 },
{ name: "Kadawatha Interchange", latitude: 7.0840, longitude: 79.9926, sequenceOrder: 4 },
{ name: "Gampaha Bus Stand", latitude: 7.0872, longitude: 80.0144, sequenceOrder: 5 },
],
},
];

// Sample bus fleet seeded on each route
const COLOMBO_BUSES = [
{ busNumber: "WP-CA-1001", route: "Colombo Fort → Kandy", driverName: "Kumara Perera", driverContact: "+94771234001", capacity: 45, busType: "AC", status: "Active", latitude: 6.9271, longitude: 79.8612, speedKmph: 55 },
{ busNumber: "WP-CA-1002", route: "Colombo Fort → Kandy", driverName: "Sunil Jayawardena", driverContact: "+94771234002", capacity: 45, busType: "Non-AC", status: "Active", latitude: 7.0840, longitude: 79.9926, speedKmph: 45 },
{ busNumber: "WP-CA-2001", route: "Colombo Fort → Galle", driverName: "Ruwan Fernando", driverContact: "+94771234003", capacity: 50, busType: "AC", status: "Active", latitude: 6.6833, longitude: 79.9000, speedKmph: 60 },
{ busNumber: "SP-GA-1501", route: "Colombo Fort → Galle", driverName: "Anura Bandara", driverContact: "+94771234004", capacity: 50, busType: "Express", status: "On Route", latitude: 6.0535, longitude: 80.2208, speedKmph: 65 },
{ busNumber: "WP-CA-3001", route: "Colombo Fort → Negombo", driverName: "Dinesh Silva", driverContact: "+94771234005", capacity: 40, busType: "Non-AC", status: "Active", latitude: 7.0033, longitude: 79.8833, speedKmph: 40 },
{ busNumber: "WP-CA-3002", route: "Colombo Fort → Negombo", driverName: "Mahinda Rajapakshe", driverContact: "+94771234006", capacity: 40, busType: "AC", status: "Active", latitude: 7.1500, longitude: 79.8500, speedKmph: 50 },
{ busNumber: "SP-MT-4001", route: "Colombo Fort → Matara", driverName: "Prasanna Kumara", driverContact: "+94771234007", capacity: 50, busType: "Express", status: "On Route", latitude: 6.2500, longitude: 80.1000, speedKmph: 70 },
{ busNumber: "CP-NE-5001", route: "Colombo Fort → Nuwara Eliya", driverName: "Saman Wijesinghe", driverContact: "+94771234008", capacity: 45, busType: "AC", status: "Active", latitude: 7.2906, longitude: 80.6337, speedKmph: 40 },
{ busNumber: "NP-JF-6001", route: "Colombo Fort → Jaffna", driverName: "Vijaya Rajan", driverContact: "+94771234009", capacity: 50, busType: "Sleeper", status: "Active", latitude: 8.3114, longitude: 80.4037, speedKmph: 65 },
{ busNumber: "WP-LC-7001", route: "Colombo Pettah → Kadawatha (Local)", driverName: "Nimal de Silva", driverContact: "+94771234010", capacity: 55, busType: "Non-AC", status: "Active", latitude: 6.9487, longitude: 79.8593, speedKmph: 30 },
{ busNumber: "WP-LC-7002", route: "Colombo Pettah → Kadawatha (Local)", driverName: "Asanka Gunasekara", driverContact: "+94771234011", capacity: 55, busType: "Non-AC", status: "Active", latitude: 6.9567, longitude: 79.9267, speedKmph: 35 },
{ busNumber: "WP-GP-8001", route: "Colombo Fort → Gampaha", driverName: "Lakshman Peris", driverContact: "+94771234012", capacity: 45, busType: "Non-AC", status: "Active", latitude: 7.0872, longitude: 80.0144, speedKmph: 45 },
];

async function seed() {
await connectDb();

// Clear existing demo data
await Route.deleteMany({});
await Bus.deleteMany({});
console.log("🧹 Cleared existing routes and buses");

// Insert routes
const routes = await Route.insertMany(COLOMBO_ROUTES);
console.log(`✅ Inserted ${routes.length} Colombo routes`);

// Link buses to routes
const busesWithRouteId = COLOMBO_BUSES.map((bus) => {
const route = routes.find((r) => r.routeName === bus.route);
return { ...bus, routeId: route ? route._id : null };
});

const buses = await Bus.insertMany(busesWithRouteId);
console.log(`✅ Inserted ${buses.length} buses on Colombo routes`);

console.log("\n🚍 Seeded Colombo routes:");
routes.forEach((r) => console.log(` • ${r.routeName} (${r.distanceKm} km, LKR ${r.farePrice})`));

process.exit(0);
}

seed().catch((err) => {
console.error("❌ Seed failed:", err);
process.exit(1);
});
4 changes: 2 additions & 2 deletions backend/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ app.get("/api/health", (req, res) => {
});

app.get("/", (req, res) => {
res.send("✅ RouteIQ Engine Engine 2.0 Running & Operational");
res.send("✅ RouteIQ Colombo — Sri Lanka Smart Transit Engine 2.0 Running & Operational");
});

// ⚡ Socket.IO Integration
Expand Down Expand Up @@ -117,7 +117,7 @@ app.use(errorHandler);

const PORT = process.env.PORT || 5000;
server.listen(PORT, () => {
console.log(`🚀 Busgo Engine 2.0 server listening on port ${PORT}`);
console.log(`🚀 RouteIQ Colombo Engine 2.0 server listening on port ${PORT}`);
});

module.exports = { app, server };
4 changes: 2 additions & 2 deletions backend/tests/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ assert.strictEqual(decoded.email, 'test@routeiq.io', 'JWT Email mismatch');
assert.strictEqual(decoded.role, 'customer', 'JWT Role mismatch');
console.log(' ✅ JWT Token Generation & Verification: PASSED');

// 3. Haversine Distance Formula Test (Delhi to Noida ~20km)
// 3. Haversine Distance Formula Test (Colombo Fort → Kadawatha ~20km)
function calculateHaversineDistance(lat1, lon1, lat2, lon2) {
const R = 6371;
const dLat = ((lat2 - lat1) * Math.PI) / 180;
Expand All @@ -41,7 +41,7 @@ function calculateHaversineDistance(lat1, lon1, lat2, lon2) {
return R * c;
}

const distance = calculateHaversineDistance(28.6139, 77.2090, 28.5355, 77.3910);
const distance = calculateHaversineDistance(6.9271, 79.8612, 7.0840, 79.9926);
assert.ok(distance > 15 && distance < 25, `Haversine distance out of expected range: ${distance} km`);
console.log(` ✅ Spherical Haversine GPS Distance Algorithm (${distance.toFixed(2)} km): PASSED`);

Expand Down
28 changes: 14 additions & 14 deletions frontend/src/components/AddBusForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export default function AddBusForm({ addBusToList }) {
busNumber: "",
driverName: "",
route: "",
latitude: "28.6139",
longitude: "77.2090",
latitude: "6.9271",
longitude: "79.8612",
busType: "AC",
});
const [error, setError] = useState("");
Expand All @@ -35,8 +35,8 @@ export default function AddBusForm({ addBusToList }) {
busNumber: "",
driverName: "",
route: "",
latitude: "28.6139",
longitude: "77.2090",
latitude: "6.9271",
longitude: "79.8612",
busType: "AC",
});
setError("");
Expand All @@ -50,11 +50,11 @@ export default function AddBusForm({ addBusToList }) {
return (
<div className="bg-white border border-slate-200 rounded-3xl p-6 shadow-md">
<h3 className="text-lg font-black text-slate-900 mb-4 flex items-center gap-2">
<Bus className="w-5 h-5 text-rose-600" /> Register New Bus Vehicle
<Bus className="w-5 h-5 text-blue-600" /> Register New Bus Vehicle
</h3>

{error && (
<div className="p-3 bg-rose-50 text-rose-700 text-xs font-bold rounded-xl mb-4 border border-rose-200">
<div className="p-3 bg-blue-50 text-blue-700 text-xs font-bold rounded-xl mb-4 border border-blue-200">
{error}
</div>
)}
Expand All @@ -65,10 +65,10 @@ export default function AddBusForm({ addBusToList }) {
<input
type="text"
required
placeholder="e.g. BUS-808"
placeholder="e.g. WP-CA-1001"
value={form.busNumber}
onChange={(e) => setForm({ ...form, busNumber: e.target.value })}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-rose-500"
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>

Expand All @@ -80,7 +80,7 @@ export default function AddBusForm({ addBusToList }) {
placeholder="Driver Name"
value={form.driverName}
onChange={(e) => setForm({ ...form, driverName: e.target.value })}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-rose-500"
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>

Expand All @@ -89,10 +89,10 @@ export default function AddBusForm({ addBusToList }) {
<input
type="text"
required
placeholder="e.g. City Center → Airport"
placeholder="e.g. Colombo Fort → Katunayake Airport"
value={form.route}
onChange={(e) => setForm({ ...form, route: e.target.value })}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-rose-500"
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>

Expand All @@ -103,7 +103,7 @@ export default function AddBusForm({ addBusToList }) {
step="any"
value={form.latitude}
onChange={(e) => setForm({ ...form, latitude: e.target.value })}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-rose-500"
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>

Expand All @@ -114,15 +114,15 @@ export default function AddBusForm({ addBusToList }) {
step="any"
value={form.longitude}
onChange={(e) => setForm({ ...form, longitude: e.target.value })}
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-rose-500"
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>

<div className="sm:col-span-2 md:col-span-1 flex items-end">
<button
type="submit"
disabled={loading}
className="w-full py-2.5 bg-rose-600 hover:bg-rose-700 text-white font-bold rounded-xl transition shadow"
className="w-full py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl transition shadow"
>
{loading ? "Adding..." : "Register Bus"}
</button>
Expand Down
Loading
Loading