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
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)")
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.
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.