[routing] OSM tag based time and turn penalties

Signed-off-by: Henry Sternberg <dev@bluelightmaps.com>
Co-Authored-By: eisa01 <eisa01@gmail.com>
Co-Authored-By: x7z4w <x7z4w@noreply.codeberg.org>
Co-Authored-By: Yannik Bloscheck <git@yannikbloscheck.com>
This commit is contained in:
Henry Sternberg
2025-07-28 17:48:44 +01:00
committed by x7z4w
parent daf2a7d8e7
commit c59b63d784
29 changed files with 1301 additions and 45 deletions

View File

@@ -186,6 +186,8 @@ set(SRC
restriction_writer.hpp
road_access_generator.cpp
road_access_generator.hpp
road_penalty_generator.cpp
road_penalty_generator.hpp
routing_city_boundaries_processor.cpp
routing_city_boundaries_processor.hpp
routing_helpers.cpp

View File

@@ -21,6 +21,7 @@
#include "generator/raw_generator.hpp"
#include "generator/restriction_generator.hpp"
#include "generator/road_access_generator.hpp"
#include "generator/road_penalty_generator.hpp"
#include "generator/routing_index_generator.hpp"
#include "generator/routing_world_roads_generator.hpp"
#include "generator/search_index_builder.hpp"
@@ -466,6 +467,7 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
string const restrictionsFilename = genInfo.GetIntermediateFileName(RESTRICTIONS_FILENAME);
string const roadAccessFilename = genInfo.GetIntermediateFileName(ROAD_ACCESS_FILENAME);
string const roadPenaltyFilename = genInfo.GetIntermediateFileName(ROAD_PENALTY_FILENAME);
BuildRoutingIndex(dataFile, country, *countryParentGetter);
auto routingGraph = CreateIndexGraph(dataFile, country, *countryParentGetter);
@@ -475,7 +477,8 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv)
/// @todo CHECK return result doesn't work now for some small countries like Somalie.
if (!BuildRoadRestrictions(*routingGraph, dataFile, restrictionsFilename, osmToFeatureFilename) ||
!BuildRoadAccessInfo(dataFile, roadAccessFilename, *osm2feature))
!BuildRoadAccessInfo(dataFile, roadAccessFilename, *osm2feature) ||
!BuildRoadPenalty(dataFile, roadPenaltyFilename, *osm2feature))
{
LOG(LERROR, ("Routing build failed for", dataFile));
}

View File

@@ -0,0 +1,253 @@
#include "generator/road_penalty_generator.hpp"
#include "generator/feature_builder.hpp"
#include "generator/intermediate_data.hpp"
#include "generator/osm_element.hpp"
#include "generator/routing_helpers.hpp"
#include "routing/road_penalty_serialization.hpp"
#include "routing/routing_helpers.hpp"
#include "routing/vehicle_mask.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/files_container.hpp"
#include "coding/internal/file_data.hpp"
#include "coding/varint.hpp"
#include "coding/write_to_sink.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include <optional>
namespace routing_builder
{
using namespace feature;
using namespace generator;
using namespace routing;
using std::string, std::vector;
namespace
{
// Unified penalty mapping for all OSM tags that create road penalties
std::map<OsmElement::Tag, RoadPenalty::Type> const kUnifiedPenaltyMapping = {
// Traffic calming measures
{OsmElement::Tag("traffic_calming", "rumble_strip"), RoadPenalty::Type::SmallCalming},
{OsmElement::Tag("traffic_calming", "island"), RoadPenalty::Type::SmallCalming},
{OsmElement::Tag("traffic_calming", "cushion"), RoadPenalty::Type::MediumCalming},
{OsmElement::Tag("traffic_calming", "chicane"), RoadPenalty::Type::MediumCalming},
{OsmElement::Tag("traffic_calming", "choker"), RoadPenalty::Type::MediumCalming},
{OsmElement::Tag("traffic_calming", "table"), RoadPenalty::Type::MediumCalming},
{OsmElement::Tag("traffic_calming", "bump"), RoadPenalty::Type::LargeCalming},
{OsmElement::Tag("traffic_calming", "hump"), RoadPenalty::Type::LargeCalming},
{OsmElement::Tag("crossing", "unmarked"), RoadPenalty::Type::LargeCalming},
// Barrier penalties
{OsmElement::Tag("barrier", "gate"), RoadPenalty::Type::Gate},
{OsmElement::Tag("barrier", "lift_gate"), RoadPenalty::Type::Gate},
{OsmElement::Tag("barrier", "swing_gate"), RoadPenalty::Type::Gate},
// Junction penalties
{OsmElement::Tag("highway", "stop"), RoadPenalty::Type::UncontrolledJunction},
{OsmElement::Tag("highway", "give_way"), RoadPenalty::Type::UncontrolledJunction},
{OsmElement::Tag("crossing", "uncontrolled"), RoadPenalty::Type::UncontrolledJunction},
{OsmElement::Tag("crossing", "marked"), RoadPenalty::Type::UncontrolledJunction},
// highway=crossing ommitted as often accompanied by traffic_signals
{OsmElement::Tag("highway", "traffic_signals"), RoadPenalty::Type::ControlledJunction},
{OsmElement::Tag("railway", "crossing"), RoadPenalty::Type::ControlledJunction},
{OsmElement::Tag("railway", "level_crossing"), RoadPenalty::Type::ControlledJunction},
};
std::optional<RoadPenalty::Type> GetPenaltyByMapping(OsmElement const & elem)
{
for (auto const & tag : elem.m_tags)
{
auto const it = kUnifiedPenaltyMapping.find(tag);
if (it != kUnifiedPenaltyMapping.cend())
return it->second;
}
return {};
}
void ReadRoadPenalty(std::string const & penaltyPath, routing::OsmWay2FeaturePoint & way2Feature,
RoadPenaltyByVehicleType & penalties)
{
FileReader reader(penaltyPath);
ReaderSource src(reader);
// Read node penalties grouped by way
uint64_t nodeWayCount = ReadPrimitiveFromSource<uint64_t>(src);
while (nodeWayCount-- > 0)
{
uint64_t wayID = ReadPrimitiveFromSource<uint64_t>(src);
uint32_t nodeCount = ReadPrimitiveFromSource<uint32_t>(src);
for (uint32_t i = 0; i < nodeCount; ++i)
{
uint32_t nodeIdx = ReadPrimitiveFromSource<uint32_t>(src);
m2::PointU coord;
coord.x = ReadPrimitiveFromSource<uint32_t>(src);
coord.y = ReadPrimitiveFromSource<uint32_t>(src);
VehicleMask vehicleMask = ReadPrimitiveFromSource<VehicleMask>(src);
// Read penalty type and derive time based on vehicle type
RoadPenalty::Type type;
routing::Load(src, type);
// For each vehicle type, create penalty with vehicle-specific time
for (uint8_t vType = 0; vType < static_cast<uint8_t>(VehicleType::Count); ++vType)
{
if (vehicleMask & (1 << vType))
{
// Create penalty with time specific to this vehicle type
auto vehicleSpecificPenalty = RoadPenalty::Penalty(type, static_cast<VehicleType>(vType));
// Process this location only once
RoadPenalty::PointToPenalty pointToPenalty;
way2Feature.ForEachNodeIdx(wayID, nodeIdx, coord, [&](uint32_t featureID, uint32_t nodeIdx)
{ pointToPenalty[RoadPoint(featureID, nodeIdx)] = vehicleSpecificPenalty; });
if (!pointToPenalty.empty())
{
auto existingPenalties = penalties[vType].GetPointToPenalty();
existingPenalties.insert(pointToPenalty.begin(), pointToPenalty.end());
penalties[vType].SetPointPenalties(std::move(existingPenalties));
}
}
}
}
}
}
} // namespace
// RoadPenaltyCollector implementation
RoadPenaltyCollector::RoadPenaltyCollector(string const & filename, IDRInterfacePtr cache)
: generator::CollectorInterface(filename)
, m_cache(std::move(cache))
{
// Empty - initialization handled in member initializer list
}
std::shared_ptr<CollectorInterface> RoadPenaltyCollector::Clone(IDRInterfacePtr const & cache) const
{
return std::make_shared<RoadPenaltyCollector>(GetFilename(), cache ? cache : m_cache);
}
void RoadPenaltyCollector::CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & elem)
{
// Track roads for barrier processing
if (elem.IsWay() && routing::IsCarRoad(fb.GetTypes()))
m_roads.AddWay(elem);
// Process only nodes for penalty types
if (elem.IsNode())
{
auto penaltyType = GetPenaltyByMapping(elem);
if (penaltyType)
m_nodesWithType.Add(elem.m_id, ms::LatLon(elem.m_lat, elem.m_lon), *penaltyType);
}
}
void RoadPenaltyCollector::MergeInto(RoadPenaltyCollector & collector) const
{
m_nodesWithType.MergeInto(collector.m_nodesWithType);
m_roads.MergeInto(collector.m_roads);
}
void RoadPenaltyCollector::Save()
{
auto const fileName = GetFilename();
LOG(LINFO, ("Saving road penalty values to", fileName));
FileWriter writer(fileName);
// All vehicles use the same penalty types
// Group node penalties by way
struct NodePenaltyEntry
{
uint32_t nodeIdx = 0;
m2::PointU coord;
RoadPenalty::Type type = RoadPenalty::Type::None;
};
std::map<uint64_t, std::vector<NodePenaltyEntry>> penaltiesByWay;
m_roads.ForEachWayWithIndex([&](uint64_t wayID, std::vector<uint64_t> const & nodes, size_t idx)
{
std::vector<NodePenaltyEntry> wayNodePenalties;
for (uint32_t nodeIdx = 0; nodeIdx < nodes.size(); ++nodeIdx)
{
uint64_t const nodeID = nodes[nodeIdx];
auto const * barrierEntry = m_nodesWithType.Find(nodeID);
if (barrierEntry == nullptr)
continue;
NodePenaltyEntry entry;
entry.nodeIdx = nodeIdx;
entry.coord = barrierEntry->m_coord;
entry.type = barrierEntry->m_t;
wayNodePenalties.push_back(entry);
}
if (!wayNodePenalties.empty())
penaltiesByWay[wayID] = std::move(wayNodePenalties);
}, m_cache);
// Write node penalties grouped by way
uint64_t const wayCount = penaltiesByWay.size();
WriteToSink(writer, wayCount);
for (auto const & [wayID, entries] : penaltiesByWay)
{
WriteToSink(writer, wayID);
WriteToSink(writer, static_cast<uint32_t>(entries.size()));
for (auto const & entry : entries)
{
WriteToSink(writer, entry.nodeIdx);
WriteToSink(writer, entry.coord.x);
WriteToSink(writer, entry.coord.y);
WriteToSink(writer, kAllVehiclesMask);
// Store only penalty type (time derived from vehicle type when loading)
routing::Save(writer, entry.type);
}
}
}
bool BuildRoadPenalty(string const & dataFilePath, string const & roadPenaltyPath,
routing::OsmWay2FeaturePoint & way2feature)
{
LOG(LINFO, ("Generating road penalty info for", dataFilePath));
try
{
RoadPenaltyByVehicleType penalties;
ReadRoadPenalty(roadPenaltyPath, way2feature, penalties);
FilesContainerW cont(dataFilePath, FileWriter::OP_WRITE_EXISTING);
auto writer = cont.GetWriter(ROAD_PENALTY_FILE_TAG);
// Write number of vehicle types
uint32_t const vehicleTypeCount = penalties.size();
WriteToSink(*writer, vehicleTypeCount);
// Write penalty data for each vehicle type
for (size_t i = 0; i < penalties.size(); ++i)
RoadPenaltySerializer::Serialize(*writer, penalties[i]);
return true;
}
catch (RootException const & ex)
{
LOG(LWARNING, ("No road penalty created:", ex.Msg()));
return false;
}
}
} // namespace routing_builder

View File

@@ -0,0 +1,70 @@
#pragma once
#include "generator/collector_interface.hpp"
#include "generator/feature_builder.hpp"
#include "generator/osm_element.hpp"
#include "generator/way_nodes_mapper.hpp"
#include "routing/road_penalty.hpp"
#include "routing/vehicle_mask.hpp"
#include <array>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
namespace routing
{
class OsmWay2FeaturePoint;
template <class Sink>
void Save(Sink & sink, RoadPenalty::Type const & type)
{
WriteToSink(sink, static_cast<uint8_t>(type));
}
template <class Source>
void Load(Source & src, RoadPenalty::Type & type)
{
uint8_t const res = ReadPrimitiveFromSource<uint8_t>(src);
CHECK_LESS(res, static_cast<uint8_t>(RoadPenalty::Type::Count), ());
type = static_cast<RoadPenalty::Type>(res);
}
} // namespace routing
namespace routing_builder
{
using RoadPenalty = routing::RoadPenalty;
using VehicleType = routing::VehicleType;
using RoadPenaltyByVehicleType = std::array<RoadPenalty, static_cast<size_t>(VehicleType::Count)>;
class RoadPenaltyCollector : public generator::CollectorInterface
{
public:
RoadPenaltyCollector(std::string const & filename, IDRInterfacePtr cache);
std::shared_ptr<CollectorInterface> Clone(IDRInterfacePtr const & cache = {}) const override;
void CollectFeature(feature::FeatureBuilder const & fb, OsmElement const & elem) override;
IMPLEMENT_COLLECTOR_IFACE(RoadPenaltyCollector);
void MergeInto(RoadPenaltyCollector & collector) const;
protected:
void Save() override;
private:
IDRInterfacePtr m_cache;
// Store only penalty types during collection phase
generator::WayNodesMapper<RoadPenalty::Type> m_nodesWithType;
generator::WaysIDHolder m_roads;
};
bool BuildRoadPenalty(std::string const & dataFilePath, std::string const & roadPenaltyPath,
routing::OsmWay2FeaturePoint & way2feature);
} // namespace routing_builder

View File

@@ -88,9 +88,16 @@ public:
{
auto ft = m_featureGetter.GetFeatureByIndex(featureID);
CHECK(ft, (featureID));
CHECK(ft->GetGeomType() == feature::GeomType::Line, (featureID));
// Converison should work with the same logic as in WayNodesMapper::EncodePoint.
// Skip non-line features (e.g., barriers on area boundaries)
if (ft->GetGeomType() != feature::GeomType::Line)
{
// Use LDEBUG to reduce log spam for features processed multiple times per vehicle type
LOG(LDEBUG, ("Skipping non-line feature", featureID, "for node penalty mapping"));
continue;
}
// Conversion should work with the same logic as in WayNodesMapper::EncodePoint.
auto const mercatorPt = PointUToPointD(pt, kPointCoordBits, fullRect);
double minSquareDist = 1.0E6;

View File

@@ -21,6 +21,7 @@
// #include "generator/node_mixer.hpp"
#include "generator/restriction_writer.hpp"
#include "generator/road_access_generator.hpp"
#include "generator/road_penalty_generator.hpp"
#include "platform/platform.hpp"
@@ -107,6 +108,8 @@ TranslatorCountry::TranslatorCountry(std::shared_ptr<FeatureProcessorInterface>
info.GetIntermediateFileName(RESTRICTIONS_FILENAME), cache->GetCache()));
collectors->Append(std::make_shared<routing_builder::RoadAccessCollector>(
info.GetIntermediateFileName(ROAD_ACCESS_FILENAME), cache->GetCache()));
collectors->Append(std::make_shared<routing_builder::RoadPenaltyCollector>(
info.GetIntermediateFileName(ROAD_PENALTY_FILENAME), cache->GetCache()));
collectors->Append(std::make_shared<routing_builder::CameraCollector>(
info.GetIntermediateFileName(CAMERAS_TO_WAYS_FILENAME), cache->GetCache()));
collectors->Append(std::make_shared<MiniRoundaboutCollector>(info.GetIntermediateFileName(MINI_ROUNDABOUTS_FILENAME),

View File

@@ -146,6 +146,13 @@ public:
void MergeInto(WaysMapper & mapper) const { mapper.m_ways.insert(m_ways.begin(), m_ways.end()); }
template <class Fn>
void ForEach(Fn && fn) const
{
for (auto const & [id, value] : m_ways)
fn(id, value);
}
template <class Sink>
void Serialize(Sink & sink) const
{