From 1a1545ba63cd47f6e5ec7f04e74b4a14738fc983 Mon Sep 17 00:00:00 2001 From: Pablo Hoch Date: Fri, 29 Nov 2024 17:07:53 +0100 Subject: [PATCH] return constraint support, allow roundtrip for direct connections --- include/motis/gbfs/data.h | 1 + include/motis/gbfs/mode.h | 2 ++ openapi.yaml | 9 ++++++ src/endpoints/routing.cc | 4 ++- src/gbfs/mode.cc | 12 ++++++++ src/gbfs/osr_mapping.cc | 7 ++++- src/gbfs/parser.cc | 12 +++----- src/gbfs/update.cc | 21 +++++++++---- src/street_routing.cc | 4 ++- ui/src/lib/i18n/de.ts | 3 +- ui/src/lib/i18n/en.ts | 3 +- ui/src/lib/i18n/fr.ts | 3 +- ui/src/lib/i18n/translation.ts | 1 + ui/src/lib/openapi/schemas.gen.ts | 8 +++++ ui/src/lib/openapi/types.gen.ts | 44 ++++++++++++++++++++++++--- ui/src/routes/ConnectionDetail.svelte | 7 ++++- 16 files changed, 116 insertions(+), 25 deletions(-) diff --git a/include/motis/gbfs/data.h b/include/motis/gbfs/data.h index ed12d50a1..9f1effe2a 100644 --- a/include/motis/gbfs/data.h +++ b/include/motis/gbfs/data.h @@ -315,6 +315,7 @@ struct provider_products { std::vector vehicle_types_; vehicle_form_factor form_factor_{vehicle_form_factor::kBicycle}; propulsion_type propulsion_type_{propulsion_type::kHuman}; + return_constraint return_constraint_{return_constraint::kNone}; bool has_vehicles_to_rent_{}; }; diff --git a/include/motis/gbfs/mode.h b/include/motis/gbfs/mode.h index 207579d4f..98a769ec6 100644 --- a/include/motis/gbfs/mode.h +++ b/include/motis/gbfs/mode.h @@ -15,6 +15,8 @@ vehicle_form_factor from_api_form_factor(api::RentalFormFactorEnum); api::RentalPropulsionTypeEnum to_api_propulsion_type(propulsion_type); propulsion_type from_api_propulsion_type(api::RentalPropulsionTypeEnum); +api::RentalReturnConstraintEnum to_api_return_constraint(return_constraint); + bool products_match( provider_products const& prod, std::optional> const& form_factors, diff --git a/openapi.yaml b/openapi.yaml index c5db86d3a..f85e9d221 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1403,6 +1403,13 @@ components: - PLUG_IN_HYBRID - HYDROGEN_FUEL_CELL + RentalReturnConstraint: + type: string + enum: + - NONE + - ANY_STATION + - ROUNDTRIP_STATION + Rental: description: Vehicle rental type: object @@ -1434,6 +1441,8 @@ components: $ref: '#/components/schemas/RentalFormFactor' propulsionType: $ref: '#/components/schemas/RentalPropulsionType' + returnConstraint: + $ref: '#/components/schemas/RentalReturnConstraint' Leg: type: object diff --git a/src/endpoints/routing.cc b/src/endpoints/routing.cc index fad7349c0..10987d48c 100644 --- a/src/endpoints/routing.cc +++ b/src/endpoints/routing.cc @@ -169,7 +169,9 @@ std::vector routing::get_offsets( } auto provider_rd = std::shared_ptr{}; for (auto const& prod : provider->products_) { - if (!gbfs::products_match(prod, form_factors, propulsion_types)) { + if (prod.return_constraint_ == + gbfs::return_constraint::kRoundtripStation || + !gbfs::products_match(prod, form_factors, propulsion_types)) { continue; } if (!provider_rd) { diff --git a/src/gbfs/mode.cc b/src/gbfs/mode.cc index d6ae25b65..e4533f91b 100644 --- a/src/gbfs/mode.cc +++ b/src/gbfs/mode.cc @@ -84,6 +84,18 @@ propulsion_type from_api_propulsion_type( throw utl::fail("invalid rental propulsion type"); } +api::RentalReturnConstraintEnum to_api_return_constraint( + return_constraint const rc) { + switch (rc) { + case return_constraint::kNone: return api::RentalReturnConstraintEnum::NONE; + case return_constraint::kAnyStation: + return api::RentalReturnConstraintEnum::ANY_STATION; + case return_constraint::kRoundtripStation: + return api::RentalReturnConstraintEnum::ROUNDTRIP_STATION; + } + std::unreachable(); +} + bool products_match( provider_products const& prod, std::optional> const& form_factors, diff --git a/src/gbfs/osr_mapping.cc b/src/gbfs/osr_mapping.cc index 15087e556..7b90f10da 100644 --- a/src/gbfs/osr_mapping.cc +++ b/src/gbfs/osr_mapping.cc @@ -69,6 +69,10 @@ struct osr_mapping { break; } + if (prod.return_constraint_ == return_constraint::kAnyStation) { + default_restrictions.station_parking_ = true; + } + if (default_restrictions.ride_end_allowed_ && !default_restrictions.station_parking_) { rd.end_allowed_.one_out(); @@ -242,7 +246,8 @@ struct osr_mapping { for (auto const [vehicle_idx, vs] : utl::enumerate(provider_.vehicle_status_)) { if (vs.is_disabled_ || vs.is_reserved_ || !vs.station_id_.empty() || - !vs.home_station_id_.empty() || + (!vs.home_station_id_.empty() && + prod.return_constraint_ != return_constraint::kRoundtripStation) || !prod.includes_vehicle_type(vs.vehicle_type_idx_)) { continue; } diff --git a/src/gbfs/parser.cc b/src/gbfs/parser.cc index b649fc526..012ce4b37 100644 --- a/src/gbfs/parser.cc +++ b/src/gbfs/parser.cc @@ -226,6 +226,7 @@ void load_station_status(gbfs_provider& provider, json::value const& root) { auto const& vta = station_obj.at("vehicle_types_available").as_array(); auto unrestricted_available = 0U; auto any_station_available = 0U; + auto roundtrip_available = 0U; for (auto const& vt : vta) { auto const vehicle_type_id = static_cast(vt.at("vehicle_type_id").as_string()); @@ -239,12 +240,14 @@ void load_station_status(gbfs_provider& provider, json::value const& root) { provider.vehicle_types_[vehicle_type_idx].return_constraint_) { case return_constraint::kNone: ++unrestricted_available; break; case return_constraint::kAnyStation: ++any_station_available; break; - case return_constraint::kRoundtripStation: break; + case return_constraint::kRoundtripStation: + ++roundtrip_available; + break; } } } station.status_.num_vehicles_available_ = - unrestricted_available + any_station_available; + unrestricted_available + any_station_available + roundtrip_available; } if (station_obj.contains("vehicle_docks_available")) { @@ -364,11 +367,6 @@ void load_vehicle_status(gbfs_provider& provider, json::value const& root) { if (auto const it = provider.vehicle_types_map_.find(type_id); it != end(provider.vehicle_types_map_)) { type_idx = it->second; - if (provider.vehicle_types_[it->second].return_constraint_ == - return_constraint::kRoundtripStation) { - // roundtrip vehicles currently not supported - continue; - } } provider.vehicle_status_.emplace_back(vehicle_status{ diff --git a/src/gbfs/update.cc b/src/gbfs/update.cc index 8f1325096..e330a9b63 100644 --- a/src/gbfs/update.cc +++ b/src/gbfs/update.cc @@ -431,6 +431,16 @@ struct gbfs_update { part.refine(vt_indices); } + // refine by return constraints + auto by_return_constraint = + hash_map>{}; + for (auto const& vt : provider.vehicle_types_) { + by_return_constraint[vt.return_constraint_].push_back(vt.idx_); + } + for (auto const& [_, vt_indices] : by_return_constraint) { + part.refine(vt_indices); + } + // refine by return stations // TODO: only do this if the station is not in a zone where vehicles // can be returned anywhere @@ -457,12 +467,11 @@ struct gbfs_update { auto& prod = provider.products_.emplace_back(); prod.idx_ = prod_idx; prod.vehicle_types_ = set; - prod.form_factor_ = - provider.vehicle_types_.at(prod.vehicle_types_.front()) - .form_factor_; - prod.propulsion_type_ = - provider.vehicle_types_.at(prod.vehicle_types_.front()) - .propulsion_type_; + auto const first_vt = + provider.vehicle_types_.at(prod.vehicle_types_.front()); + prod.form_factor_ = first_vt.form_factor_; + prod.propulsion_type_ = first_vt.propulsion_type_; + prod.return_constraint_ = first_vt.return_constraint_; prod.has_vehicles_to_rent_ = utl::any_of(provider.stations_, [&](auto const& st) { diff --git a/src/street_routing.cc b/src/street_routing.cc index f0cc9703e..bdffd3934 100644 --- a/src/street_routing.cc +++ b/src/street_routing.cc @@ -183,7 +183,9 @@ struct sharing { .url_ = provider_.sys_info_.url_, .formFactor_ = gbfs::to_api_form_factor(products_.form_factor_), .propulsionType_ = - gbfs::to_api_propulsion_type(products_.propulsion_type_)}; + gbfs::to_api_propulsion_type(products_.propulsion_type_), + .returnConstraint_ = + gbfs::to_api_return_constraint(products_.return_constraint_)}; }; api::Itinerary dummy_itinerary(api::Place const& from, diff --git a/ui/src/lib/i18n/de.ts b/ui/src/lib/i18n/de.ts index 4e2b1d111..16b21441e 100644 --- a/ui/src/lib/i18n/de.ts +++ b/ui/src/lib/i18n/de.ts @@ -33,7 +33,8 @@ const translations: Translations = { return `Fahrt ${n} Stationen`; } }, - sharingProvider: 'Anbieter' + sharingProvider: 'Anbieter', + roundtripStationReturnConstraint: 'Das Fahrzeug muss wieder an der Abfahrtsstation abgestellt werden.' }; export default translations; diff --git a/ui/src/lib/i18n/en.ts b/ui/src/lib/i18n/en.ts index 36296282e..83b78da00 100644 --- a/ui/src/lib/i18n/en.ts +++ b/ui/src/lib/i18n/en.ts @@ -33,7 +33,8 @@ const translations: Translations = { return `${n} intermediate stops`; } }, - sharingProvider: 'Provider' + sharingProvider: 'Provider', + roundtripStationReturnConstraint: 'The vehicle must be returned to the departure station.' }; export default translations; diff --git a/ui/src/lib/i18n/fr.ts b/ui/src/lib/i18n/fr.ts index 579ff102e..41cdd6087 100644 --- a/ui/src/lib/i18n/fr.ts +++ b/ui/src/lib/i18n/fr.ts @@ -33,7 +33,8 @@ const translations: Translations = { } }, sharingProvider: 'Fournisseur', - transfers: 'Transferts' + transfers: 'Transferts', + roundtripStationReturnConstraint: 'Le véhicule doit être retourné à la station de départ.' }; export default translations; diff --git a/ui/src/lib/i18n/translation.ts b/ui/src/lib/i18n/translation.ts index 1485ed981..6710b474f 100644 --- a/ui/src/lib/i18n/translation.ts +++ b/ui/src/lib/i18n/translation.ts @@ -28,6 +28,7 @@ export type Translations = { track: string; tripIntermediateStops: (n: number) => string; sharingProvider: string; + roundtripStationReturnConstraint: string; }; const translations: Map = new Map( diff --git a/ui/src/lib/openapi/schemas.gen.ts b/ui/src/lib/openapi/schemas.gen.ts index 0dde6705d..b408c572f 100644 --- a/ui/src/lib/openapi/schemas.gen.ts +++ b/ui/src/lib/openapi/schemas.gen.ts @@ -420,6 +420,11 @@ export const RentalPropulsionTypeSchema = { enum: ['HUMAN', 'ELECTRIC_ASSIST', 'ELECTRIC', 'COMBUSTION', 'COMBUSTION_DIESEL', 'HYBRID', 'PLUG_IN_HYBRID', 'HYDROGEN_FUEL_CELL'] } as const; +export const RentalReturnConstraintSchema = { + type: 'string', + enum: ['NONE', 'ANY_STATION', 'ROUNDTRIP_STATION'] +} as const; + export const RentalSchema = { description: 'Vehicle rental', type: 'object', @@ -458,6 +463,9 @@ export const RentalSchema = { }, propulsionType: { '$ref': '#/components/schemas/RentalPropulsionType' + }, + returnConstraint: { + '$ref': '#/components/schemas/RentalReturnConstraint' } } } as const; diff --git a/ui/src/lib/openapi/types.gen.ts b/ui/src/lib/openapi/types.gen.ts index 532c796a4..d70a4f537 100644 --- a/ui/src/lib/openapi/types.gen.ts +++ b/ui/src/lib/openapi/types.gen.ts @@ -342,6 +342,8 @@ export type RentalFormFactor = 'BICYCLE' | 'CARGO_BICYCLE' | 'CAR' | 'MOPED' | ' export type RentalPropulsionType = 'HUMAN' | 'ELECTRIC_ASSIST' | 'ELECTRIC' | 'COMBUSTION' | 'COMBUSTION_DIESEL' | 'HYBRID' | 'PLUG_IN_HYBRID' | 'HYDROGEN_FUEL_CELL'; +export type RentalReturnConstraint = 'NONE' | 'ANY_STATION' | 'ROUNDTRIP_STATION'; + /** * Vehicle rental */ @@ -376,6 +378,7 @@ export type Rental = { rentalUriWeb?: string; formFactor?: RentalFormFactor; propulsionType?: RentalPropulsionType; + returnConstraint?: RentalReturnConstraint; }; export type Leg = { @@ -734,6 +737,14 @@ export type PlanData = { * */ directRentalPropulsionTypes?: Array; + /** + * Optional. Only applies to direct connections. + * + * A list of rental providers that are allowed to be used for direct connections. + * If empty (the default), all providers are allowed. + * + */ + directRentalProviders?: Array<(string)>; /** * \`latitude,longitude,level\` tuple in degrees OR stop id */ @@ -758,6 +769,13 @@ export type PlanData = { * */ maxHours?: number; + /** + * Optional. Default is 25 meters. + * + * Maximum matching distance in meters to match geo coordinates to the street network. + * + */ + maxMatchingDistance: number; /** * Optional. Default is 15min which is `900`. * Maximum time in seconds for the last street leg. @@ -811,7 +829,7 @@ export type PlanData = { */ postTransitModes?: Array; /** - * Optional. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + * Optional. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalFormFactors`). * * A list of vehicle type form factors that are allowed to be used from the last transit stop to the `to` coordinate. * If empty (the default), all form factors are allowed. @@ -820,14 +838,22 @@ export type PlanData = { */ postTransitRentalFormFactors?: Array; /** - * Optional. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + * Optional. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalPropulsionTypes`). * - * A list of vehicle propulsion types that are allowed to be used from the last transit stop to the `to` coordinate.. + * A list of vehicle propulsion types that are allowed to be used from the last transit stop to the `to` coordinate. * If empty (the default), all propulsion types are allowed. * Example: `HUMAN,ELECTRIC,ELECTRIC_ASSIST`. * */ postTransitRentalPropulsionTypes?: Array; + /** + * Optional. Only applies if the `to` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalProviders`). + * + * A list of rental providers that are allowed to be used from the last transit stop to the `to` coordinate. + * If empty (the default), all providers are allowed. + * + */ + postTransitRentalProviders?: Array<(string)>; /** * Optional. Default is `WALK`. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). * @@ -836,7 +862,7 @@ export type PlanData = { */ preTransitModes?: Array; /** - * Optional. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + * Optional. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalFormFactors`). * * A list of vehicle type form factors that are allowed to be used from the `from` coordinate to the first transit stop. * If empty (the default), all form factors are allowed. @@ -845,7 +871,7 @@ export type PlanData = { */ preTransitRentalFormFactors?: Array; /** - * Optional. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directModes`). + * Optional. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalPropulsionTypes`). * * A list of vehicle propulsion types that are allowed to be used from the `from` coordinate to the first transit stop. * If empty (the default), all propulsion types are allowed. @@ -853,6 +879,14 @@ export type PlanData = { * */ preTransitRentalPropulsionTypes?: Array; + /** + * Optional. Only applies if the `from` place is a coordinate (not a transit stop). Does not apply to direct connections (see `directRentalProviders`). + * + * A list of rental providers that are allowed to be used from the `from` coordinate to the first transit stop. + * If empty (the default), all providers are allowed. + * + */ + preTransitRentalProviders?: Array<(string)>; /** * Optional. Default is `false`. * diff --git a/ui/src/routes/ConnectionDetail.svelte b/ui/src/routes/ConnectionDetail.svelte index 2d352efc5..e6386fd8e 100644 --- a/ui/src/routes/ConnectionDetail.svelte +++ b/ui/src/routes/ConnectionDetail.svelte @@ -53,7 +53,7 @@ {/snippet} {#snippet streetLeg(l: Leg)} -
+
{formatDurationSec(l.duration)} {getModeName(l)} @@ -64,6 +64,11 @@ {t.sharingProvider}: {l.rental.systemName} {/if} + {#if l.rental?.returnConstraint == 'ROUNDTRIP_STATION'} + + {t.roundtripStationReturnConstraint} + + {/if}
{/snippet}