MSP implementation guide
The role of Map Support Package (MSP) components is to convert different map formats into Foretify’s internal map representation. A specific MSP component should be implemented per map format. This conversion happens in the implementation of the map_support_package.load_map(map_info: string, traffic_side_request: traffic_side_request)-> bool method, where the implementer uses a set of provided OSC API methods, the msp_api, to populate Foretify’s map.
Foretify map is made of a basic layer of lane and road graphs with additional layers of supplementary data that associate paths on the map with sets of properties, e.g., classification by curvature, description of junctions, and so on. The supplementary data is modeled as road_elements.
map_support_package.load_map() populates the basic layer. See the Step-by-step implementation example for details. The MSP interface also provides hooks to add MSP-specific road_elements - map_support_package.mark_road_elements() and map_support_package.mark_volatile_road_elements().
Foretify internal map representation (the basic layer)
The basic map units are msp_roads and msp_lanes:
-
msp_road: A longitudinal interval of a one-way road or of one side of a two-way road (i.e., including all its lanes) with a constant number of lanes
- ‘lanes’ in this context include driving-lanes, side-walks, road-sides, and so on.
-
msp_lane: A single lane along an msp_road
The following image illustrates some msp_roads highlighted with blue borders with their msp_lanes highlighted with green borders.
Map topology
Map topology is represented by two directed graphs—a directed graph of msp_lanes and a directed graph of msp_roads. Graph directionality corresponds to traffic driving direction. Following are some requirements and details of msp_lanes and msp_roads.
-
msp_lanes in an msp_road are ordered from right to left (relative to driving direction).
-
msp_road \(R_b\) is a next of msp_road \(R_a\) if and only if some msp_lane \(R_b.lanes[i]\) is a next of msp_lane \(R_a.lanes[j]\).
-
msp_lanes (and subsequently msp_roads) may only connect at their ends.
-
An msp_lane may have at most one next msp_lane per next msp_road.
-
An msp_lane may have at most one previous msp_lane per previous msp_road.
Topology semantics
Topology corresponds to movement in the following manners:
-
Normally driving from msp_road \(R_a\) to \(R_b\) is possible/allowed only if \(R_b\) is next of \(R_a\).
-
Normally driving from msp_lane \(L_a\) to \(L_b\) is possible/allowed only if at least one of the following is true:
-
\(L_b\) is next of \(L_a\)
-
\(L_a\) and \(L_b\) are neighbors in the same msp_road, i.e., \(L_a=R.lanes[i]\) and \(L_b=R.lanes[i+1]\) for some msp_road \(R\).
-
In this case, movement is considered to be a “lane change.”
-
An edge case of a “lane change” is if \(L_c\) is a next of \(L_a\) and a neighbor of \(L_b\).
-
Map geometry
Map geometry is represented in a 2D Euclidean plane with supplementary data on surface elevation in the corresponding 3D Euclidean space.
Length
The nominal length of an msp_lane is the length of the reference line going through the center of the lane (projected to the x,y-plane). Each msp_lane node in the graph contains its nominal length.
The nominal length of an msp_road is not well defined. It should represent some reference line describing the road’s shape (projected to the x,y-plane). Each msp_road node in the graph contains its nominal length.
A bijection between longitudinal-offset on each msp_lane to the corresponding offset on its msp_road must be provided. See the msp_api method msp_lane.add_point() in the msp_api (msp_api.osc) section.
Shape
Shape of the lanes is represented as a polyline that describes the lane’s center-line. Each polyline point must be provided with:
-
Global x,y,z coordinates
-
Lane width projected to the x,y-plane
See the msp_api method msp_lane.add_point() in the msp_api (msp_api.osc) section.
Geometry and connectivity
Topologically connected lanes are required to be geometrically connected as well, i.e., if msp_lane \(L_b\) is a next of msp_lane \(L_a\) then \(global\_coordinate(L_a, L_a.length) == global\_coordinate(L_b, 0)\).
Restrictions
Following are restrictions on msp_lanes.
-
msp_lanes in the same msp_road must not overlap.
-
Gaps between msp_lanes in the same msp_road are forbidden, i.e., any point inside an msp_road is necessarily on one of its msp_lanes.
Opposite roads (other side of a two-way road)
Information on opposite msp_roads (opposite sides of a two-way road) must be provided via the msp_api. An msp_road may have at most one opposite msp_road and this relation is symmetric.
Information on opposite msp_roads is used by higher-level constructs (e.g., road elements) to enable definition of scenarios in opposite driving directions.
Junctions
Following are possible relations between msp_road and junctions:
-
An msp_road may enter at most one junction.
-
An msp_road may exit from at most one junction.
-
An msp_road may be entirely or partially inside at most one junction.
Following are implications of these relations:
-
If an msp_road intersects/overlaps with more than one msp_road, then all intersections/overlaps belong to the same junction.
-
A single msp_road may not cross a junction (i.e., it may not start before a junction and end after the junction).
The MSP API
Following are OSC2 types relevant for MSP implementation:
# Lane use type declaration
# To be extended by later modules
#
enum msp_lane_use: [none, driving, shoulder, biking, sidewalk, border, parking, rail]
# Road static sign type declaration
#
enum traffic_sign_kind: [stop, yield, other]
# Traffic light types declaration
#
# bulb icons
#
enum bulb_icon_kind: [
unknown,
none,
arrow_straight,
arrow_left,
arrow_right,
pedestrian,
walk,
dont_walk,
bicycle,
countdown
]
# bulb colors
#
enum bulb_color_kind: [
unknown,
red,
yellow,
green,
blue,
white
]
# Represents a single bulb
#
struct msp_traffic_light_bulb:
var map_id: string
var icon: bulb_icon_kind
var color: bulb_color_kind
# Represents a physical traffic light box
#
struct msp_physical_traffic_light:
var internal_id: uint
# Loaded from map if it has a definition of physical TL
#
var map_id: string
var bulbs: list of msp_traffic_light_bulb
var logical_tl: msp_traffic_light
# A logical-traffic light. Represents a set of physical traffic-light "boxes" (msp_physical_traffic_light).
# Its bulbs/state is a union of the bulbs of its member boxes
#
struct msp_traffic_light inherits msp_traffic_signal:
# Loaded from map if it has a definition of logical TL
#
var map_id: string
var bulbs: list of msp_traffic_light_bulb
var bulbs_state: list of bulb_state_kind
var tl_boxes: list of msp_physical_traffic_light
# enum line_kind - lane border-line types. Double lines (like solid_broken) are
# specified from right to left (relative to associated lane direction)
#
enum line_kind: [
none,
solid,
broken,
solid_solid,
solid_broken,
broken_solid,
broken_broken,
botts_dots,
grass,
curb,
edge
]
# enum line_color - lane border-line colors
#
enum line_color: [blue, green, red, white, yellow, orange]
# side left/right
#
enum av_side: [left = 1, right = -1]
# Coordinates in 3D space
#
struct msp_coordinates:
x: length
y: length
z: length
The msp_api (msp_api.osc)
##############################################################
# API for map creation
##############################################################
# msp_lane
#
extend msp_lane:
# add native map id to this msp_lane
# <map_id, map_offset> is unique per msp_lane.
# By default 'map_offset' is on lane reference line. To use road reference line set
# map_support_package.native_longitudinal_reference_type configuration flag to 'road'
# When a singlen road reference line is used for a bi-directional road, 'offset_is_in_road_direction' indicates
# the direction of the map_offset.
#
def set_native_map_id(map_id: string, map_offset: length, offset_is_in_road_direction: bool) is undefined
# add a next msp_lane to this msp_lane
# If lane A is a next of lane B then updating B as next of A also updates A as previous of B
#
def add_next_lane(lane: msp_lane) is undefined
# add a previous msp_lane to this msp_lane
# If lane A is a next of lane B then updating A as previous of B also updates B as next of A
#
def add_previous_lane(lane: msp_lane) is undefined
# add a lane usage to this msp_lane
#
def add_lane_usage(lane_use: msp_lane_use) is undefined
# add point to center reference polyline
# returns the new length of the lane's center polyline
#
def add_point(point: msp_coordinates, width: length, road_offset: length)-> length is undefined
# set speed limit
# (speed-limit can also be set for multiple lanes in a single call to msp_road.set_speed_limit)
#
def add_speed_limit(limit: speed, start_lane_offset: length, end_lane_offset: length) is undefined
# set sign
# (A sign can also be set for multiple lanes in a single call to msp_road.set_sign)
#
def add_sign_reference(map_id: string, lane_offset: length) is undefined
# set traffic light
# 'map_id' must correspond to a 'group_map_id' provided to map.add_traffic_light()
# (A traffic-light can also be set for multiple lanes in a single call to msp_road.set_traffic_light_group)
#
def add_traffic_light_reference(map_id: string, lane_offset: length) is undefined
# add lane mark
#
def add_lane_mark(side: av_side,
line_type: line_kind,
line_color: line_color,
start_lane_offset: length,
end_lane_offset: length) is undefined
# msp_road
#
extend msp_road:
def add_left_lane(lane: msp_lane) is undefined
def add_right_lane(lane: msp_lane) is undefined
def set_opposite_msp_road(road: msp_road) is undefined
# set speed limit
# 'from_lane' and 'to_lane' are 1-based starting from right-most lane in this msp_road.
# (may also be set per lane via lane.set_speed_limit)
#
def add_speed_limit(limit: speed,
start_road_offset: length,
end_road_offset: length,
from_lane: uint,
to_lane: uint) is undefined
# set sign
# (may also be set per lane via lane.set_sign)
#
def add_sign_reference(map_id: string,
road_offset: length,
from_lane: uint,
to_lane: uint) is undefined
# set traffic light
# 'map_id' must correspond to a 'group_map_id' provided to map.add_traffic_light()
# (may also be set per lane via lane.set_traffic_light_group)
#
def add_traffic_light_reference(map_id: string,
road_offset: length,
from_lane: uint,
to_lane: uint) is undefined
# set road category, default is 'regular'.
#
def set_road_category(category: msp_road_category) is undefined
# map
#
extend map:
# utility function to create opposite routes. The given routes must be opposite from start to end.
# This method may break the given msp_roads (and consequently their msp_lanes) such that every two opposite
# msp_roads are opposites from satrt to end. The method will create new msp_roads and re-populate 'in_out_route'
# and 'in_out_opposite_route' with the new msp_roads.
#
# def create_opposite_routes(in_out_route: list of msp_road, in_out_opposite_route: list of msp_road)-> bool is undefined
# set the map name and version
#
def set_map_name_and_version(name: string, version: string) is undefined
# set the driving side of the map (default is 'map_config.traffic_side_request')
#
def set_driving_side(side: traffic_side_request) is undefined
# add a traffic_light to the map. multiple calls with same 'group_map_id' and different 'box_map_id's will add multiple
# boxes to the group.
# If sim_config.tl_api_level is 'logical' then box_map_id is not used (may be empty).
#
def add_traffic_light(bulbs: list of msp_traffic_light_bulb,
group_map_id: string,
box_map_id: string) is undefined
# add a sign to the map
#
def add_sign(sign_type: traffic_sign_kind, map_id: string) is undefined
# add a crosswalk to the map
# param 'map_id' - unique id of the crosswalk
# param 'polygon' - list of four points defining the outline of the crosswalk
#
def add_crosswalk(map_id: string,
polygon: list of msp_coordinates,
mark_type: crosswalk_mark_type,
mark_color: crosswalk_mark_color) is undefined
# Add map projection info. E.g., info for converting to some global coordinate system. The value of 'projection'
# can later be obtained via method 'map.get_projection_data()' (for example for passing it to the simulator).
#
def set_projection_data(projection: string) is undefined
MSP implementation coding guidelines
Following are coding guidelines for implementing MSP components.
-
No order is imposed on the API calls. For example, you can add points to a lane before or after the lane was added to the road, you can specify speed limits at the road level for some range of lanes before lanes were added to the road, and so on.
-
Upon creation of a new msp_lane instance (msp_lane::create()), it is automatically added into top.map.lanes and its internal_id field is set to its index in that list, i.e., top.map.lanes[some_lane.internal_id] == some_lane.
-
Upon creation of a new msp_road instance (msp_road::create()), it is automatically added into top.map.roads and its internal_id field is set to its index in that list, i.e., top.map.roads[some_road.internal_id] == some_road.
-
Except for msp_lane.internal_id and msp_road.internal_id, you should not assume any of the map-related OSC2 fields are set during the map_support_package.load_map() method. msp_api methods update internal data structures and the changes are applied to the OSC2 structures only after map_support_package.load_map() returns.
-
In the implementation of map_support_package.mark_road_elements(), you can assume that all map-related OSC2 fields are already set.
-
By default, there is an assumption that config.test.map (which is passed as the map_info parameter to the load_map method) is a path to a file. Foretify first expands environment variables then checks if that file exists. If no such file is found, an error is issued. If MSP uses any other identification for maps, the following code is required:
extend map_support_package:
set map_resource_locator_type = other ## Default value is file_path
Step-by-step MSP implementation example
Step 1: Add definition of OSC2 method map_support_package.load_map
Define the OSC2 method map_support_package.load_map() to invoke your cpp implementation. Add a new .osc file, for example my_map_format.osc with the following content:
extend map_kind: [my_map_format]
extend map_support_package:
set kind = my_map_format
# Optional - if using map_info which is not a file path
# set map_resource_locator_type = other
def load_map(map_info: string, traffic_side_request: traffic_side_request)-> bool \
is only external cpp("load_map", "libmy_map_format_msp.so")
Scenarios that use my_map_format maps should import ‘my_map_format.osc’. Typically this import directive is placed in the relevant ssp configuration osc file ‘
Step 2: Generate Foretify osc-cpp interface
The Foretify osc-cpp interface includes:
-
The cpp method declaration of the load_map() method you should implement:
foretify::bool_t map_support_package_t::load_map(foretify::string_t map_info, foretify::traffic_side_request_t traffic_side_request) -
The cpp interface to invoke OSC2 methods, in particular the msp_api methods.
Generate the Foretify osc-cpp interface using $FTX/bin/create_osc_headers:
create_osc_headers --log_system DEBUG --out_style cpp --load my_map_format.osc --out_file my_map_format_top
This will create files my_map_format_top.h and my_map_format_top.cpp.
Step 3: Write your load_map() CPP implementation
Write your load_map() CPP implementation in a file, my_map_format.cpp, for example.
#include "my_map_format_top.h"
namespace foretify {
bool_t map_support_package_t::load_map(string_t map_info, traffic_side_request_t traffic_side_request) {
// sketch of the map that this method creates:
// - Two straight roads: "mapE2W" and "mapW2E", each composed of two lane-sections, 50m each, and each section has
// two lanes.
// - "mapE2W" goes from East to West, "mapW2E" goes from West to East
// - lane width is constant 3.6m.
// - Between lane sections there are logical traffic lights "TL1" and "TL2", each has two boxes
// - two lanes have stop and yield signs 10m before their end
//
//| |10_m native name: "mapE2W"
//|====|=========================================|================================================|
//|stop| r1_l1 <-- | r2_l1 <-- |- 12.6_m
//|--------------------------------------- TL1_f | TL1_n -----------------------------------------|
//| r1_l2 <-- | r2_l2 <-- |- 9_m
//|==============================================|================================================|
//| r4_l2 --> | r3_l2 --> yield| |- 5.4_m
//|--------------------------------------- TL2_n | TL2_f ------------------------------------|----|
//| r4_l1 --> | r3_l1 --> | |- 1.8_m
//|==============================================|===========================================|====|
//| native name: "mapW2E" | |90_m|
//|0_m |50_m |100_m
// Set map name and version
top().map().invoke_set_map_name_and_version(string_t::create("test_map_1"), string_t::create("v0.0"));
// Add traffic_lights to map
// create red-yellow-green bulbs-list for traffic-light
msp_traffic_light_bulb_t green_bulb = msp_traffic_light_bulb_t::create();
green_bulb.set_color(bulb_color_kind_t::green());
green_bulb.set_icon(bulb_icon_kind_t::none());
msp_traffic_light_bulb_t yellow_bulb = msp_traffic_light_bulb_t::create();
yellow_bulb.set_color(bulb_color_kind_t::yellow());
yellow_bulb.set_icon(bulb_icon_kind_t::none());
msp_traffic_light_bulb_t red_bulb = msp_traffic_light_bulb_t::create();
red_bulb.set_color(bulb_color_kind_t::red());
red_bulb.set_icon(bulb_icon_kind_t::none());
list_t<msp_traffic_light_bulb_t> bulb_list = list_t<msp_traffic_light_bulb_t>::create();
bulb_list.append(red_bulb);
bulb_list.append(yellow_bulb);
bulb_list.append(green_bulb);
// Add logical traffic-light TL1 with two physical boxes: TL1_f and TL1_n
top().map().invoke_add_traffic_light(bulb_list, string_t::create("TL1"), string_t::create("TL1_n"));
top().map().invoke_add_traffic_light(bulb_list, string_t::create("TL1"), string_t::create("TL1_f"));
// Add logical traffic-light TL2 with two physical boxes: TL2_f and TL2_n
top().map().invoke_add_traffic_light(bulb_list, string_t::create("TL2"), string_t::create("TL2_n"));
top().map().invoke_add_traffic_light(bulb_list, string_t::create("TL2"), string_t::create("TL2_f"));
// Add signs to map
top().map().invoke_add_sign(traffic_sign_kind_t::stop(), string_t::create("stop_1"));
top().map().invoke_add_sign(traffic_sign_kind_t::yield(), string_t::create("yield_1"));
// Allocate working struct for coordinates
msp_coordinates_t c = msp_coordinates_t::create();
// create msp_road r1 for map road "mapE2W" at offset 0_m
msp_road_t r1 = msp_road_t::create();
// add traffic light TL1 to lanes 1 and 2 of r1 (this can be done before or after the lanes have been added)
r1.invoke_add_traffic_light_reference(string_t::create("TL1"), 0_m, 1, 2);
// add stop sign to lane 1 of r1
r1.invoke_add_sign_reference(string_t::create("stop_1"), 40_m, 1, 1);
// add 30_kph speed-limit to first 25_m of r1 (both lanes)
r1.invoke_add_speed_limit(30_kph, 0_m, 25_m, 1, 2);
// create msp_lane r1_l1, rightmost in r1
msp_lane_t r1_l1 = msp_lane_t::create();
// add first coordinate of r1_l1 center line with width 3.6_m, corresponding to road-offset 0_m
c.set_x(50_m);
c.set_y(12.6_m);
c.set_z(0_m);
r1_l1.invoke_add_point(c, 3.6_m, 0_m);
// add second (and last) coordinate of r1_l1 center line with width 3.6_m, corresponding to road-offset 50_m
c.set_x(0_m);
c.set_y(12.6_m);
r1_l1.invoke_add_point(c, 3.6_m, 50_m);
// set native map name and offset of r1_l1. Last argument is 'false' because the native reference line is
// opposite to lane/traffic direction
r1_l1.invoke_set_native_map_id(string_t::create("mapE2W_1"), 0_m, false);
// add r1_l1 to r1
r1.invoke_add_right_lane(r1_l1);
// set usage of r1_l1 to driving
r1_l1.invoke_add_lane_usage(msp_lane_use_t::driving());
// add 40_kph speed-limit to r1_l1 from offset 25_m to offset 50_m
r1_l1.invoke_add_speed_limit(40_kph, 25_m, 50_m);
// add lane border marks to r1_l1
r1_l1.invoke_add_lane_mark(av_side_t::right(), line_kind_t::solid(), line_color_t::yellow(), 0_m, 50_m);
// left border mark of r1_l1 is also the right border mark of r1_l2 (added below)
r1_l1.invoke_add_lane_mark(av_side_t::left(), line_kind_t::broken(), line_color_t::white(), 0_m, 50_m);
// r1_l2
msp_lane_t r1_l2 = msp_lane_t::create();
c.set_x(50_m);
c.set_y(9_m);
r1_l2.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(0_m);
c.set_y(9_m);
r1_l2.invoke_add_point(c, 3.6_m, 50_m);
r1_l2.invoke_set_native_map_id(string_t::create("mapE2W_2"), 0_m, false);
r1.invoke_add_left_lane(r1_l2);
r1_l2.invoke_add_lane_usage(msp_lane_use_t::driving());
r1_l2.invoke_add_speed_limit(50_kph, 25_m, 50_m);
r1_l2.invoke_add_lane_mark(av_side_t::left(), line_kind_t::solid(), line_color_t::yellow(), 0_m, 50_m);
// r2
msp_road_t r2 = msp_road_t::create();
// r2_l1
msp_lane_t r2_l1 = msp_lane_t::create();
c.set_x(100_m);
c.set_y(12.6_m);
r2_l1.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(50_m);
c.set_y(12.6_m);
r2_l1.invoke_add_point(c, 3.6_m, 50_m);
r2_l1.invoke_set_native_map_id(string_t::create("mapE2W_1"), 50_m, false);
r2.invoke_add_right_lane(r2_l1);
r2_l1.invoke_add_lane_usage(msp_lane_use_t::driving());
// set msp_lane r2_l1 as a previous of msp_lane r1_l1 (this will also set r1_l1 as a next of r2_l1)
r1_l1.invoke_add_previous_lane(r2_l1);
// r2_l2
msp_lane_t r2_l2 = msp_lane_t::create();
c.set_x(100_m);
c.set_y(9_m);
r2_l2.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(50_m);
c.set_y(9_m);
r2_l2.invoke_add_point(c, 3.6_m, 50_m);
r2_l2.invoke_set_native_map_id(string_t::create("mapE2W_2"), 50_m, false);
r2.invoke_add_left_lane(r2_l2);
r2_l2.invoke_add_lane_usage(msp_lane_use_t::driving());
r1_l2.invoke_add_previous_lane(r2_l2);
// r3
msp_road_t r3 = msp_road_t::create();
r3.invoke_add_sign_reference(string_t::create("yield_1"), 40_m, 2, 2);
// r3_l1
msp_lane_t r3_l1 = msp_lane_t::create();
c.set_x(50_m);
c.set_y(1.8_m);
r3_l1.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(100_m);
c.set_y(1.8_m);
r3_l1.invoke_add_point(c, 3.6_m, 50_m);
r3_l1.invoke_set_native_map_id(string_t::create("mapW2E_1"), 50_m, true);
r3_l1.invoke_add_lane_usage(msp_lane_use_t::driving());
r3.invoke_add_right_lane(r3_l1);
// r3_l2
msp_lane_t r3_l2 = msp_lane_t::create();
c.set_x(50_m);
c.set_y(5.4_m);
r3_l2.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(100_m);
c.set_y(5.4_m);
r3_l2.invoke_add_point(c, 3.6_m, 50_m);
r3_l2.invoke_set_native_map_id(string_t::create("mapW2E_2"), 50_m, true);
r3.invoke_add_left_lane(r3_l2);
r3_l2.invoke_add_lane_usage(msp_lane_use_t::driving());
// r4
msp_road_t r4 = msp_road_t::create();
r4.invoke_add_traffic_light_reference(string_t::create("TL2"), 50_m, 1, 2);
// r4_l1
msp_lane_t r4_l1 = msp_lane_t::create();
c.set_x(0_m);
c.set_y(1.8_m);
r4_l1.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(50_m);
c.set_y(1.8_m);
r4_l1.invoke_add_point(c, 3.6_m, 50_m);
r4_l1.invoke_set_native_map_id(string_t::create("mapW2E_1"), 0_m, true);
r4_l1.invoke_add_lane_usage(msp_lane_use_t::driving());
r4.invoke_add_right_lane(r4_l1);
// set msp_lane r3_l1 as next of msp_lane r4_l1 (this will also set r4_l1 as previous of r3_l1)
r4_l1.invoke_add_next_lane(r3_l1);
// r4_l2
msp_lane_t r4_l2 = msp_lane_t::create();
c.set_x(0_m);
c.set_y(5.4_m);
r4_l2.invoke_add_point(c, 3.6_m, 0_m);
c.set_x(50_m);
c.set_y(5.4_m);
r4_l2.invoke_add_point(c, 3.6_m, 50_m);
r4_l2.invoke_set_native_map_id(string_t::create("mapW2E_2"), 0_m, true);
r4.invoke_add_left_lane(r4_l2);
r4_l2.invoke_add_lane_usage(msp_lane_use_t::driving());
r4_l2.invoke_add_next_lane(r3_l2);
// set opposite roads
r1.invoke_set_opposite_msp_road(r4);
r2.invoke_set_opposite_msp_road(r3);
return true;
}
} // namespace foretify
Releasing temporary resources allocated for ‘map_support_package.load_map()’ and ‘map_support_package.mark_road_elements()’
The map_support_package provides an additional hook for releasing any temporary resources used for load_map() and mark_road_elements(). The empty method map_support_package.cleanup() can be extended (using “is also”) for this end. map_support_package.cleanup() is called immediately after map_support_package.mark_road_elements().
Step 4: Build libmy_map_format_msp.so
Build libmy_map_format_msp.so from my_map_format.cpp and my_map_format_top.cpp, and place it under $FTX_LIB.
Add MSP-specific road_elements
Route elements were previously called road elements. In this section the terms are used interchangeably.
For a full explanation of all route_elements, see route_elements.
See MSP-specific route_elements for detailed information about MSP-specific route_elements.
road_element types
Road_element types classify routes or sub-routes on the map by sets of characteristics. Each road_element type defines a set of traits by which it classifies map routes. For example, speed_limit_section classifies drivable routes by their speed limit. Every continuous route with a constant speed limit is associated with an occurrence of a speed_limit_section.
road_element occurrences
For a given road_element type, different value ranges for the associated properties define the occurrences of this type on the map. For example, slope_section classifies routes by their slope such that:
-
Within an occurrence of a slope_section, the slope does not vary by more than 5 degrees (configurable).
-
Extending the route of a slope_section occurrence in either direction invalidates the 5-degree limitation.
-
slope_section occurrences do not overlap.
Every occurrence of some road_element type is associated with:
-
A set of values for the type’s properties
-
A route on the map
Limitations of the road_element type
-
A road_element type is defined by an OSC2 struct that inherits structs road_element.
- Field names id, part_id and length are already used in road_element, so they cannot be used in inheriting types.
-
A road_element type can only have generable primitive field types: numeric, boolean or enum.
The only exceptions to fields being of primitive types are references to other road_element occurrences. In such cases, the referenced road_element occurrence is assigned by constraint. For example, lane_section representing a piece of road with a constant number of lanes. (See the complete lane_section declaration in the code snippet below.) lane_section refers to the one_way_road road_element that contains it using two fields and a keep constraint:
-
one_way_road_id: uint- The ID of the containing one_way_road. -
one_way_road: one_way_road- A one_way_road road_element occurrence.keep (it.id == one_way_road_id)- A constraint for setting the value of the one_way_road field.
OSC2 code: lane_section# lane_section - divides a 'one_way_road' into longest sections with a constant number of driving lanes # struct lane_section inherits road_element: # number of driving lanes # lanes: int # index (zero based) by order in containing one_way_road. # index: uint # the one_way_road this lane_section is on # one_way_road_id: uint one_way_road: one_way_road with: keep (it.id == one_way_road_id) -
road_element occurrences' routes
The route associated with an occurrence of a road_element is represented by a list of road_parts. A road_part represents a sub-range of msp_lanes in an msp_road, on some range of the msp_road’s length:
struct road_part:
# The msp_road
var road: msp_road
# start offset of the road_part within 'road'
var start_offset: length
# end offset of the road_part within 'road'
var end_offset: length
# rightmost lane in the road_part (index is 1-based)
var min_lane_idx: uint
# leftmost lane in the road_part (index is 1-based)
var max_lane_idx: uint
-
Only the first element in the list can have
start_offset != 0m. -
Only the last element in the list can have
end_offset != road.length. -
There must be at least one connected pair of lanes between the lanes of two successive road_parts.
Add occurrences of a road_element type
The following sections provide steps for adding occurrences of road_element types.
Where to add occurrences of road_element types
The MSP mark_road_elements() method can be extended to add road_element occurrences. mark_road_elements() is called after load_map() has completed and after all general (not msp-specific) road_elements have been defined. The general route elements can be useful for computing routes for other road_elements, for example, for identifying the location of junctions, traffic lights, and more. Use the following steps to add occurrences of road_element types.
Add occurrences of road_element types
Use the following steps to add occurrences of road_element types.
-
Create a new instance of the road_element type.
For example, in cpp:
my_element_type_t element = my_element_type_t::create(); -
Set all primitive fields of the specific type.
For example:
element.set_num_lanes(x);-
Do not set values of inherited fields
id,length, andpart_id. -
Do not set values of fields of road_element types. (They are set by a constraint.)
-
-
Construct the associated route as a list of road_part.
For example:
list_t<road_part_t> element_route = list_t<road_part_t>::create().The MSP implementer can use two techniques to obtain the route in terms of msp_roads:
-
Use map method
native_to_msp_position(native_lane: string, lon_offset: length)-> msp_lane_position- Where
native_laneis the string provided via msp_apimsp_lane.set_native_map_id.
- Where
-
During load_map, construct a mapping between the allocated msp_road and/or msp_lane ids to native map elements and access
map.roads[road_id]ormap.lanes[lane_id].
-
-
Call the road_element register method with the associated route —
element.invoke_register(element_route).
CPP utilities
Some utility methods that may come in handy for the CPP implementation of road_elements are declared in $FTX/includes/road_element_utils/road_element_utils.h and implemented in shared_object $FTX/lib/libroad_element_utils.so. See road_element_utils.h for details.
Example of adding an MSP-specific road_element
An example of an MSP-specific road_element is a road_element that represents roads of the native map format. For fields, use any properties that you may want to constrain, sample or collect coverage for in a scenario.
Step 1: Add a road_element declaration
In file my_map_road_elements.osc, add the following:
# a road_element representing native map roads
#
struct my_map_road inherits road_element:
my_map_id: int
min_driving_lanes: uint
# add any other fields for values you may want to (a) constraint in a scenario or
# (b) sample and collect coverage on
Step 2: Import my_map_road_elements.osc into foretify_top
The MSP OSC file where map_support_package.load_map() is defined is a good place to add import "my_map_road_elements.osc".
Step 3: Define extension of map_support_package.mark_road_elements()
The MSP OSC file where map_support_package.load_map() is defined is also a good place for adding the map_support_package.mark_road_elements() definition.
extend map_support_package:
def mark_road_elements() is also external cpp("mark_road_elements", "libmy_map_msp.so")
Step 4: CPP implementation of foretify::map_support_package_t::mark_road_elements()
-
A less naive implementation would probably prepare some mappings during the execution of the
map_support_package.load_map()method. For a self contained example, it is avoided here. -
For the sake of the example, assume that:
-
The map-ids associated with lanes (assigned via an
msp_lane.set_native_map_id()msp_api call duringmap_support_package.load_map()) have the format <native_road_id>:. -
native_road_id is an unsigned integer and native_lane_id is a signed integer.
-
native_road_id is identical for both driving directions of a native-road, and driving directions are distinguished by the sign of native_lane_id.
-
(These assumptions may be suitable for an OpenDRIVE MSP implementation).
#include "my_map_format_top.h"
#include "road_element_utils.h" // for road_elements::create_road_part()
namespace foretify {
void map_support_package_t::mark_road_elements() {
using MyRoadId = int_t;
// native road as a list of msp_roads
using MyRoadRoute = std::map<distance_t, msp_road_t>;
// map of native roads by native road id
using MyRoads = std::unordered_map<MyRoadId, MyRoadRoute>;
MyRoads my_roads;
// populate my_roads
for (msp_road_t msp_road : top().map().roads()) {
if (msp_road.is_null() || msp_road.is_virtual()) {
continue;
}
msp_lane_t representative_lane = msp_road.lanes()[0];
std::string representative_lane_id = representative_lane.map_id().str();
auto pos = representative_lane_id.find_last_of(':');
int_t lane_id_sign = std::stol(representative_lane_id.substr(pos + 1)) < 0 ? -1 : 1;
MyRoadId road_id = std::stol(representative_lane_id.substr(0, pos)) * lane_id_sign;
MyRoadRoute& route = my_roads[road_id];
route.emplace(representative_lane.map_offset(), msp_road);
}
// create and register my_map_road_t occurrences
for (const auto& road : my_roads) {
list_t<road_part_t> road_parts = list_t<road_part_t>::create();
uint_t min_driving_lanes = std::numeric_limits<uint_t>::max();
uint_t max_driving_lanes = 0;
for (const auto& route : road.second) {
// create a road_part that contains only the driving lanes of the msp_road
road_part_t road_part = road_elements::create_road_part(route.second);
road_parts.append(road_part);
uint_t num_driving_lanes = road_part.max_lane_idx() - road_part.min_lane_idx() + 1;
min_driving_lanes = std::min(min_driving_lanes, num_driving_lanes);
max_driving_lanes = std::max(max_driving_lanes, num_driving_lanes);
}
my_map_road_t may_map_road = my_map_road_t::create();
may_map_road.set_my_map_id(road.first);
may_map_road.set_min_driving_lanes(min_driving_lanes);
may_map_road.set_max_driving_lanes(max_driving_lanes);
may_map_road.invoke_register(road_parts);
}
}
} //namespace foretify
road_element_utils.h
namespace road_elements {
using MspRoadId = uint64_t;
using ElementId = uint64_t;
/*
* @param lane - an msp-lane
* @param usage - a lane-use
* @return 'true' if lane.lane_uses() has 'usage', 'false' otherwise
*/
extern bool is_lane_of_type(const foretify::msp_lane_t& lane, foretify::msp_lane_use_t usage);
/*
* @param lane - an msp-lane
* @param usages_il - a list of lane-uses
* @return 'true' if lane.lane_uses() has one of the elements in 'usages_il', 'false' otherwise
*/
extern bool is_lane_of_types(const foretify::msp_lane_t& lane,
std::initializer_list<foretify::msp_lane_use_t> usages_il);
/*
* @param road - an msp-road
* @param usages_il - a list of lane-uses
* @return leftmost lane with at least one msp_lane_use of the specified uses
*/
extern foretify::msp_lane_t get_max_lane_of_types(foretify::msp_road_t road,
std::initializer_list<foretify::msp_lane_use_t> usages_il);
/*
* @param road - an msp-road
* @param usages_il - a list of lane-uses
* @return rightmost lane with at least one msp_lane_use of the specified uses
*/
extern foretify::msp_lane_t get_min_lane_of_types(foretify::msp_road_t road,
std::initializer_list<foretify::msp_lane_use_t> usages_il);
/*
* @param lane - an msp-lane
* @return 'true' if the lane is designated for car usage, 'false' otherwise
*/
extern bool is_driving_lane(const foretify::msp_lane_t& lane);
/*
* @param road - an msp-road
* @param with_side_lanes - if 'true' then the road-part will include shoulders and sidewalks, otherwise it will include
* only driving lanes
* @return an allocated road-part that corresponds to the driving lanes of the given road, from
* start to end of the road.
*/
extern foretify::road_part_t create_road_part(foretify::msp_road_t road, bool with_side_lanes = false);
/*
* Return the direction of movement when moving from 'from_angle' to 'to_angle'. There are eight
* possible direction values, counting clockwise: straight, slight_right, right, sharp_right,
* backwards, sharp_left, left and slight_left.
* @param from_angle - source angle of the movement
* @param to_angle - destination angle of the movement
* @return the direction of the movement
*/
extern foretify::direction_t get_direction(foretify::angle_t from_angle, foretify::angle_t to_angle);
/*
* Return the direction of movement when moving from 'from_lane' to 'to_lane'. There are eight
* possible direction values, counting clockwise: straight, slight_right, right, sharp_right,
* backwards, sharp_left, left and slight_left. Typically 'from_lane' would be a junction entry and
* 'to_lane' would be a junction exit. The direction is determined by the angle difference between
* the end of 'from_lane' to the start of 'to_lane'
* @param from_lane - source of the movement
* @param to lane - destination of the movement
* @return the direction of the movement
*/
extern foretify::direction_t get_direction(foretify::msp_lane_t from_lane, foretify::msp_lane_t to_lane);
/*
* given a list of lanes, return the first driving lane in the list.
* @param lanes - a list of msp-lanes
* @return the first driving lane in 'lanes'
*/
extern foretify::msp_lane_t get_min_driving_lane(const foretify::list_t<foretify::msp_lane_t>& lanes);
/*
* given a msp-road return the rightmost driving lane (lanes are indexed from right to left,
* starting at 1).
* @param rd - a msp-road
* @return the rightmost driving lane in 'rd'
*/
extern foretify::msp_lane_t get_min_driving_lane(foretify::msp_road_t msp_road);
/*
* given a msp-road return the leftmost driving lane (lanes are indexed from right to left, starting
* at 1).
* @param rd - a msp-road
* @return the leftmost driving lane in 'rd'
*/
extern foretify::msp_lane_t get_max_driving_lane(foretify::msp_road_t msp_road);
/*
* given a msp-road return the inner-most driving lane - the lane closest to opposite direction.
* @param rd - a msp-road
* @return the inner-most driving lane in 'rd'
*/
extern foretify::msp_lane_t get_innermost_driving_lane(foretify::msp_road_t msp_road);
/*
* given a msp-road return the outer-most driving lane - the lane closest to curb.
* @param rd - a msp-road
* @return the inner-most driving lane in 'rd'
*/
extern foretify::msp_lane_t get_outermost_driving_lane(foretify::msp_road_t msp_road);
/*
* @param road - a msp-road
* @return 'true' if 'road' has at least one driving lane
*/
extern bool is_driving_road(const foretify::msp_road_t& road);
/*
* @param road - a msp-road
* @return the number of driving lanes in 'road'
*/
extern foretify::uint_t get_num_driving_lanes(const foretify::msp_road_t& road);
/*
* @param roads - an iterate-able container of msp_road_t elements
* @return a list of road_parts corresponding to the drive-able lanes of the roads in the container
* (according to iteration order)
*/
template<typename ROAD_LIST>
foretify::list_t<foretify::road_part_t> road_parts_from_roads(ROAD_LIST roads) {
foretify::list_t<foretify::road_part_t> road_parts = foretify::list_t<foretify::road_part_t>::create();
for (foretify::msp_road_t road : roads) {
if (!is_driving_road(road)) {
road_parts.clear();
break;
}
road_parts.append(road_elements::create_road_part(road));
}
return road_parts;
}
/*
* @param ln - a msp-lane
* @return the global Cartesian coordinates of the start of 'ln'
*/
extern foretify::msp_coordinates_t get_lane_start(foretify::msp_lane_t ln);
/*
* @param ln - a msp-lane
* @return the global Cartesian coordinates of the end of 'ln'
*/
extern foretify::msp_coordinates_t get_lane_end(foretify::msp_lane_t ln);
/*
* @param road_part - a road-part
* @return the min speed-limit on 'road_part'.
* If there is no speed limit along road_part then speed_t::max_value() is returned.
*/
extern foretify::speed_t get_min_speed_limit(foretify::road_part_t road_part);
/*
* @param road_part - a road-part
* @return the max speed-limit (that is not unlimitted / speed_t::max()) on 'road_part'
* If there is no speed limit along road_part then speed_t::max_value() is returned.
*/
extern foretify::speed_t get_max_speed_limit(foretify::road_part_t road_part);
/*
* @param road - an msp-road
* @return the minimal speed-limit in 'road'
*/
extern foretify::speed_t get_min_speed_limit(foretify::msp_road_t road);
/*
* @param road - an msp-road
* @return the maximal speed-limit in 'road'
*/
extern foretify::speed_t get_max_speed_limit(foretify::msp_road_t road);
/*
* @param lane1 - an msp-lane
* @param lane2 - an msp-lane
* @return 'true' if 'lane1' and 'lane2' are next/previous of each other, in the same road or in
* opposite roads
*/
extern bool lanes_connected(const foretify::msp_lane_t& lane1, const foretify::msp_lane_t& lane2);
/*
* @param road - an msp-road
* @return a list of previous roads of 'road' that are connected via driving lanes
*/
extern std::list<foretify::msp_road_t> get_previous_roads_by_driving_lanes(const foretify::msp_road_t& road);
/*
* @param road - an msp-road
* @return a list of next roads of 'road' that are connected via driving lanes
*/
extern std::list<foretify::msp_road_t> get_next_roads_by_driving_lanes(const foretify::msp_road_t& road);
/*
* @param road1 - an msp-road
* @param road2 - an msp-road
* @return 'true' if 'road1' and 'road2' has some shared next road (connected by driving lanes)
*/
extern bool roads_share_same_next(const foretify::msp_road_t& road1, const foretify::msp_road_t& road2);
/*
* @param road1 - an msp-road
* @param road2 - an msp-road
* @return 'true' if 'road1' and 'road2' has some shared previous road (connected by driving lanes)
*/
extern bool roads_share_same_previous(const foretify::msp_road_t& road1, const foretify::msp_road_t& road2);
/*
* @param msg - an error message to log
* log 'msg' as error to "ROAD_ELEMENTS" dev log and throw RUNTIME_INTERNAL_ERROR.
*/
extern void log_error_and_throw_internal_error(const std::string& msg);
} // namespace road_elements
