From f3946bae01b16dd24a5473f679a51ec35d16f58e Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Wed, 3 Apr 2024 11:40:45 +0200 Subject: [PATCH 01/10] add vehicle_id to schedule adherence report --- .../transitclock/core/reports/Reports.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/org/transitclock/core/reports/Reports.java b/core/src/main/java/org/transitclock/core/reports/Reports.java index c9673ffc3..f58a0d48c 100644 --- a/core/src/main/java/org/transitclock/core/reports/Reports.java +++ b/core/src/main/java/org/transitclock/core/reports/Reports.java @@ -1,12 +1,13 @@ /* (C)2023 */ package org.transitclock.core.reports; -import java.text.ParseException; import org.json.JSONArray; import org.json.JSONObject; import org.transitclock.domain.webstructs.WebAgency; import org.transitclock.utils.Time; +import java.text.ParseException; + public class Reports { private static final int MAX_ROWS = 50000; @@ -251,7 +252,8 @@ public static String getScheduleAdhByStops( // + " abs(((ad.time / 1000) - (ad.scheduled_time / 1000))) AS // difference_in_seconds, \n" + " s.id AS stop_id, \n" - + " ad.stop_order AS stop_order \n" + + " ad.stop_order AS stop_order, \n" + + " ad.vehicle_id AS v_id \n" // + " FROM arrivals_departures ad, stops s \n" + "WHERE " // To get stop name @@ -276,7 +278,8 @@ public static String getScheduleAdhByStops( // + " ((ad.time / 1000) - (ad.scheduled_time / 1000)) AS // difference_in_seconds, \n" + " s.id AS stop_id, \n" - + " ad.stop_order AS stop_order \n" + + " ad.stop_order AS stop_order, \n" + + " ad.vehicle_id AS v_id \n" // + " FROM arrivals_departures ad, Stops s \n" + "WHERE " // To get stop name @@ -294,18 +297,18 @@ public static String getScheduleAdhByStops( + " \n" + " ORDER BY direction_id, ad.stop_order, s.name \n" + "), \n" - + "trips_late_query_v2 AS ( SELECT" - + " array_to_string(array_agg(trips_late::text || ' (' ||" - + " difference_in_seconds::text || ')' order by trips_late::text), '; ') AS" - + " trips_late, \n" + + "trips_late_query_v2 AS ( SELECT \n" + + " array_to_string(array_agg('trip: ' || trips_late::text || ' (' ||" + + " difference_in_seconds::text || '), vehicle: ' || v_id::text order by trips_late::text), '; ')" + + " AS trips_late, \n" + " stop_id, \n" + " stop_order \n" + " FROM trips_late_query_with_time \n" + " GROUP BY stop_id, stop_order \n" + " ), \n" - + " trips_early_query_v2 AS ( \n" - + " SELECT array_to_string(array_agg(trips_early::text || ' (' ||" - + " difference_in_seconds::text || ')' order by trips_early::text), '; ')" + + " trips_early_query_v2 AS ( SELECT \n" + + " array_to_string(array_agg('trip: ' || trips_early::text || ' (' ||" + + " difference_in_seconds::text || '), vehicle: ' || v_id::text order by trips_early::text), '; ')" + " AS trips_early, \n" + " stop_id, \n" + " stop_order \n" From b6820dd1bfa56008e9ce951e010d7dfda17142f2 Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Thu, 4 Apr 2024 16:16:52 +0200 Subject: [PATCH 02/10] fix: GtfsRtVehicleFeed --- .../org/transitclock/api/data/gtfs/GtfsRtVehicleFeed.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/transitclock/api/data/gtfs/GtfsRtVehicleFeed.java b/app/src/main/java/org/transitclock/api/data/gtfs/GtfsRtVehicleFeed.java index feebfae7f..f64d55982 100644 --- a/app/src/main/java/org/transitclock/api/data/gtfs/GtfsRtVehicleFeed.java +++ b/app/src/main/java/org/transitclock/api/data/gtfs/GtfsRtVehicleFeed.java @@ -8,14 +8,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.transitclock.api.utils.AgencyTimezoneCache; +import org.transitclock.service.VehiclesServiceImpl; +import org.transitclock.service.contract.VehiclesInterface; import org.transitclock.service.dto.IpcAvl; import org.transitclock.service.dto.IpcVehicleConfig; import org.transitclock.service.dto.IpcVehicleGtfsRealtime; -import org.transitclock.service.contract.VehiclesInterface; -import org.transitclock.service.VehiclesServiceImpl; import org.transitclock.utils.Time; -import java.rmi.RemoteException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; @@ -146,7 +145,7 @@ private FeedMessage createMessage(Collection vehicles) { for (IpcVehicleGtfsRealtime vehicle : vehicles) { IpcAvl newAvl = new IpcAvl( - vehicle.getVehicleName(), + vehicle.getId(), vehicle.getAvl().getTime(), vehicle.getAvl().getLatitude(), vehicle.getAvl().getLongitude(), From 1b97344ba3bd655b5ba399c379d94088ecd718bd Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Wed, 10 Apr 2024 18:09:10 +0200 Subject: [PATCH 03/10] add new report for stops --- .../transitclock/core/reports/Reports.java | 133 ++++++++++++++++++ .../transitclock/core/reports/SqlUtils.java | 10 ++ 2 files changed, 143 insertions(+) diff --git a/core/src/main/java/org/transitclock/core/reports/Reports.java b/core/src/main/java/org/transitclock/core/reports/Reports.java index f58a0d48c..47640592e 100644 --- a/core/src/main/java/org/transitclock/core/reports/Reports.java +++ b/core/src/main/java/org/transitclock/core/reports/Reports.java @@ -643,6 +643,138 @@ public static String getScheduleAdhByStops_v2( return jsonString; } + public static String getReportForStopId (String agencyId, + String stop, + String beginDate, + String allowableEarly, + String allowableLate, + String beginTime, + String endTime, + int numDays) { + if (allowableEarly == null || allowableEarly.isEmpty()) allowableEarly = "1.0"; + String allowableEarlyMinutesStr = "'" + SqlUtils.convertMinutesToSecs(allowableEarly) + " seconds'"; + + if (allowableLate == null || allowableLate.isEmpty()) allowableLate = "4.0"; + String allowableLateMinutesStr = "'" + SqlUtils.convertMinutesToSecs(allowableLate) + " seconds'"; + + String sql = " WITH early AS (SELECT time AS date_time,\n" + + " s.name AS name,\n" + + " ad.route_id AS route,\n" + + " ad.trip_id AS trip,\n" + + " ad.block_id AS block,\n" + + " ad.vehicle_id AS vehicle,\n" + + " ad.scheduled_time AS schedule,\n" + + " regexp_replace(CAST(DATE_TRUNC('second', ad.scheduled_time::timestamp) - " + + " DATE_TRUNC('second', ad.time::timestamp) AS VARCHAR),\n" + + " '^00:', ''\n" + + " ) AS difference\n" + + " FROM arrivals_departures ad,\n" + + " stops s\n" + + " WHERE ad.config_rev = s.config_rev\n" + + " AND ad.stop_id = s.id\n" + + " AND ad.scheduled_time IS NOT NULL \n" + +// Specifies which stops to provide data for + SqlUtils.stopClause(stop, "ad") + +// Defines time range + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + " \n" + + " AND scheduled_time - time > " + allowableEarlyMinutesStr + + " \n" + + " ORDER BY date_time),\n" + + " ontime AS (SELECT time AS date_time,\n" + + " s.name AS name,\n" + + " ad.route_id AS route,\n" + + " ad.trip_id AS trip,\n" + + " ad.block_id AS block,\n" + + " ad.vehicle_id AS vehicle,\n" + + " ad.scheduled_time AS schedule,\n" + + " regexp_replace(\n" + + " CAST(DATE_TRUNC('second', ad.scheduled_time::timestamp) - " + + " DATE_TRUNC('second', ad.time::timestamp) AS VARCHAR),\n" + + " '^(-)?00:', '\\1'\n" + + " ) AS difference\n" + + " FROM arrivals_departures ad,\n" + + " stops s\n" + + " WHERE ad.config_rev = s.config_rev\n" + + " AND ad.stop_id = s.id\n" + + " AND ad.scheduled_time IS NOT NULL \n" + +// Specifies which stops to provide data for + SqlUtils.stopClause(stop, "ad") + +// Defines time range + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + " \n" + + " AND scheduled_time - time <= " + allowableEarlyMinutesStr + + " \n" + + " AND time - scheduled_time <= " + allowableLateMinutesStr + + " \n" + + " ORDER BY date_time),\n" + + " late AS (SELECT time AS date_time,\n" + + " s.name AS name,\n" + + " ad.route_id AS route,\n" + + " ad.trip_id AS trip,\n" + + " ad.block_id AS block,\n" + + " ad.vehicle_id AS vehicle,\n" + + " ad.scheduled_time AS schedule,\n" + + " regexp_replace(\n" + + " CAST(DATE_TRUNC('second', ad.scheduled_time::timestamp) - " + + " DATE_TRUNC('second', ad.time::timestamp) AS VARCHAR),\n" + + " '^(-)00:', '\\1'\n" + + " ) AS difference\n" + + " FROM arrivals_departures ad,\n" + + " stops s\n" + + " WHERE ad.config_rev = s.config_rev\n" + + " AND ad.stop_id = s.id\n" + + " AND ad.scheduled_time IS NOT NULL \n" + +// Specifies which stops to provide data for + SqlUtils.stopClause(stop, "ad") + +// Defines time range + " \n" + + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + " \n" + + " AND time - scheduled_time > " + allowableLateMinutesStr + + " \n" + + " ORDER BY date_time) \n" + +// " SELECT ('early', jsonb_agg(jsonb_build_object(\n" + +// " 'date_time', early.date_time,\n" + +// " 'name', early.name,\n" + +// " 'route_id', early.route,\n" + +// " 'trip_id', early.trip,\n" + +// " 'block_id', early.block,\n" + +// " 'vehicle_id', early.vehicle,\n" + +// " 'schedule_time', early.schedule,\n" + +// " 'difference', early.difference)) FILTER (WHERE early.date_time IS NOT NULL\n" + +// " ),\n" + +// " 'ontime', jsonb_agg(jsonb_build_object(\n" + +// " 'date_time', ontime.date_time,\n" + +// " 'name', ontime.name,\n" + +// " 'route_id', ontime.route,\n" + +// " 'trip_id', ontime.trip,\n" + +// " 'block_id', ontime.block,\n" + +// " 'vehicle_id', ontime.vehicle,\n" + +// " 'schedule_time', ontime.schedule,\n" + +// " 'difference', ontime.difference)) FILTER (WHERE ontime.date_time IS NOT NULL\n" + +// " ),\n" + +// " 'late', jsonb_agg(jsonb_build_object(\n" + +// " 'date_time', late.date_time,\n" + +// " 'name', late.name,\n" + +// " 'route_id', late.route,\n" + +// " 'trip_id', late.trip,\n" + +// " 'block_id', late.block,\n" + +// " 'vehicle_id', late.vehicle,\n" + +// " 'schedule_time', late.schedule,\n" + +// " 'difference', late.difference)) FILTER (WHERE late.date_time IS NOT NULL)\n" + +// " )\n" + +// " AS " + stop + "\n" + + "SELECT * FROM \n" + + " early\n" + + " FULL OUTER JOIN\n" + + " ontime ON early.date_time = ontime.date_time\n" + + " FULL OUTER JOIN\n" + + " late ON ontime.date_time = late.date_time\n"; + + return GenericJsonQuery.getJsonString(agencyId, sql); + } + /** * Queries agency for AVL data and returns result as a JSON string. Limited to returning * MAX_ROWS (50,000) data points. @@ -693,3 +825,4 @@ public static boolean hasLastAvlJsonInHours(String agencyId, String vehicleId, i return json.length() > 50; } } + diff --git a/core/src/main/java/org/transitclock/core/reports/SqlUtils.java b/core/src/main/java/org/transitclock/core/reports/SqlUtils.java index f138336d0..1c9360da5 100644 --- a/core/src/main/java/org/transitclock/core/reports/SqlUtils.java +++ b/core/src/main/java/org/transitclock/core/reports/SqlUtils.java @@ -92,6 +92,16 @@ public static String routeClause(String r, String tableAliasName) { return " AND " + tableAlias + "route_short_name IN " + routeIdentifiers; } + public static String stopClause(String id, String tableAliasName) { + if (id == null || id.isEmpty()) + return ""; + + String tableAlias = ""; + if (tableAliasName != null && !tableAliasName.isEmpty()) tableAlias = tableAliasName + "."; + + return " AND " + tableAlias + "stop_id = '" + id + "'"; + } + /** * Creates a SQL clause for specifying a time range. Looks at the request parameters * "beginDate", "numDays", "beginTime", and "endTime" From 751e0e6b5379a7d56c2728fb689d6623ebdac36b Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Thu, 11 Apr 2024 14:28:07 +0200 Subject: [PATCH 04/10] add new endpoint for stop report --- .../api/resources/TransitimeApi.java | 77 ++++++++++------- .../transitclock/core/reports/Reports.java | 84 +++++++------------ .../transitclock/core/reports/SqlUtils.java | 3 +- 3 files changed, 78 insertions(+), 86 deletions(-) diff --git a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java index 5ec918ef2..d87b2dabb 100644 --- a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java +++ b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java @@ -6,22 +6,7 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.servers.Server; -import java.rmi.RemoteException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import jakarta.ws.rs.BeanParam; -import jakarta.ws.rs.DefaultValue; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.QueryParam; -import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; @@ -36,25 +21,16 @@ import org.transitclock.domain.structs.Agency; import org.transitclock.domain.structs.ExportTable; import org.transitclock.domain.structs.Location; -import org.transitclock.service.dto.IpcActiveBlock; -import org.transitclock.service.dto.IpcBlock; -import org.transitclock.service.dto.IpcCalendar; -import org.transitclock.service.dto.IpcDirectionsForRoute; -import org.transitclock.service.dto.IpcPrediction; -import org.transitclock.service.dto.IpcPredictionsForRouteStopDest; -import org.transitclock.service.dto.IpcRoute; -import org.transitclock.service.dto.IpcRouteSummary; -import org.transitclock.service.dto.IpcSchedule; -import org.transitclock.service.dto.IpcServerStatus; -import org.transitclock.service.dto.IpcTrip; -import org.transitclock.service.dto.IpcTripPattern; -import org.transitclock.service.dto.IpcVehicle; -import org.transitclock.service.dto.IpcVehicleConfig; import org.transitclock.service.contract.ConfigInterface; import org.transitclock.service.contract.PredictionsInterface; import org.transitclock.service.contract.PredictionsInterface.RouteStop; import org.transitclock.service.contract.ServerStatusInterface; import org.transitclock.service.contract.VehiclesInterface; +import org.transitclock.service.dto.*; + +import java.rmi.RemoteException; +import java.util.*; +import java.util.stream.Collectors; /** * Contains the API commands for the Transitime API for getting real-time vehicle and prediction @@ -215,7 +191,7 @@ public Response getVehiclesToBlock( public Response getAvlReport( @BeanParam StandardParameters stdParameters, @Parameter(description = "Vehicle id") @QueryParam(value = "v") String vehicleId, - @Parameter(description = "Begin date(MM-DD-YYYY.") @QueryParam(value = "beginDate") String beginDate, + @Parameter(description = "Begin date(MM-DD-YYYY or YYYY-MM-DD)") @QueryParam(value = "beginDate") String beginDate, @Parameter(description = "Num days.", required = false) @QueryParam(value = "numDays") int numDays, @Parameter(description = "Begin time(HH:MM)") @QueryParam(value = "beginTime") String beginTime, @Parameter(description = "End time(HH:MM)") @QueryParam(value = "endTime") String endTime) @@ -310,7 +286,7 @@ public Response getTrips( public Response scheduleAdhReport( @BeanParam StandardParameters stdParameters, @Parameter(description = "Route id") @QueryParam(value = "r") String routeId, - @Parameter(description = "Begin date(MM-DD-YYYY.") @QueryParam(value = "beginDate") String beginDate, + @Parameter(description = "Begin date(MM-DD-YYYY or YYYY-MM-DD)") @QueryParam(value = "beginDate") String beginDate, @Parameter(description = "Num days.", required = false) @QueryParam(value = "numDays") int numDays, @Parameter(description = "Begin time(HH:MM)") @QueryParam(value = "beginTime") String beginTime, @Parameter(description = "End time(HH:MM)") @QueryParam(value = "endTime") String endTime, @@ -357,6 +333,43 @@ public Response getLastAvlJsonData(@BeanParam StandardParameters stdParameters) } } + @Operation( + summary = "Returns schedule adherence report for single stop.", + description = "Returns schedule adherence report for single stop.", + tags = {"report", "stop"}) + @Path("/reports/schedStopAdh") + @GET + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + public Response reportForStopById( + @BeanParam StandardParameters stdParameters, + @Parameter(description = "Stop Id", required = true) @QueryParam(value = "id") String stopId, + @Parameter(description = "Begin date(MM-DD-YYYY or YYYY-MM-DD)", required = true) @QueryParam(value = "beginDate") String beginDate, + @Parameter(description = "Num days.", required = true) @QueryParam(value = "numDays") int numDays, + @Parameter(description = "Begin time(HH:MM)") @QueryParam(value = "beginTime") String beginTime, + @Parameter(description = "End time(HH:MM)") @QueryParam(value = "endTime") String endTime, + @Parameter(description = "Allowable early in mins(default 1.0)") @QueryParam(value = "allowableEarly") + String allowableEarly, + @Parameter(description = "Allowable late in mins(default 4.0") @QueryParam(value = "allowableLate") + String allowableLate) + throws WebApplicationException { + stdParameters.validate(); + try { + String response = Reports.getReportForStopById( + stdParameters.getAgencyId(), + stopId, + beginDate, + allowableEarly, + allowableLate, + beginTime, + endTime, + numDays); + return stdParameters.createResponse(response); + } catch (Exception e) { + // If problem getting data then return a Bad Request + throw WebUtils.badRequestException(e); + } + } + /** * Handles the vehicleIds command. Returns list of vehicle IDs. * diff --git a/core/src/main/java/org/transitclock/core/reports/Reports.java b/core/src/main/java/org/transitclock/core/reports/Reports.java index 47640592e..b49e90213 100644 --- a/core/src/main/java/org/transitclock/core/reports/Reports.java +++ b/core/src/main/java/org/transitclock/core/reports/Reports.java @@ -264,7 +264,7 @@ public static String getScheduleAdhByStops( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND scheduled_time-time > " + allowableEarlyMinutesStr @@ -290,7 +290,7 @@ public static String getScheduleAdhByStops( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND time-scheduled_time > " + allowableLateMinutesStr @@ -345,7 +345,7 @@ public static String getScheduleAdhByStops( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" // Grouping and ordering is a bit complicated since might also be looking // at old arrival/departure data that doen't have stoporder defined. Also, @@ -486,7 +486,7 @@ public static String getScheduleAdhByStops_v2( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND scheduled_time-time > " + allowableEarlyMinutesStr @@ -511,7 +511,7 @@ public static String getScheduleAdhByStops_v2( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND time-scheduled_time > " + allowableLateMinutesStr @@ -567,7 +567,7 @@ public static String getScheduleAdhByStops_v2( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" // Grouping and ordering is a bit complicated since might also be looking // at old arrival/departure data that doen't have stoporder defined. Also, @@ -597,7 +597,7 @@ public static String getScheduleAdhByStops_v2( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND scheduled_time-time > " + allowableEarlyMinutesStr @@ -621,7 +621,7 @@ public static String getScheduleAdhByStops_v2( // Specifies which routes to provide data for + SqlUtils.routeClause(route, "ad") + "\n" - + SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + "\n" + " AND time-scheduled_time > " + allowableLateMinutesStr @@ -643,7 +643,14 @@ public static String getScheduleAdhByStops_v2( return jsonString; } - public static String getReportForStopId (String agencyId, + + /** + * Queries agency for Stop ID and returns result as a JSON string. Limited to returning + * MAX_ROWS (50,000) data points. + * + * @return Stop reports in JSON format. Can be empty JSON array if no data meets criteria. + */ + public static String getReportForStopById (String agencyId, String stop, String beginDate, String allowableEarly, @@ -657,7 +664,7 @@ public static String getReportForStopId (String agencyId, if (allowableLate == null || allowableLate.isEmpty()) allowableLate = "4.0"; String allowableLateMinutesStr = "'" + SqlUtils.convertMinutesToSecs(allowableLate) + " seconds'"; - String sql = " WITH early AS (SELECT time AS date_time,\n" + + String sql = " WITH early AS (SELECT time AS early,\n" + " s.name AS name,\n" + " ad.route_id AS route,\n" + " ad.trip_id AS trip,\n" + @@ -675,13 +682,14 @@ public static String getReportForStopId (String agencyId, " AND ad.scheduled_time IS NOT NULL \n" + // Specifies which stops to provide data for SqlUtils.stopClause(stop, "ad") + + " \n" + // Defines time range - SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + " \n" + " AND scheduled_time - time > " + allowableEarlyMinutesStr + " \n" + - " ORDER BY date_time),\n" + - " ontime AS (SELECT time AS date_time,\n" + + " ORDER BY early),\n" + + " on_time AS (SELECT time AS on_time,\n" + " s.name AS name,\n" + " ad.route_id AS route,\n" + " ad.trip_id AS trip,\n" + @@ -700,15 +708,16 @@ public static String getReportForStopId (String agencyId, " AND ad.scheduled_time IS NOT NULL \n" + // Specifies which stops to provide data for SqlUtils.stopClause(stop, "ad") + + " \n" + // Defines time range - SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + " \n" + " AND scheduled_time - time <= " + allowableEarlyMinutesStr + " \n" + " AND time - scheduled_time <= " + allowableLateMinutesStr + " \n" + - " ORDER BY date_time),\n" + - " late AS (SELECT time AS date_time,\n" + + " ORDER BY on_time),\n" + + " late AS (SELECT time AS late,\n" + " s.name AS name,\n" + " ad.route_id AS route,\n" + " ad.trip_id AS trip,\n" + @@ -727,52 +736,21 @@ public static String getReportForStopId (String agencyId, " AND ad.scheduled_time IS NOT NULL \n" + // Specifies which stops to provide data for SqlUtils.stopClause(stop, "ad") + -// Defines time range " \n" + - SqlUtils.timeRangeClause(agencyId, "ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + +// Defines time range + SqlUtils.timeRangeClause("ad.time", MAX_NUM_DAYS, numDays, beginTime, endTime, beginDate) + " \n" + " AND time - scheduled_time > " + allowableLateMinutesStr + " \n" + - " ORDER BY date_time) \n" + -// " SELECT ('early', jsonb_agg(jsonb_build_object(\n" + -// " 'date_time', early.date_time,\n" + -// " 'name', early.name,\n" + -// " 'route_id', early.route,\n" + -// " 'trip_id', early.trip,\n" + -// " 'block_id', early.block,\n" + -// " 'vehicle_id', early.vehicle,\n" + -// " 'schedule_time', early.schedule,\n" + -// " 'difference', early.difference)) FILTER (WHERE early.date_time IS NOT NULL\n" + -// " ),\n" + -// " 'ontime', jsonb_agg(jsonb_build_object(\n" + -// " 'date_time', ontime.date_time,\n" + -// " 'name', ontime.name,\n" + -// " 'route_id', ontime.route,\n" + -// " 'trip_id', ontime.trip,\n" + -// " 'block_id', ontime.block,\n" + -// " 'vehicle_id', ontime.vehicle,\n" + -// " 'schedule_time', ontime.schedule,\n" + -// " 'difference', ontime.difference)) FILTER (WHERE ontime.date_time IS NOT NULL\n" + -// " ),\n" + -// " 'late', jsonb_agg(jsonb_build_object(\n" + -// " 'date_time', late.date_time,\n" + -// " 'name', late.name,\n" + -// " 'route_id', late.route,\n" + -// " 'trip_id', late.trip,\n" + -// " 'block_id', late.block,\n" + -// " 'vehicle_id', late.vehicle,\n" + -// " 'schedule_time', late.schedule,\n" + -// " 'difference', late.difference)) FILTER (WHERE late.date_time IS NOT NULL)\n" + -// " )\n" + -// " AS " + stop + "\n" + + " ORDER BY late) \n" + "SELECT * FROM \n" + " early\n" + " FULL OUTER JOIN\n" + - " ontime ON early.date_time = ontime.date_time\n" + + " on_time ON early.early = on_time.on_time\n" + " FULL OUTER JOIN\n" + - " late ON ontime.date_time = late.date_time\n"; + " late ON on_time.on_time = late.late\n"; - return GenericJsonQuery.getJsonString(agencyId, sql); + return GenericJsonQuery.getJsonString(agencyId, sql).replaceFirst("\\bdata\\b", stop); } /** diff --git a/core/src/main/java/org/transitclock/core/reports/SqlUtils.java b/core/src/main/java/org/transitclock/core/reports/SqlUtils.java index 1c9360da5..62a53e4e1 100644 --- a/core/src/main/java/org/transitclock/core/reports/SqlUtils.java +++ b/core/src/main/java/org/transitclock/core/reports/SqlUtils.java @@ -199,7 +199,6 @@ public static String timeRangeClause(HttpServletRequest request, String timeColu * INTERVAL '1 day' AND time::time BETWEEN '12:00' AND '24:00'" */ public static String timeRangeClause( - String agencyId, String timeColumnName, int maxNumDays, int numDays, @@ -227,10 +226,12 @@ public static String timeRangeClause( SimpleDateFormat currentFormat = new SimpleDateFormat("MM-dd-yyyy"); SimpleDateFormat requiredFormat = new SimpleDateFormat("yyyy-MM-dd"); + if (beginDate.charAt(4) != '-') { try { beginDate = requiredFormat.format(currentFormat.parse(beginDate)); } catch (ParseException e) { logger.error("Exception happened while processing time-range clause", e); + } } return " AND %s BETWEEN '%s' AND TIMESTAMP '%s' + INTERVAL '%d day' %s " .formatted(timeColumnName, beginDate, beginDate, numDays, timeSql); From 28f089939e49e6f52b84240cea3e93d91ac1c2f4 Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Fri, 19 Apr 2024 14:50:28 +0200 Subject: [PATCH 05/10] add a new method getRoutesByStopId --- .../service/ConfigServiceImpl.java | 43 +++++++++++-------- .../service/contract/ConfigInterface.java | 18 ++++---- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/org/transitclock/service/ConfigServiceImpl.java b/core/src/main/java/org/transitclock/service/ConfigServiceImpl.java index 79abb0cd0..0a6d35353 100644 --- a/core/src/main/java/org/transitclock/service/ConfigServiceImpl.java +++ b/core/src/main/java/org/transitclock/service/ConfigServiceImpl.java @@ -4,23 +4,10 @@ import lombok.extern.slf4j.Slf4j; import org.transitclock.Core; import org.transitclock.core.dataCache.VehicleDataCache; -import org.transitclock.domain.structs.Agency; -import org.transitclock.domain.structs.Block; -import org.transitclock.domain.structs.Calendar; -import org.transitclock.domain.structs.Route; -import org.transitclock.domain.structs.Trip; -import org.transitclock.domain.structs.TripPattern; -import org.transitclock.domain.structs.VehicleConfig; +import org.transitclock.domain.structs.*; import org.transitclock.gtfs.DbConfig; import org.transitclock.service.contract.ConfigInterface; -import org.transitclock.service.dto.IpcBlock; -import org.transitclock.service.dto.IpcCalendar; -import org.transitclock.service.dto.IpcDirectionsForRoute; -import org.transitclock.service.dto.IpcRoute; -import org.transitclock.service.dto.IpcRouteSummary; -import org.transitclock.service.dto.IpcSchedule; -import org.transitclock.service.dto.IpcTrip; -import org.transitclock.service.dto.IpcTripPattern; +import org.transitclock.service.dto.*; import java.util.ArrayList; import java.util.Collection; @@ -49,7 +36,7 @@ public static ConfigInterface instance() { * automatically cause the object to continue to run and serve requests. * * @return the singleton ConfigServer object. Usually does not need to used since the server - * will be fully running. + * will be fully running. */ public static ConfigServiceImpl start() { if (singleton == null) { @@ -99,8 +86,7 @@ public Collection getRoutes() { * @see org.transitclock.ipc.interfaces.ConfigInterface#getRoute(java.lang.String) */ @Override - public IpcRoute getRoute(String routeIdOrShortName, String directionId, String stopId, String tripPatternId) - { + public IpcRoute getRoute(String routeIdOrShortName, String directionId, String stopId, String tripPatternId) { // Determine the route Route dbRoute = getRoute(routeIdOrShortName); if (dbRoute == null) { @@ -363,7 +349,7 @@ public List getBlockIds(String serviceId) { } /* (non-Javadoc) - * @see org.transitclock.ipc.interfaces.ConfigInterface#getBlockIds() + * @see org.transitclock.ipc.interfaces.ConfigInterface#getServiceIdsWithBlockIds() */ @Override public Map> getServiceIdsWithBlockIds() { @@ -371,4 +357,23 @@ public Map> getServiceIdsWithBlockIds() { .getDbConfig() .getBlockIdsForAllServiceIds(); } + + /* (non-Javadoc) + * @see org.transitclock.ipc.interfaces.ConfigInterface#getRoutesByStopId() + */ + @Override + public List getRoutesByStopId(String stopId) { + List routes = new ArrayList<>(); + if (stopId != null) { + DbConfig dbConfig = Core.getInstance().getDbConfig(); + if (dbConfig == null) return routes; + + routes = dbConfig.getRoutes().stream() + .filter(dbRoute -> dbRoute.getStops().stream() + .anyMatch(stop -> stop.getId().equals(stopId))) + .map(dbRoute -> new IpcRoute(dbRoute, null, null, null)) + .collect(Collectors.toList()); + } + return routes; + } } diff --git a/core/src/main/java/org/transitclock/service/contract/ConfigInterface.java b/core/src/main/java/org/transitclock/service/contract/ConfigInterface.java index d8e59e759..abb727a04 100644 --- a/core/src/main/java/org/transitclock/service/contract/ConfigInterface.java +++ b/core/src/main/java/org/transitclock/service/contract/ConfigInterface.java @@ -2,14 +2,7 @@ package org.transitclock.service.contract; import org.transitclock.domain.structs.Agency; -import org.transitclock.service.dto.IpcBlock; -import org.transitclock.service.dto.IpcCalendar; -import org.transitclock.service.dto.IpcDirectionsForRoute; -import org.transitclock.service.dto.IpcRoute; -import org.transitclock.service.dto.IpcRouteSummary; -import org.transitclock.service.dto.IpcSchedule; -import org.transitclock.service.dto.IpcTrip; -import org.transitclock.service.dto.IpcTripPattern; +import org.transitclock.service.dto.*; import java.util.Collection; import java.util.List; @@ -182,4 +175,13 @@ IpcRoute getRoute(String routeIdOrShortName, String directionId, String stopId, * @return Map of service IDs with belong block IDs */ Map> getServiceIdsWithBlockIds(); + + + /** + * Obtains list of routes which contained stops with following stop ID + * + * @param stopId + * @return list of IpcRoute + */ + List getRoutesByStopId(String stopId); } From d3e12572b5566099e6114853757d8c88d0f36d6c Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Fri, 19 Apr 2024 15:47:36 +0200 Subject: [PATCH 06/10] fix /command/routesDetails parameter stopId --- .../org/transitclock/api/resources/TransitimeApi.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java index d87b2dabb..6ce2ebf57 100644 --- a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java +++ b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java @@ -1032,6 +1032,15 @@ public Response getRouteDetails( ipcRoutes = new ArrayList(); ipcRoutes.add(route); + } if (stopId != null) { + + ipcRoutes = inter.getRoutesByStopId(stopId); + + // If the stop doesn't exist then throw exception such that + // Bad Request with an appropriate message is returned. + if (ipcRoutes == null || ipcRoutes.isEmpty()) + throw WebUtils.badRequestException("Routes for stop ID= " + stopId + " does not exist."); + } else { // Multiple routes specified ipcRoutes = inter.getRoutes(routeIdsOrShortNames); From 9faf2682cd988f7861527da77339fed717f0a4a6 Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Fri, 19 Apr 2024 17:09:18 +0200 Subject: [PATCH 07/10] fix conditions if -> else if --- .../main/java/org/transitclock/api/resources/TransitimeApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java index 6ce2ebf57..ce1ab4e26 100644 --- a/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java +++ b/app/src/main/java/org/transitclock/api/resources/TransitimeApi.java @@ -1032,7 +1032,7 @@ public Response getRouteDetails( ipcRoutes = new ArrayList(); ipcRoutes.add(route); - } if (stopId != null) { + } else if (stopId != null) { ipcRoutes = inter.getRoutesByStopId(stopId); From 0068757d2cb275c12edd90c7dce31674b521cddf Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Thu, 16 May 2024 22:41:17 +0200 Subject: [PATCH 08/10] changed the foreign keys for travel_times_for_trip_to_travel_times_for_path & fixed the name used for export-table --- .../transitclock/domain/structs/ExportTable.java | 2 +- ...516223700__fix-wrong-column-for-export-table.sql | 1 + .../V20240516223701__fix-wrong-relation.sql | 13 +++++++++++++ extensions/traccar/pom.xml | 2 ++ pom.xml | 1 + 5 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/db/migration/V20240516223700__fix-wrong-column-for-export-table.sql create mode 100644 core/src/main/resources/db/migration/V20240516223701__fix-wrong-relation.sql diff --git a/core/src/main/java/org/transitclock/domain/structs/ExportTable.java b/core/src/main/java/org/transitclock/domain/structs/ExportTable.java index da52bd282..23f3b32b6 100644 --- a/core/src/main/java/org/transitclock/domain/structs/ExportTable.java +++ b/core/src/main/java/org/transitclock/domain/structs/ExportTable.java @@ -44,7 +44,7 @@ public class ExportTable implements Serializable { @Column(name = "export_status") private int exportStatus; - @Column(name = "first_name") + @Column(name = "file_name") private String fileName; @Column(name = "file") diff --git a/core/src/main/resources/db/migration/V20240516223700__fix-wrong-column-for-export-table.sql b/core/src/main/resources/db/migration/V20240516223700__fix-wrong-column-for-export-table.sql new file mode 100644 index 000000000..4d7d9bc50 --- /dev/null +++ b/core/src/main/resources/db/migration/V20240516223700__fix-wrong-column-for-export-table.sql @@ -0,0 +1 @@ +ALTER TABLE export_table RENAME COLUMN first_name TO file_name; \ No newline at end of file diff --git a/core/src/main/resources/db/migration/V20240516223701__fix-wrong-relation.sql b/core/src/main/resources/db/migration/V20240516223701__fix-wrong-relation.sql new file mode 100644 index 000000000..3e9eff0d6 --- /dev/null +++ b/core/src/main/resources/db/migration/V20240516223701__fix-wrong-relation.sql @@ -0,0 +1,13 @@ +-- removing faulty constraints definitions +ALTER TABLE travel_times_for_trip_to_travel_times_for_path + DROP CONSTRAINT fk_tratimfortritotratimforpat_on_travel_times_for_stop_path; + +ALTER TABLE travel_times_for_trip_to_travel_times_for_path + DROP CONSTRAINT fk_tratimfortritotratimforpat_on_travel_times_for_trip; + +-- recreating new constraints +ALTER TABLE travel_times_for_trip_to_travel_times_for_path + ADD CONSTRAINT fk_tratimfortritotratimforpat_on_travel_times_for_stop_path FOREIGN KEY (for_path_id) REFERENCES travel_times_for_stop_paths (id); + +ALTER TABLE travel_times_for_trip_to_travel_times_for_path + ADD CONSTRAINT fk_tratimfortritotratimforpat_on_travel_times_for_trip FOREIGN KEY (for_trip_id) REFERENCES travel_times_for_trips (id); \ No newline at end of file diff --git a/extensions/traccar/pom.xml b/extensions/traccar/pom.xml index a4f26532d..a8b2cf10d 100644 --- a/extensions/traccar/pom.xml +++ b/extensions/traccar/pom.xml @@ -20,6 +20,8 @@ 1.0.0 + jar + diff --git a/pom.xml b/pom.xml index 3daaad8f0..9f88e4227 100644 --- a/pom.xml +++ b/pom.xml @@ -209,6 +209,7 @@ 17 false + false From f857636f084fb4da3acf08b2a82e6afa667514e0 Mon Sep 17 00:00:00 2001 From: Timur Shykhsefiyeu Date: Fri, 17 May 2024 14:02:04 +0200 Subject: [PATCH 09/10] fixed for refereced columns between TravelTimesForTrip and StopPath --- .../domain/structs/TravelTimesForTrip.java | 4 ++-- .../domain/structs/TripPattern.java | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/transitclock/domain/structs/TravelTimesForTrip.java b/core/src/main/java/org/transitclock/domain/structs/TravelTimesForTrip.java index 911a17061..e2bb99045 100644 --- a/core/src/main/java/org/transitclock/domain/structs/TravelTimesForTrip.java +++ b/core/src/main/java/org/transitclock/domain/structs/TravelTimesForTrip.java @@ -80,10 +80,10 @@ public class TravelTimesForTrip implements Serializable { @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "travel_times_for_trip_to_travel_times_for_path", joinColumns = { - @JoinColumn(name = "for_path_id", referencedColumnName = "id") + @JoinColumn(name = "for_trip_id", referencedColumnName = "id") }, inverseJoinColumns = { - @JoinColumn(name = "for_trip_id", referencedColumnName = "id") + @JoinColumn(name = "for_path_id", referencedColumnName = "id") }) @Cascade({CascadeType.SAVE_UPDATE}) @OrderColumn(name = "list_index") diff --git a/core/src/main/java/org/transitclock/domain/structs/TripPattern.java b/core/src/main/java/org/transitclock/domain/structs/TripPattern.java index 55e2af9a8..d8b9c8494 100644 --- a/core/src/main/java/org/transitclock/domain/structs/TripPattern.java +++ b/core/src/main/java/org/transitclock/domain/structs/TripPattern.java @@ -3,7 +3,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.hibernate.CallbackException; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.annotations.Cascade; @@ -15,10 +14,8 @@ import jakarta.persistence.*; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; /** * A trip pattern, as obtained from stop_times.txt GTFS file. A trip pattern defines what stops are @@ -570,9 +567,9 @@ public boolean isStopAtOrAfterStop(String stopId1, String stopId2) { * @return */ public List getStopIds() { - List list = new ArrayList(stopPaths.size()); - for (StopPath stopPath : stopPaths) list.add(stopPath.getStopId()); - return list; + return stopPaths.stream() + .map(StopPath::getStopId) + .collect(Collectors.toList()); } /** @@ -581,7 +578,9 @@ public List getStopIds() { * @return ID of last stop */ public String getLastStopIdForTrip() { - return stopPaths.get(stopPaths.size() - 1).getStopId(); + return Optional.ofNullable(getStopPath(stopPaths.size() - 1)) + .map(StopPath::getStopId) + .orElse(null); } /** @@ -602,7 +601,8 @@ public double getLength() { * @return The specified StopPath or null if index out of range */ public StopPath getStopPath(int index) { - if (index < 0 || index >= stopPaths.size()) return null; + if (index < 0 || index >= stopPaths.size()) + return null; return stopPaths.get(index); } From ca6d96904f3e373992fa0e09be75fc84555ec2cb Mon Sep 17 00:00:00 2001 From: Hubert Date: Sat, 18 May 2024 12:36:33 +0200 Subject: [PATCH 10/10] Fix punctuality reports --- core/src/main/java/org/transitclock/core/reports/Reports.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/transitclock/core/reports/Reports.java b/core/src/main/java/org/transitclock/core/reports/Reports.java index b49e90213..b2ce0d6e6 100644 --- a/core/src/main/java/org/transitclock/core/reports/Reports.java +++ b/core/src/main/java/org/transitclock/core/reports/Reports.java @@ -140,7 +140,7 @@ public static String getTripWithTravelTimes(String agencyId, String tripId, Stri + " LATERAL(SELECT * from arrivals_departures ad where ad.trip_id =" + " arrivals_departures.trip_id AND ad.direction_id =" + " arrivals_departures.direction_id AND ad.stop_id =" - + " arrivals_departures.stop_id AND ad.type = 'DEPARTURE' AND" + + " arrivals_departures.stop_id AND LOWER(ad.type) = LOWER('DEPARTURE') AND" + " DATE(ad.avl_time) = DATE(arrivals_departures.avl_time) AND ad.time >=" + " arrivals_departures.time ORDER BY ad.time ASC LIMIT 1) ADDeparture ON" + " True LEFT JOIN trip_scheduled_times_list ON" @@ -196,7 +196,7 @@ public static String getTripsWithTravelTimes(String agencyId, String date) { + " LATERAL(SELECT * from arrivals_departures ad where ad.trip_id =" + " arrivals_departures.trip_id AND ad.direction_id =" + " arrivals_departures.direction_id AND ad.stop_id =" - + " arrivals_departures.stop_id AND ad.type = 'DEPARTURE' AND" + + " arrivals_departures.stop_id AND LOWER(ad.type) = LOWER('DEPARTURE') AND" + " DATE(ad.avl_time) = DATE(arrivals_departures.avl_time) AND ad.time >=" + " arrivals_departures.time ORDER BY ad.time ASC LIMIT 1) ADDeparture ON" + " True LEFT JOIN trip_scheduled_times_list ON"