feat: Add native Android driver tracking app and real-time backend/frontend bus tracking system

This commit is contained in:
Shanmuga Krishnan S M
2026-07-24 21:22:37 +05:30
parent 8e87bd72cd
commit 85b128bef5
71 changed files with 206796 additions and 1368 deletions

View File

@@ -33,8 +33,10 @@ public class DataInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// Clear all previous mock data from the database
// Clear all previous mock data from the database to force re-seeding with geocoded values
noteRepository.deleteAll();
busRouteRepository.deleteAll();
// Run initial scan to populate database with whatever is currently in /uploads
try {
notePyqController.syncUploads();
@@ -44,50 +46,65 @@ public class DataInitializer implements CommandLineRunner {
}
// Seed Bus Routes
if (busRouteRepository.count() == 0) {
try {
ObjectMapper mapper = new ObjectMapper();
InputStream is = getClass().getResourceAsStream("/bus_routes.json");
if (is != null) {
List<Map<String, Object>> routesList = mapper.readValue(is, new TypeReference<List<Map<String, Object>>>() {});
List<BusRoute> routesToSave = new ArrayList<>();
for (Map<String, Object> routeMap : routesList) {
BusRoute br = BusRoute.builder()
.number((String) routeMap.get("number"))
.name((String) routeMap.get("name"))
.from((String) routeMap.get("from"))
.to((String) routeMap.get("to"))
.departureTime((String) routeMap.get("departureTime"))
.arrivalTime((String) routeMap.get("arrivalTime"))
.color((String) routeMap.get("color"))
.build();
List<Map<String, String>> stopsList = (List<Map<String, String>>) routeMap.get("stops");
List<BusStop> stops = new ArrayList<>();
if (stopsList != null) {
for (int i = 0; i < stopsList.size(); i++) {
Map<String, String> stopMap = stopsList.get(i);
stops.add(BusStop.builder()
.route(br)
.name(stopMap.get("name"))
.time(stopMap.get("time"))
.stopOrder(i + 1)
.build());
}
try {
ObjectMapper mapper = new ObjectMapper();
InputStream is = getClass().getResourceAsStream("/bus_routes.json");
if (is != null) {
List<Map<String, Object>> routesList = mapper.readValue(is, new TypeReference<List<Map<String, Object>>>() {});
List<BusRoute> routesToSave = new ArrayList<>();
for (Map<String, Object> routeMap : routesList) {
List<List<Double>> polyList = (List<List<Double>>) routeMap.get("polyline");
String polyJson = null;
if (polyList != null) {
try {
polyJson = mapper.writeValueAsString(polyList);
} catch (Exception e) {
System.err.println("⚠️ Failed to serialize polyline: " + e.getMessage());
}
br.setStops(stops);
routesToSave.add(br);
}
busRouteRepository.saveAll(routesToSave);
System.out.println("🌱 Database successfully seeded with " + routesToSave.size() + " Bus Routes and stops!");
} else {
System.err.println("⚠️ Could not find bus_routes.json in resources!");
BusRoute br = BusRoute.builder()
.number((String) routeMap.get("number"))
.name((String) routeMap.get("name"))
.from((String) routeMap.get("from"))
.to((String) routeMap.get("to"))
.departureTime((String) routeMap.get("departureTime"))
.arrivalTime((String) routeMap.get("arrivalTime"))
.color((String) routeMap.get("color"))
.fromLat(routeMap.get("from_lat") != null ? ((Number) routeMap.get("from_lat")).doubleValue() : null)
.fromLng(routeMap.get("from_lng") != null ? ((Number) routeMap.get("from_lng")).doubleValue() : null)
.toLat(routeMap.get("to_lat") != null ? ((Number) routeMap.get("to_lat")).doubleValue() : null)
.toLng(routeMap.get("to_lng") != null ? ((Number) routeMap.get("to_lng")).doubleValue() : null)
.polyline(polyJson)
.build();
List<Map<String, Object>> stopsList = (List<Map<String, Object>>) routeMap.get("stops");
List<BusStop> stops = new ArrayList<>();
if (stopsList != null) {
for (int i = 0; i < stopsList.size(); i++) {
Map<String, Object> stopMap = stopsList.get(i);
stops.add(BusStop.builder()
.route(br)
.name((String) stopMap.get("name"))
.time((String) stopMap.get("time"))
.lat(stopMap.get("lat") != null ? ((Number) stopMap.get("lat")).doubleValue() : null)
.lng(stopMap.get("lng") != null ? ((Number) stopMap.get("lng")).doubleValue() : null)
.stopOrder(i + 1)
.build());
}
}
br.setStops(stops);
routesToSave.add(br);
}
} catch (Exception e) {
System.err.println("❌ Failed to seed bus routes: " + e.getMessage());
e.printStackTrace();
busRouteRepository.saveAll(routesToSave);
System.out.println("🌱 Database successfully seeded with " + routesToSave.size() + " Bus Routes and stops!");
} else {
System.err.println("⚠️ Could not find bus_routes.json in resources!");
}
} catch (Exception e) {
System.err.println("❌ Failed to seed bus routes: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,53 @@
package com.rit.portal.controller;
import com.rit.portal.dto.DriverLocationUpdate;
import com.rit.portal.model.BusLocation;
import com.rit.portal.service.BusLocationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/bus-locations")
@CrossOrigin(originPatterns = "*")
public class BusLocationController {
@Autowired
private BusLocationService busLocationService;
private static final String REQUIRED_PIN = "RITDRIVER";
@PostMapping("/{routeNumber}")
public ResponseEntity<Map<String, String>> updateLocation(
@PathVariable String routeNumber,
@RequestBody DriverLocationUpdate update) {
if (update == null || update.getLatitude() == null || update.getLongitude() == null) {
return ResponseEntity.badRequest().body(Map.of("error", "Invalid coordinates"));
}
if (!REQUIRED_PIN.equals(update.getPin())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("error", "Invalid driver PIN"));
}
busLocationService.updateLocation(routeNumber, update.getLatitude(), update.getLongitude());
return ResponseEntity.ok(Map.of("status", "success", "message", "Location updated successfully"));
}
@GetMapping
public ResponseEntity<List<BusLocation>> getActiveLocations() {
return ResponseEntity.ok(busLocationService.getActiveLocations());
}
@GetMapping("/{routeNumber}")
public ResponseEntity<BusLocation> getLocation(@PathVariable String routeNumber) {
BusLocation loc = busLocationService.getLocation(routeNumber);
if (loc == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(loc);
}
}

View File

@@ -0,0 +1,14 @@
package com.rit.portal.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DriverLocationUpdate {
private Double latitude;
private Double longitude;
private String pin;
}

View File

@@ -39,6 +39,22 @@ public class BusRoute {
@Column(name = "color_code")
private String color;
@Column(name = "from_lat")
private Double fromLat;
@Column(name = "from_lng")
private Double fromLng;
@Column(name = "to_lat")
private Double toLat;
@Column(name = "to_lng")
private Double toLng;
@Column(name = "polyline_data", columnDefinition = "TEXT")
@com.fasterxml.jackson.annotation.JsonRawValue
private String polyline;
@OneToMany(mappedBy = "route", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@OrderBy("stopOrder ASC")
@Builder.Default

View File

@@ -28,6 +28,12 @@ public class BusStop {
@Column(name = "arrival_time", nullable = false)
private String time;
@Column(name = "latitude")
private Double lat;
@Column(name = "longitude")
private Double lng;
@Column(name = "stop_order", nullable = false)
private Integer stopOrder;
}

View File

@@ -0,0 +1,16 @@
package com.rit.portal.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.time.Instant;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BusLocation {
private String routeNumber;
private Double latitude;
private Double longitude;
private Instant lastUpdated;
}

View File

@@ -0,0 +1,43 @@
package com.rit.portal.service;
import com.rit.portal.model.BusLocation;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.List;
@Service
public class BusLocationService {
private final Map<String, BusLocation> locationCache = new ConcurrentHashMap<>();
private static final Duration STALE_DURATION = Duration.ofMinutes(2);
public void updateLocation(String routeNumber, Double latitude, Double longitude) {
locationCache.put(routeNumber, new BusLocation(
routeNumber,
latitude,
longitude,
Instant.now()
));
}
public BusLocation getLocation(String routeNumber) {
BusLocation loc = locationCache.get(routeNumber);
if (loc != null) {
if (Instant.now().isAfter(loc.getLastUpdated().plus(STALE_DURATION))) {
locationCache.remove(routeNumber);
return null;
}
}
return loc;
}
public List<BusLocation> getActiveLocations() {
Instant threshold = Instant.now().minus(STALE_DURATION);
// Clean up stale entries on read
locationCache.entrySet().removeIf(entry -> entry.getValue().getLastUpdated().isBefore(threshold));
return List.copyOf(locationCache.values());
}
}