aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Costasdev.Busurbano.Backend/Controllers/VigoController.cs76
-rw-r--r--src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs30
-rw-r--r--src/Costasdev.Busurbano.Backend/Types/ConsolidatedCirculation.cs1
-rw-r--r--src/frontend/app/components/StopMapModal.tsx50
-rw-r--r--src/frontend/app/components/StopMapSheet.tsx12
5 files changed, 138 insertions, 31 deletions
diff --git a/src/Costasdev.Busurbano.Backend/Controllers/VigoController.cs b/src/Costasdev.Busurbano.Backend/Controllers/VigoController.cs
index 151baa3..1f81bf1 100644
--- a/src/Costasdev.Busurbano.Backend/Controllers/VigoController.cs
+++ b/src/Costasdev.Busurbano.Backend/Controllers/VigoController.cs
@@ -49,18 +49,60 @@ public class VigoController : ControllerBase
[HttpGet("GetShape")]
public async Task<IActionResult> GetShape(
[FromQuery] string shapeId,
- [FromQuery] int startPointIndex = 0
+ [FromQuery] int? startPointIndex = null,
+ [FromQuery] double? busLat = null,
+ [FromQuery] double? busLon = null,
+ [FromQuery] int? busShapeIndex = null,
+ [FromQuery] double? stopLat = null,
+ [FromQuery] double? stopLon = null,
+ [FromQuery] int? stopShapeIndex = null
)
{
- // Include a significant number of previous points to ensure continuity and context
- // Backtrack 15 points to cover any potential gaps or dense point sequences
- var adjustedStartIndex = Math.Max(0, startPointIndex - 15);
- var path = await _shapeService.GetShapePathAsync(shapeId, adjustedStartIndex);
+ var path = await _shapeService.GetShapePathAsync(shapeId, 0);
if (path == null)
{
return NotFound();
}
+ // Determine bus point
+ object? busPoint = null;
+ if (busShapeIndex.HasValue && busShapeIndex.Value >= 0 && busShapeIndex.Value < path.Count)
+ {
+ var p = path[busShapeIndex.Value];
+ busPoint = new { lat = p.Latitude, lon = p.Longitude, index = busShapeIndex.Value };
+ }
+ else if (busLat.HasValue && busLon.HasValue)
+ {
+ var idx = await _shapeService.FindClosestPointIndexAsync(shapeId, busLat.Value, busLon.Value);
+ if (idx.HasValue && idx.Value >= 0 && idx.Value < path.Count)
+ {
+ var p = path[idx.Value];
+ busPoint = new { lat = p.Latitude, lon = p.Longitude, index = idx.Value };
+ }
+ }
+ else if (startPointIndex.HasValue && startPointIndex.Value >= 0 && startPointIndex.Value < path.Count)
+ {
+ var p = path[startPointIndex.Value];
+ busPoint = new { lat = p.Latitude, lon = p.Longitude, index = startPointIndex.Value };
+ }
+
+ // Determine stop point
+ object? stopPoint = null;
+ if (stopShapeIndex.HasValue && stopShapeIndex.Value >= 0 && stopShapeIndex.Value < path.Count)
+ {
+ var p = path[stopShapeIndex.Value];
+ stopPoint = new { lat = p.Latitude, lon = p.Longitude, index = stopShapeIndex.Value };
+ }
+ else if (stopLat.HasValue && stopLon.HasValue)
+ {
+ var idx = await _shapeService.FindClosestPointIndexAsync(shapeId, stopLat.Value, stopLon.Value);
+ if (idx.HasValue && idx.Value >= 0 && idx.Value < path.Count)
+ {
+ var p = path[idx.Value];
+ stopPoint = new { lat = p.Latitude, lon = p.Longitude, index = idx.Value };
+ }
+ }
+
// Convert to GeoJSON LineString
var coordinates = path.Select(p => new[] { p.Longitude, p.Latitude }).ToList();
@@ -72,7 +114,11 @@ public class VigoController : ControllerBase
type = "LineString",
coordinates = coordinates
},
- properties = new { }
+ properties = new
+ {
+ busPoint,
+ stopPoint
+ }
};
return Ok(geoJson);
@@ -214,7 +260,7 @@ public class VigoController : ControllerBase
// 2) From the valid trips, pick the one with smallest Abs(TimeDiff).
// This handles "as late as it gets" (large negative TimeDiff) by preferring smaller delays if available,
// but accepting large delays if that's the only option (and better than an invalid early trip).
- const int maxEarlyArrivalMinutes = 3;
+ const int maxEarlyArrivalMinutes = 5;
var bestMatch = possibleCirculations
.Select(c => new
@@ -259,6 +305,7 @@ public class VigoController : ControllerBase
var isRunning = closestCirculation.StartingDateTime()!.Value <= now;
Position? currentPosition = null;
+ int? stopShapeIndex = null;
// Calculate bus position only for realtime trips that have already departed
if (isRunning && !string.IsNullOrEmpty(closestCirculation.ShapeId))
@@ -266,7 +313,9 @@ public class VigoController : ControllerBase
var shape = await _shapeService.LoadShapeAsync(closestCirculation.ShapeId);
if (shape != null && stopLocation != null)
{
- currentPosition = _shapeService.GetBusPosition(shape, stopLocation, estimate.Meters);
+ var result = _shapeService.GetBusPosition(shape, stopLocation, estimate.Meters);
+ currentPosition = result.BusPosition;
+ stopShapeIndex = result.StopIndex;
}
}
@@ -288,7 +337,8 @@ public class VigoController : ControllerBase
Minutes = estimate.Minutes,
Distance = estimate.Meters
},
- CurrentPosition = currentPosition
+ CurrentPosition = currentPosition,
+ StopShapeIndex = stopShapeIndex
});
usedTripIds.Add(closestCirculation.TripId);
@@ -310,6 +360,12 @@ public class VigoController : ControllerBase
continue; // already represented via a matched realtime
}
+ var minutes = (int)(sched.CallingDateTime()!.Value - now).TotalMinutes;
+ if (minutes == 0)
+ {
+ continue;
+ }
+
consolidatedCirculations.Add(new ConsolidatedCirculation
{
Line = sched.Line,
@@ -317,7 +373,7 @@ public class VigoController : ControllerBase
Schedule = new ScheduleData
{
Running = sched.StartingDateTime()!.Value <= now,
- Minutes = (int)(sched.CallingDateTime()!.Value - now).TotalMinutes,
+ Minutes = minutes,
TripId = sched.TripId,
ServiceId = sched.ServiceId,
ShapeId = sched.ShapeId,
diff --git a/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs b/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
index 37b76ee..7263ad0 100644
--- a/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
+++ b/src/Costasdev.Busurbano.Backend/Services/ShapeTraversalService.cs
@@ -68,27 +68,45 @@ public class ShapeTraversalService
var result = new List<Position>();
// Ensure startIndex is within bounds
if (startIndex < 0) startIndex = 0;
+ // If startIndex is beyond the end, return empty list
if (startIndex >= shape.Points.Count) return result;
for (int i = startIndex; i < shape.Points.Count; i++)
{
- result.Add(TransformToLatLng(shape.Points[i]));
+ var pos = TransformToLatLng(shape.Points[i]);
+ pos.ShapeIndex = i;
+ result.Add(pos);
}
return result;
}
+ public async Task<int?> FindClosestPointIndexAsync(string shapeId, double lat, double lon)
+ {
+ var shape = await LoadShapeAsync(shapeId);
+ if (shape == null) return null;
+
+ // Transform input WGS84 to EPSG:25829
+ // Input is [Longitude, Latitude]
+ var inverseTransform = _transformation.MathTransform.Inverse();
+ var transformed = inverseTransform.Transform(new[] { lon, lat });
+
+ var location = new Epsg25829 { X = transformed[0], Y = transformed[1] };
+
+ return FindClosestPointIndex(shape.Points, location);
+ }
+
/// <summary>
/// Calculates the bus position by reverse-traversing the shape from the stop location
/// </summary>
/// <param name="shape">The shape points (in EPSG:25829 meters)</param>
/// <param name="stopLocation">The stop location (in EPSG:25829 meters)</param>
/// <param name="distanceMeters">Distance in meters from the stop to traverse backwards</param>
- /// <returns>The lat/lng position of the bus, or null if not calculable</returns>
- public Position? GetBusPosition(Shape shape, Epsg25829 stopLocation, int distanceMeters)
+ /// <returns>The lat/lng position of the bus and the stop index on the shape</returns>
+ public (Position? BusPosition, int StopIndex) GetBusPosition(Shape shape, Epsg25829 stopLocation, int distanceMeters)
{
if (shape.Points.Count == 0 || distanceMeters <= 0)
{
- return null;
+ return (null, -1);
}
// Find the closest point on the shape to the stop
@@ -99,7 +117,7 @@ public class ShapeTraversalService
if (busPoint == null)
{
- return null;
+ return (null, closestPointIndex);
}
var forwardPoint = shape.Points[forwardIndex];
@@ -114,7 +132,7 @@ public class ShapeTraversalService
var pos = TransformToLatLng(busPoint);
pos.OrientationDegrees = (int)Math.Round(bearing);
pos.ShapeIndex = forwardIndex;
- return pos;
+ return (pos, closestPointIndex);
}
/// <summary>
diff --git a/src/Costasdev.Busurbano.Backend/Types/ConsolidatedCirculation.cs b/src/Costasdev.Busurbano.Backend/Types/ConsolidatedCirculation.cs
index 2ac3206..ff6dbde 100644
--- a/src/Costasdev.Busurbano.Backend/Types/ConsolidatedCirculation.cs
+++ b/src/Costasdev.Busurbano.Backend/Types/ConsolidatedCirculation.cs
@@ -8,6 +8,7 @@ public class ConsolidatedCirculation
public ScheduleData? Schedule { get; set; }
public RealTimeData? RealTime { get; set; }
public Position? CurrentPosition { get; set; }
+ public int? StopShapeIndex { get; set; }
public string[] NextStreets { get; set; } = [];
}
diff --git a/src/frontend/app/components/StopMapModal.tsx b/src/frontend/app/components/StopMapModal.tsx
index 67435b4..91cd513 100644
--- a/src/frontend/app/components/StopMapModal.tsx
+++ b/src/frontend/app/components/StopMapModal.tsx
@@ -25,6 +25,7 @@ export interface ConsolidatedCirculationForMap {
line: string;
route: string;
currentPosition?: Position;
+ stopShapeIndex?: number;
schedule?: {
shapeId?: string;
};
@@ -90,15 +91,32 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({
if (!mapRef.current) return;
const points: { lat: number; lon: number }[] = [];
- if (stop.latitude && stop.longitude) {
- points.push({ lat: stop.latitude, lon: stop.longitude });
- }
+ if (
+ shapeData?.properties?.busPoint &&
+ shapeData?.properties?.stopPoint &&
+ shapeData?.geometry?.coordinates
+ ) {
+ const busIdx = shapeData.properties.busPoint.index;
+ const stopIdx = shapeData.properties.stopPoint.index;
+ const coords = shapeData.geometry.coordinates;
- if (selectedBus?.currentPosition) {
- points.push({
- lat: selectedBus.currentPosition.latitude,
- lon: selectedBus.currentPosition.longitude,
- });
+ const start = Math.min(busIdx, stopIdx);
+ const end = Math.max(busIdx, stopIdx);
+
+ for (let i = start; i <= end; i++) {
+ points.push({ lat: coords[i][1], lon: coords[i][0] });
+ }
+ } else {
+ if (stop.latitude && stop.longitude) {
+ points.push({ lat: stop.latitude, lon: stop.longitude });
+ }
+
+ if (selectedBus?.currentPosition) {
+ points.push({
+ lat: selectedBus.currentPosition.latitude,
+ lon: selectedBus.currentPosition.longitude,
+ });
+ }
}
if (points.length === 0) return;
@@ -132,7 +150,7 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({
} as any);
}
} catch {}
- }, [stop, selectedBus]);
+ }, [stop, selectedBus, shapeData]);
// Load style without traffic layers for the stop map
useEffect(() => {
@@ -218,10 +236,18 @@ export const StopMapModal: React.FC<StopMapModalProps> = ({
const shapeId = selectedBus.schedule.shapeId;
const shapeIndex = selectedBus.currentPosition.shapeIndex;
+ const stopShapeIndex = selectedBus.stopShapeIndex;
+ const stopLat = stop.latitude;
+ const stopLon = stop.longitude;
+
+ let url = `${regionConfig.shapeEndpoint}?shapeId=${shapeId}&busShapeIndex=${shapeIndex}`;
+ if (stopShapeIndex !== undefined) {
+ url += `&stopShapeIndex=${stopShapeIndex}`;
+ } else {
+ url += `&stopLat=${stopLat}&stopLon=${stopLon}`;
+ }
- fetch(
- `${regionConfig.shapeEndpoint}?shapeId=${shapeId}&startPointIndex=${shapeIndex}`
- )
+ fetch(url)
.then((res) => {
if (res.ok) return res.json();
return null;
diff --git a/src/frontend/app/components/StopMapSheet.tsx b/src/frontend/app/components/StopMapSheet.tsx
index 7dab82b..d70fcb6 100644
--- a/src/frontend/app/components/StopMapSheet.tsx
+++ b/src/frontend/app/components/StopMapSheet.tsx
@@ -19,6 +19,7 @@ export interface ConsolidatedCirculationForMap {
line: string;
route: string;
currentPosition?: Position;
+ stopShapeIndex?: number;
schedule?: {
shapeId?: string;
};
@@ -61,9 +62,14 @@ export const StopMap: React.FC<StopMapProps> = ({
) {
const key = `${c.schedule.shapeId}_${c.currentPosition.shapeIndex}`;
if (!shapes[key]) {
- fetch(
- `${regionConfig.shapeEndpoint}?shapeId=${c.schedule.shapeId}&startPointIndex=${c.currentPosition.shapeIndex}`
- )
+ let url = `${regionConfig.shapeEndpoint}?shapeId=${c.schedule.shapeId}&busShapeIndex=${c.currentPosition.shapeIndex}`;
+ if (c.stopShapeIndex !== undefined) {
+ url += `&stopShapeIndex=${c.stopShapeIndex}`;
+ } else {
+ url += `&stopLat=${stop.latitude}&stopLon=${stop.longitude}`;
+ }
+
+ fetch(url)
.then((res) => {
if (res.ok) return res.json();
return null;