NetworkParser.java
package org.opentrafficsim.road.network.factory.xml.parser;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.djunits.unit.DirectionUnit;
import org.djunits.value.vdouble.scalar.Angle;
import org.djunits.value.vdouble.scalar.Direction;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.LinearDensity;
import org.djunits.value.vdouble.scalar.Speed;
import org.djutils.draw.line.PolyLine2d;
import org.djutils.draw.line.Polygon2d;
import org.djutils.draw.point.OrientedPoint2d;
import org.djutils.draw.point.Point2d;
import org.djutils.eval.Eval;
import org.djutils.exceptions.Throw;
import org.opentrafficsim.core.definitions.Definitions;
import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
import org.opentrafficsim.core.geometry.Bezier;
import org.opentrafficsim.core.geometry.ContinuousArc;
import org.opentrafficsim.core.geometry.ContinuousBezierCubic;
import org.opentrafficsim.core.geometry.ContinuousClothoid;
import org.opentrafficsim.core.geometry.ContinuousLine;
import org.opentrafficsim.core.geometry.ContinuousPolyLine;
import org.opentrafficsim.core.geometry.ContinuousStraight;
import org.opentrafficsim.core.geometry.Flattener;
import org.opentrafficsim.core.geometry.Flattener.MaxAngle;
import org.opentrafficsim.core.geometry.Flattener.MaxDeviation;
import org.opentrafficsim.core.geometry.Flattener.MaxDeviationAndAngle;
import org.opentrafficsim.core.geometry.Flattener.NumSegments;
import org.opentrafficsim.core.geometry.OtsGeometryException;
import org.opentrafficsim.core.geometry.OtsGeometryUtil;
import org.opentrafficsim.core.geometry.OtsLine2d;
import org.opentrafficsim.core.gtu.GtuException;
import org.opentrafficsim.core.gtu.GtuType;
import org.opentrafficsim.core.network.Centroid;
import org.opentrafficsim.core.network.Connector;
import org.opentrafficsim.core.network.LinkType;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.road.network.RoadNetwork;
import org.opentrafficsim.road.network.factory.xml.XmlParserException;
import org.opentrafficsim.road.network.factory.xml.utils.Cloner;
import org.opentrafficsim.road.network.factory.xml.utils.ParseUtil;
import org.opentrafficsim.road.network.factory.xml.utils.RoadLayoutOffsets;
import org.opentrafficsim.road.network.factory.xml.utils.RoadLayoutOffsets.CseData;
import org.opentrafficsim.road.network.lane.CrossSectionElement;
import org.opentrafficsim.road.network.lane.CrossSectionLink;
import org.opentrafficsim.road.network.lane.CrossSectionLink.Priority;
import org.opentrafficsim.road.network.lane.CrossSectionSlice;
import org.opentrafficsim.road.network.lane.Lane;
import org.opentrafficsim.road.network.lane.LaneGeometryUtil;
import org.opentrafficsim.road.network.lane.LaneType;
import org.opentrafficsim.road.network.lane.Shoulder;
import org.opentrafficsim.road.network.lane.Stripe;
import org.opentrafficsim.road.network.lane.Stripe.Type;
import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder;
import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder.FixedWidthGenerator;
import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder.RelativeWidthGenerator;
import org.opentrafficsim.road.network.lane.conflict.ConflictBuilder.WidthGenerator;
import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
import org.opentrafficsim.xml.bindings.types.ArcDirectionType.ArcDirection;
import org.opentrafficsim.xml.bindings.types.StringType;
import org.opentrafficsim.xml.generated.BasicRoadLayout;
import org.opentrafficsim.xml.generated.CseLane;
import org.opentrafficsim.xml.generated.CseShoulder;
import org.opentrafficsim.xml.generated.CseStripe;
import org.opentrafficsim.xml.generated.FlattenerType;
import org.opentrafficsim.xml.generated.Link;
import org.opentrafficsim.xml.generated.Link.LaneOverride;
import org.opentrafficsim.xml.generated.Network;
import org.opentrafficsim.xml.generated.RoadLayout;
import org.opentrafficsim.xml.generated.SpeedLimit;
import org.opentrafficsim.xml.generated.TrafficLightType;
import nl.tudelft.simulation.dsol.SimRuntimeException;
/**
* NetworkParser parses the Network tag of the OTS network.
* <p>
* Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/averbraeck" target="_blank">Alexander Verbraeck</a>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
*/
public final class NetworkParser
{
/** */
private NetworkParser()
{
// utility class
}
/**
* Parse the Nodes.
* @param otsNetwork RoadNetwork; the network to insert the parsed objects in
* @param network Network; the Network tag
* @param nodeDirections Map<String,Direction>; a map of the node ids and their default directions
* @param eval Eval; expression evaluator.
* @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
*/
public static void parseNodes(final RoadNetwork otsNetwork, final Network network,
final Map<String, Direction> nodeDirections, final Eval eval) throws NetworkException
{
for (org.opentrafficsim.xml.generated.Centroid xmlCentroid : network.getCentroid())
{
new Centroid(otsNetwork, xmlCentroid.getId(), xmlCentroid.getCoordinate().get(eval));
}
for (org.opentrafficsim.xml.generated.Node xmlNode : network.getNode())
{
new Node(otsNetwork, xmlNode.getId(), xmlNode.getCoordinate().get(eval), nodeDirections.get(xmlNode.getId()));
}
}
/**
* Calculate the default angles of the Nodes, in case they have not been set. This is based on the Straight Link elements in
* the XML file.
* @param otsNetwork RoadNetwork; the network to insert the parsed objects in
* @param network Network; the Network tag
* @param eval Eval; expression evaluator.
* @return a map of nodes and their default direction
*/
public static Map<String, Direction> calculateNodeAngles(final RoadNetwork otsNetwork, final Network network,
final Eval eval)
{
Map<String, Direction> nodeDirections = new LinkedHashMap<>();
Map<String, Point2d> points = new LinkedHashMap<>();
for (org.opentrafficsim.xml.generated.Node xmlNode : network.getNode())
{
if (xmlNode.getDirection() != null)
{
nodeDirections.put(xmlNode.getId(), xmlNode.getDirection().get(eval));
}
points.put(xmlNode.getId(), xmlNode.getCoordinate().get(eval));
}
for (Link xmlLink : network.getLink())
{
if (xmlLink.getStraight() != null)
{
Point2d startPoint = points.get(xmlLink.getNodeStart().get(eval));
Point2d endPoint = points.get(xmlLink.getNodeEnd().get(eval));
double direction = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
if (!nodeDirections.containsKey(xmlLink.getNodeStart().get(eval)))
{
nodeDirections.put(xmlLink.getNodeStart().get(eval), new Direction(direction, DirectionUnit.EAST_RADIAN));
}
if (!nodeDirections.containsKey(xmlLink.getNodeEnd().get(eval)))
{
nodeDirections.put(xmlLink.getNodeEnd().get(eval), new Direction(direction, DirectionUnit.EAST_RADIAN));
}
}
}
for (org.opentrafficsim.xml.generated.Node xmlNode : network.getNode())
{
if (!nodeDirections.containsKey(xmlNode.getId()))
{
System.err.println("Warning: Node " + xmlNode.getId() + " does not have a (calculated) direction");
}
}
return nodeDirections;
}
/**
* Build the links with the correct design line.
* @param otsNetwork RoadNetwork; the network to insert the parsed objects in
* @param definitions Definitions; parsed definitions.
* @param network Network; the Network tag
* @param nodeDirections Map<String,Direction>; a map of the node ids and their default directions
* @param simulator OtsSimulatorInterface; the simulator
* @param designLines Map<String, ContinuousLine>; map to store created design lines.
* @param flatteners Map<String, Flattener>; flattener per link id.
* @param eval Eval; expression evaluator.
* @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
* @throws OtsGeometryException when the design line is invalid
*/
static void parseLinks(final RoadNetwork otsNetwork, final Definitions definitions, final Network network,
final Map<String, Direction> nodeDirections, final OtsSimulatorInterface simulator,
final Map<String, ContinuousLine> designLines, final Map<String, Flattener> flatteners, final Eval eval)
throws NetworkException, OtsGeometryException
{
for (org.opentrafficsim.xml.generated.Connector xmlConnector : network.getConnector())
{
String nodeId = xmlConnector.getNode().get(eval);
Node node = (Node) otsNetwork.getNode(nodeId);
if (null == node)
{
simulator.getLogger().always().debug("No node (" + nodeId + ") for Connector " + xmlConnector.getId());
}
String centroidId = xmlConnector.getCentroid().get(eval);
Node centroid = (Node) otsNetwork.getNode(centroidId);
if (null == centroid)
{
simulator.getLogger().always().debug("No centroid (" + centroidId + ") for Connector " + xmlConnector.getId());
}
String id = xmlConnector.getId();
double demandWeight = xmlConnector.getDemandWeight().get(eval);
LinkType linkType = definitions.get(LinkType.class, xmlConnector.getType().get(eval));
Connector link = xmlConnector.getOutbound().get(eval) ? new Connector(otsNetwork, id, centroid, node, linkType)
: new Connector(otsNetwork, id, node, centroid, linkType);
link.setDemandWeight(demandWeight);
}
Flattener defaultFlattener =
network.getFlattener() == null ? new NumSegments(64) : getFlattener(null, network.getFlattener(), eval);
for (Link xmlLink : network.getLink())
{
Node startNode = (Node) otsNetwork.getNode(xmlLink.getNodeStart().get(eval));
Node endNode = (Node) otsNetwork.getNode(xmlLink.getNodeEnd().get(eval));
Point2d startPoint = startNode.getPoint();
Point2d endPoint = endNode.getPoint();
Point2d[] coordinates = null;
// start and end with heading, adjusted with offset
double startHeading = startNode.getHeading().si;
OrientedPoint2d start = new OrientedPoint2d(startPoint.x, startPoint.y, startHeading);
if (xmlLink.getOffsetStart() != null)
{
start = OtsGeometryUtil.offsetPoint(start, xmlLink.getOffsetStart().get(eval).si);
}
double endHeading = endNode.getHeading().si;
OrientedPoint2d end = new OrientedPoint2d(endPoint.x, endPoint.y, endHeading);
if (xmlLink.getOffsetEnd() != null)
{
end = OtsGeometryUtil.offsetPoint(end, xmlLink.getOffsetEnd().get(eval).si);
}
ContinuousLine designLine;
Flattener flattener = defaultFlattener;
if (xmlLink.getStraight() != null)
{
designLine = new ContinuousStraight(start, Math.hypot(end.x - start.x, end.y - start.y));
}
else if (xmlLink.getPolyline() != null)
{
int intermediatePoints = xmlLink.getPolyline().getCoordinate().size();
coordinates = new Point2d[intermediatePoints + 2];
coordinates[0] = startPoint;
coordinates[intermediatePoints + 1] = endPoint;
for (int p = 0; p < intermediatePoints; p++)
{
coordinates[p + 1] = xmlLink.getPolyline().getCoordinate().get(p).get(eval);
}
designLine = new ContinuousPolyLine(new PolyLine2d(true, coordinates));
}
else if (xmlLink.getArc() != null)
{
flattener = getFlattener(defaultFlattener, xmlLink.getArc().getFlattener(), eval);
double radius = xmlLink.getArc().getRadius().get(eval).si;
boolean left = xmlLink.getArc().getDirection().get(eval).equals(ArcDirection.LEFT);
while (left && endHeading < startHeading)
{
endHeading += 2.0 * Math.PI;
}
while (!left && endHeading > startHeading)
{
endHeading -= 2.0 * Math.PI;
}
designLine = new ContinuousArc(start, radius, left, Angle.instantiateSI(Math.abs(endHeading) - startHeading));
}
else if (xmlLink.getBezier() != null)
{
flattener = getFlattener(defaultFlattener, xmlLink.getBezier().getFlattener(), eval);
double shape = xmlLink.getBezier().getShape().get(eval);
boolean weighted = xmlLink.getBezier().isWeighted();
Point2d[] designPoints = Bezier.cubicControlPoints(start, end, shape, weighted);
designLine = new ContinuousBezierCubic(designPoints[0], designPoints[1], designPoints[2], designPoints[3]);
}
else if (xmlLink.getClothoid() != null)
{
FlattenerType flattenerType =
xmlLink.getClothoid().getFlattener().isEmpty() ? null : xmlLink.getClothoid().getFlattener().get(0);
flattener = getFlattener(defaultFlattener, flattenerType, eval);
// fields in getClothoid() appear as lists as StartCurvature and EndCurvature appear in multiple options
if (!xmlLink.getClothoid().getInterpolated().isEmpty())
{
designLine = new ContinuousClothoid(start, end);
}
else
{
LinearDensity startCurvature = xmlLink.getClothoid().getStartCurvature().get(0).get(eval);
LinearDensity endCurvature = xmlLink.getClothoid().getEndCurvature().get(0).get(eval);
if (!xmlLink.getClothoid().getLength().isEmpty())
{
Length length = xmlLink.getClothoid().getLength().get(0).get(eval);
designLine = ContinuousClothoid.withLength(start, length.si, startCurvature.si, endCurvature.si);
}
else
{
Throw.when(xmlLink.getClothoid().getA().isEmpty(), NetworkException.class,
"Clothoid for link %s is not correctly specified.", xmlLink.getId());
Length a = xmlLink.getClothoid().getA().get(0).get(eval);
designLine = new ContinuousClothoid(start, a.si, startCurvature.si, endCurvature.si);
}
}
}
else
{
throw new NetworkException("Making link, but link " + xmlLink.getId()
+ " has no filled straight, arc, bezier, polyline, or clothoid definition");
}
designLines.put(xmlLink.getId(), designLine);
flatteners.put(xmlLink.getId(), flattener);
PolyLine2d flattenedLine = designLine.flatten(flattener);
LaneKeepingPolicy laneKeepingPolicy = xmlLink.getLaneKeeping().get(eval);
LinkType linkType = definitions.get(LinkType.class, xmlLink.getType().get(eval));
// TODO: elevation data
CrossSectionLink link = new CrossSectionLink(otsNetwork, xmlLink.getId(), startNode, endNode, linkType,
new OtsLine2d(flattenedLine), null, laneKeepingPolicy);
if (xmlLink.getPriority() != null)
{
Priority priority = xmlLink.getPriority().get(eval);
link.setPriority(priority);
}
}
}
/**
* Build the links with the correct design line.
* @param otsNetwork RoadNetwork; the network to insert the parsed objects in
* @param definitions Definitions; parsed definitions.
* @param network Network; the Network tag
* @param simulator OtsSimulatorInterface; the simulator
* @param roadLayoutMap the map of the tags of the predefined RoadLayout tags in Definitions
* @param linkTypeSpeedLimitMap map of speed limits per link type
* @param designLines Map<String, ContinuousLine>; design lines per link id.
* @param flatteners Map<String, Flattener>; flattener per link id.
* @param eval Eval; expression evaluator.
* @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
* @throws OtsGeometryException when the design line is invalid
* @throws XmlParserException when the stripe type cannot be recognized
* @throws SimRuntimeException in case of simulation problems building the car generator
* @throws GtuException when construction of the Strategical Planner failed
*/
static void applyRoadLayout(final RoadNetwork otsNetwork, final Definitions definitions, final Network network,
final OtsSimulatorInterface simulator, final Map<String, RoadLayout> roadLayoutMap,
final Map<LinkType, Map<GtuType, Speed>> linkTypeSpeedLimitMap, final Map<String, ContinuousLine> designLines,
final Map<String, Flattener> flatteners, final Eval eval)
throws NetworkException, OtsGeometryException, XmlParserException, SimRuntimeException, GtuException
{
for (Link xmlLink : network.getLink())
{
CrossSectionLink csl = (CrossSectionLink) otsNetwork.getLink(xmlLink.getId());
List<CrossSectionElement> cseList = new ArrayList<>();
Map<String, Lane> lanes = new LinkedHashMap<>();
// Get the RoadLayout (either defined here, or via pointer to Definitions)
BasicRoadLayout roadLayoutTag;
if (xmlLink.getDefinedLayout() != null)
{
Throw.when(xmlLink.getRoadLayout() != null, XmlParserException.class,
"Link %s Ambiguous RoadLayout; both DefinedRoadLayout and RoadLayout defined", xmlLink.getId());
String definedLayoutId = xmlLink.getDefinedLayout().get(eval);
RoadLayout roadLayoutTagBase = roadLayoutMap.get(definedLayoutId);
Throw.when(roadLayoutTagBase == null, XmlParserException.class, "Link %s Could not find defined RoadLayout %s",
xmlLink.getId(), definedLayoutId);
// Process LaneOverrides
roadLayoutTag = Cloner.cloneRoadLayout(roadLayoutTagBase);
for (LaneOverride laneOverride : xmlLink.getLaneOverride())
{
for (CseLane lane : ParseUtil.getObjectsOfType(roadLayoutTag.getStripeOrLaneOrShoulder(), CseLane.class))
{
if (lane.getId().equals(laneOverride.getLane().get(eval)))
{
if (laneOverride.getSpeedLimit().size() > 0)
{
lane.getSpeedLimit().clear();
lane.getSpeedLimit().addAll(laneOverride.getSpeedLimit());
}
}
}
}
}
else
{
roadLayoutTag = xmlLink.getRoadLayout();
if (roadLayoutTag == null)
{
throw new XmlParserException("Link " + xmlLink.getId() + " No RoadLayout defined");
}
}
// calculate for each lane and stripe what the start and end offset is
List<CseData> cseDataList = new ArrayList<>();
Map<Object, Integer> cseTagMap = new LinkedHashMap<>();
RoadLayoutOffsets.calculateOffsets(roadLayoutTag, cseDataList, cseTagMap, eval);
// Stripe
ContinuousLine designLine = designLines.get(xmlLink.getId());
Flattener flattener = flatteners.get(xmlLink.getId());
for (CseStripe stripeTag : ParseUtil.getObjectsOfType(roadLayoutTag.getStripeOrLaneOrShoulder(), CseStripe.class))
{
CseData cseData = cseDataList.get(cseTagMap.get(stripeTag));
makeStripe(csl, designLine, flattener, cseData.centerOffsetStart, cseData.centerOffsetEnd, stripeTag, cseList,
eval);
}
// Other CrossSectionElement
for (org.opentrafficsim.xml.generated.CrossSectionElement cseTag : ParseUtil.getObjectsOfType(
roadLayoutTag.getStripeOrLaneOrShoulder(), org.opentrafficsim.xml.generated.CrossSectionElement.class))
{
CseData cseData = cseDataList.get(cseTagMap.get(cseTag));
List<CrossSectionSlice> slices = LaneGeometryUtil.getSlices(designLine, cseData.centerOffsetStart,
cseData.centerOffsetEnd, cseData.widthStart, cseData.widthEnd);
PolyLine2d centerLine =
designLine.flattenOffset(LaneGeometryUtil.getCenterOffsets(designLine, slices), flattener);
PolyLine2d leftEdge =
designLine.flattenOffset(LaneGeometryUtil.getLeftEdgeOffsets(designLine, slices), flattener);
PolyLine2d rightEdge =
designLine.flattenOffset(LaneGeometryUtil.getRightEdgeOffsets(designLine, slices), flattener);
Polygon2d contour = LaneGeometryUtil.getContour(leftEdge, rightEdge);
// Lane
if (cseTag instanceof CseLane)
{
CseLane laneTag = (CseLane) cseTag;
LaneType laneType = definitions.get(LaneType.class, laneTag.getLaneType().get(eval));
Map<GtuType, Speed> speedLimitMap = new LinkedHashMap<>();
LinkType linkType = csl.getType();
speedLimitMap.putAll(linkTypeSpeedLimitMap.computeIfAbsent(linkType, (l) -> new LinkedHashMap<>()));
for (SpeedLimit speedLimitTag : roadLayoutTag.getSpeedLimit())
{
GtuType gtuType = definitions.get(GtuType.class, speedLimitTag.getGtuType().get(eval));
speedLimitMap.put(gtuType, speedLimitTag.getLegalSpeedLimit().get(eval));
}
for (SpeedLimit speedLimitTag : laneTag.getSpeedLimit())
{
GtuType gtuType = definitions.get(GtuType.class, speedLimitTag.getGtuType().get(eval));
speedLimitMap.put(gtuType, speedLimitTag.getLegalSpeedLimit().get(eval));
}
Lane lane =
new Lane(csl, laneTag.getId(), new OtsLine2d(centerLine), contour, slices, laneType, speedLimitMap);
cseList.add(lane);
lanes.put(lane.getId(), lane);
}
// Shoulder
else if (cseTag instanceof CseShoulder)
{
CseShoulder shoulderTag = (CseShoulder) cseTag;
// FIXME: laneType is a mandatory field, but parsing or eclipse is not taking the new field
// although specified in XML, the value is always null
LaneType laneType = shoulderTag.getLaneType() == null ? SHOULDER
: definitions.get(LaneType.class, shoulderTag.getLaneType().get(eval));
String id = shoulderTag.getId() != null ? shoulderTag.getId() : UUID.randomUUID().toString();
CrossSectionElement shoulder = new Shoulder(csl, id, new OtsLine2d(centerLine), contour, slices, laneType);
cseList.add(shoulder);
}
}
// TrafficLight
for (TrafficLightType trafficLight : xmlLink.getTrafficLight())
{
String laneId = trafficLight.getLane().get(eval);
Throw.when(!lanes.containsKey(laneId), NetworkException.class,
"Link: %s, TrafficLight with id %s on Lane %s - Lane not found", xmlLink.getId(), trafficLight.getId(),
laneId);
Lane lane = lanes.get(laneId);
Length position = ParseUtil.parseLengthBeginEnd(trafficLight.getPosition().get(eval), lane.getLength());
TrafficLight obj = new TrafficLight(trafficLight.getId(), lane, position, simulator);
for (StringType nodeId : trafficLight.getTurnOnRed())
{
obj.addTurnOnRed(otsNetwork.getNode(nodeId.get(eval)));
}
}
}
}
/** Temporary fix for CseShoulder.getLaneType() always being null. */
// FIXME
private static final LaneType SHOULDER = new LaneType("Shoulder");
/**
* Parse a stripe on a road.
* @param csl CrossSectionLink; the CrossSectionLine
* @param designLine ContinuousLine; design line.
* @param flattener Flattener; flattener.
* @param startOffset Length; the offset of the start node
* @param endOffset Length; the offset of the end node
* @param stripeTag CseStripe; the CseStripe tag in the XML file
* @param cseList List<CrossSectionElement>; the list of CrossSectionElements to which the stripes should be added
* @param eval Eval; expression evaluator.
* @throws OtsGeometryException when creation of the center line or contour geometry fails
* @throws NetworkException when id of the stripe not unique
* @throws XmlParserException when the stripe type cannot be recognized
*/
private static void makeStripe(final CrossSectionLink csl, final ContinuousLine designLine, final Flattener flattener,
final Length startOffset, final Length endOffset, final CseStripe stripeTag,
final List<CrossSectionElement> cseList, final Eval eval)
throws OtsGeometryException, NetworkException, XmlParserException
{
Type type = stripeTag.getType().get(eval);
Length width = stripeTag.getDrawingWidth() != null ? stripeTag.getDrawingWidth().get(eval) : type.defaultWidth();
List<CrossSectionSlice> slices = LaneGeometryUtil.getSlices(designLine, startOffset, endOffset, width, width);
PolyLine2d centerLine = designLine.flattenOffset(LaneGeometryUtil.getCenterOffsets(designLine, slices), flattener);
PolyLine2d leftEdge = designLine.flattenOffset(LaneGeometryUtil.getLeftEdgeOffsets(designLine, slices), flattener);
PolyLine2d rightEdge = designLine.flattenOffset(LaneGeometryUtil.getRightEdgeOffsets(designLine, slices), flattener);
Polygon2d contour = LaneGeometryUtil.getContour(leftEdge, rightEdge);
cseList.add(new Stripe(type, csl, new OtsLine2d(centerLine), contour, slices));
}
/**
* Build conflicts.
* @param otsNetwork RoadNetwork; the network to insert the parsed objects in
* @param network Network; the Network tag
* @param eval Eval; expression evaluator.
* @throws OtsGeometryException if building conflicts fails
* @throws XmlParserException if Conflicts tag contains no valid element
*/
static void buildConflicts(final RoadNetwork otsNetwork, final Network network, final Eval eval)
throws OtsGeometryException, XmlParserException
{
if (network.getConflicts() != null && network.getConflicts().getNone() == null)
{
WidthGenerator widthGenerator;
if (network.getConflicts().getFixedWidth() != null)
{
widthGenerator = new FixedWidthGenerator(network.getConflicts().getFixedWidth().get(eval));
}
else if (network.getConflicts().getRelativeWidth() != null)
{
widthGenerator = new RelativeWidthGenerator(network.getConflicts().getRelativeWidth().get(eval));
}
else if (network.getConflicts().getDefaultWidth() != null)
{
widthGenerator = new FixedWidthGenerator(Length.instantiateSI(2.0));
}
else
{
throw new XmlParserException("Conflicts tag contains no valid element.");
}
otsNetwork.getSimulator().getLogger().always().info("Generating conflicts");
Map<String, Set<org.opentrafficsim.core.network.Link>> conflictCandidateMap = new LinkedHashMap<>();
for (Link link : network.getLink())
{
if (link.getConflictId() != null)
{
if (!conflictCandidateMap.containsKey(link.getConflictId().get(eval)))
{
conflictCandidateMap.put(link.getConflictId().get(eval), new LinkedHashSet<>());
}
conflictCandidateMap.get(link.getConflictId().get(eval)).add(otsNetwork.getLink(link.getId()));
}
}
otsNetwork.getSimulator().getLogger().always().info("Map size of conflict candidate regions = {}",
conflictCandidateMap.size());
// TODO: if there is any conflict ID specified, conflictCandidateMap is filled, and no other conflict anywhere will
// be generated. How can we combine generation and specifying conflict IDs?
// TODO: specify where conflicts are directly?
if (conflictCandidateMap.size() == 0)
{
ConflictBuilder.buildConflictsParallel(otsNetwork, otsNetwork.getSimulator(), widthGenerator);
}
else
{
ConflictBuilder.buildConflictsParallel(otsNetwork, conflictCandidateMap, otsNetwork.getSimulator(),
widthGenerator);
}
otsNetwork.getSimulator().getLogger().always().info("Object map size = {}", otsNetwork.getObjectMap().size());
}
}
/**
* Returns a flattener. If the flattener tag is empty, the flattener is obtained from the network.
* @param defaultFlattener Flattener; default flattener from the network, may be {@code null} to parse default flattener.
* @param flattenerType FlattenerType; XML tag of flattener.
* @param eval Eval; evaluator.
* @return Flattener.
* @throws NetworkException if the flattener is not correctly defined.
*/
private static Flattener getFlattener(final Flattener defaultFlattener, final FlattenerType flattenerType, final Eval eval)
throws NetworkException
{
if (flattenerType == null)
{
return defaultFlattener;
}
if (flattenerType.getNumSegments() != null)
{
return new NumSegments(flattenerType.getNumSegments().get(eval));
}
else if (flattenerType.getDeviationAndAngle() != null)
{
if (flattenerType.getDeviationAndAngle().getMaxDeviation() != null)
{
if (flattenerType.getDeviationAndAngle().getMaxAngle() != null)
{
return new MaxDeviationAndAngle(
getDeviation(flattenerType.getDeviationAndAngle().getMaxDeviation().get(eval)),
getAngle(flattenerType.getDeviationAndAngle().getMaxAngle().get(eval)));
}
return new MaxDeviation(getDeviation(flattenerType.getDeviationAndAngle().getMaxDeviation().get(eval)));
}
else if (flattenerType.getDeviationAndAngle().getMaxAngle() != null)
{
return new MaxAngle(getAngle(flattenerType.getDeviationAndAngle().getMaxAngle().get(eval)));
}
throw new NetworkException("No deviation and/or angle for flattener specified.");
}
throw new NetworkException("No flattener specified.");
}
/**
* Returns a safe deviation value (>=0.001).
* @param length Length; deviation.
* @return double; safe deviation.
*/
private static double getDeviation(final Length length)
{
return length.si < 0.001 ? 0.001 : length.si;
}
/**
* Returns a safe angle value (>=0.01).
* @param angle Length; angle.
* @return double; safe angle.
*/
private static double getAngle(final Angle angle)
{
return angle.si < 0.01 ? 0.01 : angle.si;
}
}