Skip to content

Commit

Permalink
Add headings and correlated lat/lon to verbose matrix output (valhall…
Browse files Browse the repository at this point in the history
  • Loading branch information
chrstnbwnkl authored Jan 31, 2025
1 parent d1f472a commit 868cb5d
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* ADDED: Australian English language translations [#5057](https://github.com/valhalla/valhalla/pull/5057)
* ADDED: Support `"access:conditional"` conditional restrictions like `"access:conditional"="no @ (Oct-May)"` [#5048](https://github.com/valhalla/valhalla/pull/5048)
* CHANGED: Speed up pbf parsing by using libosmium [#5070](https://github.com/valhalla/valhalla/pull/5070)
* ADDED: headings and correlated ll's in verbose matrix output [#5072](https://github.com/valhalla/valhalla/pull/5072)

## Release Date: 2024-10-10 Valhalla 3.5.1
* **Removed**
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/api/matrix/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ The following parameters are only present in `"verbose": true` mode:
| :---- | :----------- |
| `sources` | The sources passed to the request. |
| `targets` | The targets passed to the request. |
| `sources_to_targets` | An array of time and distance between the sources and the targets.<br>The array is <b>row-ordered</b>, meaning the time and distance from the first location to all others forms the first row of the array, followed by the time and distance from the second source location to all target locations, etc.<br>The Object contained in the arrays contains the following fields:<ul><li><code>distance</code>: The computed distance between each set of points. Distance will always be 0.00 for the first element of the time-distance array for <code>one_to_many</code>, the last element in a <code>many_to_one</code>, and the first and last elements of a <code>many_to_many</code>.</li><li><code>time</code>: The computed time between each set of points. Time will always be 0 for the first element of the time-distance array for <code>one_to_many</code>, the last element in a <code>many_to_one</code>, and the first and last elements of a <code>many_to_many</code>.</li><li><code>to_index</code>: The destination index into the locations array.</li><li><code>from_index</code>: The origin index into the locations array.</li><li><code>date_time</code>: When a user will arrive at/depart from this location. See <a href="#time-dependent-matrices">the part above</a> where we explain how time dependent matices work for further context.<br> Note: If the time is above the setting <code>max_timedep_distance_matrix</code> this is skipped.<br>Note: If the departure/arrival time is unspecified it is not computed.</li><li><code>time_zone_offset</code>, <code>time_zone_name</code>: time zone at the target location. See <a href="#time-dependent-matrices">here</a> on requesting time dependent matrices. Note: this is skipped if the time is greater than <code>max_timedep_distance_matrix</code> or no route was found for the location pair.</li></ul> |
| `sources_to_targets` | An array of time and distance between the sources and the targets.<br>The array is <b>row-ordered</b>, meaning the time and distance from the first location to all others forms the first row of the array, followed by the time and distance from the second source location to all target locations, etc.<br>The Object contained in the arrays contains the following fields:<ul><li><code>distance</code>: The computed distance between each set of points. Distance will always be 0.00 for the first element of the time-distance array for <code>one_to_many</code>, the last element in a <code>many_to_one</code>, and the first and last elements of a <code>many_to_many</code>.</li><li><code>time</code>: The computed time between each set of points. Time will always be 0 for the first element of the time-distance array for <code>one_to_many</code>, the last element in a <code>many_to_one</code>, and the first and last elements of a <code>many_to_many</code>.</li><li><code>to_index</code>: The destination index into the locations array.</li><li><code>from_index</code>: The origin index into the locations array.</li><li><code>date_time</code>: When a user will arrive at/depart from this location. See <a href="#time-dependent-matrices">the part above</a> where we explain how time dependent matices work for further context.<br> Note: If the time is above the setting <code>max_timedep_distance_matrix</code> this is skipped.<br>Note: If the departure/arrival time is unspecified it is not computed.</li><li><code>time_zone_offset</code>, <code>time_zone_name</code>: time zone at the target location. See <a href="#time-dependent-matrices">here</a> on requesting time dependent matrices. Note: this is skipped if the time is greater than <code>max_timedep_distance_matrix</code> or no route was found for the location pair.</li><li>`begin_heading` **beta**: the heading at the beginning of path in degrees</li><li>`end_heading` **beta**: the heading at the end of the path in degrees</li><li>`begin_lat` **beta**: the latitude of the correlated source location for this connection</li><li>`begin_lon` **beta**: the longitude of the correlated source location for this connection</li><li>`begin_lat` **beta**: the latitude of the correlated target location for this connection</li><li>`begin_lon` **beta**: the longitude of the correlated target location for this connection</li></ul> |

### Concise mode (`"verbose": false`)

Expand Down
6 changes: 6 additions & 0 deletions proto/matrix.proto
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ message Matrix {
repeated string time_zone_offsets = 9;
repeated string time_zone_names = 10;
repeated bool second_pass = 11;
repeated float begin_heading = 12;
repeated float end_heading = 13;
repeated double begin_lat = 14;
repeated double begin_lon = 15;
repeated double end_lat = 16;
repeated double end_lon = 17;
}
56 changes: 40 additions & 16 deletions src/thor/costmatrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ bool CostMatrix::SourceToTarget(Api& request,
const float max_matrix_distance) {
request.mutable_matrix()->set_algorithm(Matrix::CostMatrix);
bool invariant = request.options().date_time_type() == Options::invariant;
auto shape_format = request.options().shape_format();

// Set the mode and costing
mode_ = mode;
Expand Down Expand Up @@ -242,7 +241,7 @@ bool CostMatrix::SourceToTarget(Api& request,

// resize/reserve all properties of Matrix on first pass only
valhalla::Matrix& matrix = *request.mutable_matrix();
reserve_pbf_arrays(matrix, best_connection_.size(), costing_->pass());
reserve_pbf_arrays(matrix, best_connection_.size(), request.options().verbose(), costing_->pass());

// Form the matrix PBF output
graph_tile_ptr tile;
Expand All @@ -256,10 +255,8 @@ bool CostMatrix::SourceToTarget(Api& request,
uint32_t target_idx = connection_idx % target_location_list.size();
uint32_t source_idx = connection_idx / target_location_list.size();

// first recost and form the path, if desired (either time and/or geometry requested)
const auto shape = RecostFormPath(graphreader, best_connection, source_location_list[source_idx],
target_location_list[target_idx], source_idx, target_idx,
time_infos[source_idx], invariant, shape_format);
std::string shape = RecostFormPath(graphreader, best_connection, request, source_idx, target_idx,
connection_idx, time_infos[source_idx], invariant);

float time = best_connection.cost.secs;
if (time < kMaxCost && request.options().verbose()) {
Expand All @@ -282,6 +279,7 @@ bool CostMatrix::SourceToTarget(Api& request,
matrix.mutable_second_pass()->Set(connection_idx, true);
connection_failed = true;
}

matrix.mutable_from_indices()->Set(connection_idx, source_idx);
matrix.mutable_to_indices()->Set(connection_idx, target_idx);
matrix.mutable_distances()->Set(connection_idx, best_connection.distance);
Expand Down Expand Up @@ -1190,16 +1188,15 @@ void CostMatrix::SetTargets(baldr::GraphReader& graphreader,
// Form the path from the edfge labels and optionally return the shape
std::string CostMatrix::RecostFormPath(GraphReader& graphreader,
BestCandidate& connection,
const valhalla::Location& source,
const valhalla::Location& target,
Api& request,
const uint32_t source_idx,
const uint32_t target_idx,
const uint32_t connection_idx,
const baldr::TimeInfo& time_info,
const bool invariant,
const ShapeFormat shape_format) {
const bool invariant) {
// no need to look at source == target or missing connectivity
if ((!has_time_ && shape_format == no_shape) || connection.cost.secs == 0.f ||
connection.distance == kMaxCost) {
if ((!has_time_ && request.options().shape_format() == no_shape && !request.options().verbose()) ||
connection.cost.secs == 0.f || connection.distance == kMaxCost) {
return "";
}

Expand Down Expand Up @@ -1257,8 +1254,10 @@ std::string CostMatrix::RecostFormPath(GraphReader& graphreader,
path_edges.emplace_back(std::move(opp_edge_id));
}

const auto& source_edge = find_correlated_edge(source, path_edges.front());
const auto& target_edge = find_correlated_edge(target, path_edges.back());
const auto& source_edge =
find_correlated_edge(request.options().sources(source_idx), path_edges.front());
const auto& target_edge =
find_correlated_edge(request.options().targets(target_idx), path_edges.back());
float source_pct = static_cast<float>(source_edge.percent_along());
float target_pct = static_cast<float>(target_edge.percent_along());

Expand All @@ -1284,9 +1283,34 @@ std::string CostMatrix::RecostFormPath(GraphReader& graphreader,
// update the existing best_connection cost
connection.cost = new_cost;
}
if (request.options().verbose()) {

request.mutable_matrix()->mutable_begin_lat()->Set(connection_idx, source_edge.ll().lat());
request.mutable_matrix()->mutable_begin_lon()->Set(connection_idx, source_edge.ll().lng());
request.mutable_matrix()->mutable_end_lat()->Set(connection_idx, source_edge.ll().lat());
request.mutable_matrix()->mutable_end_lon()->Set(connection_idx, source_edge.ll().lng());

// get begin/end heading using the path's begin/end edge shapes
const DirectedEdge* start_edge =
graphreader.directededge(static_cast<GraphId>(source_edge.graph_id()), tile);
std::vector<PointLL> shp = tile->edgeinfo(start_edge).shape();
if (!start_edge->forward())
std::reverse(shp.begin(), shp.end());
request.mutable_matrix()
->mutable_begin_heading()
->Set(connection_idx, PointLL::HeadingAlongPolyline(shp, start_edge->length() * source_pct));
const DirectedEdge* end_edge =
graphreader.directededge(static_cast<GraphId>(target_edge.graph_id()), tile);
shp = tile->edgeinfo(end_edge).shape();
if (!end_edge->forward())
std::reverse(shp.begin(), shp.end());
request.mutable_matrix()
->mutable_end_heading()
->Set(connection_idx, PointLL::HeadingAlongPolyline(shp, end_edge->length() * target_pct));
}

// bail if no shape was requested
if (shape_format == no_shape)
if (request.options().shape_format() == no_shape)
return "";

auto source_vertex = PointLL{source_edge.ll().lng(), source_edge.ll().lat()};
Expand Down Expand Up @@ -1325,7 +1349,7 @@ std::string CostMatrix::RecostFormPath(GraphReader& graphreader,
}

// encode to 6 precision for geojson as well, which the serializer expects
return encode<decltype(points)>(points, shape_format != polyline5 ? 1e6 : 1e5);
return encode<decltype(points)>(points, request.options().shape_format() != polyline5 ? 1e6 : 1e5);
}

template <const MatrixExpansionType expansion_direction, const bool FORWARD>
Expand Down
4 changes: 3 additions & 1 deletion src/thor/timedistancebssmatrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,10 @@ bool TimeDistanceBSSMatrix::ComputeMatrix(Api& request,

// Initialize destinations once for all origins
InitDestinations<expansion_direction>(graphreader, destinations);

// reserve the PBF vectors
reserve_pbf_arrays(*request.mutable_matrix(), origins.size() * destinations.size());
reserve_pbf_arrays(*request.mutable_matrix(), origins.size() * destinations.size(),
request.options().verbose());

for (int origin_index = 0; origin_index < origins.size(); ++origin_index) {
edgelabels_.reserve(max_reserved_labels_count_);
Expand Down
3 changes: 2 additions & 1 deletion src/thor/timedistancematrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ bool TimeDistanceMatrix::ComputeMatrix(Api& request,
// Initialize destinations once for all origins
InitDestinations<expansion_direction>(graphreader, destinations);
// reserve the PBF vectors
reserve_pbf_arrays(*request.mutable_matrix(), num_elements, costing_->pass());
reserve_pbf_arrays(*request.mutable_matrix(), num_elements, request.options().verbose(),
costing_->pass());

for (int origin_index = 0; origin_index < origins.size(); ++origin_index) {
// reserve some space for the next dijkstras (will be cleared at the end of the loop)
Expand Down
25 changes: 25 additions & 0 deletions src/tyr/matrix_serializer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ json::ArrayPtr serialize_row(const valhalla::Matrix& matrix,
const auto& date_time = matrix.date_times()[i];
const auto& time_zone_offset = matrix.time_zone_offsets()[i];
const auto& time_zone_name = matrix.time_zone_names()[i];
const auto& begin_lat = matrix.begin_lat()[i];
const auto& begin_lon = matrix.begin_lon()[i];
const auto& end_lat = matrix.end_lat()[i];
const auto& end_lon = matrix.end_lon()[i];
const auto& begin_heading = matrix.begin_heading()[i];
const auto& end_heading = matrix.end_heading()[i];
if (time != kMaxCost) {
map = json::map({{"from_index", static_cast<uint64_t>(source_index)},
{"to_index", static_cast<uint64_t>(target_index + (i - start_td))},
Expand All @@ -156,6 +162,25 @@ json::ArrayPtr serialize_row(const valhalla::Matrix& matrix,
map->emplace("time_zone_name", time_zone_name);
}

if (begin_heading != kInvalidHeading) {
map->emplace("begin_heading", json::fixed_t{begin_heading, 0});
}

if (end_heading != kInvalidHeading) {
map->emplace("end_heading", json::fixed_t{end_heading, 0});
}
if (begin_lat != INVALID_LL) {
map->emplace("begin_lat", json::fixed_t{begin_lat, 6});
}
if (begin_lon != INVALID_LL) {
map->emplace("begin_lon", json::fixed_t{begin_lon, 6});
}
if (end_lat != INVALID_LL) {
map->emplace("end_lat", json::fixed_t{end_lat, 6});
}
if (end_lon != INVALID_LL) {
map->emplace("end_lon", json::fixed_t{end_lon, 6});
}
if (matrix.shapes().size() && shape_format != no_shape) {
// TODO(nils): tdmatrices don't have "shape" support yet
if (!matrix.shapes()[i].empty()) {
Expand Down
100 changes: 100 additions & 0 deletions test/gurka/test_matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,106 @@ TEST(StandAlone, HGVNoAccessPenalty) {
}
}

TEST(StandAlone, VerboseResponse) {

const std::string ascii_map = R"(
A-1-------B----C----D----E--2-------F
| |
J----K
| |
| |
3 4
| |
L----M
)";

const gurka::ways ways = {
{"AB", {{"highway", "residential"}}}, {"BC", {{"highway", "residential"}}},
{"CD", {{"highway", "residential"}}}, {"DE", {{"highway", "residential"}}},
{"FE", {{"highway", "residential"}}}, {"CJ", {{"highway", "residential"}}},
{"JK", {{"highway", "residential"}}}, {"JLMK", {{"highway", "residential"}}},
{"KD", {{"highway", "residential"}}},
};

const auto layout = gurka::detail::map_to_coordinates(ascii_map, 100);
gurka::map map = gurka::buildtiles(layout, ways, {}, {}, "test/data/matrix_verbose_response",
{{"service_limits.max_timedep_distance_matrix", "50000"}});
rapidjson::Document res_doc;
std::string res;
{
auto api = gurka::do_action(valhalla::Options::sources_to_targets, map, {"1", "2", "3", "4"},
{"1", "2", "3", "4"}, "auto", {{"/prioritize_bidirectional", "1"}},
nullptr, &res);

res_doc.Parse(res.c_str());

// sanity check
EXPECT_EQ(api.matrix().algorithm(), Matrix::CostMatrix);

for (size_t i = 0; i < 4; ++i) {
for (size_t j = 0; j < 4; ++j) {
bool key_should_exist = true;
if (i == j)
key_should_exist = false;
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"begin_heading"),
key_should_exist);
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"end_heading"),
key_should_exist);
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"begin_lat"),
key_should_exist);
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"begin_lon"),
key_should_exist);
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"end_lat"),
key_should_exist);
EXPECT_EQ(res_doc["sources_to_targets"].GetArray()[i].GetArray()[j].GetObject().HasMember(
"end_lon"),
key_should_exist);
}
}
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[0]
.GetArray()[1]
.GetObject()["begin_heading"]
.GetDouble(),
90);
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[0]
.GetArray()[1]
.GetObject()["end_heading"]
.GetDouble(),
90);
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[1]
.GetArray()[0]
.GetObject()["begin_heading"]
.GetDouble(),
270);
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[1]
.GetArray()[0]
.GetObject()["end_heading"]
.GetDouble(),
270);
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[0]
.GetArray()[2]
.GetObject()["begin_heading"]
.GetDouble(),
90);
EXPECT_EQ(res_doc["sources_to_targets"]
.GetArray()[0]
.GetArray()[2]
.GetObject()["end_heading"]
.GetDouble(),
180);
}
}

/************************************************************************ */

std::string encode_shape(const std::vector<std::string>& nodes, valhalla::gurka::nodelayout& layout) {
Expand Down
10 changes: 8 additions & 2 deletions test/matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -486,13 +486,19 @@ TEST(Matrix, default_matrix) {

EXPECT_TRUE(json.HasMember("sources_to_targets"));

// contains 4 keys i.e, "distance", "time", "to_index" and "from_index"
EXPECT_EQ(json["sources_to_targets"].GetArray()[0][0].MemberCount(), 4);
// contains 10 keys
EXPECT_EQ(json["sources_to_targets"].GetArray()[0][0].MemberCount(), 10);

EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("distance"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("time"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("to_index"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("from_index"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("begin_heading"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("end_heading"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("begin_lat"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("begin_lon"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("end_lat"));
EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].HasMember("end_lon"));

EXPECT_TRUE(json["sources_to_targets"].GetArray()[0][0].IsObject());

Expand Down
Loading

0 comments on commit 868cb5d

Please sign in to comment.