diff --git a/packages/core-utils/package.json b/packages/core-utils/package.json index bbdf7251e..3e679b901 100644 --- a/packages/core-utils/package.json +++ b/packages/core-utils/package.json @@ -1,6 +1,6 @@ { "name": "@opentripplanner/core-utils", - "version": "16.0.4", + "version": "16.0.5", "description": "Core functionality that is shared among numerous UI components", "engines": { "node": ">=13" diff --git a/packages/core-utils/src/__tests__/__snapshots__/query.js.snap b/packages/core-utils/src/__tests__/__snapshots__/query.js.snap index 7a77e7ccc..2f3ae17c3 100644 --- a/packages/core-utils/src/__tests__/__snapshots__/query.js.snap +++ b/packages/core-utils/src/__tests__/__snapshots__/query.js.snap @@ -263,6 +263,10 @@ exports[`query parseLocationString should return location with coordinates as na exports[`query parseLocationString should return null for null input 1`] = `null`; +exports[`query parseLocationString should return null with name as name with no coords (but with commas) for input 1`] = `null`; + +exports[`query parseLocationString should return null with name as name with no coords for input 1`] = `null`; + exports[`query planParamsToQuery should parse a depart at query 1`] = ` { "bannedRoutes": "897ABC", diff --git a/packages/core-utils/src/__tests__/query.js b/packages/core-utils/src/__tests__/query.js index 8a013422d..559f1286c 100644 --- a/packages/core-utils/src/__tests__/query.js +++ b/packages/core-utils/src/__tests__/query.js @@ -218,6 +218,13 @@ describe("query", () => { parseLocationString("33.983929829,-87.3892387982") ).toMatchSnapshot(); }); + + it("should return null with name as name with no coords for input", () => { + expect(parseLocationString("1 cool street")).toMatchSnapshot(); + }); + it("should return null with name as name with no coords (but with commas) for input", () => { + expect(parseLocationString("148, se street")).toMatchSnapshot(); + }); }); describe("planParamsToQuery", () => { diff --git a/packages/core-utils/src/query.js b/packages/core-utils/src/query.js index 0cc1f8446..75aba6338 100644 --- a/packages/core-utils/src/query.js +++ b/packages/core-utils/src/query.js @@ -247,7 +247,7 @@ async function getFirstGeocodeResult( if (firstResult) { return geocoder.getLocationFromGeocodedFeature(firstResult); } - return null; + return { name: `(Geocoder failure) ${text}` }; }); } @@ -265,7 +265,7 @@ export function parseLocationString(value) { ? stringToCoords(parts[1]) : stringToCoords(parts[0]); const name = parts[1] ? parts[0] : coordsToString(coordinates); - return coordinates.length === 2 + return coordinates.length === 2 && coordinates.every(c => !!c) ? { name: name || null, lat: coordinates[0] || null, diff --git a/packages/timetable/package.json b/packages/timetable/package.json index d16017e59..904a6f799 100644 --- a/packages/timetable/package.json +++ b/packages/timetable/package.json @@ -1,6 +1,6 @@ { "name": "@opentripplanner/timetable", - "version": "1.0.0", + "version": "1.1.0", "description": "A component to display a timetable for a given route pattern", "main": "lib/index.js", "module": "esm/index.js", @@ -11,13 +11,13 @@ "private": false, "peerDependencies": { "react": "catalog:", + "react-intl": "catalog:", "styled-components": "catalog:" }, "scripts": { "tsc": "tsc" }, "dependencies": { - "react-intl": "catalog:", "toposort": "^2.0.2" }, "devDependencies": { diff --git a/packages/timetable/src/index.tsx b/packages/timetable/src/index.tsx index a88b2715b..187c9909b 100644 --- a/packages/timetable/src/index.tsx +++ b/packages/timetable/src/index.tsx @@ -15,9 +15,10 @@ const TBody = styled.tbody` white-space: nowrap; `; -const TH = styled.th` +const TH = styled.th<{ closed?: boolean }>` min-width: ${COLUMN_WIDTH}; padding: 1rem; + text-decoration: ${props => (props.closed ? "line-through" : "")}; `; const TR = styled.tr` @@ -25,8 +26,9 @@ const TR = styled.tr` vertical-align: middle; `; -const TD = styled.td` +const TD = styled.td<{ closed?: boolean }>` padding: 1rem; + text-decoration: ${props => (props.closed ? "line-through" : "")}; `; interface PatternStop { @@ -176,6 +178,8 @@ interface TimeTableProps { route: Route; /** Whether to show only timepoint stops in the timetable */ timepointsOnly: boolean; + /** A set of gtfsId values for stops that are closed and should be shown with strikethrough */ + closedStops?: Set; /** If the topological sort of the stop IDs fails for any reason, a `false` value here * will cause the timetable to use a fallback "naive" stop sorting */ @@ -194,6 +198,7 @@ interface TimeTableProps { const TimeTable = (props: TimeTableProps): JSX.Element => { const { + closedStops, directionId, errorOnStopSorting, includeDwellStops, @@ -314,27 +319,35 @@ const TimeTable = (props: TimeTableProps): JSX.Element => { }, [allTrips, comparator]); return ( - - +
+ {(showBlockId ? [{ id: "blockIdHeader", name: "Block ID" }] : []) .concat(filteredPatternStops) .map((s, index) => { return ( - ); })} - + {timetableTrips.map((t, index) => { - const rowValues: string[] = showBlockId ? [t.blockId] : []; + const rowValues: { closed: boolean; value: string }[] = showBlockId + ? [{ closed: false, value: t.blockId }] + : []; filteredPatternStops.forEach(patternStop => { const stopDetail = t.stops.get(patternStop.id); - rowValues.push( - stopDetail + rowValues.push({ + closed: closedStops?.has(patternStop.id) || false, + value: stopDetail ? intl ? intl.formatTime(stopDetail.time) : stopDetail.time.toLocaleTimeString("en-us", { @@ -344,13 +357,15 @@ const TimeTable = (props: TimeTableProps): JSX.Element => { minute: "2-digit" }) : "-" - ); + }); }); return ( - + {rowValues.map((r, rowIndex) => ( - + ))} ); diff --git a/packages/timetable/src/stories/Timetable.story.tsx b/packages/timetable/src/stories/Timetable.story.tsx index d3c4a76a7..87e990dfa 100644 --- a/packages/timetable/src/stories/Timetable.story.tsx +++ b/packages/timetable/src/stories/Timetable.story.tsx @@ -1,5 +1,6 @@ // eslint-disable-next-line prettier/prettier import type { Meta, StoryObj } from "@storybook/react-vite" +import React from "react"; import TimeTable from ".." @@ -7,13 +8,33 @@ import twinCitiesRouteMock from "../../__mocks__/route-mock.json" const meta = { component: TimeTable, - title: "Timetable" + title: "Timetable", + argTypes: { + closedStops: { + options: ["Grand St NE & 29th Ave NE (Direction: 1)", "46th St & I-35W (Direction: 0)"], + control: { type: "check" }, + mapping: { + "Grand St NE & 29th Ave NE (Direction: 1)": "2:14634", + "46th St & I-35W (Direction: 0)": "2:53545" + } + }, + directionId: { + control: { type: "radio" }, + options: [0, 1] + } + } } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { + // eslint-disable-next-line react/display-name + render: (args) => { + const { closedStops } = args; + // eslint-disable-next-line react/jsx-props-no-spreading + return + }, args: { directionId: 0, route: twinCitiesRouteMock.data.route, diff --git a/packages/timetable/src/stories/__snapshots__/Timetable.story.tsx.snap b/packages/timetable/src/stories/__snapshots__/Timetable.story.tsx.snap index f56e14bd7..e0e87a1ac 100644 --- a/packages/timetable/src/stories/__snapshots__/Timetable.story.tsx.snap +++ b/packages/timetable/src/stories/__snapshots__/Timetable.story.tsx.snap @@ -1,199 +1,199 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Timetable Default smoke-test 1`] = ` -
+ {s.name}
{r} + {r.value} +
- + - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - diff --git a/packages/transitive-overlay/package.json b/packages/transitive-overlay/package.json index 8083e4fea..f0345d62b 100644 --- a/packages/transitive-overlay/package.json +++ b/packages/transitive-overlay/package.json @@ -23,18 +23,19 @@ "@opentripplanner/building-blocks": "workspace:^", "@opentripplanner/core-utils": "workspace:^", "@opentripplanner/itinerary-body": "workspace:^", - "@turf/bbox": "^6.5.0", - "@turf/bearing": "^6.5.0", - "@turf/destination": "^6.5.0", - "@turf/distance": "^6.5.0", - "@turf/line-arc": "^6.5.0", + "@turf/bbox": "^7.3.5", + "@turf/bearing": "^7.3.5", + "@turf/destination": "^7.3.5", + "@turf/distance": "^7.3.5", + "@turf/line-arc": "^7.3.5", "@turf/line-distance": "^4.7.3", - "@turf/midpoint": "^6.5.0", + "@turf/midpoint": "^7.3.5", "lodash.isequal": "^4.5.0" }, "devDependencies": { "@opentripplanner/endpoints-overlay": "workspace:^", "@opentripplanner/types": "workspace:^", + "@types/mapbox__polyline": "^1.0.5", "maplibre-gl": "catalog:" }, "peerDependencies": { diff --git a/packages/transitive-overlay/src/index.tsx b/packages/transitive-overlay/src/index.tsx index b8f7cb255..bda2f5c61 100644 --- a/packages/transitive-overlay/src/index.tsx +++ b/packages/transitive-overlay/src/index.tsx @@ -14,6 +14,12 @@ import colors from "@opentripplanner/building-blocks"; import bbox from "@turf/bbox"; +import { + FeatureCollection, + GeoJsonProperties, + Geometry, + LineString +} from "geojson"; import { getRouteLayerLayout, patternToRouteFeature } from "./route-layers"; import { drawArc, getFromToAnchors, itineraryToTransitive } from "./util"; import routeArrow from "./images/route_arrow.png"; @@ -35,7 +41,7 @@ import capsule16 from "./images/16.png"; import capsule17 from "./images/17.png"; import rectangle from "./images/square.png"; -const CAPSULES = { +const CAPSULES: Record = { 3: capsule3, 4: capsule4, 5: capsule5, @@ -54,7 +60,7 @@ const CAPSULES = { }; // These are based on the sprites in the image folder -const WIDTH_IMAGE_SIZES = { +const WIDTH_IMAGE_SIZES: Record = { 6: 1283, 7: 1450, 8: 1617, @@ -79,7 +85,7 @@ const { blue, grey, red } = colors; const MICROMOBILITY_ORANGE = "#f5a729"; // TODO: BETTER COLORS -const modeColorMap = { +const modeColorMap: Record = { CAR: grey[500], BICYCLE: red[600], SCOOTER: MICROMOBILITY_ORANGE, @@ -199,7 +205,7 @@ const TransitiveCanvasOverlay = ({ function generateCapsulePadding( width: number - ): [number, number, number, number] { + ): [number, number, number, number] | undefined { // Low widths have no padding if (!WIDTH_IMAGE_SIZES[width]) { return undefined; @@ -266,12 +272,12 @@ const TransitiveCanvasOverlay = ({ }); useEffect(() => { - loadImages(map, mapImages); + map && loadImages(map, mapImages); }, []); const geojson: GeoJSON.FeatureCollection< GeoJSON.Geometry, - Record + GeoJsonProperties > = { type: "FeatureCollection", features: transitiveData @@ -293,22 +299,27 @@ const TransitiveCanvasOverlay = ({ ...(transitiveData.journeys || []).flatMap( (journey: TransitiveJourney) => journey.segments - .filter(segment => segment.streetEdges?.length > 0) + .filter( + segment => + segment.streetEdges?.length && + segment.streetEdges?.length > 0 + ) .map(segment => ({ ...segment, - geometries: segment.streetEdges.map(edge => { - return transitiveData.streetEdges.find( - entry => entry.edge_id === edge + geometries: segment.streetEdges?.flatMap(edge => { + const entry = transitiveData.streetEdges.find( + e => e.edge_id === edge ); + return entry ? [entry] : []; }) })) - .flatMap(segment => { - return segment.geometries.map(geometry => { + .flatMap(segment => + (segment.geometries ?? []).map(geometry => { const straight = polyline.toGeoJSON( geometry.geometry.points ); return { - type: "Feature", + type: "Feature" as const, properties: { type: "street-edge", color: @@ -318,9 +329,9 @@ const TransitiveCanvasOverlay = ({ mode: segment.type }, geometry: segment.arc ? drawArc(straight) : straight - } as GeoJSON.Feature; - }); - }) + }; + }) + ) ), // Extract the first and last stops of each transit segment for display. ...(transitiveData.journeys || []) @@ -328,7 +339,7 @@ const TransitiveCanvasOverlay = ({ .filter(segment => segment.type === "TRANSIT") .map(segment => transitiveData.patterns.find( - p => p.pattern_id === segment.patterns[0]?.pattern_id + p => p.pattern_id === segment.patterns?.[0]?.pattern_id ) ) .filter(pattern => !!pattern) @@ -346,10 +357,10 @@ const TransitiveCanvasOverlay = ({ stop => ({ type: "Feature", - properties: { name: stop.stop_name, type: "stop" }, + properties: { name: stop?.stop_name, type: "stop" }, geometry: { type: "Point", - coordinates: [stop.stop_lon, stop.stop_lat] + coordinates: [stop?.stop_lon, stop?.stop_lat] } } as GeoJSON.Feature) ), @@ -362,7 +373,9 @@ const TransitiveCanvasOverlay = ({ : [] }; - const zoomToGeoJSON = geoJson => { + const zoomToGeoJSON = ( + geoJson: FeatureCollection | LineString + ) => { if (!boundsFitting) return; const b = bbox(geoJson); const bounds: [number, number, number, number] = [b[0], b[1], b[2], b[3]]; diff --git a/packages/transitive-overlay/src/route-layers.ts b/packages/transitive-overlay/src/route-layers.ts index c6cd7698b..6966157f4 100644 --- a/packages/transitive-overlay/src/route-layers.ts +++ b/packages/transitive-overlay/src/route-layers.ts @@ -18,21 +18,23 @@ export function patternToRouteFeature( // Concatenate geometries (arrays of coordinates) to help maplibre spread out labels (not perfect). const concatenatedLines = pattern.stops .map(stop => stop.geometry) - .filter(geometry => !!geometry) - .reduce((result, geom, index) => { + .filter((geo): geo is string => !!geo) + .reduce<[number, number][]>((result, geom, index) => { const coords = polyline.decode(geom); // Remove the first element (except for the first array) because it is a duplicate // of the last element of the previous array. if (index !== 0) coords.shift(); return result.concat(coords); }, []); - const routeName = route.route_short_name || route.route_long_name || ""; + const routeName = route?.route_short_name || route?.route_long_name || ""; const properties = { - color: route.route_color ? `#${route.route_color}` : blue[900], + color: route?.route_color ? `#${route.route_color}` : blue[900], name: routeName, - routeType: route.route_type, - textColor: route.route_text_color ? `#${route.route_text_color}` : grey[50], + routeType: route?.route_type, + textColor: route?.route_text_color + ? `#${route.route_text_color}` + : grey[50], type: "route" }; diff --git a/packages/transitive-overlay/src/util.ts b/packages/transitive-overlay/src/util.ts index 7bee3e738..956cf7082 100644 --- a/packages/transitive-overlay/src/util.ts +++ b/packages/transitive-overlay/src/util.ts @@ -1,9 +1,9 @@ +import bearing from "@turf/bearing"; +import destination from "@turf/destination"; +import distance from "@turf/distance"; import lineArc from "@turf/line-arc"; import lineDistance from "@turf/line-distance"; import midpoint from "@turf/midpoint"; -import destination from "@turf/destination"; -import bearing from "@turf/bearing"; -import distance from "@turf/distance"; import coreUtils from "@opentripplanner/core-utils"; import { getPlaceName } from "@opentripplanner/itinerary-body"; @@ -13,11 +13,15 @@ import { Leg, Place, TransitiveData, + TransitiveJourney, + TransitivePattern, TransitivePlace, + TransitiveRoute, TransitiveStop } from "@opentripplanner/types"; -import { IntlShape } from "react-intl"; import { PositionAnchor } from "maplibre-gl"; +import { IntlShape } from "react-intl"; +import { LineString } from "geojson"; const { getLegBounds, @@ -48,7 +52,7 @@ function stopToTransitive( return { stop_id: id, // Don't render this stop name if another one or similar exists. - stop_name: stopNameExists ? null : normalizedStopName, + stop_name: stopNameExists ? undefined : normalizedStopName, stop_lat: stop.lat, stop_lon: stop.lon }; @@ -58,7 +62,7 @@ function stopToTransitive( * Helper function to obtain the stop ID for an OTP2 or OTP1 place. */ function getStopId(place: Place) { - return typeof place.stop === "object" ? place.stop.id : place.stopId; + return (place.stop?.id ? place.stop.id : place.stopId) ?? ""; } /** @@ -70,7 +74,7 @@ function addStop( knownStopNames: Record ) { const stopId = getStopId(place); - if (!stops[stopId]) { + if (stopId && !stops[stopId]) { stops[stopId] = stopToTransitive(place, stopId, knownStopNames); } } @@ -99,13 +103,18 @@ export function getFromToAnchors( // Compute general direction of travel to better position from/to markers. let direction; - if (fromPlace && toPlace) { + if ( + fromPlace?.place_lat && + fromPlace.place_lon && + toPlace?.place_lat && + toPlace.place_lon + ) { const directionLat = fromPlace.place_lat < toPlace.place_lat ? "N" : "S"; const directionLon = fromPlace.place_lon < toPlace.place_lon ? "E" : "W"; direction = `${directionLat}${directionLon}`; } - let fromAnchor; - let toAnchor; + let fromAnchor: PositionAnchor = "top-left"; + let toAnchor: PositionAnchor = "top-left"; switch (direction) { case "NE": fromAnchor = "top-right"; @@ -231,7 +240,7 @@ export function itineraryToTransitive( } = options; const isFlexCheck = isFlexOverride || isFlex; - const tdata = { + const tdata: TransitiveData = { journeys: [], streetEdges: [], places: [], @@ -239,11 +248,11 @@ export function itineraryToTransitive( routes: [], stops: [] }; - const routes = {}; + const routes: Record = {}; const knownStopNames = {}; let patternId = 0; - const journey = { + const journey: TransitiveJourney = { journey_id: "itin", // This string is not shown in the UI journey_name: "Itinerary-derived Journey", @@ -280,13 +289,13 @@ export function itineraryToTransitive( "from", streetEdgeId, leg, - idx > 0 ? itin.legs[idx - 1] : null + idx > 0 ? itin.legs[idx - 1] : undefined ); const toPlaceId = getPlaceId( "to", streetEdgeId, leg, - idx < itin.legs.length - 1 ? itin.legs[idx + 1] : null, + idx < itin.legs.length - 1 ? itin.legs[idx + 1] : undefined, toVertexType ); let addFromPlace = false; @@ -362,7 +371,7 @@ export function itineraryToTransitive( const hasIntermediateStopGeometry = hasInterStopGeometry && leg.intermediateStops && - leg.interStopGeometry.length === leg.intermediateStops.length + 1; + leg.interStopGeometry?.length === leg.intermediateStops.length + 1; // Coordinates of the leg geometry, used to draw the stop marker on the line, // otherwise the logical stop is often times off the line. @@ -373,16 +382,18 @@ export function itineraryToTransitive( // create leg-specific pattern const ptnId = `ptn_${patternId}`; - const pattern = { + const pattern: TransitivePattern = { pattern_id: ptnId, - // This string is not shown in the UI pattern_name: `Pattern ${patternId}`, - route_id: routeId, + route_id: routeId ?? "", stops: [] }; // Add the "from" end of transit legs to the list of stops. - const fromStop = makeStop(leg.from, hasLegGeometry && legCoords[0]); + const fromStop = makeStop( + leg.from, + hasLegGeometry ? legCoords[0] : undefined + ); addStop(fromStop, newStops, knownStopNames); pattern.stops.push({ stop_id: getStopId(leg.from) }); @@ -397,18 +408,24 @@ export function itineraryToTransitive( leg.intermediateStops.forEach((stop, i) => { // FIXME: line up the coordinates of the stops so they appear on the line. addStop(stop, newStops, knownStopNames); - pattern.stops.push({ - // Intermediate stops are still exclusively using stopId. - stop_id: stop.stopId, - geometry: - hasIntermediateStopGeometry && leg.interStopGeometry[i].points - }); + // TODO: Should we be looking at using stop.stop.id? or the getStopId function? + if (stop.stopId) { + pattern.stops.push({ + // Intermediate stops are still exclusively using stopId. + stop_id: stop.stopId, + geometry: hasIntermediateStopGeometry + ? leg.interStopGeometry?.[i]?.points + : undefined + }); + } }); } // Add the "to" end of transit legs to the list of stops. // (Do not label stop names if they repeat.) - const lastCoord = hasLegGeometry && legCoords[legCoords.length - 1]; + const lastCoord = hasLegGeometry + ? legCoords[legCoords.length - 1] + : undefined; const modifiedToStop = makeStop( renameLegFlexStops(leg, isFlexCheck), lastCoord @@ -416,12 +433,10 @@ export function itineraryToTransitive( addStop(modifiedToStop, newStops, knownStopNames); pattern.stops.push({ stop_id: getStopId(modifiedToStop), - geometry: - // Some legs don't have intermediateStopGeometry, but do have valid legGeometry - (hasInterStopGeometry || hasLegGeometry) && - (hasIntermediateStopGeometry - ? leg.interStopGeometry[leg.interStopGeometry.length - 1].points - : leg.legGeometry.points) + // Some legs don't have intermediateStopGeometry, but do have valid legGeometry + geometry: hasIntermediateStopGeometry + ? leg.interStopGeometry?.[leg.interStopGeometry.length - 1]?.points + : leg.legGeometry?.points }); // add route to the route dictionary @@ -432,24 +447,24 @@ export function itineraryToTransitive( : getLegRouteShortName(leg)) || ""; const basicRouteAttributes = { - agency_id: leg.agencyId, - route_id: routeId, + agency_id: leg.agencyId ?? "", + route_id: routeId ?? "", route_short_name: routeLabel }; - if (typeof leg.route === "object" && leg.route !== null) { + if (typeof leg.route === "object" && leg.route !== null && routeId) { routes[routeId] = { ...basicRouteAttributes, route_long_name: leg.route.longName || "", - route_type: leg.route.type, + route_type: leg.route.type ?? 0, route_color: leg.route.color, route_text_color: leg.route.textColor }; - } else { + } else if (routeId) { routes[routeId] = { ...basicRouteAttributes, route_long_name: leg.routeLongName || "", - route_type: leg.routeType, + route_type: leg.routeType ?? 0, route_color: leg.routeColor, route_text_color: leg.routeTextColor }; @@ -460,10 +475,7 @@ export function itineraryToTransitive( // add the pattern reference to the journey object journey.segments.push({ - arc: - typeof disableFlexArc === "undefined" - ? isFlexCheck(leg) - : !disableFlexArc, + arc: disableFlexArc === undefined ? isFlexCheck(leg) : !disableFlexArc, type: "TRANSIT", patterns: [ { @@ -492,8 +504,7 @@ export function itineraryToTransitive( return tdata; } -// typescript TODO: TYPE -const drawArc = (straight: any) => { +const drawArc = (straight: LineString) => { // Create clone of plain route that only includes first and last point straight.coordinates = [ straight.coordinates[0], diff --git a/packages/transitive-overlay/tsconfig.json b/packages/transitive-overlay/tsconfig.json index 1501a23ae..88f577299 100644 --- a/packages/transitive-overlay/tsconfig.json +++ b/packages/transitive-overlay/tsconfig.json @@ -5,7 +5,8 @@ "lib": ["es2019", "dom"], "outDir": "./lib", "rootDir": "./src", - "skipLibCheck": true + "skipLibCheck": true, + "strict": true }, "include": ["src/**/*"], "references": [ diff --git a/packages/types/package.json b/packages/types/package.json index b45560678..8d609991b 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -31,5 +31,8 @@ "private": false, "scripts": { "tsc": "tsc" + }, + "dependencies": { + "@types/geojson": "^7946.0.16" } } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4447d3fdf..b9689b67b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -580,21 +580,22 @@ export type TransitivePlace = { placeId?: string; type: string; }; +export type TransitiveSegment = { + arc?: boolean; + from?: TransitivePlace; + patterns?: { + pattern_id: string; + from_stop_index: number; + to_stop_index: number; + }[]; + streetEdges?: number[]; + to?: TransitivePlace; + type: string; +}; export type TransitiveJourney = { journey_id: string; journey_name: string; - segments: { - arc?: boolean; - from: TransitivePlace; - patterns?: { - pattern_id: string; - from_stop_index: number; - to_stop_index: number; - }[]; - streetEdges: number[]; - to: TransitivePlace; - type: string; - }[]; + segments: TransitiveSegment[]; }; export type TransitivePattern = { pattern_id: string; @@ -616,7 +617,7 @@ export type TransitiveRoute = { }; export type TransitiveStop = { stop_id: string; - stop_name: string; + stop_name?: string; stop_lat: number; stop_lon: number; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e414afbfa..c0a4379a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1175,26 +1175,26 @@ importers: specifier: workspace:^ version: link:../itinerary-body '@turf/bbox': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 '@turf/bearing': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 '@turf/destination': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 '@turf/distance': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 '@turf/line-arc': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 '@turf/line-distance': specifier: ^4.7.3 version: 4.7.3 '@turf/midpoint': - specifier: ^6.5.0 - version: 6.5.0 + specifier: ^7.3.5 + version: 7.3.5 lodash.isequal: specifier: ^4.5.0 version: 4.5.0 @@ -1211,6 +1211,9 @@ importers: '@opentripplanner/types': specifier: workspace:^ version: link:../types + '@types/mapbox__polyline': + specifier: ^1.0.5 + version: 1.0.5 maplibre-gl: specifier: 'catalog:' version: 5.6.1 @@ -1329,7 +1332,11 @@ importers: specifier: 'catalog:' version: 5.6.1 - packages/types: {} + packages/types: + dependencies: + '@types/geojson': + specifier: ^7946.0.16 + version: 7946.0.16 packages/vehicle-rental-overlay: dependencies: @@ -3885,53 +3892,53 @@ packages: '@turf/along@7.3.1': resolution: {integrity: sha512-z84b9PKsUB69BhkeHA6oPqRO7VaJHwTid1SpuIbwWzDqHTpq8buJBKlrKgHIIthuVr5P/AZiEXmf3R4ifRhDmw==} - '@turf/bbox@6.5.0': - resolution: {integrity: sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==} - - '@turf/bearing@6.5.0': - resolution: {integrity: sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==} + '@turf/bbox@7.3.5': + resolution: {integrity: sha512-oG1ya/HtBjAIg4TimbWx+nOYPbY0bCvt82Bq8tm6sBw3qqtbOyRSfDz79Sq90TnH7DXJprJ1qnVGKNtZ6jemfw==} '@turf/bearing@7.3.1': resolution: {integrity: sha512-ex78l/LiY6uO6jO8AJepyWE6/tiWEbXjKLOgqUfJSkW23UcMVlhbAKzXDjbsdz9T66sXFC/6QNAh8oaZzmoo6w==} - '@turf/circle@6.5.0': - resolution: {integrity: sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==} + '@turf/bearing@7.3.5': + resolution: {integrity: sha512-/qabIt/IuPsGlE6RukJ0zOirc6afNxoK7fK1WBNVnHuJjeOpSkW+7q1QW5XQGXBMv3odcD0ZgRR+MBiAHduYSA==} - '@turf/destination@6.5.0': - resolution: {integrity: sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==} + '@turf/circle@7.3.5': + resolution: {integrity: sha512-Tse7GJrx0rxaQ5BdY6pHZ9HFPrZwRMry2yaqq94UsUyonhy1m7U2icB3Zp4M8o4XjHIGTCq9i2BYcGJram51Sw==} '@turf/destination@7.3.1': resolution: {integrity: sha512-yyiJtbQJ4AB9Ny/FKDDNuWI9Sg4Jtd2PMpQPqOV3AFq8NNkg0xJSNmDHDxupb3oPqPWYPxyfVI3tBoF+Xhhoig==} + '@turf/destination@7.3.5': + resolution: {integrity: sha512-x6ylChhOlIbucRSw7wF6z2gSEqqQl+dE0nSH5AL/ojZkqqGYahiw+2P4A8ZMuDM6rzH+FIqRYDes0o4nSArZCw==} + '@turf/distance@4.7.3': resolution: {integrity: sha512-nluvFy7qjZFq4B6E+KAlKdwdg9fbgJMIe/5O7jveg56gcXZ5Bvgx3lFUxAY016+l907OeX9lIZCNr/Y96FMyEA==} - '@turf/distance@6.5.0': - resolution: {integrity: sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==} - '@turf/distance@7.3.1': resolution: {integrity: sha512-DK//doTGgYYjBkcWUywAe7wbZYcdP97hdEJ6rXYVYRoULwGGR3lhY96GNjozg6gaW9q2eSNYnZLpcL5iFVHqgw==} + '@turf/distance@7.3.5': + resolution: {integrity: sha512-uQAC63zg/l91KUxzfhqio7Ii3+UXTrPOVJScIdRj6EO6+9XHI4kC+AdyIS4cPAv14sZfJLIBxzMnzcGrss+kEA==} + '@turf/helpers@4.7.3': resolution: {integrity: sha512-hk5ANVazb80zK6tjJYAG76oCRTWMQo6SAFu5E1dVnlb2kT3FHLoPcOB9P11duwDafMwywz24KZ+FUorB5e728w==} - '@turf/helpers@6.5.0': - resolution: {integrity: sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==} - '@turf/helpers@7.3.1': resolution: {integrity: sha512-zkL34JVhi5XhsuMEO0MUTIIFEJ8yiW1InMu4hu/oRqamlY4mMoZql0viEmH6Dafh/p+zOl8OYvMJ3Vm3rFshgg==} + '@turf/helpers@7.3.5': + resolution: {integrity: sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==} + '@turf/invariant@4.7.3': resolution: {integrity: sha512-MtlIn9aPC4gZsflLEnfBl/o/SJjZU/yuvj+fo9qRXUOI8wBiKaUcZIT4x1jdLW0NGvU7wMPWJSFJanLCQ9eU1w==} - '@turf/invariant@6.5.0': - resolution: {integrity: sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==} - '@turf/invariant@7.3.1': resolution: {integrity: sha512-IdZJfDjIDCLH+Gu2yLFoSM7H23sdetIo5t4ET1/25X8gi3GE2XSqbZwaGjuZgNh02nisBewLqNiJs2bo+hrqZA==} - '@turf/line-arc@6.5.0': - resolution: {integrity: sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==} + '@turf/invariant@7.3.5': + resolution: {integrity: sha512-ZVIvsBvjr8lO7WxC5zYNjRsjSDvyGvWkJMjuWaJjTU8x+1tmfNnw3gDX/TI2Sit83gcRYLYkNo23lB/udqx/Hg==} + + '@turf/line-arc@7.3.5': + resolution: {integrity: sha512-XrPicIoN7XCtiJ7e7ELje7GjMZrBw/nuJnz0mQoCwwHwmX8Oz9oRfb6uaiS8NeR4WBzUVdkmVR/ro0kW4lypog==} '@turf/line-distance@4.7.3': resolution: {integrity: sha512-U6sFReqpMJq/LhOoDna4w1Vs2gliJUhFa1g94WrZrVz7l4IHldy7Q0Va0CyQ00qN5n3f09/vf4F+q2+ThHbL4g==} @@ -3940,11 +3947,11 @@ packages: '@turf/meta@4.7.4': resolution: {integrity: sha512-cvwz4EI9BjrgRHxmJ3Z3HKxq6k/fj/V95kwNZ8uWNLncCvrb7x1jUBDkQUo1tShnYdZ8eBgA+a2bvJmAM+MJ0A==} - '@turf/meta@6.5.0': - resolution: {integrity: sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==} + '@turf/meta@7.3.5': + resolution: {integrity: sha512-r+ohqxoyqeigFB0oFrQx/YEHIkOKqcKpCjvZkvZs7Tkv+IFco5MezAd2zd4rzK+0DfFgDP3KpJc7HqrYjvEjhg==} - '@turf/midpoint@6.5.0': - resolution: {integrity: sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==} + '@turf/midpoint@7.3.5': + resolution: {integrity: sha512-y92O2YDDBkZp7jAUuUPgof/HiXHjJSkKjEtQRjWXZcjTlzHd/Fqg6/twarY2wNkENK2EuaaiV9MDy74FeRG2Ow==} '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -5261,6 +5268,7 @@ packages: conventional-changelog-core@5.0.1: resolution: {integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==} engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead. conventional-changelog-preset-loader@3.0.0: resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} @@ -6455,7 +6463,7 @@ packages: git-raw-commits@3.0.0: resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} engines: {node: '>=14'} - deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-remote-origin-url@2.0.0: @@ -6465,7 +6473,7 @@ packages: git-semver-tags@5.0.1: resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} engines: {node: '>=14'} - deprecated: This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead. + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-up@6.0.0: @@ -12803,7 +12811,7 @@ snapshots: js-yaml: 4.1.0 libnpmpublish: 9.0.9 load-json-file: 6.2.0 - lodash: 4.17.21 + lodash: 4.18.1 make-dir: 4.0.0 minimatch: 3.0.5 multimatch: 5.0.0 @@ -14074,15 +14082,12 @@ snapshots: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/bbox@6.5.0': + '@turf/bbox@7.3.5': dependencies: - '@turf/helpers': 6.5.0 - '@turf/meta': 6.5.0 - - '@turf/bearing@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 + '@turf/helpers': 7.3.5 + '@turf/meta': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 '@turf/bearing@7.3.1': dependencies: @@ -14091,15 +14096,19 @@ snapshots: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/circle@6.5.0': + '@turf/bearing@7.3.5': dependencies: - '@turf/destination': 6.5.0 - '@turf/helpers': 6.5.0 + '@turf/helpers': 7.3.5 + '@turf/invariant': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 - '@turf/destination@6.5.0': + '@turf/circle@7.3.5': dependencies: - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 + '@turf/destination': 7.3.5 + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 '@turf/destination@7.3.1': dependencies: @@ -14108,16 +14117,18 @@ snapshots: '@types/geojson': 7946.0.16 tslib: 2.8.1 + '@turf/destination@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@turf/invariant': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + '@turf/distance@4.7.3': dependencies: '@turf/helpers': 4.7.3 '@turf/invariant': 4.7.3 - '@turf/distance@6.5.0': - dependencies: - '@turf/helpers': 6.5.0 - '@turf/invariant': 6.5.0 - '@turf/distance@7.3.1': dependencies: '@turf/helpers': 7.3.1 @@ -14125,20 +14136,26 @@ snapshots: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/helpers@4.7.3': {} + '@turf/distance@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@turf/invariant': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 - '@turf/helpers@6.5.0': {} + '@turf/helpers@4.7.3': {} '@turf/helpers@7.3.1': dependencies: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/invariant@4.7.3': {} - - '@turf/invariant@6.5.0': + '@turf/helpers@7.3.5': dependencies: - '@turf/helpers': 6.5.0 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/invariant@4.7.3': {} '@turf/invariant@7.3.1': dependencies: @@ -14146,11 +14163,19 @@ snapshots: '@types/geojson': 7946.0.16 tslib: 2.8.1 - '@turf/line-arc@6.5.0': + '@turf/invariant@7.3.5': + dependencies: + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 + + '@turf/line-arc@7.3.5': dependencies: - '@turf/circle': 6.5.0 - '@turf/destination': 6.5.0 - '@turf/helpers': 6.5.0 + '@turf/circle': 7.3.5 + '@turf/destination': 7.3.5 + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 '@turf/line-distance@4.7.3': dependencies: @@ -14160,16 +14185,20 @@ snapshots: '@turf/meta@4.7.4': {} - '@turf/meta@6.5.0': + '@turf/meta@7.3.5': dependencies: - '@turf/helpers': 6.5.0 + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 - '@turf/midpoint@6.5.0': + '@turf/midpoint@7.3.5': dependencies: - '@turf/bearing': 6.5.0 - '@turf/destination': 6.5.0 - '@turf/distance': 6.5.0 - '@turf/helpers': 6.5.0 + '@turf/bearing': 7.3.5 + '@turf/destination': 7.3.5 + '@turf/distance': 7.3.5 + '@turf/helpers': 7.3.5 + '@types/geojson': 7946.0.16 + tslib: 2.8.1 '@tybys/wasm-util@0.9.0': dependencies: @@ -17421,7 +17450,7 @@ snapshots: cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 - lodash: 4.17.21 + lodash: 4.18.1 mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 @@ -17978,7 +18007,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 15.14.9 + '@types/node': 25.6.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -18354,7 +18383,7 @@ snapshots: junit-report-builder@5.1.1: dependencies: - lodash: 4.17.21 + lodash: 4.18.1 make-dir: 3.1.0 xmlbuilder: 15.1.1 @@ -20083,7 +20112,7 @@ snapshots: dependencies: find-up-simple: 1.0.0 read-pkg: 9.0.1 - type-fest: 4.26.1 + type-fest: 4.41.0 read-package-up@12.0.0: dependencies: @@ -21779,11 +21808,11 @@ snapshots: wait-on@7.2.0: dependencies: - axios: 1.7.7 + axios: 1.15.2 joi: 17.13.3 - lodash: 4.17.21 + lodash: 4.18.1 minimist: 1.2.8 - rxjs: 7.8.1 + rxjs: 7.8.2 transitivePeerDependencies: - debug
Pleasant Ave & 50th St / Busch Terrace Nicollet Ave S & 46th St E 46th St & I-35W 4th Ave S & 38th St E 4th Ave S & Lake St E 3rd Ave S & Franklin Ave E Nicollet Mall & Alice Rainville Pl Nicollet Mall & 7th St S Nicollet Mall & 3rd St S Hennepin Ave E & Wilder St Lowry Ave NE & 2nd St / 1st St NE Grand St NE & 29th Ave NE Columbia Heights Transit Center C
+
- + 06:00 + 06:02 + 06:06 + 06:11 + 06:16 + 06:20 + 06:24 + 06:28 + 06:31 + 06:39 + 06:42 + 06:52
+
16:10 + - + 16:26 + 16:31 + 16:36 + - + - + - + - + - + - + - + -
+
- + 01:37 + 01:39 + 01:43 + 01:47 + 01:52 + 01:56 + 02:00 + 02:13 + 02:15 + 02:23 + 02:26 + 02:36