blaise 0.1.4

A fast, local-first engine for GTFS transit data. Handles routing, fuzzy search, and geospatial queries without relying on external APIs.
Documentation
use crate::{
    raptor::{
        Parent, ParentType,
        location::{Location, Point},
    },
    repository::Repository,
    shared::{Distance, time::Time},
};
use serde::Serialize;

#[derive(Debug, Clone)]
pub struct Leg {
    pub from: Location,
    pub to: Location,
    pub departue_time: Time,
    pub arrival_time: Time,
    pub stops: Vec<LegStop>,
    pub leg_type: LegType,
}

#[derive(Debug, Clone, Copy, Serialize)]
pub enum LegType {
    Transit(u32),
    Transfer,
    Walk,
}

impl From<ParentType> for LegType {
    fn from(value: ParentType) -> Self {
        match value {
            ParentType::Transit(trip_idx) => Self::Transit(trip_idx),
            ParentType::Transfer => Self::Transfer,
            ParentType::Walk => Self::Walk,
        }
    }
}

#[derive(Debug, Clone)]
pub struct LegStop {
    pub location: Location,
    pub departure_time: Time,
    pub arrival_time: Time,
    pub distance_traveled: Option<Distance>,
}

impl LegStop {
    pub(crate) fn generate_stops(parent: &Parent, repository: &Repository) -> Vec<Self> {
        match parent.parent_type {
            ParentType::Transit(trip_idx) => {
                let trip = &repository.trips[trip_idx as usize];
                let stop_times = repository.stop_times_by_trip_idx(trip.index);
                let mut stops = Vec::with_capacity(stop_times.len());
                if let Point::Stop(from_idx) = parent.from
                    && let Point::Stop(to_idx) = parent.to
                {
                    let mut in_trip = false;
                    for stop_time in stop_times {
                        if stop_time.stop_idx == from_idx {
                            in_trip = true;
                        }
                        if in_trip {
                            let stop = &repository.stops[stop_time.stop_idx as usize];
                            stops.push(LegStop {
                                location: Location::Stop(stop.id.clone()),
                                departure_time: stop_time.departure_time,
                                arrival_time: stop_time.arrival_time,
                                distance_traveled: stop_time.distance_traveled,
                            });
                            if stop_time.stop_idx == to_idx && in_trip {
                                break;
                            }
                        }
                    }
                }

                stops
            }
            ParentType::Transfer => vec![],
            ParentType::Walk => vec![],
        }
    }
}

#[derive(Debug, Clone)]
pub struct Itinerary {
    pub from: Location,
    pub to: Location,
    pub legs: Vec<Leg>,
}

impl Itinerary {
    pub(crate) fn new(
        from: Location,
        to: Location,
        path: Vec<Parent>,
        repository: &Repository,
    ) -> Self {
        let legs = path
            .into_iter()
            .map(|parent| {
                let leg_from = point_to_location(&parent.from, repository);
                let leg_to = point_to_location(&parent.to, repository);
                Leg {
                    from: leg_from,
                    to: leg_to,
                    departue_time: parent.departure_time,
                    arrival_time: parent.arrival_time,
                    stops: LegStop::generate_stops(&parent, repository),
                    leg_type: parent.parent_type.into(),
                }
            })
            .collect();
        Self { from, to, legs }
    }
}

fn point_to_location(point: &Point, repository: &Repository) -> Location {
    match point {
        Point::Coordinate(coordinate) => (*coordinate).into(),
        Point::Stop(idx) => {
            let stop = &repository.stops[*idx as usize];
            Location::Stop(stop.id.clone())
        }
    }
}