Skip to content

Commit

Permalink
Improve trip display name heuristics
Browse files Browse the repository at this point in the history
  • Loading branch information
jbruechert committed Jan 5, 2025
1 parent e5b1a20 commit a5edeb7
Showing 1 changed file with 73 additions and 30 deletions.
103 changes: 73 additions & 30 deletions src/loader/gtfs/trip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,44 +190,87 @@ void trip::interpolate() {

std::string trip::display_name(timetable const& tt) const {
auto const is_digit = [](char const x) { return x >= '0' && x <= '9'; };
if (route_->clasz_ == clasz::kBus) {
return route_->short_name_.empty() ? "Bus " + short_name_
: "Bus " + route_->short_name_;
} else if (route_->clasz_ == clasz::kTram) {
return route_->short_name_.empty() ? "Tram " + short_name_
: "Tram " + route_->short_name_;
}

auto const trip_name_is_number =
!short_name_.empty() && utl::all_of(short_name_, is_digit);
if (!route_->short_name_.starts_with("IC") &&
route_->agency_ != provider_idx_t::invalid() &&
tt.providers_[route_->agency_].long_name_ == "DB Fernverkehr AG") {
if (route_->clasz_ == clasz::kHighSpeed) {
return trip_name_is_number
? fmt::format("ICE {}", utl::parse<int>(short_name_))
: fmt::format("ICE {}", route_->short_name_);
} else if (route_->clasz_ == clasz::kLongDistance) {
return trip_name_is_number
? fmt::format("IC {}", utl::parse<int>(short_name_))
: fmt::format("IC {}", route_->short_name_);
// Depending on the class, there are different expectations from users
// if the special case doesn't work based on available data, fall back to
// generic code afterwards
switch (route_->clasz_) {
// For subway / metro, only the line matters since it's frequent enough.
case clasz::kSubway:
case clasz::kMetro:
if (!route_->short_name_.empty()) {
return route_->short_name_;
}
break;
// Deutsche Bahn heuristics to classify ICEs and ICs
case clasz::kHighSpeed:
case clasz::kLongDistance: {
auto const trip_name_is_number =
!short_name_.empty() && utl::all_of(short_name_, is_digit);
if (!route_->short_name_.starts_with("IC") &&
route_->agency_ != provider_idx_t::invalid() &&
tt.providers_[route_->agency_].long_name_ == "DB Fernverkehr AG") {
if (route_->clasz_ == clasz::kHighSpeed) {
return trip_name_is_number
? fmt::format("ICE {}", utl::parse<int>(short_name_))
: fmt::format("ICE {}", route_->short_name_);
} else if (route_->clasz_ == clasz::kLongDistance) {
return trip_name_is_number
? fmt::format("IC {}", utl::parse<int>(short_name_))
: fmt::format("IC {}", route_->short_name_);
}
}

break;
}
default: {
}
}

auto const starts_with_letter_and_ends_with_number =
[](std::string_view line_id) {
return !(line_id.front() >= '0' && line_id.front() <= '9') &&
(line_id.back() >= '0' && line_id.back() <= '9');
};
// route name looks like product name, trip name is number
auto const is_product_name = [=](std::string_view route) {
return utl::all_of(route, [=](char const c) { return !is_digit(c); });
};

auto const is_trip_number = [=](std::string_view trip_name) {
return utl::all_of(trip_name, is_digit);
};

if (starts_with_letter_and_ends_with_number(route_->short_name_)) {
return route_->short_name_;
} else if (trip_name_is_number) {
if (!route_->short_name_.empty() && is_product_name(route_->short_name_) &&
!short_name_.empty() && is_trip_number(short_name_)) {
return fmt::format("{} {}", route_->short_name_,
utl::parse<int>(short_name_));
} else {
return fmt::format("{} {}", route_->short_name_, short_name_);
}

// take the first thing that starts with a letter and ends with a number
auto const starts_with_letter_and_ends_with_number =
[=](std::string_view line_id) {
return !is_digit(line_id.front()) && is_digit(line_id.back());
};

auto precedence = std::array{std::string_view{short_name_},
std::string_view{route_->short_name_},
std::string_view{route_->long_name_}};

for (auto const candidate : precedence) {
if (candidate.empty()) {
continue;
}

if (starts_with_letter_and_ends_with_number(candidate)) {
return std::string(candidate);
}
}

// prefer trip name over route short name,
// prefer route short name over route long name
for (auto const candidate : precedence) {
if (!candidate.empty()) {
return std::string(candidate);
}
}

return {};
}

clasz trip::get_clasz(timetable const& tt) const {
Expand Down

0 comments on commit a5edeb7

Please sign in to comment.