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 @@ -138,6 +138,7 @@
* CHANGED: Use rapidjson for transit_available serializer [#5430](https://github.com/valhalla/valhalla/pull/5430)
* CHANGED: Switch from CircleCI to Github Actions [#5427](https://github.com/valhalla/valhalla/pull/5427)
* CHANGED: Use rapidjson for isochrone serializer [#5429](https://github.com/valhalla/valhalla/pull/5429)
* ADDED: add attribute controller support for matrix responses [#5471](https://github.com/valhalla/valhalla/pull/5471)
* ADDED: Allow pedestrian routing through highway=via_ferrata [#5480](https://github.com/valhalla/valhalla/pull/5480)
* ADDED: generic level change maneuver [#5431](https://github.com/valhalla/valhalla/pull/5431)
* ADDED: Publish timezone db on Github Actions artifacts [#5479](https://github.com/valhalla/valhalla/pull/5479)
Expand Down
46 changes: 46 additions & 0 deletions docs/docs/api/matrix/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ However, there are important limitations of the `/sources_to_targets` service's
- `date_time.type = 0/1` or `date_time` on any source, when there's more sources than targets
- `date_time.type = 2` or `date_time` on any target, when there's more or equal amount of targets than/as sources

### Attribute filters

The `matrix` action allows you to apply filters to `include` or `exclude` specific attribute filter keys in your response. These filters are optional and can be added to the action string inside of the `filters` object.

If no filters are used, all attributes are enabled and returned in the `matrix` response.

These are the available filter keys. Review their [descriptions](#verbose-mode-verbose-true) and see [how to use them](#example-matrix-requests-with-attribute-filters) inside the request for more information.

```
// Matrix connection keys
matrix_connection.distance
matrix_connection.time
matrix_connection.begin_heading
matrix_connection.end_heading
matrix_connection.begin_lat
matrix_connection.begin_lon
matrix_connection.end_lat
matrix_connection.end_lon
matrix_connection.date_time
matrix_connection.time_zone_offset
matrix_connection.time_zone_name
matrix_connection.shape
```

## Outputs of the matrix service

Depending on the `verbose` (default: `true`) request parameter, the result of the Time-Distance Matrix service is different.
Expand All @@ -85,6 +109,7 @@ In both (`"verbose": true` and `"verbose": false`) cases, these parameters are p

See the [HTTP return codes](../turn-by-turn/api-reference.md#http-status-codes-and-conditions) for more on messages you might receive from the service.


### Verbose mode (`"verbose": true`)

The following parameters are only present in `"verbose": true` mode:
Expand All @@ -101,6 +126,27 @@ The following parameters are only present in `"verbose": true` mode:
| :---- | :----------- |
| `sources_to_targets` | Returns an object with <code>durations</code> and <code>distances</code> as <b>row-ordered</b> contents of the values above. |

### Example `matrix` requests with attribute filters

*Include only distance and time*

```json
{"sources":[{"lat":40.744014,"lon":-73.990508}],"targets":[{"lat":40.744014,"lon":-73.990508},{"lat":40.739735,"lon":-73.979713}],"costing":"pedestrian","filters":{"action":"include","attributes":["matrix_connection.distance","matrix_connection.time"]}}
```

*Include only distance, time, begin heading and end heading*

```json
{"sources":[{"lat":40.744014,"lon":-73.990508}],"targets":[{"lat":40.744014,"lon":-73.990508},{"lat":40.739735,"lon":-73.979713}],"costing":"pedestrian","filters":{"action":"include","attributes":["matrix_connection.distance","matrix_connection.time","matrix_connection.begin_heading","matrix_connection.end_heading"]}}
```

*Exclude shape data and time zone name*

```json
{"sources":[{"lat":40.744014,"lon":-73.990508}],"targets":[{"lat":40.744014,"lon":-73.990508},{"lat":40.739735,"lon":-73.979713}],"costing":"pedestrian","filters":{"action":"exclude","attributes":["matrix_connection.shape","matrix_connection.time_zone_name"]}}
```
If no filters are specified, all available attributes are included in the response.

## Demonstration

[View an interactive demo](https://valhalla.github.io/demos/matrix//).
14 changes: 14 additions & 0 deletions src/baldr/attributes_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,20 @@ const std::unordered_map<std::string, bool> AttributesController::kDefaultAttrib
{kShapeAttributesSpeed, false},
{kShapeAttributesSpeedLimit, false},
{kShapeAttributesClosure, false},

// Matrix connection keys
{kMatrixConnectionDistance, true},
{kMatrixConnectionTime, true},
{kMatrixConnectionBeginHeading, true},
{kMatrixConnectionEndHeading, true},
{kMatrixConnectionBeginLat, true},
{kMatrixConnectionBeginLon, true},
{kMatrixConnectionEndLat, true},
{kMatrixConnectionEndLon, true},
{kMatrixConnectionDateTime, true},
{kMatrixConnectionTimeZoneOffset, true},
{kMatrixConnectionTimeZoneName, true},
{kMatrixConnectionShape, false},
};

AttributesController::AttributesController() {
Expand Down
5 changes: 3 additions & 2 deletions src/thor/matrix_action.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ std::string thor_worker_t::matrix(Api& request) {
auto _ = measure_scope_time(request);

auto& options = *request.mutable_options();
controller = AttributesController(options);
adjust_scores(options);
auto costing = parse_costing(request);

Expand Down Expand Up @@ -119,7 +120,7 @@ std::string thor_worker_t::matrix(Api& request) {
if (algo->name() != "costmatrix") {
algo->SourceToTarget(request, *reader, mode_costing, mode,
max_matrix_distance.find(costing)->second);
return tyr::serializeMatrix(request);
return tyr::serializeMatrix(request, controller);
}

// for costmatrix try a second pass if the first didn't work out
Expand All @@ -146,7 +147,7 @@ std::string thor_worker_t::matrix(Api& request) {
add_warning(request, 400, get_unfound_indices(request.matrix().second_pass()));
};

return tyr::serializeMatrix(request);
return tyr::serializeMatrix(request, controller);
}
} // namespace thor
} // namespace valhalla
90 changes: 57 additions & 33 deletions src/tyr/matrix_serializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ void locations(const google::protobuf::RepeatedPtrField<valhalla::Location>& loc

void serialize_row(const valhalla::Matrix& matrix,
rapidjson::writer_wrapper_t& writer,
const AttributesController& controller,
size_t start_td,
const size_t td_count,
const size_t source_index,
Expand All @@ -151,46 +152,60 @@ void serialize_row(const valhalla::Matrix& matrix,
const auto& begin_heading = matrix.begin_heading()[i];
const auto& end_heading = matrix.end_heading()[i];
writer.start_object();

if (time != kMaxCost) {
writer("from_index", source_index);
writer("to_index", target_index + (i - start_td));
writer("time", static_cast<uint64_t>(time));
writer("distance", static_cast<double>(matrix.distances()[i] * distance_scale));
if (!date_time.empty()) {

if (controller(kMatrixConnectionTime)) {
writer("time", static_cast<uint64_t>(time));
}

if (controller(kMatrixConnectionDistance)) {
writer("distance", static_cast<double>(matrix.distances()[i] * distance_scale));
}

if (controller(kMatrixConnectionDateTime) && !date_time.empty()) {
writer("date_time", date_time);
}

if (!time_zone_offset.empty()) {
if (controller(kMatrixConnectionTimeZoneOffset) && !time_zone_offset.empty()) {
writer("time_zone_offset", time_zone_offset);
}

if (!time_zone_name.empty()) {
if (controller(kMatrixConnectionTimeZoneName) && !time_zone_name.empty()) {
writer("time_zone_name", time_zone_name);
}

writer.set_precision(1);
if (begin_heading != kInvalidHeading) {
if (controller(kMatrixConnectionBeginHeading) && begin_heading != kInvalidHeading) {
writer("begin_heading", begin_heading);
}

if (end_heading != kInvalidHeading) {
if (controller(kMatrixConnectionEndHeading) && end_heading != kInvalidHeading) {
writer("end_heading", end_heading);
}

writer.set_precision(tyr::kCoordinatePrecision);
if (begin_lat != INVALID_LL) {
if (controller(kMatrixConnectionBeginLat) && begin_lat != INVALID_LL) {
writer("begin_lat", begin_lat);
}
if (begin_lon != INVALID_LL) {

if (controller(kMatrixConnectionBeginLon) && begin_lon != INVALID_LL) {
writer("begin_lon", begin_lon);
}
if (end_lat != INVALID_LL) {

if (controller(kMatrixConnectionEndLat) && end_lat != INVALID_LL) {
writer("end_lat", end_lat);
}
if (end_lon != INVALID_LL) {

if (controller(kMatrixConnectionEndLon) && end_lon != INVALID_LL) {
writer("end_lon", end_lon);
}

writer.set_precision(tyr::kDefaultPrecision);
if (matrix.shapes().size() && shape_format != no_shape) {
if (matrix.shapes().size() &&
(controller(kMatrixConnectionShape) || shape_format != no_shape)) {
// TODO(nils): tdmatrices don't have "shape" support yet
if (!matrix.shapes()[i].empty()) {
switch (shape_format) {
Expand All @@ -207,15 +222,20 @@ void serialize_row(const valhalla::Matrix& matrix,
} else {
writer("from_index", source_index);
writer("to_index", target_index + (i - start_td));
writer("time", nullptr);
writer("distance", nullptr);
if (controller(kMatrixConnectionTime)) {
writer("time", nullptr);
}
if (controller(kMatrixConnectionDistance)) {
writer("distance", nullptr);
}
}
writer.end_object();
}
writer.end_array();
}

std::string serialize(const Api& request, double distance_scale) {
std::string
serialize(const Api& request, const AttributesController& controller, double distance_scale) {
rapidjson::writer_wrapper_t writer(4096);
writer.set_precision(tyr::kDefaultPrecision);
writer.start_object();
Expand All @@ -224,7 +244,7 @@ std::string serialize(const Api& request, double distance_scale) {
if (options.verbose()) {
writer.start_array("sources_to_targets");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
serialize_row(request.matrix(), writer, source_index * options.targets_size(),
serialize_row(request.matrix(), writer, controller, source_index * options.targets_size(),
options.targets_size(), source_index, 0, distance_scale, options.shape_format());
}
writer.end_array(); // sources_to_targets
Expand All @@ -238,27 +258,31 @@ std::string serialize(const Api& request, double distance_scale) {
} // slim it down
else {
writer.start_object("sources_to_targets");

writer.start_array("durations");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
const auto first_td = source_index * options.targets_size();
writer.start_array();
serialize_duration(request.matrix(), writer, first_td, options.targets_size());
if (controller(kMatrixConnectionTime)) {
writer.start_array("durations");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
const auto first_td = source_index * options.targets_size();
writer.start_array();
serialize_duration(request.matrix(), writer, first_td, options.targets_size());
writer.end_array();
}
writer.end_array();
}
writer.end_array();

writer.start_array("distances");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
const auto first_td = source_index * options.targets_size();
writer.start_array();
serialize_distance(request.matrix(), writer, first_td, options.targets_size(), distance_scale);
if (controller(kMatrixConnectionDistance)) {
writer.start_array("distances");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
const auto first_td = source_index * options.targets_size();
writer.start_array();
serialize_distance(request.matrix(), writer, first_td, options.targets_size(),
distance_scale);
writer.end_array();
}
writer.end_array();
}
writer.end_array();

if (!(options.shape_format() == no_shape ||
(request.matrix().algorithm() != Matrix::CostMatrix))) {
if ((controller(kMatrixConnectionShape) || options.shape_format() != no_shape) &&
request.matrix().algorithm() == Matrix::CostMatrix) {
Comment on lines +284 to +285
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition.

writer.start_array("shapes");
for (int source_index = 0; source_index < options.sources_size(); ++source_index) {
const auto first_td = source_index * options.targets_size();
Expand Down Expand Up @@ -292,7 +316,7 @@ std::string serialize(const Api& request, double distance_scale) {
namespace valhalla {
namespace tyr {

std::string serializeMatrix(Api& request) {
std::string serializeMatrix(Api& request, const AttributesController& controller) {
double distance_scale = (request.options().units() == Options::miles) ? kMilePerMeter : kKmPerMeter;

// error if we failed finding any connection
Expand All @@ -308,7 +332,7 @@ std::string serializeMatrix(Api& request) {
case Options_Format_osrm:
return osrm_serializers::serialize(request);
case Options_Format_json:
return valhalla_serializers::serialize(request, distance_scale);
return valhalla_serializers::serialize(request, controller, distance_scale);
case Options_Format_pbf:
return serializePbf(request);
default:
Expand Down
39 changes: 39 additions & 0 deletions test/attributes_controller.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,45 @@ TEST(AttrController, TestAdminAttributeEnabled) {
TryCategoryAttributeEnabled(controller, kAdminCategory, true);
}

TEST(AttrController, TestMatrixAttributeEnabled) {
AttributesController controller;

// Test default
TryCategoryAttributeEnabled(controller, kMatrixConnectionCategory, true);

// Test all matrix attributes disabled
controller.disable_all();
TryCategoryAttributeEnabled(controller, kMatrixConnectionCategory, false);

// Test one matrix attribute enabled
controller.attributes.at(kMatrixConnectionDistance) = true;
TryCategoryAttributeEnabled(controller, kMatrixConnectionCategory, true);

// Test some matrix attributes enabled
controller.disable_all();
controller.attributes.at(kMatrixConnectionBeginHeading) = true;
controller.attributes.at(kMatrixConnectionEndHeading) = true;
controller.attributes.at(kMatrixConnectionDistance) = false;
controller.attributes.at(kMatrixConnectionShape) = true;
TryCategoryAttributeEnabled(controller, kMatrixConnectionCategory, true);
}

TEST(AttrController, TestMatrixAttributesFiltering) {
// Test include filter action
valhalla::Options options;
options.set_filter_action(valhalla::FilterAction::include);
options.add_filter_attributes(kMatrixConnectionDistance);
options.add_filter_attributes(kMatrixConnectionTime);

AttributesController controller(options, true);

// Only included attributes should be enabled
EXPECT_TRUE(controller(kMatrixConnectionDistance));
EXPECT_TRUE(controller(kMatrixConnectionTime));
EXPECT_FALSE(controller(kMatrixConnectionBeginHeading));
EXPECT_FALSE(controller(kMatrixConnectionEndHeading));
}

} // namespace

int main(int argc, char* argv[]) {
Expand Down
Loading