-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
217 lines (173 loc) · 7.19 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import { WebSocketServer } from "ws";
import express from "express";
import http from "http";
import ScheduledBus from "./model/scheduledbus.model.js";
import { haversineDistance, calculateSpeed } from "./utils/time.js";
import dotenv from "dotenv";
import BusStop from "./model/stops.model.js";
import connectDB from "./database.js";
const app = express();
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
dotenv.config();
connectDB();
wss.on("connection", (ws) => {
console.log("🚍 Client connected for location updates");
ws.on("message", async (message) => {
try {
const data = JSON.parse(message);
if (data.type === "locationUpdate") {
// Driver is sending a location update
const { scheduledBusId, latitude, longitude } = data;
if (!scheduledBusId || latitude === undefined || longitude === undefined) {
return ws.send(JSON.stringify({ error: "Invalid data format" }));
}
const updatedBus = await updateBusLocation(scheduledBusId, latitude, longitude);
const buses = await ScheduledBus.find().populate("route driver bus");
wss.clients.forEach((client) => {
if (client.readyState === ws.OPEN) {
client.send(JSON.stringify({ type: "busUpdate", buses }));
}
});
}
if (data.type === "busStopRequest") {
// Passenger is requesting buses for a specific stop
const { busStopId, status } = data;
if (!busStopId) {
return ws.send(JSON.stringify({ error: "Bus stop ID is required" }));
}
const options = {
page: 1,
limit: 10,
sortBy: 'createdAt:asc',
populate: 'driver,route,bus,route.stops.stopId', // Ensure stops are populated correctly
};
let schedules = await ScheduledBus.paginate({}, options);
// Populate origin and destination after querying
schedules.results = await ScheduledBus.populate(schedules.results, {
path: 'route.origin route.destination',
model: 'BusStop', // Ensure 'BusStop' matches your Mongoose model name
});
if (busStopId) {
schedules = schedules.results.filter((schedule) => {
const stopMatches = schedule.route.stops.some((stop) =>
stop.stopId._id.toString() === busStopId
);
return stopMatches;
});
}
if (status && status.length > 0) {
schedules = schedules.filter((schedule) => status.includes(schedule.status));
}
if (schedules.length === 0) {
console.log('No scheduled buses found for the provided bus stop or status.');
}
ws.send(JSON.stringify({ type: "busStopResponse", buses: schedules }));
}
} catch (error) {
console.error("❌ Error processing WebSocket message:", error);
ws.send(JSON.stringify({ error: "Failed to process request" }));
}
});
ws.on("close", () => {
console.log("❌ Client disconnected from WebSocket");
});
});
const broadcastAnnouncements = async () => {
try {
const buses = await ScheduledBus.find({ status: "On Route" }).populate("bus route");
const stops = await BusStop.find();
if (buses.length === 0) return;
let announcementMessage = `📢 பயணிகளின் கவனத்திற்கு!`;
buses.forEach((bus) => {
const nearestStop = stops.find(stop =>
haversineDistance(bus.location.latitude, bus.location.longitude, stop.coordinates.lat, stop.coordinates.lng) < 0.05
);
if (nearestStop) {
announcementMessage += ` பஸ் ${bus.bus?.busNumber} தற்போது ${nearestStop.name} அருகில் உள்ளது.`;
}
});
console.log("🚍 Sending announcement:", announcementMessage);
wss.clients.forEach((client) => {
if (client.readyState === client.OPEN) {
client.send(JSON.stringify({ type: "announcement", message: announcementMessage }));
}
});
} catch (error) {
console.error("❌ Error broadcasting announcement:", error);
}
};
// Trigger announcements every 5 minutes
setInterval(broadcastAnnouncements, 10000);
const PORT = process.env.PORT || 4000;
server.listen(PORT, () => console.log(`🚀 Location WebSocket Server running on port ${PORT}`));
const updateBusLocation = async (scheduledBusId, latitude, longitude) => {
try {
const scheduledBus = await ScheduledBus.findById(scheduledBusId)
.populate({
path: 'route',
populate: [
{ path: 'origin', model: 'BusStop' },
{ path: 'destination', model: 'BusStop' }
]
});
if (!scheduledBus) throw new Error("Scheduled bus not found");
const { location, route, leftAt = [] } = scheduledBus;
if (!route || !route.origin || !route.destination || !route.stops || !route.totalDistance) {
throw new Error("Route data is invalid or missing required details");
}
const stops = await BusStop.find({ '_id': { $in: route.stops.map(stop => stop.stopId) } });
if (!stops.length) throw new Error("No stops found for this route");
const prevLat = location?.latitude;
const prevLng = location?.longitude;
const prevTimestamp = location.lastUpdated ? new Date(location.lastUpdated).getTime() : null;
// Distance calculations
const distanceFromOrigin = haversineDistance(
route.origin.coordinates.lat, route.origin.coordinates.lng, latitude, longitude
);
let remainingDistance = haversineDistance(
latitude, longitude, route.destination.coordinates.lat, route.destination.coordinates.lng
);
let completionPercentage = (distanceFromOrigin / route.totalDistance) * 100;
remainingDistance = Math.max(remainingDistance, 0);
completionPercentage = Math.max(0, Math.min(completionPercentage, 100));
// Speed calculation
const speed = calculateSpeed(prevLat, prevLng, prevTimestamp, latitude, longitude);
// Check for nearest stop
const THRESHOLD_DISTANCE = 0.05; // 50 meters in degrees
const passedStop = stops.find(stop => {
return haversineDistance(latitude, longitude, stop.coordinates.lat, stop.coordinates.lng) < THRESHOLD_DISTANCE;
});
if (passedStop) {
const stopId = passedStop._id;
// Ensure stop is not already recorded
const alreadyRecorded = leftAt.some(entry => entry.stop.toString() === stopId.toString());
if (!alreadyRecorded) {
leftAt.push({
stop: stopId,
time: new Date()
});
}
}
// Prepare update fields
const updateFields = {
"location.latitude": latitude,
"location.longitude": longitude,
"location.lastUpdated": new Date(),
distanceTraveled: distanceFromOrigin.toFixed(2),
distanceRemaining: remainingDistance.toFixed(2),
journeyCompletion: completionPercentage.toFixed(2),
leftAt
};
if (speed !== null) updateFields.speed = speed;
const updatedBus = await ScheduledBus.findByIdAndUpdate(
scheduledBusId,
{ $set: updateFields },
{ new: true }
);
return updatedBus;
} catch (error) {
console.error("❌ Error updating bus location:", error.message);
throw new Error("Error updating bus location");
}
};