From a304c24b32c0327436bbd8c2853e60668e161b42 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Mon, 29 Dec 2025 00:41:52 +0100 Subject: Rename a lot of stuff, add Santiago real time --- .../Services/Processors/ShapeProcessor.cs | 132 +++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs (limited to 'src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs') diff --git a/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs b/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs new file mode 100644 index 0000000..f3af3a5 --- /dev/null +++ b/src/Enmarcha.Backend/Services/Processors/ShapeProcessor.cs @@ -0,0 +1,132 @@ +using Enmarcha.Sources.OpenTripPlannerGql.Queries; + +namespace Enmarcha.Backend.Services.Processors; + +public class ShapeProcessor : IArrivalsProcessor +{ + private readonly ILogger _logger; + + public ShapeProcessor(ILogger logger) + { + _logger = logger; + } + + public Task ProcessAsync(ArrivalsContext context) + { + if (context.IsReduced) + { + return Task.CompletedTask; + } + + foreach (var arrival in context.Arrivals) + { + // If shape is already populated (e.g. by VitrasaRealTimeProcessor), skip + if (arrival.Shape != null) continue; + + if (arrival.RawOtpTrip is not ArrivalsAtStopResponse.Arrival otpArrival) continue; + + var encodedPoints = otpArrival.Trip.Geometry?.Points; + if (string.IsNullOrEmpty(encodedPoints)) + { + _logger.LogDebug("No geometry found for trip {TripId}", arrival.TripId); + continue; + } + + try + { + var points = Decode(encodedPoints); + if (points.Count == 0) continue; + + var features = new List(); + + // Route LineString + features.Add(new + { + type = "Feature", + geometry = new + { + type = "LineString", + coordinates = points.Select(p => new[] { p.Lon, p.Lat }).ToList() + }, + properties = new { type = "route" } + }); + + // Stops + if (otpArrival.Trip.Stoptimes != null) + { + foreach (var stoptime in otpArrival.Trip.Stoptimes) + { + features.Add(new + { + type = "Feature", + geometry = new + { + type = "Point", + coordinates = new[] { stoptime.Stop.Lon, stoptime.Stop.Lat } + }, + properties = new + { + type = "stop", + name = stoptime.Stop.Name + } + }); + } + } + + arrival.Shape = new + { + type = "FeatureCollection", + features = features + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error decoding shape for trip {TripId}", arrival.TripId); + } + } + + return Task.CompletedTask; + } + + private static List<(double Lat, double Lon)> Decode(string encodedPoints) + { + var poly = new List<(double, double)>(); + char[] polylineChars = encodedPoints.ToCharArray(); + int index = 0; + + int currentLat = 0; + int currentLng = 0; + int next5bits; + int sum; + int shifter; + + while (index < polylineChars.Length) + { + sum = 0; + shifter = 0; + do + { + next5bits = (int)polylineChars[index++] - 63; + sum |= (next5bits & 31) << shifter; + shifter += 5; + } while (next5bits >= 32 && index < polylineChars.Length); + + currentLat += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + sum = 0; + shifter = 0; + do + { + next5bits = (int)polylineChars[index++] - 63; + sum |= (next5bits & 31) << shifter; + shifter += 5; + } while (next5bits >= 32 && index < polylineChars.Length); + + currentLng += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1); + + poly.Add((Convert.ToDouble(currentLat) / 100000.0, Convert.ToDouble(currentLng) / 100000.0)); + } + + return poly; + } +} -- cgit v1.3