Main Content

Build RoadRunner Scene with Intersection and Static Objects Using RoadRunner HD Map

This example shows how to create a RoadRunner HD Map from a Keyhole Markup Language (KML) file containing the latitude-longitude coordinates of a road intersection. You can import the RoadRunner HD Map data into RoadRunner and use it to build a 3D scene containing the road intersection and surrounding static objects, such as trees and buildings. To import the data files for the road, you must have a Mapping Toolbox™ license.

Import KML Files

In this example, you use KML files to import point shapes for the road intersection and polygon shapes for the buildings and trees. This example uses map data (© 2022 by Google) for an area around Whitesville Road in New Jersey, USA.

Read the data from the KML files using the readgeotable function, which only reads the shapes, names, and descriptions.

kmlRoadData = readgeotable("RoadIntersection.kml");
kmlTreeData = readgeotable("Trees.kml");
kmlBuildingData = readgeotable("Buildings.kml");

Plot the coordinates of the road, display the buildings, and render polygons for the tree locations.

geoplot(kmlRoadData)
hold on
geoplot(kmlTreeData)
geoplot(kmlBuildingData)
geobasemap topographic

Figure contains an axes object with type geoaxes. The geoaxes object contains 3 objects of type line, polygon, point.

Convert the geospatial table to a table of road centers to obtain the latitude and longitude coordinates for two roads meeting at an intersection.

T1 = geotable2table(kmlRoadData,["Latitude","Longitude"]);
[lat1,lon1] = polyjoin(T1.Latitude(1),T1.Longitude(1)); % Road centers of the first road meeting at the intersection
[lat2,lon2] = polyjoin(T1.Latitude(2),T1.Longitude(2)); % Road centers of the second road meeting at the intersection

Convert the geospatial table to a table of polygon vertices to obtain the latitude and longitude coordinates for the trees inside the polygons and the buildings.

T2 = geotable2table(kmlTreeData,["Latitude","Longitude"]);
[lat3,lon3] = polyjoin(T2.Latitude(1),T2.Longitude(1)); % Polygon geometry to form trees inside of polygon
[lat4,lon4] = polyjoin(T2.Latitude(2),T2.Longitude(2)); % Polygon geometry to form trees inside of polygon
[lat5,lon5] = polyjoin(T2.Latitude(3),T2.Longitude(3)); % Polygon geometry to form trees inside of polygon

T3 = geotable2table(kmlBuildingData,["Latitude","Longitude"]);
lat6 = T3.Latitude; % Geometry for buildings
lon6 = T3.Longitude; % Geometry for buildings

Create RoadRunner HD Map

Create an empty RoadRunner HD Map.

rrMap = roadrunnerHDMap;

Compute the geographic reference origin as the center of the bounding quadrangle.

[latlim,lonlim] = geoquadline([lat1; lat2],[lon1; lon2]);
lat0 = mean(latlim);
lon0 = mean(lonlim);

Set the geographic reference for the region of interest.

rrMap.GeoReference = [lat0 lon0];

Project Latitude-Longitude Coordinates to xy Map Coordinates

Read the transverse Mercator projected CRS from the RoadRunner HD Map.

p = readCRS(rrMap);

Project the latitude and longitude coordinates to xy-coordinates.

% Road geometry data
[x1,y1] = projfwd(p,lat1,lon1);
[x2,y2] = projfwd(p,lat2,lon2);
% Trees geometry data
[x3,y3] = projfwd(p,lat3,lon3);
[x4,y4] = projfwd(p,lat4,lon4);
[x5,y5] = projfwd(p,lat5,lon5);
% Buildings geometry data
[x6,y6] = projfwd(p,lat6,lon6);

Compute Geometries for Lanes and Lane Boundaries

Create the line equations for the two roads of the intersection.

coefficients1 = polyfit([x1(1) x1(2)],[y1(1) y1(2)],1);
coefficients2 = polyfit([x2(1) x2(2)],[y2(1) y2(2)],1);

Compute the lane geometries using the equations of lines.

m1 = coefficients1(1);
c1 = coefficients1(2);
% Specify lane widths, estimated using the measurement tool of Google Earth
leftLanesWidth = [0 -3.659 -3.686 -3.643 -2.988];
rightLanesWidth = [2.820 3.764 3.698];
% Find cumulative width of all lanes to compute the geometries of parallel lanes
leftLanesWidth = cumsum(leftLanesWidth);
rightLanesWidth = cumsum(rightLanesWidth);
rightLanesWidth = flip(rightLanesWidth);
d1 = [rightLanesWidth leftLanesWidth];

m2 = coefficients2(1);
c2 = coefficients2(2);
% Specify lane widths, estimated using the measurement tool of Google Earth
leftLanesWidth2 = [0 -3.614 -3.610 -3.661];
rightLanesWidth2 = [3.621 3.685];
% Find cumulative width of all lanes to compute the geometries of parallel lanes
leftLanesWidth2 = cumsum(leftLanesWidth2);
rightLanesWidth2 = cumsum(rightLanesWidth2);
rightLanesWidth2 = flip(rightLanesWidth2);
d2 = [rightLanesWidth2 leftLanesWidth2];

d = [d1 d2];
numLanes = size(d,2);
x = cell(numLanes,1);
[x{1:8}] = deal(x1);
[x{9:14}] = deal(x2);
m(1:8) = deal(m1);
m(9:14) = deal(m2);
c(1:8) = deal(c1);
c(9:14) = deal(c2);
y = cell(numLanes,1);
for i = 1:numLanes
   y{i} =  m(i)*x{i} + c(i) + d(i)*sqrt(1+m(i)^2);
end

Create Road Intersection

Create the RoadRunner HD Map road network using the computed data, and modify the roads to resemble the actual road, which consists of multiple lanes and multiple lane boundaries. Then, apply appropriate lane markings to the lane boundaries as in the actual scene. To improve performance, as the number of objects in the map increases, initialize the Lanes and LaneBoundaries properties of the HD map.

rrMap.Lanes(12,1) = roadrunner.hdmap.Lane;
rrMap.LaneBoundaries(14,1) = roadrunner.hdmap.LaneBoundary;

Assign values to the Lanes property.

laneIds = ["Lane1","Lane2","Lane3","Lane4","Lane5","Lane6","Lane7","Lane8","Lane9","Lane10","Lane11","Lane12"];
travelDirection = ["Undirected","Forward","Forward","Backward","Backward","Backward","Undirected","Backward","Backward","Backward","Forward","Forward"];
for i = 1:size(rrMap.Lanes,1)
    rrMap.Lanes(i).ID = laneIds(i);
    rrMap.Lanes(i).TravelDirection = travelDirection(i);
    rrMap.Lanes(i).LaneType = "Driving";
    if (i<8) % Assign coordinates to the Geometry properties of the lanes of the first road meeting at intersection
        rrMap.Lanes(i).Geometry = ([x{i} y{i}] + [x{i} y{i+1}])/2;
    elseif (i>8) % Assign coordinates to the Geometry properties of the lanes of the second road meeting at intersection
       rrMap.Lanes(i).Geometry = ([x{i} y{i+1}] + [x{i} y{i+2}])/2;
    end
end

Assign IDs and their corresponding coordinates to the lane boundaries.

laneBoundaries = ["LB1","LB2","LB3","LB4","LB5","LB6","LB7","LB8","LB9","LB10","LB11","LB12","LB13","LB14"];
for i = 1:size(rrMap.LaneBoundaries,1) 
    rrMap.LaneBoundaries(i).ID = laneBoundaries(i);
    rrMap.LaneBoundaries(i).Geometry = [x{i} y{i}];
end

Associate the lane boundaries with their lanes using the lane boundary IDs.

for i = 1:size(rrMap.Lanes,1)
    if (i<8) % Associate lane boundaries of the first road meeting at intersection
        leftBoundary(rrMap.Lanes(i),"LB"+i,Alignment="Forward");
        rightBoundary(rrMap.Lanes(i),"LB"+(i+1),Alignment="Forward");
    else % Associate lane boundaries of the second road meeting at intersection
        leftBoundary(rrMap.Lanes(i),"LB"+(i+1),Alignment="Backward");
        rightBoundary(rrMap.Lanes(i),"LB"+(i+2),Alignment="Backward");
    end
end

Define the file paths to the RoadRunner lane marking assets.

% Define path to a dashed single white lane marking asset
dashedSingleWhiteAsset = roadrunner.hdmap.RelativeAssetPath(AssetPath="Assets/Markings/DashedSingleWhite.rrlms");
% Define path to a solid white lane marking asset
solidWhiteAsset = roadrunner.hdmap.RelativeAssetPath(AssetPath="Assets/Markings/SolidSingleWhite.rrlms");
% Define path to a solid double yellow lane marking asset
doubleYellowAsset = roadrunner.hdmap.RelativeAssetPath(AssetPath="Assets/Markings/SolidDoubleYellow.rrlms");
rrMap.LaneMarkings(3,1) = roadrunner.hdmap.LaneMarking;
[rrMap.LaneMarkings.ID] = deal("DashedSingleWhite","SolidWhite","DoubleYellow");
[rrMap.LaneMarkings.AssetPath] = deal(dashedSingleWhiteAsset,solidWhiteAsset,doubleYellowAsset);

Specify that the markings span the entire lengths of their lane boundaries. Then, assign solid white marking to the lane boundaries near road edges, dashed white lane marking to intermediate lane boundaries, and the double yellow marking to the center lane boundary.

% Specify the span for markings as the entire lengths of their lane boundaries
markingSpan = [0 1];

markingRefDSW = roadrunner.hdmap.MarkingReference(MarkingID=roadrunner.hdmap.Reference(ID="DashedSingleWhite"));
markingAttribDSW = roadrunner.hdmap.ParametricAttribution(MarkingReference=markingRefDSW,Span=markingSpan);

markingRefSY = roadrunner.hdmap.MarkingReference(MarkingID=roadrunner.hdmap.Reference(ID="DoubleYellow"));
markingAttribSY = roadrunner.hdmap.ParametricAttribution(MarkingReference=markingRefSY,Span=markingSpan);

markingRefSW = roadrunner.hdmap.MarkingReference(MarkingID=roadrunner.hdmap.Reference(ID="SolidWhite"));
markingAttribSW = roadrunner.hdmap.ParametricAttribution(MarkingReference=markingRefSW,Span=markingSpan);

% Assign the markings to lane boundaries
[rrMap.LaneBoundaries.ParametricAttributes] = deal(markingAttribSW,markingAttribSW,markingAttribDSW,markingAttribSY,markingAttribDSW,markingAttribDSW,markingAttribSW,markingAttribSW, ...
    markingAttribSW,markingAttribDSW,markingAttribDSW,markingAttribSY,markingAttribDSW,markingAttribSW);

Compute Geometries of Static Objects

Compute the coordinates of the vertices for each polygon of trees by using the helperInsidePolygon helper function.

[X3,Y3] = helperInsidePolygon(x3,y3);
[X4,Y4] = helperInsidePolygon(x4,y4);
[X5,Y5] = helperInsidePolygon(x5,y5);
X = [X3'; X4'; X5'];
Y = [Y3'; Y4'; Y5']; 
numOfTrees = numel(X);

Create Static Objects

Define a static object for trees, and then, add the trees to the model and define their properties.

% Define tree static object type
path = roadrunner.hdmap.RelativeAssetPath(AssetPath="Assets/Props/Trees/Eucalyptus_Sm01.fbx");
rrMap.StaticObjectTypes(1) = roadrunner.hdmap.StaticObjectType(ID="StaticObjectType1",AssetPath=path);

rrMap.StaticObjects(numOfTrees,1) = roadrunner.hdmap.StaticObject;
objectRef1 = roadrunner.hdmap.Reference(ID="StaticObjectType1");

% Specify the length, width, and height values of the trees, estimated using Google Earth.
length = 9;
width = 9;
height = 23;

for i = 1:numOfTrees
    x = X(i);
    y = Y(i);
    aGeoOrientedBoundingBox = roadrunner.hdmap.GeoOrientedBoundingBox;
    aGeoOrientedBoundingBox.Center = [x y 0];
    aGeoOrientedBoundingBox.Dimension = [length width height/2];
    geoAngle3 = mathworks.scenario.common.GeoAngle3;
    geoAngle3.roll = -2;
    geoAngle3.pitch = -2;
    geoAngle3.heading = 90;
    geoOrientation3 = mathworks.scenario.common.GeoOrientation3;
    geoOrientation3.geo_angle = geoAngle3;
    aGeoOrientedBoundingBox.GeoOrientation = [deg2rad(-2) deg2rad(-2) deg2rad(90)];

    treeID = "Tree" + string(i);
    rrMap.StaticObjects(i) = roadrunner.hdmap.StaticObject(ID=treeID, ...
        Geometry=aGeoOrientedBoundingBox, ...
        ObjectTypeReference=objectRef1);
end

Define a static object for building, and then, add the buildings to the model and define its properties.

numberOfBuildings = numel(x6);
% Add static object type info
path = roadrunner.hdmap.RelativeAssetPath(AssetPath="Assets/Buildings/Downtown_30mX30m_6storey.fbx");
% Define static object type
rrMap.StaticObjectTypes(2) = roadrunner.hdmap.StaticObjectType(ID="StaticObjectType2",AssetPath=path);

% Initialize elements in the StaticObjects property of the HD map object, for better performance as the number of objects in the map increases
rrMap.StaticObjects(numberOfBuildings,1) = roadrunner.hdmap.StaticObject;
objectRef2 = roadrunner.hdmap.Reference(ID="StaticObjectType2");

% Specify the length, width, and height values of the buildings, estimated using Google Earth.
length = [26 26];
width = [26 46];
height = 28;
ID = ["Building1","Building2"];
for i= 1:numberOfBuildings
    x = x6(i);
    y = y6(i);
    aGeoOrientedBoundingBox = roadrunner.hdmap.GeoOrientedBoundingBox;
    aGeoOrientedBoundingBox.Center = [x y 0];
    aGeoOrientedBoundingBox.Dimension = [length(i) width(i) height/2];
    geoAngle3 = mathworks.scenario.common.GeoAngle3;
    geoAngle3.roll = 0;
    geoAngle3.pitch = 0;
    geoAngle3.heading = 0.4697;
    geoOrientation3 = mathworks.scenario.common.GeoOrientation3;
    geoOrientation3.geo_angle = geoAngle3;
    aGeoOrientedBoundingBox.GeoOrientation = [deg2rad(-2) deg2rad(-2) deg2rad(90)];

    rrMap.StaticObjects(i) = roadrunner.hdmap.StaticObject(ID=ID(i), ...
        Geometry=aGeoOrientedBoundingBox, ...
        ObjectTypeReference = objectRef2);
end

Set Geographic Boundaries and Reference

Setting the geographic boundaries and reference for the RoadRunner HD Map centers the scene on the imported road and enables you to insert the road into the scene without using the World Settings Tool in RoadRunner.

Set the geographic bounds for the map as the minimum and maximum coordinate values of the center boundary.

geometries = [x1 y1; x2 y2];
geoBounds = [min(geometries) 0; max(geometries) 0];
rrMap.GeographicBoundary = geoBounds;

Plot the lane centers and lane boundaries.

plot(rrMap,ShowStaticObjects=true)
title("RoadRunner HD Map of Road Intersection Scene")
xlabel("x (m)")
ylabel("y (m)")

Figure contains an axes object. The axes object with title RoadRunner HD Map of Road Intersection Scene, xlabel x (m), ylabel y (m) contains 3 objects of type line. One or more of the lines displays its values using only markers These objects represent Lane Boundaries, Lane Centers, Static Objects.

Save the RoadRunner HD Map to a file.

write(rrMap,"RoadIntersection")

Import HD Map File into RoadRunner and Build Scene

To open RoadRunner using MATLAB, specify the path to your project. This code shows a sample project folder in Windows®. Open RoadRunner using the specified path to your project.

rrProjectPath = "C:\RR\MyProjects";
rrApp = roadrunner(rrProjectPath);

Import the RoadRunner HD Map data from a specified file into the currently open scene, and build the map. To build the scene, you must have an active RoadRunner Scene Builder license.

Copy the RoadRunner HD Map file to the RoadRunner project.

copyfile("RoadIntersection.rrhd","C:\RR\MyProjects\Assets")

Specify import options for the RoadRunner HD Map.

importOptions = roadrunnerHDMapImportOptions(ImportStep="Load");

Import the RoadRunner HD Map into RoadRunner.

importScene(rrApp,"RoadIntersection.rrhd","RoadRunner HD Map",importOptions)

The scene editing canvas shows the RoadRunner HD Map of the scene. To verify the imported data, you can select control points, lanes, lane boundaries, and static objects in the scene editing canvas and view their properties from the Attributes pane.

Preview of the imported RoadRunner HD Map data.

Specify options for building a scene from the imported RoadRunner HD Map. Turn off overlap groups to enable RoadRunner to create automatic junctions at the geometric overlap of the roads.

enableOverlapGroupsOptions = enableOverlapGroupsOptions(IsEnabled=0);
buildOptions = roadrunnerHDMapBuildOptions(DetectAsphaltSurfaces=true,EnableOverlapGroupsOptions=enableOverlapGroupsOptions);

Build and visualize a scene from the imported RoadRunner HD Map data. To build scenes, you must have a RoadRunner Scene Builder license.

buildScene(rrApp,"RoadRunner HD Map",buildOptions)

The built RoadRunner scene contains intersecting roads, as well as trees and buildings. Modify the intersection to resemble the actual junction by adjusting the junction corner radius using the Junction Corner Tool.

Built RoadRunner scene.

See Also

|

Related Topics