From d94e0852a4d3a62828bda059628fcc74ff8195c4 Mon Sep 17 00:00:00 2001 From: David Szakacs Date: Fri, 13 Feb 2026 12:55:50 +0200 Subject: [PATCH 1/2] feat: support routing options for Valhalla --- docs/API.md | 25 ++++++++++++++ src/routing/valhalla_wrapper.cpp | 25 +++++++++----- src/routing/valhalla_wrapper.h | 6 +++- src/structures/vroom/input/input.cpp | 24 ++++++++++++- src/structures/vroom/input/input.h | 7 ++++ src/utils/input_parser.cpp | 50 ++++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 10 deletions(-) diff --git a/docs/API.md b/docs/API.md index 5bd5caa91..fb324d004 100644 --- a/docs/API.md +++ b/docs/API.md @@ -48,6 +48,7 @@ The problem description is read from standard input or from a file | [`shipments`](#shipments) | array of `shipment` objects describing pickup and delivery tasks | | [`vehicles`](#vehicles) | array of `vehicle` objects describing the available vehicles | | [[`matrices`](#matrices)] | optional description of per-profile custom matrices | +| [[`routing_options`](#routing-options)] | optional per-profile routing engine options (currently only works with Valhalla routing engine) | | ~~[`matrix`]~~ | optional two-dimensional array describing a custom matrix | ## Jobs @@ -339,6 +340,30 @@ values, the `location`, `start` and `end` properties become optional. Instead of the coordinates, row and column indications provided with the `*_index` keys are used during optimization. +## Routing options (Valhalla routing only) + +`routing_options` is an optional object keyed by profile name. Each value is a +JSON object of extra routing engine parameters that will be merged into the +request payload for that profile. For Valhalla, this can be used to pass +`costing_options` and other request options. + +Reserved keys `costing`, `locations`, `sources`, and `targets` are rejected. + +Example: + +```json +"routing_options": { + "auto": { + "costing_options": { + "auto": { + "use_tolls": 0.2, + "use_highways": 0.6 + } + } + } +} +``` + # Output The computed solution is written as `json` on standard output or a file diff --git a/src/routing/valhalla_wrapper.cpp b/src/routing/valhalla_wrapper.cpp index db5941a06..1a5959f47 100644 --- a/src/routing/valhalla_wrapper.cpp +++ b/src/routing/valhalla_wrapper.cpp @@ -19,14 +19,16 @@ constexpr unsigned polyline_precision = 5; constexpr unsigned valhalla_polyline_precision = 6; ValhallaWrapper::ValhallaWrapper(const std::string& profile, - const Server& server) + const Server& server, + std::string extra_options) : HttpWrapper(profile, - server, - "sources_to_targets", - "sources_to_targets", - "sources_to_targets", - "route", - R"("directions_type":"none")") { + server, + "sources_to_targets", + "sources_to_targets", + "sources_to_targets", + "route", + R"("directions_type":"none")"), + _extra_options(std::move(extra_options)) { } std::string ValhallaWrapper::get_matrix_query( @@ -45,7 +47,11 @@ std::string ValhallaWrapper::get_matrix_query( query += "{\"sources\":[" + all_locations; query += "],\"targets\":[" + all_locations; - query += R"(],"costing":")" + profile + "\"}"; + query += R"(],"costing":")" + profile + "\""; + if (!_extra_options.empty()) { + query += "," + _extra_options; + } + query += "}"; query += " HTTP/1.1\r\n"; query += "Host: " + _server.host + "\r\n"; @@ -70,6 +76,9 @@ ValhallaWrapper::get_route_query(const std::vector& locations) const { query += R"(],"costing":")" + profile + "\""; query += "," + _routing_args; + if (!_extra_options.empty()) { + query += "," + _extra_options; + } query += "}"; query += " HTTP/1.1\r\n"; diff --git a/src/routing/valhalla_wrapper.h b/src/routing/valhalla_wrapper.h index 33a164cf5..63295f3c8 100644 --- a/src/routing/valhalla_wrapper.h +++ b/src/routing/valhalla_wrapper.h @@ -16,6 +16,8 @@ namespace vroom::routing { class ValhallaWrapper : public HttpWrapper { private: + const std::string _extra_options; + std::string get_matrix_query(const std::vector& locations) const; std::string get_route_query(const std::vector& locations) const; @@ -49,7 +51,9 @@ class ValhallaWrapper : public HttpWrapper { std::string get_geometry(rapidjson::Value& result) const override; public: - ValhallaWrapper(const std::string& profile, const Server& server); + ValhallaWrapper(const std::string& profile, + const Server& server, + std::string extra_options); }; } // namespace vroom::routing diff --git a/src/structures/vroom/input/input.cpp b/src/structures/vroom/input/input.cpp index 048e2348f..e5b6c8353 100644 --- a/src/structures/vroom/input/input.cpp +++ b/src/structures/vroom/input/input.cpp @@ -93,12 +93,25 @@ void Input::add_routing_wrapper(const std::string& profile) { throw InputException("Invalid profile: " + profile + "."); } routing_wrapper = - std::make_unique(profile, search->second); + std::make_unique( + profile, + search->second, + routing_options_for_profile(profile)); } break; } #endif } +const std::string& +Input::routing_options_for_profile(const std::string& profile) const { + static const std::string empty; + if (auto search = _routing_options_by_profile.find(profile); + search != _routing_options_by_profile.end()) { + return search->second; + } + return empty; +} + void Input::check_amount_size(const Amount& amount) { const auto size = amount.size(); @@ -433,6 +446,15 @@ void Input::set_costs_matrix(const std::string& profile, Matrix&& m) { _costs_matrices.insert_or_assign(profile, std::move(m)); } +void Input::set_routing_options(const std::string& profile, + std::string routing_options) { + if (routing_options.empty()) { + return; + } + _routing_options_by_profile.insert_or_assign(profile, + std::move(routing_options)); +} + bool Input::is_used_several_times(const Location& location) const { return _locations_used_several_times.contains(location); } diff --git a/src/structures/vroom/input/input.h b/src/structures/vroom/input/input.h index ad0e96ce3..4acd5f923 100644 --- a/src/structures/vroom/input/input.h +++ b/src/structures/vroom/input/input.h @@ -70,6 +70,8 @@ class Input { _costs_matrices; std::unordered_map> _max_cost_per_hour; + std::unordered_map> + _routing_options_by_profile; Cost _cost_upper_bound{0}; std::vector _locations; std::unordered_map _locations_to_index; @@ -122,6 +124,8 @@ class Input { void set_matrices(unsigned nb_thread, bool sparse_filling = false); void add_routing_wrapper(const std::string& profile); + const std::string& + routing_options_for_profile(const std::string& profile) const; public: std::vector jobs; @@ -160,6 +164,9 @@ class Input { void set_costs_matrix(const std::string& profile, Matrix&& m); + void set_routing_options(const std::string& profile, + std::string routing_options); + const Amount& zero_amount() const { return _zero; } diff --git a/src/utils/input_parser.cpp b/src/utils/input_parser.cpp index dc073b26b..bba633bdd 100644 --- a/src/utils/input_parser.cpp +++ b/src/utils/input_parser.cpp @@ -8,9 +8,12 @@ All rights reserved (see LICENSE). */ #include +#include #include "../include/rapidjson/include/rapidjson/document.h" #include "../include/rapidjson/include/rapidjson/error/en.h" +#include "../include/rapidjson/include/rapidjson/stringbuffer.h" +#include "../include/rapidjson/include/rapidjson/writer.h" #include "utils/input_parser.h" @@ -37,6 +40,35 @@ inline std::string get_string(const rapidjson::Value& object, const char* key) { return value; } +inline std::string get_object_members_json(const rapidjson::Value& options, + const char* key) { + if (!options.IsObject()) { + throw InputException("Invalid " + std::string(key) + "."); + } + + for (const auto& entry : options.GetObject()) { + const auto name = std::string_view(entry.name.GetString(), + entry.name.GetStringLength()); + if (name == "costing" || name == "locations" || name == "sources" || + name == "targets") { + throw InputException("Invalid key in " + std::string(key) + ": " + + std::string(name) + "."); + } + } + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + options.Accept(writer); + + const std::string serialized = buffer.GetString(); + if (serialized.size() < 2 || serialized.front() != '{' || + serialized.back() != '}') { + throw InputException("Invalid " + std::string(key) + "."); + } + + return serialized.substr(1, serialized.size() - 2); +} + inline double get_double(const rapidjson::Value& object, const char* key) { double value = 1.; if (object.HasMember(key)) { @@ -696,6 +728,24 @@ void parse(Input& input, const std::string& input_str, bool geometry) { json_input["matrix"])); } } + + if (json_input.HasMember("routing_options")) { + if (!json_input["routing_options"].IsObject()) { + throw InputException("Invalid routing_options."); + } + + for (auto& profile_entry : json_input["routing_options"].GetObject()) { + if (!profile_entry.value.IsObject()) { + throw InputException("Invalid routing_options for profile " + + std::string(profile_entry.name.GetString()) + + "."); + } + + input.set_routing_options( + profile_entry.name.GetString(), + get_object_members_json(profile_entry.value, "routing_options")); + } + } } } // namespace vroom::io From bb878560d37ad4453e3daf474b774a355bd56517 Mon Sep 17 00:00:00 2001 From: David Szakacs Date: Fri, 13 Feb 2026 13:00:53 +0200 Subject: [PATCH 2/2] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b117df3..959efd791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Ability to set different task times per vehicle type (#336) - Task times can be included in the cost used internally for optimization (#1130) - Support for cost per hour spent on tasks on a vehicle basis (#1130) +- Ability to provide custom routing options that will be passed on to the routing engine, currently only supports Valhalla #### Internals