Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 25 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
25 changes: 17 additions & 8 deletions src/routing/valhalla_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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";
Expand All @@ -70,6 +76,9 @@ ValhallaWrapper::get_route_query(const std::vector<Location>& locations) const {

query += R"(],"costing":")" + profile + "\"";
query += "," + _routing_args;
if (!_extra_options.empty()) {
query += "," + _extra_options;
}
query += "}";

query += " HTTP/1.1\r\n";
Expand Down
6 changes: 5 additions & 1 deletion src/routing/valhalla_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Location>& locations) const;

std::string get_route_query(const std::vector<Location>& locations) const;
Expand Down Expand Up @@ -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
Expand Down
24 changes: 23 additions & 1 deletion src/structures/vroom/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,25 @@ void Input::add_routing_wrapper(const std::string& profile) {
throw InputException("Invalid profile: " + profile + ".");
}
routing_wrapper =
std::make_unique<routing::ValhallaWrapper>(profile, search->second);
std::make_unique<routing::ValhallaWrapper>(
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();

Expand Down Expand Up @@ -433,6 +446,15 @@ void Input::set_costs_matrix(const std::string& profile, Matrix<UserCost>&& 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);
}
Expand Down
7 changes: 7 additions & 0 deletions src/structures/vroom/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class Input {
_costs_matrices;
std::unordered_map<std::string, Cost, StringHash, std::equal_to<>>
_max_cost_per_hour;
std::unordered_map<std::string, std::string, StringHash, std::equal_to<>>
_routing_options_by_profile;
Cost _cost_upper_bound{0};
std::vector<Location> _locations;
std::unordered_map<Location, Index> _locations_to_index;
Expand Down Expand Up @@ -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<Job> jobs;
Expand Down Expand Up @@ -160,6 +164,9 @@ class Input {

void set_costs_matrix(const std::string& profile, Matrix<UserCost>&& m);

void set_routing_options(const std::string& profile,
std::string routing_options);

const Amount& zero_amount() const {
return _zero;
}
Expand Down
50 changes: 50 additions & 0 deletions src/utils/input_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ All rights reserved (see LICENSE).
*/

#include <algorithm>
#include <string_view>

#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"

Expand All @@ -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<rapidjson::StringBuffer> 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)) {
Expand Down Expand Up @@ -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